StringsOnFire
StringsOnFire

Reputation: 2786

How can I update a Django models.DateTimeField when another, specific field in the same model is updated?

I have two fields in a model here:

is_active = models.BooleanField(default=False)
active_from = models.DateTimeField(blank=True)

When is_active is set to True, I'd like active_from to be updated to the current datetime.

How do I go about doing this? I'm open to alternatives if there's a cleaner way of doing this with one field as well.

Thanks!

EDIT: I'd like to contain this within the model to keep things encapsulated. This will be part of an API.

Upvotes: 6

Views: 20226

Answers (4)

Jieter
Jieter

Reputation: 4229

One way to do it is to define a save() method on your custom model looking for change in is_active. There is no easy way to achieve this: you need to manually save the previous state of the value of is_active by defining a custom __init__-method. It could look something like this:

class MyModel(models.Model):
    is_active = models.BooleanField(default=False)
    active_from = models.DateTimeField(blank=True)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__is_active = self.active

    def save(self, *args, **kwargs):
        if self.is_active and not self.__is_active:
            self.active_from = datetime.now()
        super().save(*args, **kwargs)

This question has some answers which might help.

Upvotes: 9

StringsOnFire
StringsOnFire

Reputation: 2786

Another alternative is the FieldTracker from django-model-utils. I'm going to be using this moving forwards, as it makes more complex manipulation easier during save().

Upvotes: 1

StringsOnFire
StringsOnFire

Reputation: 2786

This is what I've pieced together so far. It seems like the most Django-esque way of doing this, but I'm happy to be corrected if not.

from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
import datetime

class MyModel(models.Model):
    is_active = models.BooleanField(default=False)
    active_from = models.DateTimeField(blank=True)

# Set active_from if active is set in an update
@receiver(pre_save, sender=MyModel)
def set_active_from_on_update(sender, instance, update_fields, **kwargs):
    if 'is_active' in update_fields and instance.is_active is True:
            instance.active_from = datetime.now()

# Set active_from if active is set on create
@receiver(post_save, sender=MyModel)
def set_active_from_on_create(sender, instance, created, **kwargs):
    if created and instance.is_active is True:
        instance.active_from = datetime.now()

My reasoning: update_fields in pre_save seems like the right place for any logic based on particular fields updating, but pre_save doesn't know if instance will be a new entry in the database or not, so post_save is needed to use the create boolean.

I think I could also do away with is_active and set active_from to null when it isn't active, but that doesn't seem as safe.

Upvotes: 2

marcusshep
marcusshep

Reputation: 1964

When you toggle is_active update active_from at the same time.

for example:

def toggle_user_active_and_update(request, *a, **kw):
    request.user.is_active = !request.user.is_active
    request.user.active_from = datetime.datetime.now()
    request.user.save()

Upvotes: 1

Related Questions