How to add a calculated field to a Django model
Daniel Roseman's solution makes a calculated field an attribute of a Model
, however it does not make it accessible via QuerySet methods (eg. .all()
, .values()
). This is because QuerySet methods call the database directly, circumventing the django Model
.
Since QuerySets access the database directly, the solution is to override the Manager
's .get_queryset()
method by appending your calculated field. The calculated field is created using .annotate()
. Finally, you set the objects
Manager in your Model
to your new Manager
.
Here is some code demonstrating this:
models.py
from django.db.models.functions import Value, Concat
from django.db import Model
class InvoiceManager(models.Manager):
"""QuerySet manager for Invoice class to add non-database fields.
A @property in the model cannot be used because QuerySets (eg. return
value from .all()) are directly tied to the database Fields -
this does not include @property attributes."""
def get_queryset(self):
"""Overrides the models.Manager method"""
qs = super(InvoiceManager, self).get_queryset().annotate(link=Concat(Value("<a href='#'>"), 'id', Value('</a>')))
return qs
class Invoice(models.Model):
# fields
# Overridden objects manager
objects = InvoiceManager()
Now, you will be able to call .values()
or .all()
and access the newly calculated link
attribute as declared in the Manager
.
It would have also been possible to use other functions in .annotate()
, such as F()
.
I believe the attribute would still not be available in object._meta.get_fields()
. I believe you can add it here, but I haven't explored how - any edits/comments would be helpful.
That's not something you do as a field. Even if that syntax worked, it would only give the value when the class was defined, not at the time you access it. You should do this as a method, and you can use the @property
decorator to make it look like a normal attribute.
@property
def name(self):
return ''.join(
[self.lastname,' ,', self.firstname, ' ', self.middlename])
self.lastname
etc appear as just their values, so no need to call any other method to convert them.