Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a Django model fields calculated at runtime?

I have a model:

class Person (models.Model):
    name     = models.CharField ()
    birthday = models.DateField ()
    age      = models.IntegerField ()

I want to make age field to behave like a property:

    def get_age (self):
        return (datetime.datetime.now() - self.birthday).days // 365

    age = property (get_age)

but at the same time I need age to be a true field, so I can find it in Person._meta.fields, and assign attributes to it: age.help_text = "Age of the person", etc.

Obviously I cannot just override Person.save() method to calculate and store age in the database, because it inevitably will become wrong later (in fact, it shouldn't be stored in the database at all).

Actually, I don't need to have setters now, but a nice solution must have setting feature.

Is it possible in Django, or probably there is a more pythonic and djangoic approach to my problem?

like image 976
Anatoly Rr Avatar asked Apr 02 '10 13:04

Anatoly Rr


3 Answers

you're going about it in a strange way. you only need to store the birthday (which you're already doing).

if you need to do queries based on age, take in the age, and then search on the birthdays that fit the requirements.

from datetime import datetime

# get people over 50
age = 50 # in years
age_ago = datetime.now() - age # use timedelta i don't know the syntax off the top of my head
Person.objects.filter(birthday__lte=age_ago) # people whose birthday is before fifty years ago

you said it yourself "[age] shouldn't be stored in the database at all"

you're right. there is no reason to have the age field... just the property will be fine.

like image 55
Brandon Henry Avatar answered Nov 12 '22 20:11

Brandon Henry


You can achieve this by using django-computed-property.

You first have to pip install django-computed-property with the following command:

pip install django-computed-property

You then add computed_property to your list of INSTALLED_APPS in settings.py:

INSTALLED_APPS = [
    ...
    'computed_property'
]

Now you can import and use the included field classes in your models like so:

from django.db import models
from computed_property import ComputedTextField

import random


class Person(models.Model):
    age = ComputedTextField(compute_from='calculation')

    @property
    def calculation(self):
        return str(random.randint(1,101))

The age field on the Person model returns a random number each time it is called to demonstrate that it is calculated at runtime.

You can read values from the age field as usual, but you may not set the field’s value. When the field is accessed and when a model instance is saved, it will compute the field’s value using the provided callable (function/lambda), property age, or attribute age

like image 2
Elisha Senoo Avatar answered Nov 12 '22 19:11

Elisha Senoo


Another way to go about it which might be simpler, is to use custom form in the admin model. Something like this:

class Person (models.Model):
    birthday = models.DateField()

class PersonForm(forms.ModelForm):
    age = forms.IntegerField() # This will not be in the database
    class Meta:
        model = Person
    def __init__(self, *args, **kwargs):
        # verify instance was passed
        instance = kwargs.get('instance')
        if instance:
            self.base_fields['age'].initial = (datetime.datetime.now() - self.birthday).days
        super(PersonForm, self).__init__(*args, **kwargs)

class FooAdmin(admin.ModelAdmin):
    form = PersonForm
    # Save the age in the birthday field
    def save_model(self, request, obj, form, change):
       obj.birthday = datetime.datetime.now() + form.cleaned_data['age']
       obj.save()
like image 1
idanzalz Avatar answered Nov 12 '22 18:11

idanzalz