Code

Opened 18 months ago

Closed 17 months ago

Last modified 17 months ago

#19731 closed Uncategorized (invalid)

previous values of ManyToManyField in model's save method

Reported by: s4mmael@… Owned by: nobody
Component: Database layer (models, ORM) Version: 1.4
Severity: Normal Keywords: manytomanyfield,save,previous
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Hello,

I'm terribly sorry for bothering you, but I believe this behavior may be incorrect. I've already asked at django-users mailing list and I've had no answer.

How to reproduce it:

    class Tag(models.Model):
        name = models.CharField(max_length=50, unique=True, db_index=True)
        parent = ForeignKey('self', null=True, blank=True, related_name='children')

    class Album(models.Model):
        name = models.CharField(max_length=64, blank=True)
        tags = models.ManyToManyField(Tag, blank=True)
        def tags_(self):
            return ', '.join([t.name for t in self.tags.all()])
        def save(self, *args, **kwargs):
            super(Album, self).save(*args, **kwargs)
            f = open('/tmp/test.txt', 'wt')
            f.write('%s\n%s\n' % (self.name, self.tags_()))
            f.close

Now let's go to django admin and create two tags: tag1, tag2. After that let's create an album 'Album_1' with tag 'tag1' and save the album. In the file /tmp/test.txt we see:


Album_1



Let's add tag2 to the album, change it's name to Album_2 and save. The file will be:


Album_2
tag1


Let's remove both tags and save:


Album_2
tag1, tag2


Let's save it again:


Album_2


So, while everything looks good in Django admin, we get old values of album.tags_() method in Album.save(), even if we call super(Album, self).save(*args, * *kwargs) before.

I've also tried to use signals like this:

    @receiver(post_save, sender=Album)
    def album_save_handler(sender, instance, **kwargs):
        f = open('/tmp/test.txt', 'wt')
        f.write('%s\n%s\n' % (instance.name, instance.tags_()))
        f.close

And even like this:

    @receiver(post_save, sender=Album)
    def album_save_handler(sender, instance, **kwargs):
        t = Album.objects.get(pk = instance.id).tags_()
        f = open('/tmp/test.txt', 'wt')
        f.write('%s\n%s\n' % (instance.name, t))
        f.close

Still no luck, results are the same: previous tags instead of the current ones.

Attachments (0)

Change History (2)

comment:1 Changed 17 months ago by russellm

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Resolution set to invalid
  • Status changed from new to closed

You've been caught by a leaky abstraction. M2M relationships aren't saved as part of the save() method.

In the admin, the main object is saved, and then the m2m relation is saved; so, by serializing the list of tags in the save method, you're printing the value of the tags before the new values have been saved.

If you want to install "post m2m save" behavior, you'd need to override the update view on the admin itself.

comment:2 Changed 17 months ago by anonymous

Russellm, thank you very much for your response! Since I'd read your explanation it was quite simple to find the solution: the 'm2m_changed' signal do the trick.

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.