Django pre_save signal - django

I needed to be able to change my model data before it's saved, so I considered using pre_save handler to be the best option:
#receiver(pre_save, weak = False)
def pre_category_save(sender, **kwargs):
if kwargs['instance'].tags is None:
kwargs['instance'].tags = kwargs['instance'].__unicode__().replace(' -> ', ', ')
Under the instance key of kwargs I expected to find the actual model instance I'm saving, but instead I got an object of LogEntry class - that's the cause why my function fails returning this error: 'LogEntry' object has no attribute 'tags'. So - how can I fix that? Checking if instance has attribute tags is not a solution, because I always get only logentry object. I can eventually overload Model.save method, though I'd rather not do this.

You haven't specified the model class that's being received by this signal, so it's connected itself to all model saves - including LogEntry. Instead, do this:
#receiver(pre_save, sender=MyModel, weak=False)
...
See the documentation.

Related

Error getting ManyToMany field from an object

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.

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.

Getting model instance by variable model name and pk

I am using Django 1.9, and now trying to override save method behaviour. The problem is that when I do instance.some_field = some_value, the self object is already modified, whereas I need to know that was the original value of some_field. To do this, the only way out seems to be fetching object from database by self's pk.
So I wonder what is GENERIC syntax - if there's any - to fetch instance from the database? By saying GENERIC, I imply that I don't want to explicitly type the model name (like MYMODEL.objects.get(...)), but rather make Django figure out the right model based on target instance's model.
To make the question clearer, let me illustrate my goal in a pseudo-code:
def save_extented(self, *args, **kwargs):
original_object = (self model).objects.get(pk = self.pk)
Is it possible ? Or maybe I don't need this, and there's some smart Django hack to fetch the instance with rolled back field values ?
You can use django-dirtyfields and pre_save signal:
#receiver(pre_save, sender=MyModel)
def pre_save_logic(sender, instance, **kwargs):
if 'my_field' in instance.get_dirty_fields():
do_some_logic()

Django model.save() not working with loaddata

I have a model which is overriding save() to slugify a field:
class MyModel(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField(max_length=200)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(MyModel, self).save(*args, **kwargs)
When I run load data to load a fixture, this save() does not appear to be called because the slug field is empty in the database. Am I missing something?
I can get it to work by a pre_save hook signal, but this is a bit of a hack and it would be nice to get save() working.
def mymodel_pre_save(sender, **kwargs):
instance = kwargs['instance']
instance.slug = slugify(instance.name)
pre_save.connect(mymodel_pre_save, sender=MyModel)
Thanks in advance.
No you're not. save() is NOT called by loaddata, by design (its way more resource intensive, I suppose). Sorry.
EDIT: According to the docs, pre-save is not called either (even though apparently it is?).
Data is saved to the database as-is, according to https://docs.djangoproject.com/en/dev/ref/django-admin/#what-s-a-fixture
I'm doing something similar now - I need a second model to have a parallel entry for each of the first model in the fixture. The second model can be enabled/disabled, and has to retain that value across loaddata calls. Unfortunately, having a field with a default value (and leaving that field out of the fixture) doesn't seem to work - it gets reset to the default value when the fixture is loaded (The two models could have been combined otherwise).
So I'm on Django 1.4, and this is what I've found so far:
You're correct that save() is not called. There's a special DeserializedObject that does the insertion, by calling save_base() on the Model class - overriding save_base() on your model won't do anything since it's bypassed anyway.
#Dave is also correct: the current docs still say the pre-save signal is not called, but it is. It's behind a condition: if origin and not meta.auto_created
origin is the class for the model being saved, so I don't see why it would ever be falsy.
meta.auto_created has been False so far with everything I've tried, so I'm not yet sure what it's for. Looking at the Options object, it seems to have something to do with abstract models.
So yes, the pre_save signal is indeed being sent.
Further down, there's a post_save signal behind the same condition that is also being sent.
Using the post_save signal works. My models are more complex, including a ManyToMany on the "Enabled" model, but basically I'm using it like this:
from django.db.models.signals import post_save
class Info(models.Model):
name = models.TextField()
class Enabled(models.Model):
info = models.ForeignKey(Info)
def create_enabled(sender, instance, *args, **kwards):
if Info == sender:
Enabled.objects.get_or_create(id=instance.id, info=instance)
post_save.connect(create_enabled)
And of course, initial_data.json only defines instances of Info.