populate a field with the result of subtracting two other fields - django

i have the following model
class FeeModel(models.Model):
user=models.ForeignKey(User,on_delete=models.CASCADE,null=True)
total_fee=models.IntegerField(default=100000)
paid_fee=models.IntegerField()
remaining_fee=models.IntegerField(default=0)
i need the remaining_fee to be filled by the result of (total_fee - paid_fee). How would i do that?

You can do this in numerous ways and places. A pre_save signal is one approach.
(In models.py, below your FeeModel class)
from django.db.models.signals import pre_save
from django.dispatch import receiver
#receiver(pre_save, sender=FeeModel)
def set_remaining_fee(sender, instance, *args, **kwargs):
instance.remaining_fee = (instance.total_fee - instance.paid_fee)
How this works: A user enters the values for total_fee and paid_fee into a form. Upon submit and just before save(), the signal calculates the difference and applies it to the remaining_fee field. Note that this will run every time the model instance is saved.

Related

How to trigger Django's pre_delete signal?

I have a User model and a UserImage model that contains a foreign key to a User. The foreign key is set to CASCADE delete.
Here is what the receivers look like in my models.py:
#receiver(pre_delete, sender=User)
def deleteFile(sender, instance, **kwargs):
print("User pre_delete triggered")
instance.thumbnail.delete()
#receiver(pre_delete, sender=UserImage)
def deleteFile(sender, instance, **kwargs):
print("UserImage pre_delete triggered")
instance.image.delete()
When I execute the following lines of code:
>>> User.objects.last().delete()
"UserImage pre_delete triggered"
For some reason the associated UserImage signal is being received but the actual User model's signal is not.
Am I missing something?
If you read the documentation carefully you will see that the delete() method on a model will execute purely in SQL (if possible). So the delete() method on UserImage will not be called by Django, thus the signal will not be triggered. If you want it to be triggered you could override the delete method on your User model to also call the delete() on the related object. Something like this:
class User(models.Model):
def delete(self, using=None):
self.userimage_set.all().delete()
super().delete(using=using)
UPDATE:
I did not read the question correctly so I have to update my answer. I think what is happening is that both signals have the same name and thus the first one is overwritten by the second one, and thus only the second one is executed. I would suggest changing the function name to something else and see if that changes things.

Distinguish which field has changed in signal.instance , Django/signals

Lets say I have a model called BookModel with 4 fields : (title, author, price, publish_year).
And I have a handler in signals:
#receiver([post_save, post_delete], sender=BookModel)
def signal_handler(sender, instance, **kwargs):
…..
Question is how to distinguish a situation when specific model field has changed during save(). For example if price has changed I want to do stuff. Better explain in pseudo code...
#receiver([post_save, post_delete], sender=BookModel)
def signal_handler(sender, instance, **kwargs):
# pseudo code bellow
if field “price” has changed:
do stuff
else:
do nothing
According the docs if I use “update_fields” in save() - it is possible, but what if I dont use it???
Also is it possible to distinguish a situation when I received signal from post_save or from post_delete still using 1 handler?
#receiver([post_save, post_delete], sender=BookModel)
def signal_handler(sender, instance, **kwargs):
# pseudo code bellow
if signal is post_save:
if field “price” has changed:
do stuff
else:
do nothing
else:
do other stuff
Thanks
You can try django-model-utils's FieldTracker to track changes in model fields. It also use with post_save signal.
Checking changes using signals
The field tracker methods may also be used in pre_save and post_save signal handlers to identify field changes on model save.

How to avoid race condition in updating model field to newest timestamp

I need to keep track of the most recent time a user was mentioned in a post, and update this field each time a new post is created based on it's post time.
My current code looks like this:
from django.db.models.signals import post_save
from django.dispatch import receiver
from messageboard.models import Post
#receiver(post_save, sender=Post)
def user_last_mentioned_updater(sender, instance, **kwargs):
for users in instance.mentions:
user.last_mentioned = max(user.last_mentioned, instance.timestamp)
user.save()
However, if two posts are processed concurrently, this could potentially leave the last_mentioned field at the timestamp of the earlier post.
Unfortunately, F doesn't support the max operation, when I try it I get a TypeError: unorderable types: datetime.datetime() > F():
user.last_mentioned = max(F('last_mentioned'), instance.timestamp)
How can I avoid this race condition?
If it matters, for the moment I'm using Postgresql for the ORM, although this may be subject to change.
Here's a version that should be free of race conditions and more efficient:
#receiver(post_save, sender=Post)
def user_last_mentioned_updater(sender, instance, **kwargs)
User.objects.filter(
id__in=[u.id for u in instance.mentions],
last_mentioned__lt=instance.timestamp,
).update(last_mentioned=instance.timestamp)
That is, we select the mentioned users who's timestamps need updating, and update them, all in a single SQL statement.

