Django admin: как форматировать поля только для чтения?

у меня есть модель, Director С двумя полями данных и двумя подклассами (код ниже). Я пытаюсь создать страницу администратора для каждого директора, которая показывает соответствующий экземпляр подкласса, а не Director экземпляр; эта часть в основном проста (я создаю встроенный для каждого подкласса, даю основной ModelAdmin форму со всеми исключенными полями и имею основной ModelAdmin только наборы форм запроса из строк, которые имеют соответствующий экземпляр-код; есть нерешенная проблема с этот подход, который я отмечаю ниже,но не является фокусом этого вопроса).

проблема в том, что я хочу массировать значения, отображаемые пользователю, одно из которых отображается в поле только для чтения, а другое-нет. Обработка заключается в том, что я хочу изменить магическое значение (date(1,1,1)) в строку "On incorporation".

даты в полях readonly не отображаются в формате, очень удобном для синтаксического анализа, и я хотел бы уменьшить ненужную зависимость от javascript, поэтому я бы предпочел серверное решение.

код ниже отображает формы, как я хочу их, за исключением того, что значения даты не массируются вообще, и при сохранении появляется ложное сообщение "пожалуйста, исправьте ошибку ниже", даже если ошибок нет, и все поля сохраняются правильно.

мой вопрос: как перехватить значения, которые будут отображаться на странице, как в полях readonly, так и в полях forms, и изменить их на показать строку по моему выбору?

модели (насколько материал):

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

Я понимаю, что существует асимметрия между DirectorsCorporateInline и DirectorsIndividualInline; это потому, что я тестирую экземпляр с DirectorsIndividual экземпляра. Приведенный выше код относится к полям модели, не показанным в моделях, поскольку они не являются существенными для проблемы дат; должно быть возможно сделать их нематериальными для проблемы ложной ошибки не изменяя эти поля (хотя я понимаю, что это менее полезно для этой проблемы, я хочу, чтобы этот вопрос был в основном сосредоточен на одной проблеме). EnhancedAdmin это ModelAdmin подкласс с некоторыми незначительными изменениями, которые не должны быть следствием. Дополнительный код может быть показан по мотивированному запросу, но я не хочу путать с нерелевантным кодом.

для полноты: я использую django 1.3.1 на python 2.7.2.

4 ответов


определите функцию-член вашего 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 в админке.

EDIT: я должен добавить, что это одно быстрое решение. Более надежным решением является подкласс 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)

самый простой способ - сделать это, определив пользовательский обратный вызов в 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_L10N is True, это отобразит datetime в локальном времени средства просмотра, что, вероятно, то, что вы хотите. Если вы хотите сохранить USE_L10N as False тогда вы можете переопределить его поведение следующим образом:return localize(obj.my_datetime, use_l10n=True).


как предложил @drjimbob (и carljm на #django), решение заключается в создании функции-члена или свойства на модели, например:

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 - the 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 обрабатывает магическое значение. Вместо этого вы можете поместить этот код в пользовательское поле.


Я бы массировал значения полей с помощью javascript. Ты можешь!--3-->переопределить шаблоны администратора и прикрепите свой код javascript в {% block extrahead %} блок (дополнительная информация из книга Джанго). Поместите свой пример функции magic massage в .ready() (Если вы используете jQuery).

Я надеюсь, что это сработает для вас, потому что я хотел бы сделать что-то подобное, но еще не реализовал. :)