Django admin: как форматировать поля только для чтения?
у меня есть модель, Director
С двумя полями данных и двумя подклассами (код ниже). Я пытаюсь создать страницу администратора для каждого директора, которая показывает соответствующий экземпляр подкласса, а не Director
экземпляр; эта часть в основном проста (я создаю встроенный для каждого подкласса, даю основной ModelAdmin форму со всеми исключенными полями и имею основной ModelAdmin только наборы форм запроса из строк, которые имеют соответствующий экземпляр-код; есть нерешенная проблема с этот подход, который я отмечаю ниже,но не является фокусом этого вопроса).
проблема в том, что я хочу массировать значения, отображаемые пользователю, одно из которых отображается в поле только для чтения, а другое-нет. Обработка заключается в том, что я хочу изменить магическое значение (date(1,1,1)
) в строку "On incorporation"
.
даты в полях readonly не отображаются в формате, очень удобном для синтаксического анализа, и я хотел бы уменьшить ненужную зависимость от javascript, поэтому я бы предпочел серверное решение.
код ниже отображает формы, как я хочу их, за исключением того, что значения даты не массируются вообще, и при сохранении появляется ложное сообщение "пожалуйста, исправьте ошибку ниже", даже если ошибок нет, и все поля сохраняются правильно.
мой вопрос: как перехватить значения, которые будут отображаться на странице, как в полях readonly, так и в полях forms, и изменить их на показать строку по моему выбору?
модели (насколько материал):
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
код администратора:
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's 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))
Я понимаю, что существует асимметрия между DirectorsCorporateInline
и DirectorsIndividualInline
; это потому, что я тестирую экземпляр с DirectorsIndividual
экземпляра. Приведенный выше код относится к полям модели, не показанным в моделях, поскольку они не являются существенными для проблемы дат; должно быть возможно сделать их нематериальными для проблемы ложной ошибки не изменяя эти поля (хотя я понимаю, что это менее полезно для этой проблемы, я хочу, чтобы этот вопрос был в основном сосредоточен на одной проблеме). EnhancedAdmin
это ModelAdmin
подкласс с некоторыми незначительными изменениями, которые не должны быть следствием. Дополнительный код может быть показан по мотивированному запросу, но я не хочу путать с нерелевантным кодом.
для полноты: я использую django 1.3.1 на python 2.7.2.
4 ответов
определите функцию-член вашего Director
класс, который отображает readonly_field, как вы хотите.
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
а затем просто добавьте 'date_of_appointment_str'
в список readonly_fields
в админке.
EDIT: я должен добавить, что это одно быстрое решение. Более надежным решением является подкласс models.DateField
на MyCustomDateField
это действует как DateField
за исключением того, что когда значение date(1,1,1)
оно представляет как "на включении" или когда потребитель сохраняет "на включении" оно сохраняет значение как date(1,1,1)
. Это гарантирует, что вы можете повторно использовать эту функцию везде, где появляется этот тип поля. Однако, если он появляется только в одном месте; это может быть излишним.
вам понадобится что-то вроде (это непроверено; вам может потребоваться дополнительно изменить ваши формы DateField
и / или другие вещи; например, если вы используете django-south, вам придется добавить пользовательские правила самоанализа).
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)
самый простой способ - сделать это, определив пользовательский обратный вызов в ModelAdmin
. Допустим, поле называется 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)
my_datetime_localized.short_description = 'Date / time'
Примечание: если settings.USE_L10N
is True
, это отобразит datetime в локальном времени средства просмотра, что, вероятно, то, что вы хотите. Если вы хотите сохранить USE_L10N
as False
тогда вы можете переопределить его поведение следующим образом:return localize(obj.my_datetime, use_l10n=True)
.
как предложил @drjimbob (и carljm на #django), решение заключается в создании функции-члена или свойства на модели, например:
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'
Примечание date_of_appointment_formatted.short_description
- the ModelAdmin
использовать short_description
как метки readonly_field
.
чтобы получить свойства, работающие с полями модели, необходима пользовательская форма:
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\'s 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
требуется настраиваемое поле для отображения свойства; настраиваемый __init__
установить начальное значение для поля с свойство и пользовательское сохранение, чтобы задать свойство модели из поля формы.
в моем примере сохранение также должно быть известно о магическом значении, из-за того, как DateField
обрабатывает магическое значение. Вместо этого вы можете поместить этот код в пользовательское поле.
Я бы массировал значения полей с помощью javascript. Ты можешь!--3-->переопределить шаблоны администратора и прикрепите свой код javascript в {% block extrahead %}
блок (дополнительная информация из книга Джанго). Поместите свой пример функции magic massage в .ready()
(Если вы используете jQuery).
Я надеюсь, что это сработает для вас, потому что я хотел бы сделать что-то подобное, но еще не реализовал. :)