Reputation: 8722
My Content model has a many-to-many relationship to the Tag model. When I save a Content object, I want to add the relationships dynamically. Im doing this the following way.
# models.py
def tag_content(content_id):
obj = Content.objects.get(pk=content_id)
print obj # Checking
obj.tags = [1, 2, 3] # Adding the relationships using the Tag IDs
class Tag(models.Model):
name = models.CharField(max_length=255)
class Content(models.Model):
title = models.CharField(max_length=255)
is_tagged = models.BooleanField(default=False)
tags = models.ManyToManyField(Tag, blank=True)
def save(self, *args, **kwargs):
super(Content, self).save(*args, **kwargs)
if not self.is_tagged:
tag_content(self.pk) # calling the tagging method
In other words, when a Content object is saved, its tags field is related to 3 different Tag object models. Just to let you know, I do have the Tags with pks = 1, 2, and 3 in database.
However, this simply doesn't work. The save method calls the tag_content method, since the print obj statement works. However, the many-to-many field is not set and remains empty. The funny thing is, If I run the following commands in shell, the tags field is set perfectly.
# python manage.py shell
from myapp.models import *
obj = Content.objects.get(pk=1)
tag_content(obj.pk)
So how come the shell version works, but the other one doesn't? Any help is appreciated.
Upvotes: 3
Views: 3660
Reputation: 3755
You can't work on an m2m relationship in a custom save
method because of the way Django writes those relationships to the database. When saving a model instance with an m2m relationship, Django first writes the object then goes in again and writes the appropriate m2m relationships. Since the m2m stuff comes "second," trying to work with the relationship in a custom save fails.
The solution is to use a post-save signal. Remove the custom save stuff and add this below your model definitions, making sure to import receiver
and post_save
:
@receiver(post_save, sender = Content)
def update_m2m_relationships_on_save(sender, **kwargs):
if not kwargs['instance'].is_tagged:
tag_content(kwargs['instance'].pk)
Your tag_content
function should probably swap is_tagged
to True
and then save the instance; if that boolean is never flipped then this could just run in an endless loop. You can also just pass in the object instead of passing in the pk:
def tag_content(thing_to_tag):
thing_to_tag.tags.add([1,2,3])
thing_to_tag.is_tagged = True
thing_to_tag.save()
return thing_to_tag
Note the use of .add()
, which is important when adding to an m2m relationship.
Upvotes: 5