2012-01-02 23 views
7

Tengo un modelo, Director con dos DateFields y dos subclases (código a continuación). Estoy intentando crear una página de administrador para cada Director que muestra la instancia de subclase correspondiente, y no la instancia Director; esta parte es en su mayoría fácil (creo un en línea para cada subclase, le doy a ModelAdmin principal un formulario con todos los campos excluidos, y el ModelAdmin principal solo solicita conjuntos a partir de las líneas que tienen una instancia correspondiente - el código; hay un problema sin resolver con este enfoque, que menciono a continuación, pero no es el foco de esta pregunta).administrador de Django: ¿cómo formatear campos de solo lectura?

El problema que tengo es que quiero dar masajes a los valores mostrados al usuario, uno de los cuales se muestra en un campo de solo lectura, uno de los cuales no lo es. El procesamiento es que quiero cambiar un valor mágico (date(1,1,1)) a la cadena "On incorporation".

Las fechas en los campos de solo lectura no se muestran en un formato muy amigable para el análisis, y me gustaría reducir la dependencia innecesaria de javascript, por lo que preferiría una solución del lado del servidor.

El código siguiente muestra los formularios como los quiero, excepto que los valores de fecha no se masajean en absoluto, y cuando se guardan, aparece un mensaje falso "Corrija el error a continuación", aunque no haya errores, y todos los campos se guardan correctamente

Mi pregunta es: ¿cómo puedo interceptar los valores que se mostrarán en la página, tanto en campos de solo lectura como en campos de formularios, y modificarlos para mostrar una cadena de mi elección?

Los modelos (hasta ahora como materiales):

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 

La clave de administrador:

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

Soy consciente de que hay una asimetría entre DirectorsCorporateInline y DirectorsIndividualInline; eso es porque estoy probando en una instancia con una instancia DirectorsIndividual. El código anterior se refiere a los campos modelo que no se muestran en los modelos, porque no son importantes para el problema de las fechas; debería ser posible volverlos inmateriales para el problema del error espurio sin alterar esos campos (aunque me doy cuenta de que es menos útil para ese tema, quiero que esta cuestión se centre principalmente en un tema). EnhancedAdmin es una subclase ModelAdmin con algunas modificaciones menores que no deberían ser una consecuencia. Se puede mostrar un código adicional por solicitud razonada, pero no quiero confundirlo con un código irrelevante.

Para completar: Estoy usando django 1.3.1 en python 2.7.2.

Respuesta

3

Define una función de miembro de tu clase Director que representa el readonly_field como quieras.

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 

y luego sólo tiene que añadir 'date_of_appointment_str' a su lista de readonly_fields en el admin.

EDITAR: Debo añadir que esta es una solución rápida. Una solución más robusta es la subclase models.DateField en un MyCustomDateField que actúa como un DateField excepto que cuando el valor es date(1,1,1) se representa como "En incorporación" o cuando un usuario guarda "En incorporación" guarda el valor como date(1,1,1). Esto aseguraría que pueda reutilizar esta funcionalidad en cualquier lugar donde aparezca este tipo de campo. Sin embargo, si solo aparece en un lugar; esto puede ser excesivo.

Necesitaría algo así como (esto no se ha probado, es posible que deba modificar sus formularios DateField y otras cosas, por ejemplo, si usa django-south tendrá que agregar reglas de introspección personalizadas).

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

Me gustaría dar masajes a los valores del campo con javascript. Puede override las plantillas de administrador y adjuntar su código de JavaScript en el bloque {% block extrahead %} (más información de django book). Ponga su ejemplo de función de masaje mágico en .ready() (si usa jQuery).

Espero que esto funcione para usted, porque me gustaría hacer algo similar, pero aún no lo he implementado. :)

+0

En realidad, no se representan de forma consistente, lo que hará que el javascript sea un dolor; Probablemente tendré que hacerlo, así que gracias por la información. – Marcin

1

Como @drjimbob (y carljm en #django) sugirió, la solución es crear una función miembro o la propiedad del modelo, por ejemplo:

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' 

Nota del date_of_appointment_formatted.short_description - la ModelAdmin utilizará el short_description como la etiqueta para un readonly_field.

para obtener las propiedades de trabajar con campos del modelo, se necesita un formulario personalizado:

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) 

El ModelForm necesita un campo personalizado para mostrar la propiedad; un __init__ personalizado para establecer el valor inicial para el campo de la propiedad y un guardado personalizado para establecer la propiedad del modelo en el campo del formulario.

En mi ejemplo, el guardado también tiene que ser consciente del valor mágico, debido a cómo DateField maneja el valor mágico. Podría insertar ese código en un campo personalizado en su lugar.

4

La manera más fácil es hacerlo definiendo una devolución de llamada personalizada en el ModelAdmin. Digamos que el campo se llama 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' 

Nota: si settings.USE_L10N es True, esto va a mostrar la fecha y hora en la hora local del espectador, que es probablemente lo que quiere. Si desea mantener USE_L10N como False, puede anular su comportamiento de la siguiente manera: return localize(obj.my_datetime, use_l10n=True).

Cuestiones relacionadas