5

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.

StringsOnFire
  • 2,726
  • 5
  • 28
  • 50
  • 2
    Might be good to use `pre_save` signal? https://docs.djangoproject.com/en/1.9/ref/signals/#pre-save here's an answer based on it: http://stackoverflow.com/questions/1355150/django-when-saving-how-can-you-check-if-a-field-has-changed#answer-7934958 – Shang Wang Jun 16 '16 at 19:11
  • While a signal could be used, it is considered a best practice to use them only as a last resort according to Two Scoops of Django. FYI. – FlipperPA Jun 16 '16 at 19:57
  • I haven't heard of it - is it written by the Django project developers? – StringsOnFire Jun 16 '16 at 19:58

4 Answers4

8

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.

Jieter
  • 4,101
  • 1
  • 19
  • 31
  • What does `if self.is_active and not self.__is_active` check? I don't know what the `__` changes – StringsOnFire Jun 16 '16 at 19:19
  • the `self.__is_active` is the value of the model before the changes were applied, as set in the `__init__` method. The check basically means: if is_active is True now, and was False before, update `active_from` – Jieter Jun 16 '16 at 19:28
  • this is a better answer than mine. – marcusshep Jun 16 '16 at 19:50
2

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.

StringsOnFire
  • 2,726
  • 5
  • 28
  • 50
1

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()
marcusshep
  • 1,916
  • 2
  • 18
  • 31
1

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

StringsOnFire
  • 2,726
  • 5
  • 28
  • 50