Error getting ManyToMany field from an object - django

How to execute some functionality, after creating an object in the admin area? I'm trying to use post_save signal and trying to get all objects from my field, which has type ManyToMany, I also use sorting package (sortedm2m). When I save the object I try to output this field, but when I create I get an empty queryset, and when I edit I get the old queryset, without the current changes.
class Servers(models.Model):
name = models.CharField(max_length=120, default="name")
content = SortedManyToManyField(Content)
#receiver(post_save, sender=Servers)
def create_server(sender, instance, **kwargs):
print(instance.content.all())

You have to use m2m_changed
Otherwise you can not be able to catch manytomany fields in signal.

Related

Djangos m2m_changed trigger doesn't trigger properly

I have a model that looks as follows and I wish to trigger a method every time the user_ids field get's changed. Using the post_save signal obviously didn't do anything, as ManyToMany relationships are special in that way.
class Lease(models.Model):
unit = models.ForeignKey(Unit, on_delete=models.CASCADE)
user_ids = models.ManyToManyField('user.User')
Using the m2m_changed trigger as follows also didn't do anything, which got me puzzled. I don't really understand what is wrong with this code also having tried to leave the '.user_ids' out. There are no errors or anything, it just doesn't trigger when the user_ids from the Lease model are changed.
#receiver(m2m_changed, sender=Lease.user_ids)
def update_user_unit(sender, instance, **kwargs):
print('Test')
Reading the documentation, I suppose the sender should be the intermediate model, not the ManyToMany field itself. Try this:
#receiver(m2m_changed, sender=Lease.user_ids.through)

Get Related Model From M2M Intermediate Model

In signals.py I am catching #receiver(m2m_changed, sender=Manager.employees.through).
This is getting the signal sent when a m2m relationship is created between a Manager and an Employee.
I am trying to get the Employee that this particular relationship is referencing.
I am guessing sender is the 'through' relationship object, but really I'm not sure.
If I print(sender) I get <class 'users.models.Manager_employees'>.
I have tried referenced_employee = sender.employee_id, but this gives me <django.db.models.fields.related_descriptors.ForeignKeyDeferredAttribute object at 0x03616310>.
print(sender['employee_id']) gives me 'ModelBase' object is not subscriptable.
print(sender.employee_id) gives me an error 'ModelBase' object is not subscriptable.
I'm really just trying everything I can think of at this point.
Thank you.
Like sender, the signal also pass other arguments.
#receiver(m2m_changed, sender=Manager.employees.through)
def my_signal_receiver(sender, **kwargs):
# kwargs is a dictionary
for key, value in kwargs.items():
print(key, value)
Take the following example:
an_employee = Employee.objects.create(name='Brenden')
my_manager.employees.add(an_employee)
You will have the following items in the dictionary:
kwargs['instance'] is the instance of the model being changed. In the example above, it will be my_manager
kwargs['model'] is the class being added. In this case Employee and
kwargs['pk_set'] will be {an_employee.id,}, a set of the keys being added, so you could do something like
my_employee = kwargs['model'].objects.get(id=kwargs['pk_set'][0])

Saving model instance using pre_save signal

These are my models:
class Stockdata(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,null=True,blank=True,related_name='user_stock')
company = models.ForeignKey(Company,on_delete=models.CASCADE,null=True,blank=True)
stock_name = models.CharField(max_length=32)
class Stock_journal(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,null=True,blank=True,related_name='user_closing')
company = models.ForeignKey(Company,on_delete=models.CASCADE,null=True,blank=True)
stockitem = models.OneToOneField(Stockdata,on_delete=models.CASCADE,null=True,blank=True,related_name='closingstock')
closing_stock = models.DecimalField(max_digits=10,decimal_places=2,null=True)
This is my signal:
#receiver(post_save, sender=Stockdata)
def create_default_stock_ledger(sender, instance, created, **kwargs):
if created:
Stock_journal.objects.create(user=instance.User,company=instance.Company,stockitem=instance)
I want to pass a pre_save signal of the same as I have done in my post_save signal i.e. I want to perform a pre_save signal function instead of a post_save signal..
When I try to do using pre_save signal I get the following error:
save() prohibited to prevent data loss due to unsaved related object 'stockitem'.
Any idea how to do this?
Thank you
You are assigning unsaved stockitem(Stockdata) object to a OneToOneField and thus it raises an error.
When you are assigning stockitem(Stockdata) object to OneToOneField, Id is not generated as you haven't saved stockitem object and thus as error says it will cause a data loss while saving Stock_journal model.
pre_save has different arguments than post_save. When you use created, you are actually using raw.
At that point when you call for Stock_journal.objects.create, you instance is not even saved (i.e. exist in database), thus you can't use instance in Stack_journal creation.
More about raw from django docs:
raw -
A boolean; True if the model is saved exactly as presented (i.e. when loading a fixture). One should not query/modify other records in
the database as the database might not be in a consistent state yet.

