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

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.

Related

populate a field with the result of subtracting two other fields

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.

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)

django - limit the number of users

(sorry for my english)
Only a question, exist any way to limit the number of users that can be created in a Django App?
I search in a lot of places and i only find this, but i see in the repo that the last update was 3 years ago https://github.com/1stvamp/django-limit-users
I don't know if exist any way in the core of django or if i have to override something!
Thanks very much!
While I don't have time to test https://github.com/1stvamp/django-limit-users against a new Django, it goes in the right direction using django's signals: https://docs.djangoproject.com/en/dev/ref/signals/
So for example, you could write a pre_save or a post_save handler and connect it to the signals emitted before / after saving your user model.
A simple post_save handler could look like:
def user_post_save(sender, instance, created, **kwargs):
if created and sender.objects.count() > MY_LIMIT:
instance.is_active = False
instance.save()
A simple pre_save handler would look like:
def user_pre_save(sender, instance, **kwargs):
if instance.id is None and sender.objects.count() > MY_LIMIT:
instance.is_active = False # Make sure the user isn't active
Instead of the last line in the pre_save handler, you could also raise an Exception to make sure the User isn't even saved to the DB.
Another option would be to combine this with a custom user model so instead of is_active you could use over_limit or whatever you want. The repo you linked is achieving that with a separate DisabledUser model.

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).

How to add Check Constraints for Django Model fields?

While subclassing db.models.Model, sometimes it's essential to add extra checks/constraints.
For example, I have an Event model with start_date and end_date: I want to add validation into the fields or the model so that end_date > start_date.
At least I know this can be done outside the models.Model inside the ModelForm validation. But how to attach to the fields and the models.Model?
I would not put constraints like these in the save method, it's too late. Raising an exception there, doesn't help the user who entered the data in the wrong way, because it will end up as a 500 and the user won't get the form with errors back etc.
You should really check for this in the Forms/ModelForms clean method and raise a ValidationError, so form.is_valid() returns false and you can send the errors in the form back to the user for correction.
Also note that since version 1.2, Django has had Model Validation.
It would look something like this:
class Foo(models.Model):
# ... model stuff...
def clean(self):
if self.start_date > self.end_date:
raise ValidationError('Start date is after end date')
As of Django 2.2, database level constraints are supported:
from django.db import models
from django.db.models import CheckConstraint, Q, F
class Event(models.Model):
start_date = models.DatetimeField()
end_date = models.DatetimeField()
class Meta:
constraints = [
CheckConstraint(
check = Q(end_date__gt=F('start_date')),
name = 'check_start_date',
),
]
Do it inside your save method of your model:
def save(self, *args, **kwargs):
if(self.end_date > self.start_date):
super(Foo, self).save(*args, **kwargs)
else:
raise Exception, "end_date should be greater than start_date"
As #stefanw says, it's better user experience to check in the form's clean method.
This is enough if you're very sure that there isn't, and never will be, another way to change the value. But since you can rarely be sure of that, if database consistency is important, you can add another check (in addition to the form), one of:
The easier and database-independent way is in the model's save method as #umnik700 said. Note that this still doesn't prevent other users of the database (another app, or the admin interface) from creating an inconsistent state.
To be 'completely' sure the database is consistent, you can add a database level constraint. E.g. you can create a migration with RunSQL and SQL, something like (not tested):
migrations.RunSQL('ALTER TABLE app_event ADD CONSTRAINT chronology CHECK (start_date > end_date);')
(Not tested). This may be database dependent, which is a downside of course.
In your example, it's probably not worth it (incorrect start/end times just look a bit weird, but affect only the one inconsistent event), and you don't want manual schema changes. But it's useful in cases where consistency is critical.
EDIT: You can also just save the start time and the duration, instead of the start and end times.
As of today, both postgres 9.4 and MS SQL Server >= 2008 support check constraints in sql. On top of this, there is django issue 11964 which seems to be ready for review since yesterday, so hopefully we'll see this integrated into django 2. The project rapilabs/django-db-constraints seems to implement this too.
Summarizing the answers from before, here is a complete solution I used for a project:
from django.db import models
from django.db.models import CheckConstraint, Q, F
from django.utils.translation import gettext_lazy as _
class Event(models.Model):
start_date = models.DatetimeField()
end_date = models.DatetimeField()
class Meta:
constraints = [
# Ensures constraint on DB level, raises IntegrityError (500 on debug=False)
CheckConstraint(
check=Q(end_date__gt=F('start_date')), name='check_start_date',
),
]
def clean(self):
# Ensures constraint on model level, raises ValidationError
if self.start_date > self.end_date:
# raise error for field
raise ValidationError({'end_date': _('End date cannot be smaller then start date.')})
Too bad there is no django.core.validators that can handle this :(