【问题标题】:Django admin: how to format readonly fields?Django admin:如何格式化只读字段?
【发布时间】:2012-02-01 03:31:29
【问题描述】:

我有一个模型,Director,有两个 DateField 和两个子类(代码如下)。我正在尝试为每个 Director 创建一个管理页面,该页面显示相应的子类实例,而不是 Director 实例;这部分很容易(我为每个子类创建一个内联,给主 ModelAdmin 一个排除所有字段的表单,并让主 ModelAdmin 只从具有相应实例的内联请求表单集 - 代码;有一个未解决的问题使用这种方法,我在下面会提到,但不是这个问题的重点)。

我遇到的问题是我想按摩显示给用户的值,其中一个显示在只读字段中,其中一个没有。处理是我想把一个魔法值(date(1,1,1))改成字符串"On incorporation"

只读字段中的日期不会以非常易于解析的格式呈现,我希望减少对 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 实例测试实例。上面的代码引用了模型中未显示的模型字段,因为它们对日期问题不重要;应该可以在不更改这些字段的情况下使它们对虚假错误问题无关紧要(尽管我意识到它对那个问题的帮助较小,但我想让这个问题主要集中在一个问题上)。 EnhancedAdminModelAdmin 的子类,有一些不应该造成影响的小改动。可以根据合理的请求显示额外的代码,但我不想与不相关的代码混淆。

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

【问题讨论】:

    标签: python django django-admin django-forms


    【解决方案1】:

    最简单的方法是通过在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】:

      定义 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)
      

      【讨论】:

        【解决方案3】:

        正如@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 如何处理魔法值。您可以将该代码推送到自定义字段中。

        【讨论】:

          【解决方案4】:

          我将按摩用JavaScript字段值。你可以override的管理模板,并附上你的JavaScript代码到{% block extrahead %}段(从@ 987654322一些更多的信息@)。把你的魔按摩功能,例如改成.ready()(如果你使用jQuery)。 P>

          我希望这会为你工作,因为我想这样做类似的东西,但还没有落实。 :)

          【讨论】:

          • 他们是不是真的呈现一致,这将使使用javascript中的疼痛;我可能要,所以感谢您的信息。 SPAN>
          猜你喜欢
          • 2012-11-28
          • 1970-01-01
          • 2018-07-28
          • 1970-01-01
          • 2018-06-03
          • 2018-06-12
          • 2013-01-27
          • 1970-01-01
          • 2011-07-01
          相关资源
          最近更新 更多