How to delete an object in response to a m2m_changed signal in Django?

I have two models Image and Category related via a m2m relation (defined in Category). Images may be under several categories. The API allows to remove an image from a category. In response to that I need to remove the image when it has no categories.
I have the following:
#receiver(m2m_changed, sender=Category.images.through)
def delete_image_if_has_no_categories(sender, instance, action, reverse,
model, pk_set, **kwargs):
# we only watch reverse signal, because in other cases the images are
# being moved or copied, so don't have to be deleted.
if reverse and action == 'post_remove':
if not instance.categories.exists():
instance.delete()
I have placed several debug logs to check the code is being run. And it runs. But the images remain in the DB after the instance.delete().
I have the remove_from_category view inside a transaction.atomic, but it does not help.
Any ideas?
Update
The view call this method in our Image model:
def remove_from_category(self, category_id):
self.categories.remove(category_id)
The view is called via a REST API like this DELETE /category/<catid>/<image-id>.
The images field in the Category model is defined like this:
class Category(MPTTModel):
images = models.ManyToManyField(
'Image',
related_name='categories',
null=True, blank=True,
)
Would the MPTTModel be the culprit? I'm using django-mptt==0.6.0.
I guess u can call ur method to remove when the selected item that u want delete appeas on pre_clear method (django always save the last change in ur m2m field in the pre_clear, so if the attribute is there but didnt on ur post_add this obj is being deleted, so u can force trigger ur function there
check this answer mine
https://stackoverflow.com/a/39331735/6373505

How to use save_model in an AdminForm containing a M2M field?

i'm having an annoying issue with django model system + its default admin.
Let's assume i have a very simple model like:
class Note(models.Model):
text = models.CharField(max_length=200)
def __unicode__(self):
return self.text
and a container like:
class NoteCollection(models.Model):
notelist = models.ManyToManyField(Note)
title = models.CharField(max_length=20)
def __unicode__(self):
return self.title
What i want do to it's update all the "Note" elements when a NoteCollection gets Added.
I read that m2m models have complex save mechanism, so what i was thinking is, let's read the form object, and just save the Note elements by myself!!
But when i make something like this in APPNAME/admin.py:
from models import Note,NoteCollection
from django.contrib import admin
class NoteCollectionAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# Something USEFUL HERE
pass
admin.site.register(Note)
admin.site.register(NoteCollection, NoteCollectionAdmin)
Django pops me an error: ('NoteCollection' instance needs to have a primary key value before a many-to-many relationship can be used.)
I don't even want to use the NoteCollection object at all, i'm interested in the form object, actually..
I've also found on internet some examples that use save_model with a M2M field, so i can't understand why i keep getting this error; for reference, i've just made a new-from-scrap project and i'm using an sqlite db for testing
By overriding save_model() in NoteCollectionAdmin you're preventing Django from saving your notecollection. After handling everything, Django saves the m2m table, but fails because the notecollection doesn't have an automatic ID as you didn't save it in the database.
The main problem is that Django saves m2m files after saving the objects. I tangled with that a few days ago, see http://reinout.vanrees.org/weblog/2011/11/29/many-to-many-field-save-method.html
Somewhat related question: Issue with ManyToMany Relationships not updating inmediatly after save