3

Assuming a the following model

# models.py
class Person(models.Model):
    name = models.CharField(max_length=40)
    birthdate = models.DateField()
    age = models.CharField(max_length=3) # dont mind the type, this is just an example :)

The desired behavior would be to have the age field hidden while the user creates a new Person object, and evaluate its value when the user submits the form. Also, the age field should be visible when views the object instance in admin.

To achieve this, I created a ModelAdmin with a custom ModelForm as follows

# admin.py
from django.contrib import admin
from django import forms
from .models import Person
from dateutil.relativedelta import relativedelta
import datetime

class PersonForm(forms.ModelForm):
    def clean(self):
        cleaned_data = super(PersonForm, self).clean()
        birthdate = cleaned_data.get('birthdate')
        cleaned_data['age'] = relativedelta(datetime.date.today(), birthdate).years
        return cleaned_data

class PersonAdmin(admin.ModelAdmin):
    form = PersonForm
    exclude = ('age',)

admin.site.register(Person, PersonAdmin)

However, after submitting the form, the age field does not get populated, possibly because it is excluded. If I remove the exclude then it works, but the form displays the age field which is not the desired behavior.

Demetris
  • 2,981
  • 2
  • 25
  • 33
  • You should consider whether you really need an age field at all. It would need to somehow change on every birthday. This would be better provided via a method or property that calculates the current age from the birthdate when required. – Daniel Roseman May 31 '18 at 09:07

2 Answers2

2

Instead of custom form you can use ModelAdmin's save_model method:

 class PersonAdmin(admin.ModelAdmin):

    def save_model(self, request, obj, form, change):
        birthdate = form.cleaned_data.get('birthdate')
        age = relativedelta(datetime.date.today(), birthdate).years 
        obj.age = age
        super().save_model(request, obj, form, change)

    def get_exclude(self, request, obj=None):
        if not obj:
            return ('age',)
        return self.exclude  

Also you probably can use readonly_fields:

 class PersonAdmin(admin.ModelAdmin):
    readonly_fields = ('age',)

    def save_model(self, request, obj, form, change):
        birthdate = form.cleaned_data.get('birthdate')
        age = relativedelta(datetime.date.today(), birthdate).years 
        obj.age = age
        super().save_model(request, obj, form, change)
neverwalkaloner
  • 46,181
  • 7
  • 92
  • 100
1

Or you can hide the input from form.

# admin.py
from django.contrib import admin
from django import forms
from .models import Person
from dateutil.relativedelta import relativedelta
import datetime

class PersonForm(forms.ModelForm):
    age = forms.CharField(widget=forms.HiddenInput, required=False)
    def clean(self):
        cleaned_data = super(PersonForm, self).clean()
        birthdate = cleaned_data.get('birthdate')
        cleaned_data['age'] = relativedelta(datetime.date.today(), birthdate).years
        return cleaned_data

You can use that form only when object is none (create case).

class PersonAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        if not obj:
            kwargs['form'] = PersonForm
        return super().get_form(request, obj, **kwargs)

admin.site.register(Person, PersonAdmin)
Davit Tovmasyan
  • 3,238
  • 2
  • 20
  • 36