2012-01-02 98 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)