2012-01-02 41 views
7

我有一个模型,Director有两个DateFields和两个子类(下面的代码)。我试图为每个Director显示相应的子类实例创建一个管理页面,而不是Director实例;这部分大部分都很简单(我为每个子类创建一个内联,为主ModelAdmin提供一个表单,其中包含所有字段除外,并且具有主要的仅限于ModelAdmin请求的内联表单,它们具有相应的实例 - 代码;存在未解决的问题用这种方法,我在下面注意到,但不是这个问题的焦点)。Django的管理员:如何格式化只读字段?

我的问题是,我想按摩显示给用户的值,其中一个显示在只读字段中,其中一个不是。处理过程是我想将魔术值(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。

回答

3

定义您的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) 
0

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

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

+0

他们并没有真正呈现一致,这将使JavaScript使用痛苦;我可能不得不,所以谢谢你的信息。 – Marcin 2012-01-03 12:06:13

1

作为@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

最简单的方法是通过在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) 
    end_datetime_localized.short_description = 'Date/time' 

注:如果settings.USE_L10NTrue,这将在浏览器,它可能是你想要什么样的本地时间显示的日期时间。如果你想保持USE_L10NFalse那么你可以覆盖它的行为,如下所示:return localize(obj.my_datetime, use_l10n=True)

相关问题