Django models: Set default relative to another field

I am building an app using Django 1.10 as backend.
Is it possible to set a model field's default relative to another model from the same instance?
I specifically need to set second_visit's default to be 3 weeks after the first_visit
class SomeModel(models.Model):
first_visit = models.DateField()
second_visit = models.DateField(default= second_visit_default)
def second_visit_default(self):
# Set second_visit to 3 weeks after first_visit
You cannot assign a default value on a field dependent on another before having a model instance. To achieve the same you can override the save() method of the model:
class SomeModel(models.Model):
...
def save(self, *args, **kwargs):
self.second_visit = self.first_visit + datetime.timedelta(weeks=3)
super().save(*args, **kwargs)
You can override save or usepre_save
from django.db.models.signals import pre_save
from django.dispatch import receiver
#receiver(pre_save, sender=SomeModel)
def my_handler(sender, instance, **kwargs):
instance.second_visit = # Set second_visit to 3 weeks after instance.first_visit
This is a late answer, but #BjornW addresses a valid concern in the comment on the accepted answer that is not addressed in the 2 provided answers (at the time of writing): Overwriting save is not the same thing as setting a default value; a default value only takes effect once, at the first instance creation, while modifying save affects every subsequent modification of an existing instance as well. The 2nd answer suffers from the same deviation of the principle of a default value.
Providing a default value relative to other attributes of self is not (yet?) possible, however you can use the pre_save signal to set a default value for a field that is relative to other fields.
Here would be an example:
# signals.py
from django.dispatch import receiver
from django.db.models.signals import pre_save
# do all necessary imports e.g.:
from .models import SomeModel
#receiver(pre_save, sender=SomeModel)
def set_default_second_visit(sender, instance, raw, **kwargs):
"""
Set default value for `second_visit`
"""
if instance.pk is None:
instance.second_visit = instance.second_visit or instance.first_visit + datetime.timedelta(weeks=3)
A note might be at hand:
It is not that the pre_save signal is sent only once. Actually, it is sent whenever an instance of SomeModel is saved, i.e. also if an existing entry from the db is modified. The line if instance.pk is None: is present and necessary exactly for this reason: it makes sure that that the value is set only if the object does not yet exist in the database (i.e has no primary key yet).

text manipulation in django as soon as user/admin enters it

i have a simple model:
class Article(models.Model):
name = models.CharField(max_length=1000)
custom_name = models.CharField(max_length=1000)
custom function:
def process_text(my_string):
return len(my_string)
i want the following:
custom_name = process_text(name)
Suppose the admin enters name as Mark Pilgrim then custom_name should have the auto populated value of 12.
in the admin.py can i have something like
prepopulated_fields
what would be an easy way to go about it.
Thanks!!
The easiest way to do this is to add a method that listens on the pre_save signal.
Here is a sample you can use (this code goes in your models.py for the app)
from django.db.models.signals import pre_save
from django.dispatch import receiver
# Your models go here
def process_text(mystring):
return len(mystring)
#receiver(pre_save, sender=Article)
def my_handler(sender, **kwargs):
if not kwargs['raw']:
obj = kwargs['instance']
obj.custom_name = process_string(obj.name)
The signals documentation has more information on signals, and the pre_save documentation lists what arguments the method expects.
If you have form with those fields then you can use some javascript to update the second field when the first lost focus. Then when someone will enter something in to the first field and leave the field, js can calculate length and put the value in second field.
If this should be done in the backend level - then you can for example override model's save() method. More here - https://docs.djangoproject.com/en/dev/topics/db/models/#overriding-predefined-model-methods
Cheers.
In my opinion the best option is to set the value on the form validation stage.