Администратор Django: как форматировать поля только для чтения? - PullRequest
9 голосов
/ 03 января 2012

У меня есть модель, Director с двумя полями DateFields и двумя подклассами (код ниже). Я пытаюсь создать страницу администратора для каждого директора, которая показывает соответствующий экземпляр подкласса, а не экземпляр 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))

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

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

Ответы [ 4 ]

6 голосов
/ 03 сентября 2015

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

6 голосов
/ 03 января 2012

Определите функцию-член вашего класса 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)
2 голосов
/ 05 января 2012

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

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

0 голосов
/ 03 января 2012

Я бы помассировал значения полей с помощью JavaScript.Вы можете переопределить шаблоны администратора и прикрепить свой код javascript в блок {% block extrahead %} (дополнительная информация из книги django ).Поместите пример вашей функции волшебного массажа в .ready() (если вы используете jQuery).

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

...