I have a simple Employee
model that includes firstname
, lastname
and middlename
fields.
On the admin side and li
I recently worked on a library that may solve the problem you're having quite easily.
https://github.com/brechin/django-computed-property
Install that, add to INSTALLED_APPS and then
class Employee(models.Model):
...
name = computed_property.ComputedCharField(max_length=3 * 64, compute_from='full_name')
@property
def full_name(self):
return '{LAST}, {FIRST} {MIDDLE}'.format(LAST=self.lastname, FIRST=self.firstname, MIDDLE=self.middlename')
Ok... Daniel Roseman's answer seemed like it should have worked. As is always the case, you find what you're looking for after you post the question.
From the Django 1.5 docs I found this example that worked right out of the box. Thanks to all for your help.
Here is the code that worked:
from django.db import models
from django.contrib import admin
class Employee(models.Model):
lastname = models.CharField("Last", max_length=64)
firstname = models.CharField("First", max_length=64)
middlename = models.CharField("Middle", max_length=64)
clocknumber = models.CharField(max_length=16)
def _get_full_name(self):
"Returns the person's full name."
return '%s, %s %s' % (self.lastname, self.firstname, self.middlename)
full_name = property(_get_full_name)
class Meta:
ordering = ['lastname','firstname', 'middlename']
class EmployeeAdmin(admin.ModelAdmin):
list_display = ('clocknumber','full_name')
fieldsets = [("Name", {"fields":(("lastname", "firstname", "middlename"), "clocknumber")}),
]
admin.site.register(Employee, EmployeeAdmin)
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.
In this case if you are only going to use the field for representation in admin site and such issues, you might better to consider overriding str() or unicode() method of the class as it is mentioned in django documentation here:
class Employee(models.Model):
# fields definitions
def __str__(self):
return self.lastname + ' ,' + self.firstname + ' ' + self.middlename
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.