The problem is, that after saving a model instance with new m2m_field value, I want to automatically add some more related objects to it.
class MyModel(models.Model):
m2m_field = models.ManyToManyField("app.RelatedModel")
#receiver(models.signals.m2m_changed, sender=MyModel.m2m_field.through)
def m2m_field_changed(sender, instance, **kwargs):
instance.m2m_field.add(related_object_instance)
That obviously results in an infinite loop, because after adding the instance to the m2m_field, the receiver is fired again and so on. Is there a proper way to do it?
Thanks for any help.
you have to check first if the related object has been added before or no:
#receiver(models.signals.m2m_changed, sender=MyModel.m2m_field.through)
def m2m_field_changed(sender, instance, **kwargs):
if related_object_instance not in instance.m2m_field:
instance.m2m_field.add(related_object_instance)
Related
I have two models that look like this:
class ModelOne(models.Model):
foo = models.CharField(max_length=25)
def save(self,*args,**kwargs):
a = ModelTwo.objects.get(pk=arbitrary_pk)
a.somefield.add(self) # I am worried about this line here
super(ModelOne,self).save(*args,**kwargs)
class ModelTwo(models.Model):
somefield = models.ManyToManyField(ModelOne)
The line where I am adding self to a.somefield is the line I am worried about. How can I do this without error? Currently, I am getting:
ValueError: Cannot add "<ModelOne>": the value for field "modelone" is None
Thanks in advance
You can't do that because when you call .add() you have yet to save your model. That means that the model may not have been created (so it doesn't have an ID yet).
Basically you're telling Django to update the Foreign Key with something that doesn't exist yet (NULL), which will error out. You need to make sure the model has been created before you can set the foreign key.
try moving the a.somefield.add(self) to AFTER the super() call.
You cannot save many to may field before calling actual save method, you modify code like,
def save(self,*args,**kwargs):
super(ModelOne,self).save(*args,**kwargs) # Here your self has been saved
a = ModelTwo.objects.get(pk=arbitrary_pk)
a.somefield.add(self) # Now your self can be add as ManyToMany as it is already saved in db
I hope this help.
Add the instance to the many to many field after calling the save method.
class ModelOne(models.Model):
foo = models.CharField(max_length=25)
def save(self,*args,**kwargs):
super(ModelOne,self).save(*args,**kwargs)
a = ModelTwo.objects.get(pk=arbitrary_pk)
a.somefield.add(self) #add self to the object manytomany.
a.save() #save the object.
class ModelTwo(models.Model):
somefield = models.ManyToManyField(ModelOne)
You need to save the self object first. The many to many relation needs to have the related object saved in the database first, inorder to define the relationship. Then, define the relationship using a.somefield.add(self). Then, save the a object. Otherwise, the relation won't be committed in the database.
I ended up utilizing post_save to get this to work.
I have a model like this
class Authority(models.Model):
name=models.CharField(max_length=100)
country=models.ForeignKey(Country)
category=models.ForeignKey(Category)
competitors=models.ManyToManyField("self",related_name="competitors")
I want authorities having the same country and category and itself to be automatically give an M2M relationship,so i did this
def save(self,*args,**kwargs):
z=Authority.objects.filter(country=self.country).filter(category=self.category)
this_authority=Authority.objects.get(id=self.id)
for a in z:
this_authority.competitors.add(a)
super(Authority,self).save(*args,**kwargs)
It wasn't working and not bringing any error,I also tries this below
def save(self,*args,**kwargs):
z=Authority.objects.filter(country=self.country).filter(category=self.category)
this_authority=Authority.objects.get(id=self.id)
self.competitors=z
super(Authority,self).save(*args,**kwargs)
What might be wrong with my code?Thanks in advance.
The reason this isn't working the way you expect is because of how Django handles creating m2m relationships in the database. Long story very short, when you save something like a new Authority to the database, Django first writes the new object then goes back in and writes the m2m relationships for that new object. As a result, it's tough to do anything useful to m2m relationships in a custom save method.
A post-save signal may do the trick here. kwargs['created'] = True if we're creating a new object and kwargs['instance'] is the instance whose save fired off the signal receiver.
#receiver(post_save, sender = Authority)
def update_m2m_relationships(sender, **kwargs):
if kwargs['created']: #only fire when creating new objects
competitors_to_add = Authority.objects.filter(
country = kwargs['instance'].country,
category = kwargs['instance'].category
)
for c in competitors_to_add:
c.competitors.add(kwargs['instance'])
c.save() #not creating a new object; this receiver does not fire here
kwargs['instance'].competitors.add(c)
#all competitors have been added to the instance's m2m field
kwargs['instance'].save()
It's important to only fire this when creating new objects. If you don't include that restriction, then the receiver will trigger itself as you update other objects in your for loop.
I haven't tested this out but I think it'll work. Let me know if it doesn't and I'll do my best to help.
Is it possible to add self - I mean the current object it's ManyToManyField?
class City(models.Model):
name = models.CharField(max_length=80)
country = models.ForeignKey('Country')
close_cities = models.ManyToManyField('City',blank=True, related_name='close_cities_set')
If I create let's say x = City.objects.create(...), I want the x to be a part of close_cities by default.
I can't find anything related to this problem. I tried to overwrite create() method but it did not worked.
After trying, I decided to create a signal which adds the city after creating an instance. Unfortunately this does not work but I can't figure out why. The signal is being called, the condition if created is True (checked).
#receiver(post_save,sender=myapp_models.City)
def add_self_into_many_to_many_field(sender, instance, created, **kwargs):
if created:
instance.close_cities.add(instance)
instance.save()
Do you know where is the problem?
In this case pre_save signal will be better solution.
In Your solution city.save calls add_self_into_many_to_many_field. instance.save calls add_self_into_many_to_many_field. and so on...
#receiver(pre_save, sender=myapp_models.City)
def add_self_into_many_to_many_field(sender, instance, **kwargs):
if instance.pk is None:
instance.close_cities.add(instance)
i'm writing an app with django and i need to change a specific model when
ever it been saved. i.e lets say i have a model A and a client want to save
changes to that model - i need to also save a change (only if the client
changed a certain field) to the same model (not instance).
my code:
#receiver(pre_save, sender=A)
def my_callable(sender, instance, **kwargs):
a = A.objects.filter(b=True).all()
for my_a in a:
my_a.b= False
my_a.save()
i have 2 problems with that code:
it has an infinite recursion
i don't know how to check which field had changed
and ideas?
Use .update(b=False) on the queryset:
#receiver(pre_save, sender=A)
def my_callable(sender, instance, **kwargs):
A.objects.filter(b=True).update(b=False)
The update is done in SQL, doesn't call model's save() method or trigger any signals
.update() docs
Assuming you're able to use Django1.8, this exact use case is covered in the docs actually: https://docs.djangoproject.com/en/1.8/ref/models/instances/#customizing-model-loading
Cliff notes: use the from_db method to customize loading of the model and save a copy of the instance's attributes as it is loaded, and then compare them before it is saved.
If you want to compare form data to a model instance to see if a user is changing it, do that in the view, not with a signal.
I have a situation where I need to notify some users when something in DB changes. My idea is to catch pre_save and post_save signal and make some kind of diff and mail that. Generally it works good, but I don't know how to get diff for m2m fields.
At the moment I have something like this:
def pre_save(sender, **kwargs):
pk = kwargs['instance'].pk
instance = copy.deepcopy(sender.objects.get(pk=pk))
tracking[sender] = instance
def post_save(sender, **kwargs):
instance = copy.deepcopy(kwargs['instance'])
print diff(instance, (tracking[sender])) # TODO: don't print, save diff somewhere
Diff function should work for every model (at the mommet I have four model classes). With deepcopy I can save old model, but I don't know how to save m2m fields because they are in separate table (yes, I know I can get this data, but at the momment of execution I don't know what fields are m2m and I wouldn't like to create different slot for every model). What I would like is generic solution, so I can just add models later without thinking about notification part.
My plan is to call get_data() and clear_data() functions after save() in view to clean diff that slots have generated.
Is this good way of doing this? Is there a better way? Is there django application that can do this job for me?
Excuse my English, it's not my native language.
First of all, you don't need to use deepcopy for this. Re-querying the sender from the database returns a "fresh" object.
def pre_save(sender, **kwargs):
pk = kwargs['instance'].pk
instance = sender.objects.get(pk=pk)
tracking[sender] = instance
You can get a list of all the many-to-many fields for a class, and check the values related to the current instance:
for field in sender._meta.local_many:
values = field.value_from_object(instance).objects.all()
# Now values is a list of related objects, which you can diff