Pabs
Pabs

Reputation: 46

Django model update on fetch

My model has a field that should change if it's within a date range.

It would look like this:

class Election(models.Model)
    start_date = models.DateTimeField(verbose_name = 'Start Date')
    end_date = models.DateTimeField(verbose_name = 'End date')
    active = models.BooleanField(default=False)

    def updateActive(self):
        now = timezone.now()
        if self.start_date < now and self.end_date > now:
            self.active=True
        else:
            self.active=False
        self.save()

RIght now, every time I query for this model, I call updateActive() from my views.py.

So, my question is: Is there a way to call updateActive() every time I fetch an Election object? Or keeping it constant updated?

Any idea is welcome.

Upvotes: 2

Views: 667

Answers (2)

e4c5
e4c5

Reputation: 53744

The best method would be not to have the active field at all in your model. The main reason is that when a value can be generated from a simple calculation, it should not be stored in the database. The second reason is that BooleanField cannot be effectively indexed and queries involving this field will be slow. Therefore you do not lose anything by doing the calculation instead of doing the field. The best way is to add a custom queryset like this:

class ElectionQuerySet(models.QuerySet):
    def is_active(self):
        return self.filter(start_date__lt=timezone.now()).filter(end_date__gt=timezone.now())

Now your model is really simple.

class Election(models.Model): start_date = models.DateTimeField(verbose_name = 'Start Date') end_date = models.DateTimeField(verbose_name = 'End date')

objects = ElectionQuerySet.as_manager()

Now your model is realy simple.

class Election(models.Model):
    start_date = models.DateTimeField(verbose_name = 'Start Date')
    end_date = models.DateTimeField(verbose_name = 'End date')

    objects = ElectionQuerySet.as_manager()

Yes that's all. There is no need to update the database everytime you fetch an object! You can use a simple method to find out what's active or not

Election.objects.is_active()

The result from is_active is a queryset, and you can chain it as usual

Election.objects.is_active().filter(...)

if you want to check if an election is active in the template you can do :

class Election(models.Model):
    def is_active()
         if self.start_date < now and self.end_date > now:
            return True

Upvotes: 2

Venu Dasarathy
Venu Dasarathy

Reputation: 26

You can write a custom manager for the model. While you can set the active attribute for the instances in the query itself, it doesn't seem correct from a design point of view(A get method shouldn't mutate the data it is querying). It makes sense to have an active attribute as you may want to invalidate a certain instance later manually. You could either update the active field using a background job, this way your manager would look like

class ElectionManager(models.Manager):
def get_queryset(self):
    return super().get_queryset().filter(active=True)
class Election(models.Model):
    start_date = models.DateTimeField(verbose_name = 'Start Date')
    end_date = models.DateTimeField(verbose_name = 'End date')
    active = models.BooleanField(default=False)
    elections = ElectionManager()

This way Election.elections.all() would return only active elections. If you want to filter out the query via a class method, then you can use list comprehension or generators to get the required queryset in the ElectionManager.get_queryset method.

Upvotes: 0

Related Questions