Django admin: how to format readonly fields?

I have a model, Director with two DateFields and two subclasses (code below). I am trying to create an admin page for each director that shows the corresponding instance of the subclass, not the Director instance; this part is basically simple (I create a built-in for each subclass, provide the main ModelAdmin form with all fields excluded and get only the main ModelAdmin modules for queries from inline strings that have the corresponding instance - the code, there is an unresolved problem with this approach, which I note below, but not the focus of this question).

The problem is that I want to massage the values ​​displayed to the user, one of which is shown in the readonly field, one of which is not. The processing is that I want to change the magic value ( date(1,1,1) ) to the string "On incorporation" .

Dates in readonly fields are not displayed in a format that is very convenient for parsing, and I would like to reduce unnecessary javascript dependency, so I would really prefer a server-side solution.

The code below shows the forms that I need, except that the date values ​​are not massed at all, and when saved, the false message "Please correct the error below" appears even if there are no errors and all fields are saved correctly.

My question is: how to intercept the values ​​displayed on the page, both in the readonly fields and in the form fields and change them to display a line of my choice?

Models (based on materials):

 class Director(models.Model, Specializable): date_of_appointment = models.DateField() date_ceased_to_act = models.DateField(blank=True,null=True) class DirectorsIndividual(Director): pass class DirectorsCorporate(Director): pass 

Admin Code:

 class DirectorAdmin(EnhancedAdmin): fields = () ## def formfield_for_dbfield(self, db_field, **kwargs): ## return None def queryset(self, request): """ Directors for all companies which are incorporated by the current user organisation """ individual = Individual.for_user(request.user) return Director.objects.filter(company__incorporation_ticket__ordered_by__in = Organisation.all_organisations_for_which_individual_authorised_to_incorporate(individual)) class form(forms.ModelForm): # have this return no html - that way only inlines are shown class Meta: fields = () pass def is_valid(self): self._errors = {} return True class DirectorsIndividualInline(admin.StackedInline): model = DirectorsIndividual fk_name = 'director_ptr' extra = 0 readonly_fields = ('deferred_on','company','date_of_appointment',) can_delete = False def get_readonly_fields(self, request, obj=None): if obj and obj.company and not obj.company.is_submitted(): return self.readonly_fields # allow editing of fields listed in else else: return itertools.chain(self.readonly_fields, ('individual', 'is_secretary')) def has_delete_permission(self, request, obj=None): return obj and ((obj.company and not obj.company.is_submitted()) or not obj.company) class form(forms.ModelForm): def __init__(self, *args, **kwargs): super(forms.ModelForm, self).__init__(*args, **kwargs) self.fields['surrogate_for'].required = False self.fields['representative_for'].required = False if self.instance: obj = self.instance for field in (f for f in type(obj)._meta.fields if type(f) == fields.DateField): val = field.value_from_object(obj) assert (type(val) in (datetime.date, type(None),)) # assert field.name != 'date_of_appointment' if val == inc_consts.EARLIEST_DATE: self.initial[field.name] = "On incorporation" def is_valid(self): self._errors = {} return True class DirectorsCorporateInline(admin.StackedInline): model = DirectorsCorporate fk_name = 'director_ptr' extra = 0 can_delete = False class form(forms.ModelForm): def __init__(self, *args, **kwargs): super(forms.ModelForm, self).__init__(*args, **kwargs) if True: for k in self.fields: self.fields[k].required = False def is_valid(self): self._errors = {} return True inlines = (DirectorsIndividualInline,DirectorsCorporateInline) def get_inlines(self, request, obj=None): return (inline for inline in (self.inline_instances) if inline.model.objects.filter(**{(inline.fk_name or self.model._meta.object_name.lower()) : obj })) def get_formsets(self, request, obj=None): """ only return formset for inlines for which there exists an object """ return (inline.get_formset(request, obj) for inline in self.get_inlines(request, obj)) 

I understand that there is an asymmetry between DirectorsCorporateInline and DirectorsIndividualInline ; because I'm testing an instance with a DirectorsIndividual instance. The code above refers to model fields not shown in the models, since they are not essential for the release of dates; it should be possible to make them inconsequential for a false error without changing these fields (although I understand that this is less useful for this problem, I want this question to focus mainly on one problem). EnhancedAdmin is a subclass of ModelAdmin with some minor changes that shouldn't matter. Additional code may be shown by reasoned request, but I do not want to be confused with irrelevant code.

