Ed.
Ed.

Reputation: 4577

Dynamic field calculations in Django

I'm making a todo list in django. I have a model with some properties:

class Action(models.Model):
    name = models.CharField("Action Name", max_length=200, unique = True)
    slug = models.SlugField(max_length=100, blank=True)

    complete = models.BooleanField(default=False, verbose_name="Complete?")

    effort = models.IntegerField("Effort Level", choices = LEVELS, default = 3)
    importance = models.IntegerField("Importance", choices = LEVELS, default = 3)
    enjoyment = models.IntegerField("Enjoyment", choices = LEVELS, default = 3)

    days_to_expiration = models.IntegerField("Days to Expiration", choices = DAYS_TO_EXPIRATION, default = 7)

    reset_date = models.DateField("Date of Action Reset", blank=True, null=True)

I want to sort based on priority. I define priority as:

priority = (1 + (today's date - reset_date) / days_to_expiration) * importance

This just scales up importance based on how "overdue" the task is.

Now when I pull a list of incomplete actions, I can calculate this priority number for each action item, but that doesn't seem efficient since I will end up calculating the same numbers multiple times. I could define a new field in the model:

priority = models.DecimalField("Priority", max_digits=4, decimal_places=2, blank = True, null = True)

and schedule a function to run once a day to calculate the new priority per task.

But I want to know if it's possible to program the priority calculation directly into the model. So the priority field in the Action model is necessarily by design a function of importance and days_to_expiration. It would be updated dynamically using the values of importance, days_to_expiration, and today's date. That way, priority would always be calculated automatically when I need it. Is this a possibility?

Upvotes: 0

Views: 1885

Answers (3)

Suliman Mukhtar
Suliman Mukhtar

Reputation: 46

I know its old , but I've faced a similar issue and solve it by overriding the default Manager of the model and overriding its get_queryset method then annotate the field in the database level .

something like

class ActionManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().annotate(
            priority=ExpressionWrapper(
                (1 + (datetime.now().date() - F('reset_date')) / F('days_to_expiration')) * F('importance'),
                output_field=models.DecimalField()
            )
        )

haven't test it but you should know the context

Upvotes: 1

Chris Pratt
Chris Pratt

Reputation: 239250

You could do something like:

class MyModel(models.Model):
    ...
    @property
    def priority(self):
        return (1 + (date.today() - self.reset_date) / self.days_to_expiration) * self.importance

Then, you can access it like any other attribute on your model

priority = my_model.priority

Upvotes: 1

Ismail Badawi
Ismail Badawi

Reputation: 37177

What you want is called denormalization; priority is a denormalized field. You can probably hack around with signals, or extend save methods, or maybe use manager methods in some clever way to achieve this, but a nice alternative is to use django-denorm. The tutorial goes through a situation very similar to yours.

Upvotes: 2

Related Questions