2016-04-12 4 views
1

Ich möchte einen Fremdschlüsselwert ändern, wenn er aus der Datenbank gelöscht wird. Also schaute ich auf das Dokument und verwendete die Methode on_delete = models.SET (foo). https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.SETDjango pass self to models.SET on_delete

Das ist meine Bestimmung des Modells

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) 

Und das ist meine Methode, die auf Lösch

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

genannt wird, aber hier ist das Problem, dass ich nicht Argument dieser Methode passieren kann die Deshalb kann ich nicht wissen, welches Produkt gelöscht wurde. Ich habe auch versucht, Signale wie in dieser Antwort django model on_delete pass self to models.SET()

Ich habe auch versucht, mit Signalen, aber das hat auch nicht funktioniert. Ich kann keine passende Lösung dafür finden. Lassen Sie mich wissen, wenn jemand eine Idee hat, wie dies zu erreichen ist.

EDIT

Dies ist der Code, den ich in Signale mit am

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

Das Problem ist jetzt, dass django meine Auftragszeilen als auch zu löschen versucht (als Standard on_delete auf Kaskade eingestellt ist). Und wenn ich on_Delete auf SET_NULL setze, setzt es den Fremdschlüssel auf null.

EDIT -2 Hier ist der Code ich für Vollständigkeit

@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 Veröffentlichung vollständige Umsetzung verwenden.

@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

"Ich habe keine Referenz der Bestellnummer in Receiver-Methode.". Warum nicht? Können Sie den Code, den Sie mit Signalen verwendet haben, posten? – solarissmoke

+0

In Ihrer zweiten Bearbeitung, denke ich, ist es die 'Deepcopy', die das Problem verursacht. Sehen Sie, ob Sie das in den 'post_delete'-Handler verschieben können. Außerdem müssen Sie das Abfrageset kopieren, um zu vermeiden, dass es verpatzt wird (ich habe meine Antwort unten bearbeitet). – solarissmoke

+0

@solarissmoke Ich habe versucht, tiefe Kopie zu post_delete Handler zu verschieben und es scheint zu funktionieren, danke und Entschuldigung für die späte Antwort, ging aus der Stadt für 10 Tage. – Anurag

Antwort

2

Signale sind der richtige Weg, dies zu tun.

Sie können die OrderLine aus dem Signalempfänger erhalten:

@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 ist ein queryset, die Sie über oder update in bulk laufen kann.

EDIT

Stellt sich der Ansatz oben arbeiten wird vorgeschlagen, aus nicht, weil durch die Zeit der pre_delete Signal Django bereits festgestellt hat, gefeuert hat, die im Zusammenhang Modelle mit on_delete verarbeiten muss, und diese Änderungen überschreiben.

wird dieser Ansatz funktioniert, obwohl es ein bisschen klobig ist:

Zuerst wird in einem Empfänger pre_delete: von Kopie Import Kopie

@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()) 

Dann wird in einem post_delete Empfänger:

@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: 
     # ... 

Zwischen diesen beiden Ereignissen hat Django SET_NULL (oder was auch immer Sie konfiguriert haben) auf dem Or ausgeführt derLines.

+0

Das Problem mit dieser Lösung ist, dass Django versucht, meine Bestellzeilen zu löschen, sowie Standard auf on_delete ist festgelegt, um zu kaskadieren. Und wenn ich das on_Delete auf SET_NULL setze, setzt es den Fremdschlüssel auf null – Anurag

+0

Ich dachte, dass die Idee war, den ForeignKey zu ändern, um auf ein anderes Produkt zu zeigen, bevor die Löschung geschieht? – solarissmoke

+0

Das stimmt, ich möchte die Bestellposten auf ein neues Produkt hinweisen. Ich bin mir jedoch nicht sicher, ob diese Änderung ordnungsgemäß durchgeführt wurde oder ob eine Wettlaufsituation vorliegt. – Anurag

0

Unterstützung für die Antwort @solarissmoke, die ich für die richtige Lösung halte, aber um Ihnen mehr Optionen zu geben, werde ich dies veröffentlichen. Wenn Sie die product Instanz haben, können Sie ihre orderlines haben. Wenn Sie zum Beispiel eine related_name zum OrderLine.product wie so hinzufügen:

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

können Sie dann in Ihrem Signal dies tun:

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

nur eine andere Art, dies zu tun, um die orderlines zu bekommen.