Orengelo
Orengelo

Reputation:

Django model custom save with ManyToManyField problem

I know this question has been posted multiple times but I still couldn't find a definite answer to this problem. So, here I go:

class Invoice(models.Model):
    program = models.ForeignKey(Program)
    customer = models.ForeignKey(Customer, related_name='invoices')
    participants = models.ManyToManyField(Participant, related_name='participants_set')
    subtotal = models.DecimalField(max_digits=10, decimal_places=2, default='0.00', blank=True, null=False)
    pst = models.DecimalField("PST", max_digits=10, decimal_places=2, default='0.00', blank=True, null=False)
    gst = models.DecimalField("GST", max_digits=10, decimal_places=2, default='0.00', blank=True, null=False)
    total = models.DecimalField(max_digits=10, decimal_places=2, default='0.00', blank=True, null=False)

    def save(self, **kwargs):
        super(Invoice, self).save(**kwargs)
        items = self.participants.count()
        subtotal = Decimal(self.program.fee) * items
        pst = self.program.is_pst and Decimal(PST)*subtotal or Decimal('0.00')
        gst = self.program.is_gst and Decimal(GST)*subtotal or Decimal('0.00')
        total = (subtotal + pst) + gst
        self.subtotal = subtotal
        self.pst = pst
        self.gst = gst
        self.total = total
        super(Invoice, self).save(**kwargs)

Everything works fine except self.participants.count() doesn't work. Any idea what could be the problem. Any help much appreciated.

Upvotes: 2

Views: 4016

Answers (4)

bloodzomboid
bloodzomboid

Reputation: 51

self.participants.all().count()

Upvotes: 5

Luke Murphey
Luke Murphey

Reputation: 73

I had a similar problem. I had a model that supported del.icio.us style tags. The save function would parse a list of the tags (e.g. "python django web") and convert them into individual tag object instances by calling a helper function update_tags() (see below for a simplified example). However, the ManyToManyField would not reflect the changes when I edited the object within the admin interface.

class Article(models.Model):
    tag_string = models.CharField(max_length=255, null=True, blank=True) #del.icio.us style tags, like: django python software
    tags =  models.ManyToManyField(Tag, blank=True)

    def save(self, force_insert=False, force_update=False):
        super(Article, self).save(force_insert, force_update)

        self.update_tags() #The result of this function didn't seem to be saved in the ManyToManyField

It turns out that the admin interface was overriding the changes to the ManyToManyField. The solution was simply to remove the ManyToManyField from the admin.ModelAdmin:

class ArticleAdmin(admin.ModelAdmin):
    exclude = ['tags']

Upvotes: 0

Roxy Light
Roxy Light

Reputation: 5147

I think what's happening is because you are trying a participants count during a save, the query may not be finding everything. If you're depending on this number at database creation time, I don't think the many-to-many table will be synced up correctly because the Invoice doesn't have an ID assigned yet.

Conversely, the other participants may not be saved to the database. Either way, depending on this number during a save won't work, regardless of using signals. I would recommend having a separate method that does this computation. It's cleaner, it improves save performance, and you can call it without saving.

Upvotes: 0

mikl
mikl

Reputation: 24317

Instead of overriding the save method, I'd recommend using the pre-save signal. Besides making your code a bit cleaner, it helps avoid strange issues like these :)

Upvotes: 0

Related Questions