I have a model, Foo
. It has several database properties, and several properties that are calculated based on a combination of factors. I would like to present these calculated properties to the user as if they were database properties. (The backing factors would be changed to reflect user input.) Is there a way to do this with the Django admin interface?

- 119,074
- 188
- 476
- 699
-
Do you want to present them in the admin change list or in the form? – Bernhard Vallant Feb 03 '11 at 20:45
-
@lazerscience I'm not sure what the admin change list is. Definitely the form. – Nick Heiner Feb 03 '11 at 21:07
-
If you want them presented (shown, not editable) in the form, and they're available as methods on the model, then you can just copy the file "change_form.html" to your ${TEMPLATE_ROOT}/admin/APPNAME/MODELNAME/change_form.html and edit it to your heart's content. – Elf Sternberg Feb 03 '11 at 21:37
-
@Elf I do want them to be editable. I want them to act like normal fields, except instead of being saved to a db I get to do processing to update the data accordingly. – Nick Heiner Feb 03 '11 at 21:48
-
As the Django manual says, "The admin is for trusted users editing structured content. Full stop." At this point, you're exceeding the structured content description of the admin, and the recommendation from JKM et. al. is to write your own view that does what you want it to. You're just torturing yourself trying to "do it the Admin way." The admin is a nice tool, but it should not be the be-all and end-all of your administrative toolkit. If you have needs that exceed it, write your own and then hack the admin presentation layer to provide links to it. – Elf Sternberg Feb 05 '11 at 01:32
4 Answers
I would suggest you subclass a modelform for Foo
(FooAdminForm) to add your own fields not backed by the database. Your custom validation can reside in the clean_*
methods of ModelForm.
Inside the save_model
method of FooAdmin
you get the request, an instance of Foo
and the form data, so you could do all processing of the data before/after saving the instance.
Here is an example for a model with a custom form registered with django admin:
from django import forms
from django.db import models
from django.contrib import admin
class Foo(models.Model):
name = models.CharField(max_length=30)
class FooAdminForm(forms.ModelForm):
# custom field not backed by database
calculated = forms.IntegerField()
class Meta:
model = Foo
class FooAdmin(admin.ModelAdmin):
# use the custom form instead of a generic modelform
form = FooAdminForm
# your own processing
def save_model(self, request, obj, form, change):
# for example:
obj.name = 'Foo #%d' % form.cleaned_data['calculated']
obj.save()
admin.site.register(Foo, FooAdmin)
Providing initial values for custom fields based on instance data
(I'm not sure if this is the best solution, but it should work.)
When a modelform for a existing model instance in the database is constructed, it gets passed this instance. So in FooAdminForm's __init__
one can change the fields attributes based on instance data.
def __init__(self, *args, **kwargs):
super(FooAdminForm, self).__init__(*args, **kwargs)
# only change attributes if an instance is passed
instance = kwargs.get('instance')
if instance:
self.fields['calculated'].initial = (instance.bar == 42)

- 2,423
- 27
- 30

- 11,936
- 1
- 49
- 41
-
Looks good. However, when I use a `CheckboxInput` instead of `IntegerField`, it doesn't show up. Also, how can I set the initial value of the checkbox? It says that the `check_true` function will be given the "value" of the field, but how do I set what that value is? – Nick Heiner Feb 04 '11 at 01:17
-
Use a `BooleanField` (it uses CheckboxInput as its default widget). Form fields in django accept a `initial` keyword argument, for example: `bar = forms.BooleanField(initial=True)` – Reiner Gerecke Feb 04 '11 at 01:22
-
good to know. How can I set the initial value as a function of the backing model instance? – Nick Heiner Feb 04 '11 at 01:24
-
@Rosarch I have extended my answer with a possible solution. (I wasn't sure if you get notified about that change, hence the comment) – Reiner Gerecke Feb 04 '11 at 01:41
-
1+1, Really nice! Only comment: use `super(FooAdminForm, self).__init_(...)` instead of `ModelForm.__init__(self,...)`. Either one works, but `super` is preferred. – brianmearns Dec 20 '13 at 02:01
-
Good lord please don't use `base_fields`. That's a class attribute! Instead call `__init__` first (and please use `super`) and then use `self.fields`. – DylanYoung Aug 24 '21 at 17:08
-
Also note, that you can override `save` on the model form instead, just call `super` with `commit=False` (which will return the object unsaved), modify your model as appropriate, call `save` on the object, then call `self.save_m2m()` on the form. – DylanYoung Aug 24 '21 at 17:14
It's easy enough to get arbitrary data to show up in change list or make a field show up in the form: list_display
arbitrarily takes either actual model properties, or methods defined on the model or the modeladmin, and you can subclass forms.ModelForm
to add any field type you'd like to the change form.
What's far more difficult/impossible is combining the two, i.e. having an arbitrary piece of data on the change list that you can edit in-place by specifying list_editable
. Django seems to only accept a true model property that corresponds to a database field. (even using @property
on the method in the model definition is not enough).
Has anyone found a way to edit a field not actually present on the model right from the change list page?

- 232,153
- 36
- 385
- 444
In the edit form, put the property name into readonly_fields (1.2 upwards only).
In the changelist, put it into list_display.

- 588,541
- 66
- 880
- 895
-
2But I do want the property to be editable - it just doesn't correspond directly to a database row, I need to do my own processing to change the backend data accordingly. – Nick Heiner Feb 03 '11 at 21:48
You can use the @property decorator in your model (Python >= 2.4):
class Product(models.Model):
@property
def ranking(self):
return 1
"ranking" can then be used in list_display
:
class ProductAdmin(admin.ModelAdmin):
list_display = ('ranking', 'asin', 'title')

- 1,394
- 1
- 15
- 15
-
1`@property` is not required. `list_display` will accept any method on the model (or ModelAdmin) – Chris Pratt Apr 29 '11 at 20:52