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.
Related
Just to note: I know about overwriting Model's .save() method but it won't suit me as I explained it at the end of my question.
In one of my projects, I've got more than of 30 database Models and each one of these models accept multiple CharField to store store Persian characters; However there might be some case where users are using Arabic layouts for their keyboard where some chars are differ from Persian.
For example:
The name Ali in Arabic: علي
and in Persian: علی
Or even in numbers:
345 in Arabic: ٣٤٥
And in Persian: ۳۴۵
I need to selectively, choose a set of these fields and run a function on their value before saving them (On create or update) to map these characters so I won't end up with two different form of a single word in my database.
One way to do this is overwriting the .save() method on the database Model. Is there any other way to do this so I don't have to change all my models?
Sounds like a good use of Django's Signals. With Signals you can call a function before or after save on one or more Models.
https://docs.djangoproject.com/en/3.2/topics/signals/
Django includes a “signal dispatcher” which helps decoupled applications get notified when actions occur elsewhere in the framework. In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place. They’re especially useful when many pieces of code may be interested in the same events.
django.db.models.signals.pre_save & django.db.models.signals.post_save
Sent before or after a model’s save() method is called.
https://docs.djangoproject.com/en/3.2/ref/signals/#pre-save
pre_save
django.db.models.signals.pre_save
This is sent at the beginning of a model’s save() method.
Arguments sent with this signal:
sender
The model class.
instance
The actual instance being saved.
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.
using
The database alias being used.
update_fields
The set of fields to update as passed to Model.save(), or None if update_fields wasn’t passed to save().
You can connect signals to a Single, Multiple, or All models in your application.
from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel
#receiver(pre_save)
def my_handler(sender, instance, **kwargs):
# logic here
In this case sender is the Model that is about to be saved, instance will be the object being saved.
You can create a completely custom model field however in your case it's really easier to just customize Django's CharField:
class MyCharField(models.CharField):
def to_python(self, value):
if value is None or value == "":
return value # Prevent None values being converted to "None"
if isinstance(value, str):
return self.func_to_call(value)
return self.func_to_call(str(value))
Replace your models CharField with MyCharField.
And create the .func_to_call() so it does whatever you need to map the values:
def func_to_call(self, value):
# Do whatever you want to map the values
return value
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.
it seems to me as if the post_save signal is triggered not when the entire save process has been finalized in the database, but at the end of the model.save() call.
I made this observation when executing the following code:
models.py
class PurchaseOrder(models.Model):
total_value = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
def purchase_order_post_save_handler(sender, instance, **kwargs):
project_po_value_update_thread = accounting.threads.ProjectPoValueUpdateThread(instance)
project_po_value_update_thread.start()
post_save.connect(purchase_order_post_save_handler, sender=PurchaseOrder)
threads.py
class ProjectPoValueUpdateThread(threading.Thread):
"""
thread to update the po.project with the latest po values
called via the save method of the purchase order
"""
def __init__(self, purchase_order):
self.purchase_order = purchase_order
threading.Thread.__init__(self)
def run(self):
"starts the thread to update project po values"
try:
project = self.purchase_order.project
#update active po budget
active_po_sum = PurchaseOrder.objects.filter(project=project, is_active=True).aggregate(total_value_sum=Sum('total_value'))
active_total_value_sum = active_po_sum['total_value_sum']
project.total_value = active_total_value_sum
project.save()
In some cases, this code did not update the project total_value correctly, as the instance (which I just saved) queried with PurchaseOrder.objects.filter(project=project, is_active=True) was obviously not updated. Thus it seems to me as the thread has overtaken the instance save method and queried an old version of the model.
I know how to overcome this specific problem (just take the latest value from the instance provided by the post_save signal), but I'm wondering how one could create a post_save signal that is triggered when the save action in the database finalized.
You are correct, the post_save signal is executed at the end of the save() method. That fact is in the Django docs:
django.db.models.signals.post_save
Like pre_save, but sent at the end of the save() method.
If your intent is to use a signal in several areas, you may be better suited to write your own.
edit: better link to signal definitions
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.
So I'm using a signal-triggered function on post_save to create instances of another model when the the first is saved:
The model triggering the signal:
class Product(models.Model):
# ...
colors = models.ManyToManyField(Color)
sizes = models.ManyToManyField(Size)
And the function:
def create_skus(instance, **kwargs):
for color in instance.colors.select_related():
for size in instance.colors.select_related():
SKU.objects.get_or_create(product=instance, color=color, size=size)
My issue is that create_skus should be called on post_save every time, but only seems to work on the 2nd save or after, resulting in users have to save a Product twice. What is the origin of this?
EDIT: I think this has something to do with how these M2M relations are added (i.e. instance.colors.add(<Color object>) but I'm not sure, and if you know of a workaround, I'd love you forever.
The signal is sent when the Product instance is saved, not when the Color and Size instances are saved. Therefore, on the first try, your post_save() function's Product instance will not (yet) have the Color and Size instances, as they are not saved through the Product model's save() method.
Check out these two links:
A possible solution posted by a fellow SO'er
You could also work with the m2m_changed signal.