简体   繁体   English

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

[英]Django admin: how to format readonly fields?

I have a model, Director with two DateFields, and two subclasses (code below). 我有一个模型, Director有两个DateFields,还有两个子类(下面的代码)。 I am trying to create an admin page for each Director which shows the corresponding subclass instance, and not the Director instance; 我正在尝试为每个Director创建一个管理页面,它显示相应的子类实例,而不是Director实例; this part is mostly easy (I create an inline for each subclass, give the main ModelAdmin a form with all fields excluded, and have the main ModelAdmin only request formsets from the inlines which have a corresponding instance - the code; there is an unresolved issue with this approach, which I note below, but is not the focus of this question). 这一部分大部分都很简单(我为每个子类创建一个内联,给主模型管理器一个表格,所有字段都被排除在外,并且主要的ModelAdmin请求来自内联的请求表单集,这些表单集具有相应的实例 - 代码;有一个未解决的问题用这种方法,我在下面注意到,但不是这个问题的焦点)。

The problem I have is that I want to massage the values displayed to the user, one of which is shown in a readonly field, one of which is not. 我遇到的问题是我想按下显示给用户的值,其中一个显示在只读字段中,其中一个不显示。 The processing is that I want to change a magic value ( date(1,1,1) ) to the string "On incorporation" . 处理是我想将魔术值( date(1,1,1) )更改为字符串"On incorporation"

Dates in readonly fields aren't rendered in a format very friendly to parsing, and I would like to reduce unnecessary dependence on javascript, so I would very much prefer a server-side solution. readonly字段中的日期不会以非常友好的格式呈现,我希望减少对javascript的不必要依赖,所以我更喜欢服务器端解决方案。

The code below displays the forms as I want them, except that date values are not massaged at all, and when saving, there is a spurious "Please correct the error below" message, even though there are no errors, and all fields are saved correctly. 下面的代码显示了我想要的表单,除了日期值根本没有按摩,保存时,即使没有错误,也有一个虚假的“请更正下面的错误”消息,并保存所有字段正确。

My question is: how do I intercept the values to be rendered on the page, both in readonly fields, and in forms fields, and alter them to display a string of my choosing? 我的问题是:我如何拦截要在页面上呈现的值,包括只读字段和表单字段,并更改它们以显示我选择的字符串?

The models (so far as material): 模型(到目前为止):

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

The 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'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))

I realise that there is an asymmetry between DirectorsCorporateInline and DirectorsIndividualInline ; 我意识到DirectorsCorporateInlineDirectorsIndividualInline之间存在不对称; that is because I am testing on an instance with a DirectorsIndividual instance. 那是因为我正在使用DirectorsIndividual实例测试实例。 The code above refers to model fields not shown in the models, because they are not material to the dates issue; 上面的代码是指模型中未显示的模型字段,因为它们对日期问题不重要; it should be possible to render them immaterial for the spurious error issue without altering those fields (although I realise it is less helpful for that issue, I want to keep this question mostly focused on one issue). 应该可以在不改变那些字段的情况下使它们对于虚假错误问题变得无关紧要(尽管我意识到它对这个问题没什么帮助,但我想把这个问题主要集中在一个问题上)。 EnhancedAdmin is a ModelAdmin subclass with some minor alterations which shouldn't be of consequence. EnhancedAdmin是一个ModelAdmin子类,有一些小的改动,这些改变不应该是重要的。 Extra code can be shown on reasoned request, but I don't want to confuse with irrelevant code. 额外的代码可以在合理的请求中显示,但我不想与不相关的代码混淆。

For completeness: I am using django 1.3.1 on python 2.7.2. 为了完整性:我在python 2.7.2上使用django 1.3.1。

The easiest way is to do it by defining a custom callback in the ModelAdmin . 最简单的方法是通过在ModelAdmin定义自定义回调来ModelAdmin Let's say the field is called my_datetime : 假设该字段名为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'

Note: if settings.USE_L10N is True , this will display the datetime in the local time of the viewer, which is probably what you want. 注意:如果settings.USE_L10NTrue ,则会在查看器的本地时间显示日期时间,这可能是您想要的。 If you want to keep USE_L10N as False then you can override its behaviour like so: return localize(obj.my_datetime, use_l10n=True) . 如果你想将USE_L10N保持为False那么你可以像这样覆盖它的行为: return localize(obj.my_datetime, use_l10n=True)

Define a member function of your Director class that renders the readonly_field as you want. 定义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

and then just add 'date_of_appointment_str' to your list of readonly_fields in the admin. 然后只需将'date_of_appointment_str'添加到admin中的readonly_fields列表中。

EDIT: I should add that this is one quick solution. 编辑:我应该补充一点,这是一个快速的解决方案。 A more robust solution is to subclass models.DateField into a MyCustomDateField that acts like a DateField except that when the value is date(1,1,1) it renders as "On incorporation" or when a user saves "On incorporation" it saves the value as date(1,1,1) . 更强大的解决方案是将models.DateField子类化为MyCustomDateField ,其作用类似于DateField除了当值为date(1,1,1)它呈现为“On models.DateField ”或当用户保存“On incorporation”时它会保存值为date(1,1,1) This would ensure that you can reuse this functionality everywhere this field type shows up. 这将确保您可以在此字段类型显示的任何位置重复使用此功能。 However, if it only shows up in one place; 但是,如果它只出现在一个地方; this may be overkill. 这可能是矫枉过正的。

You'd need something like (this is untested; you may need to additionally alter your the forms DateField and/or other things; eg, if you use django-south you'll have to add custom introspection rules). 你需要类似的东西(这是未经测试的;你可能需要另外改变你的形式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)

As @drjimbob (and carljm on #django) suggested, the solution is to create a member function or property on the model, eg: 正如@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'

Note the date_of_appointment_formatted.short_description - the ModelAdmin will use the short_description as the label for a readonly_field . 请注意date_of_appointment_formatted.short_description - ModelAdmin将使用short_description作为readonly_field的标签。

To get the properties working with model fields, a custom form is needed: 要使属性使用模型字段,需要自定义表单:

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)

The ModelForm needs a custom field to display the property; ModelForm需要一个自定义字段来显示属性; a custom __init__ to set the initial value for the field from the property, and a custom save, to set the model property from the form field. 自定义__init__以从属性设置字段的初始值,以及自定义保存,以从表单字段设置模型属性。

In my example, the save also has to be aware of the magic value, because of how DateField handles the magic value. 在我的例子中,save也必须知道魔术值,因为DateField如何处理魔术值。 You could push that code into a custom field instead. 您可以将该代码推送到自定义字段中。

I would massage field values with javascript. 我会用javascript按摩字段值。 You can override the admin templates, and attach your javascript code into the {% block extrahead %} block ( some more info from the django book ). 您可以覆盖管理模板,并将您的javascript代码附加到{% block extrahead %}块中(来自django书的更多信息)。 Put your magic massage function example into .ready() (if you use jQuery). 将你的魔法按摩功能示例放入.ready() (如果你使用jQuery)。

I hope this will work for you, because I would like to do something similar, but haven't implemented yet. 我希望这对你有用,因为我想做类似的事情,但还没有实现。 :) :)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM