[英]Django admin: how to format readonly fields?
我有一个模型, Director
有两个DateFields,还有两个子类(下面的代码)。 我正在尝试为每个Director创建一个管理页面,它显示相应的子类实例,而不是Director
实例; 这一部分大部分都很简单(我为每个子类创建一个内联,给主模型管理器一个表格,所有字段都被排除在外,并且主要的ModelAdmin请求来自内联的请求表单集,这些表单集具有相应的实例 - 代码;有一个未解决的问题用这种方法,我在下面注意到,但不是这个问题的焦点)。
我遇到的问题是我想按下显示给用户的值,其中一个显示在只读字段中,其中一个不显示。 处理是我想将魔术值( date(1,1,1)
)更改为字符串"On incorporation"
。
readonly字段中的日期不会以非常友好的格式呈现,我希望减少对javascript的不必要依赖,所以我更喜欢服务器端解决方案。
下面的代码显示了我想要的表单,除了日期值根本没有按摩,保存时,即使没有错误,也有一个虚假的“请更正下面的错误”消息,并保存所有字段正确。
我的问题是:我如何拦截要在页面上呈现的值,包括只读字段和表单字段,并更改它们以显示我选择的字符串?
模型(到目前为止):
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
子类,有一些小的改动,这些改变不应该是重要的。 额外的代码可以在合理的请求中显示,但我不想与不相关的代码混淆。
为了完整性:我在python 2.7.2上使用django 1.3.1。
最简单的方法是通过在ModelAdmin
定义自定义回调来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
为True
,则会在查看器的本地时间显示日期时间,这可能是您想要的。 如果你想将USE_L10N
保持为False
那么你可以像这样覆盖它的行为: return localize(obj.my_datetime, use_l10n=True)
。
定义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'
添加到admin中的readonly_fields
列表中。
编辑:我应该补充一点,这是一个快速的解决方案。 更强大的解决方案是将models.DateField
子类化为MyCustomDateField
,其作用类似于DateField
除了当值为date(1,1,1)
它呈现为“On models.DateField
”或当用户保存“On incorporation”时它会保存值为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)
正如@drjimbob(和#django上的carljm)所建议的那样,解决方案是在模型上创建成员函数或属性,例如:
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
- 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__
以从属性设置字段的初始值,以及自定义保存,以从表单字段设置模型属性。
在我的例子中,save也必须知道魔术值,因为DateField
如何处理魔术值。 您可以将该代码推送到自定义字段中。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.