Django Model Method or Calculation as Field in Database
Asked Answered
H

2

15

Using Django ~=1.11 and Python 3.6

I need to store 'calculated' variables as fields in the Django model database.

Here's a model:

from django.db import models
from datetime import date

class Person(model.Model)
    "Last Name"
    last_name = models.CharField(max_length=25)

    "Birthday"
    birth_date = models.DateField()

    "City of birth"
    city_of_birth = models.CharField(max_length=25)

I am creating a Unique ID using these fields. Specifically, I'm conjoining parts of each field into one string variable (details below). I was able to get this to work as a Property but I don't know how to store a calculated field in the database.

"Unique ID"
def get_id(self):
    a = self.last_name[:2].upper()     #First 2 letters of last name
    b = self.birth_date.strftime('%d')     #Day of the month as string
    c = self.city_of_birth[:2].upper()     #First 2 letters of city
    return a + b + c 
unique_id = property(get_id)

I want to do a similar thing with Age. Here's what I have as a calculation:

"Age calculated from Birth Date"
def get_age(self):
    return int((datetime.date.now() - self.birth_date.days) / 365.25)
age = property(get_age)

So I'd like to store the UniqueID and Age variables in the database, as fields in the Person model. What is the best practice when doing these? Do I need to initialize the fields first, then do some sort of update query to these?

Note: It is my understanding that the current code using 'property' works for rendering in the view, but it is not stored in the database.

Thanks in advance! Please help me improve what I already have.

UPDATE: Here is code that worked for me. The problem was that I needed to drop the parentheses in the save() section, after self.unique_id=self.get_unique_id . It has been suggested to drop age from the database, and leave it as a property.

class Person(models.Model):
    unique_id = models.CharField(max_length=6, blank=True)
    last_name = models.CharField(max_length=25)
    birth_date = models.DateField()
    city_of_birth = models.CharField(max_length=25)

    @property
    def get_unique_id(self):
        a = self.last_name[:2].upper()     #First 2 letters of last name
        b = self.birth_date.strftime('%d')     #Day of the month as string
        c = self.city_of_birth[:2].upper()     #First 2 letters of city
        return a + b + c 

    @property
    def age(self):
        return relativedelta(self.birth_date.days, datetime.date.now()).years

    def save(self, *args, **kwarg):
        self.unique_id = self.get_unique_id
        super(Person, self).save(*args, **kwarg)

    def __str__(self):
        return self.unique_id
Headwork answered 28/6, 2017 at 14:24 Comment(2)
you already have most of the things done. You just have to create a custom save function for your model and that will do.Fabian
Please offer an answer with specific code. I have not found a functional answer so far.Headwork
S
27

You have to override the save method of yout Model Person and create unique_id and age field in the Model.

from dateutil.relativedelta import relativedelta
from datetime import datetime

class Person(model.Model)
     unique_id = models.CharField(max_length=25)
     age = models.IntegerField()
     last_name = models.CharField(max_length=25)
     birth_date = models.DateField()
     city_of_birth = models.CharField(max_length=25)
 
     @property
     def get_unique_id(self):
         a = self.last_name[:2].upper()     #First 2 letters of last name
         b = self.birth_date.strftime('%d')     #Day of the month as string
         c = self.city_of_birth[:2].upper()     #First 2 letters of city
         return a + b + c 
 
     @property
     def get_age(self):
         return relativedelta(self.birth_date.days, datetime.date.now()).years
     

     def save(self, *args, **kwargs):
          self.unique_id = self.get_unique_id
          self.age = self.get_age
          super(Person, self).save(*args, **kwargs)

UPDATE: Previously the self.get_unique_id and self.get_age were being called with '()' which is not required for class properties.

Sabu answered 28/6, 2017 at 14:40 Comment(4)
Also I suggest use relativedelta to compute the datetime diff.Sabu
I am searching how to use relativedelta but not sure yet how to do this. If you could edit your code, I would really appreciate it!Headwork
See above. The parentheses needed to be dropped after "self.get_unique_id()". Thank you!Headwork
But save() will run every single time you save(). Shouldn't you specify to do this only in creation?Ribbentrop
R
5

A model has a clean method for this kind of thing:

This method should be used to provide custom model validation, and to modify attributes on your model if desired. For instance, you could use it to automatically provide a value for a field, or to do validation that requires access to more than a single field

So loose the property and add a field named 'unique_id' which should be a CharField. Rename get_id to clean and replace the return statement with:

self.unique_id = a + b + c

If you're certain this always generates a unique string, consider making this field the primary key. However, if this model is already migrated, you cannot name it id, since Django has already created a field id as an AutoField for you, so it will take two migrations if you're set on the name 'id' (which you can squash later).

Roncesvalles answered 28/6, 2017 at 14:39 Comment(5)
Thank you. The problem I think I will now run into is in the forms. If I go into /Admin to create an object, it requires me to put a value in for UniqueID and Age. I want these to be calculated after the form is entered. I tried putting a "required=False" in the field type options but it gave me errors.Headwork
You can exclude the field from the ModelAdmin, since it uses ModelForm under the hood. Also, "blank=True" on the model field makes it not required, but for that to work, it cannot be a primary key.Roncesvalles
Any idea how to handle this? The ModelForm for this model wants these values when I click save. I am not sure how to include it in the view. Should I open another Q&A for this? Exception Value: save() missing 2 required positional arguments: 'get_unique_id' and 'get_age'Headwork
Please do. You've probably modified your code in a way that I cannot predict :)Roncesvalles
I appreciate your help. It appears I need to see code because your suggestions are not working for me. I am new to Python and Django.Headwork

© 2022 - 2024 — McMap. All rights reserved.