Django admin:如何格式化只读字段?

时间:2012-01-02 23:31:39

标签: python django django-admin django-forms

我有一个模型,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))

我意识到DirectorsCorporateInlineDirectorsIndividualInline之间存在不对称;那是因为我正在使用DirectorsIndividual实例测试实例。上面的代码是指模型中未显示的模型字段,因为它们对日期问题不重要;应该可以在不改变那些字段的情况下使它们对于虚假错误问题变得无关紧要(尽管我意识到它对这个问题没什么帮助,但我想把这个问题主要集中在一个问题上)。 EnhancedAdmin是一个ModelAdmin子类,有一些小的改动,这些改动不应该是重要的。额外的代码可以在合理的请求中显示,但我不想与不相关的代码混淆。

为了完整性:我在python 2.7.2上使用django 1.3.1。

4 个答案:

答案 0 :(得分:6)

定义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列表中。

编辑:我应该补充一点,这是一个快速的解决方案。更健壮的解决方案是将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)

答案 1 :(得分:6)

最简单的方法是通过在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_L10NTrue,则会在查看器的本地时间显示日期时间,这可能是您想要的。如果您希望将USE_L10N保留为False,则可以覆盖其行为,如下所示:return localize(obj.my_datetime, use_l10n=True)

答案 2 :(得分:2)

正如@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__以从属性设置字段的初始值,以及自定义保存,以从表单字段设置模型属性。

在我的例子中,由于DateField处理魔法值的方式,保存也必须知道神奇的值。您可以将该代码推送到自定义字段中。

答案 3 :(得分:0)

我会用javascript按摩字段值。您可以override管理模板,并将您的javascript代码附加到{% block extrahead %}块中(来自django book的更多信息)。将您的魔法按摩功能示例放入.ready()(如果您使用jQuery)。

我希望这对你有用,因为我想做类似的事情,但还没有实现。 :)