For completeness: I am using django 1.3.1 on python 2.7.2.

+7
source share
4 answers

Define a member function of your Director class that displays readonly_field as you want.

 class Director(models.Model, Specializable): date_of_appointment = models.DateField() date_ceased_to_act = models.DateField(blank=True,null=True) def date_of_appointment_str(self): if self.date_of_appointment == datetime.date(1,1,1): return "On incorporation" else: return "%s" % (self.date_of_appointment) # format as you wish 

and then just add 'date_of_appointment_str' to the readonly_fields list in the admin.

EDIT: I have to add that this is one quick fix. A more robust solution is to subclass models.DateField in MyCustomDateField , which acts like a DateField , except that when the date(1,1,1) value is displayed as "Enabled" or when the user saves "Enabled", it saves the value date(1,1,1) . This ensures that you can reuse this functionality wherever this type of field appears. However, if it appears in only one place; it may be redundant.

You need something like this (this is untested, you may need to further modify the DateField forms and / or other things, for example, if you use django-south, you will have to add custom introspection rules).

 class MyCustomDateField(models.DateField): date_111_str = 'On incorporation' def value_to_string(self, obj): val = self._get_val_from_obj(obj) if val is None: data = '' elif val.year == val.day == val.month == 1: data = date_111_str else: data = datetime_safe.new_date(val).strftime("%Y-%m-%d") return data def get_prep_value(self, value): if value == date_111_str: value = datetime.date(1,1,1) return super(MyCustomDateField,self).get_prep_value(self, value) 
+3
source

The easiest way is to do this by defining a custom callback in ModelAdmin . Let say that the field is called my_datetime :

 from django.contrib import admin from django.utils.formats import localize class MyModelAdmin(admin.ModelAdmin): readonly_fields = ('my_datetime_localized',) def my_datetime_localized(self, obj): return localize(obj.my_datetime) end_datetime_localized.short_description = 'Date / time' 

Note: if settings.USE_L10N is True , this will display the datetime in local view time, which is probably what you want. If you want to save USE_L10N as False , you can override its behavior as follows: return localize(obj.my_datetime, use_l10n=True) .

+4
source

As suggested by @drjimbob (and carljm on #django), the solution is to create a function or member property in the model, for example:

 class Director(models.Model, Specializable): date_of_appointment = models.DateField() date_ceased_to_act = models.DateField(blank=True,null=True) #def date_formatter and def _date_format_factory omitted date_of_appointment_formatted = lambda self: self.date_formatter(getattr(self, 'date_of_appointment')) date_ceased_to_act_formatted = _date_format_factory(None, 'date_ceased_to_act') #for some reason, I get a 'classmethod/staticmethod object is not callable' error if I decorate _date_format_factory date_of_appointment_formatted.short_description = u'Date of appointment' 

Note that date_of_appointment_formatted.short_description - ModelAdmin will use short_description as the label for readonly_field .

To get properties that work with model fields, a special form is required:

 class DirectorInlineForm(EnhancedModelForm): from django.utils import formats date_ceased_to_act_formatted = forms.DateField(required = False, widget = admin.widgets.AdminDateWidget, label = u'Date officer\ appointment terminated', input_formats = formats.get_format('DATE_INPUT_FORMATS') + (Director.on_incorporation,)) class Meta: model = Director # Note that model declaration is necessary for this to work with additional fields declared def __init__(self, *args, **kwargs): super(DirectorInlineForm, self).__init__(*args, **kwargs) # set initial values from model of declared fields if self.instance: self.initial['date_ceased_to_act_formatted'] = self.instance.date_ceased_to_act_formatted def save(self, commit = True): # save logic for magic formatted fields if self._raw_value('date_ceased_to_act_formatted') == Director.on_incorporation: sval = Director.on_incorporation else: sval = self.cleaned_data['date_ceased_to_act_formatted'] self.instance.date_ceased_to_act_formatted = sval return super(forms.ModelForm, self).save(commit) 

ModelForm requires a custom field to display the property; custom __init__ to set the initial value for the field from the property and custom save to set the model property from the form field.

In my example, persistence should also be aware of the magic value, as DateField processes the magic value. Instead, you can enter this code in a custom field.

+1
source

I would massage the field values ​​using javascript. You can override admin templates and attach your javascript code to the {% block extrahead %} (more information from the django book ). Put an example of the magic massage function in .ready() (if you are using jQuery).

I hope this works for you because I would like to do something similar, but not yet implemented. :)

0
source

All Articles