2016-04-12 2 views
1

Je souhaite modifier une valeur clé étrangère lorsque son supprimée de la base de données. J'ai donc regardé le doc et utilisé la méthode on_delete = models.SET (foo). https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.SETDjango passer à l'auto models.SET on_delete

C'est ma définition du modèle

class OrderLine(models.Model): 
    product = models.ForeignKey(Product, on_delete=models.SET(getDuplicateProduct), null=True) 
    quantity = models.PositiveSmallIntegerField(default=1) 
    finalPricePerUnit = models.PositiveIntegerField() 
    order = models.ForeignKey(Order, on_delete=models.PROTECT) 
    dateCreated = models.DateTimeField(auto_now=False, auto_now_add=True) 

Et voici ma méthode qui est appelé suppression

def getDuplicateProduct(orderline): 
    productToDelete = orderline.product 
    # some logic to generate duplicate copy and returning it 

Cependant, le problème est que je ne peux pas passer argument à cette méthode C'est pourquoi je ne peux pas savoir quel produit a été supprimé. J'ai aussi essayé d'utiliser des signaux comme indiqué dans cette réponse django model on_delete pass self to models.SET()

J'ai aussi essayé d'utiliser des signaux, mais aussi ne fonctionne pas. Je n'arrive pas à trouver une solution appropriée pour cela. Dites-moi si quelqu'un a une idée sur la façon d'atteindre cet objectif.

EDIT

C'est le code que je utilise des signaux

@receiver(pre_delete, sender=Product) 
def getDuplicateProduct(sender, **kwargs): 
    product = kwargs['instance'] 
    orderlines = product.orderline_set.all() 
    #further processing 

Maintenant, le problème est que django tente de supprimer mes lignes de commande aussi bien (comme on_delete par défaut est réglé sur cascade). Et si je définis le on_Delete à SET_NULL, il définit la clé étrangère à null.

EDIT -2 Voici le code que je utilise

@receiver(pre_delete, sender=Product) 
def getDuplicateProduct(sender, **kwargs): 
    product = kwargs['instance'] 
    orderlines = product.orderline_set.all() 
    product.name = product.name + ' ' + product.get_type_display() 
    newProduct = deepcopy(product) 
    newProduct.name = product.name + ' ' + product.get_type_display() 
    newProduct.pk=None 
    newProduct.id=None 
    newProduct.save() 
    product.duplicateProductId = newProduct.id 
    product.old_orderlines = orderlines 
    product.save() 


@receiver(post_delete, sender=Product) 
def handlePostDelete(sender, **kwargs): 
    product = kwargs['instance'] 
    newProduct = Product.objects.get(id=product.duplicateProductId) 
    for orderline in product.old_orderlines: 
     orderline.product = newProduct 
     orderline.save() 

EDIT-3 affichage mise en œuvre complète d'exhaustivité.

@receiver(pre_delete, sender=Product) 
def handlePreDelete(sender, **kwargs): 
    product = kwargs['instance'] 
    orderlines = product.orderline_set.all() 
    shouldCreate=False 
    for orderline in orderlines: 
     if orderline.order.status>1: 
      shouldCreate=True 
    product.shouldCreate = shouldCreate 
    if shouldCreate: 
     product.old_orderlines = orderlines 
     product.save() 
    else: 
     product.save() 
     return None 


@receiver(post_delete, sender=Product) 
def handlePostDelete(sender, **kwargs): 
    product = kwargs['instance'] 
    shouldCreate = product.shouldCreate 
    if shouldCreate: 
     newProduct = deepcopy(product) 
     newProduct.name = product.name + ' ' + product.get_type_display() 
     newProduct.pk=None 
     newProduct.id=None 
     newProduct.save() 
     # Do whatever you want with product.old_orderlines 
     for orderline in product.old_orderlines: 
      orderline.product = newProduct 
      orderline.save()    
+0

"Je n'aurai aucune référence de ligne de commande dans la méthode du récepteur.". Pourquoi pas? Pouvez-vous poster le code que vous avez utilisé avec des signaux? – solarissmoke

+0

Dans votre deuxième édition, je pense que c'est le 'deepcopy' qui cause le problème. Voyez si vous pouvez le déplacer dans le gestionnaire 'post_delete'. Aussi, vous devez copier le jeu de requête pour éviter qu'il ne soit tabassé (j'ai modifié ma réponse ci-dessous). – solarissmoke

+0

@solarissmoke J'ai essayé de déplacer la copie profonde à handler post_delete et il semble fonctionner, merci et désolé pour réponse tardive, est sorti de la ville pendant 10 jours. – Anurag

Répondre

2

Les signaux sont la bonne façon de procéder.

Vous pouvez obtenir le OrderLine du récepteur de signal:

@receiver(pre_delete, sender=Product) 
def getDuplicateProduct(sender, **kwargs): 
    product = kwargs['instance'] 
    orderlines = product.orderline_set.all() 
    # orderlines contains all the OrderLines foreign keyed to the product. 

orderlines est un queryset que vous pouvez parcourir ou update in bulk.

EDIT

Transforme l'approche suggérée ci-dessus ne fonctionnera pas, car au moment où le signal pre_delete a tiré Django a déjà déterminé les modèles liés dont il a besoin pour traiter avec on_delete et remplacera ces changements.

Cette approche fonctionne, mais il est un peu maladroit:

D'abord, dans un récepteur pre_delete: de copie à l'importation de copie

@receiver(pre_delete, sender=Product) 
def handlePreDelete(sender, **kwargs): 
    product = kwargs['instance'] 
    # Store the OrderLines as a property of the object 
    # Have to copy it otherwise it will be empty later 
    product.old_orderlines = copy(product.orderline_set.all()) 

Puis, dans un récepteur post_delete:

@receiver(post_delete, sender=Product) 
def handlePostDelete(sender, **kwargs): 
    product = kwargs['instance'] 
    # Do whatever you want with product.old_orderlines 
    for line in product.old_orderlines: 
     # ... 

entre ces deux événements, Django aura effectué SET_NULL (ou tout ce que vous configuré) sur le ou derLines.

+0

Le problème avec cette solution est que django essaie de supprimer mes lignes de commande ainsi que par défaut on_delete est mis en cascade. Et si je mets le on_Delete à SET_NULL, il met la clé étrangère à zéro – Anurag

+0

Je pensais que l'idée était de changer le ForeignKey pour pointer vers un autre produit avant que la suppression ne se produise? – solarissmoke

+0

C'est vrai, je veux pointer les lignes de commande à un nouveau produit. Cependant je ne suis pas sûr si ce changement est correctement commis ou il y a une condition de concurrence. – Anurag

0

Donner un support à la réponse @solarissmoke, que je considère comme la bonne solution, mais pour vous donner plus d'options, je vais le signaler. Si vous avez l'instance product vous pouvez avoir son orderlines. Par exemple, si vous ajoutez un related_name à la OrderLine.product comme ceci:

class OrderLine(models.Model): 
    product = models.ForeignKey(Product, related_name='orderlines') 

vous pouvez dans votre signal de le faire:

@receiver(pre_delete, sender=Product) 
def getDuplicateProduct(sender, **kwargs): 
    product = kwargs['instance'] 
    orderlines = product.orderlines.all() 

une autre façon de le faire, pour obtenir le orderlines.