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?
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.
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 attributeage
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()
I think the solution is to create a custom widget for the birthday field. All you want is to change how the birthday is displayed and how it is edited. that is exactly the widget purpose.
Sorry I don't have the code for that, but here is a good place to start:
https://docs.djangoproject.com/en/dev/ref/forms/widgets/
精彩评论