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.