Динамические поля в Django Admin

Я хочу иметь дополнительные поля относительно значения одного поля. Поэтому я создаю пользовательскую форму для добавления новых полей.

Относительно blogpost jacobian 1 вот что я придумал:

class ProductAdminForm(forms.ModelForm):
    class Meta:
        model = Product

    def __init__(self, *args, **kwargs):
        super(ProductAdminForm, self).__init__(*args, **kwargs)
        self.fields['foo'] = forms.IntegerField(label="foo")

class ProductAdmin(admin.ModelAdmin):
    form = ProductAdminForm

admin.site.register(Product, ProductAdmin)

Но дополнительное поле 'foo' не отображается в админе. Если я добавлю это поле, все работает нормально, но не так динамично, как требуется, чтобы добавить поля относительно значения другого поля модели

class ProductAdminForm(forms.ModelForm):

    foo = forms.IntegerField(label="foo")

    class Meta:
        model = Product

class ProductAdmin(admin.ModelAdmin):
    form = ProductAdminForm

admin.site.register(Product, ProductAdmin)

Итак, есть ли какой-либо метод инициализации, который я должен запустить снова, чтобы заставить новое поле работать? Или есть ли другие попытки?

Ответ 1

Вот решение проблемы. Благодаря koniiiik я попытался решить эту проблему, расширив метод * get_fieldsets *

class ProductAdmin(admin.ModelAdmin):
    def get_fieldsets(self, request, obj=None):
        fieldsets = super(ProductAdmin, self).get_fieldsets(request, obj)
        fieldsets[0][1]['fields'] += ['foo'] 
        return fieldsets

Если вы используете несколько полей, обязательно добавьте их в нужное поле, используя соответствующий индекс.

Ответ 2

В то время как сообщение Джейкоба может работать правильно для обычного ModelForm (хотя это больше, чем полтора года), администратор - это несколько другое дело.

Весь декларативный способ определения моделей, форм ModelAdmins и whatnot сильно использует метаклассы и интроспекцию класса. То же самое и с администратором - когда вы указываете ModelAdmin использовать конкретную форму istead для создания значения по умолчанию, она исследует класс. Он получает список полей и других вещей из самого класса, не создавая его.

Однако ваш пользовательский класс не определяет дополнительное поле формы на уровне класса, вместо этого он динамически добавляет его после его создания - это слишком поздно для ModelAdmin, чтобы распознать это изменение.

Один из способов решить вашу проблему может заключаться в подклассе ModelAdmin и переопределить его метод get_fieldsets, чтобы фактически создать экземпляр класса ModelForm и получить список полей из экземпляра вместо класса. Однако вам следует иметь в виду, что это может быть несколько медленнее, чем реализация по умолчанию.

Ответ 3

Это работает для добавления динамических полей в Django 1.9.3, используя только класс ModelAdmin (без ModelForm) и переопределяя get_fields. Я пока не знаю, насколько он прочен:

class MyModelAdmin(admin.ModelAdmin):

    fields = [('title','status', ), 'description', 'contact_person',]
    exclude = ['material']

    def get_fields(self, request, obj=None):
        gf = super(MyModelAdmin, self).get_fields(request, obj)

        new_dynamic_fields = [
            ('test1', forms.CharField()),
            ('test2', forms.ModelMultipleChoiceField(MyModel.objects.all(), widget=forms.CheckboxSelectMultiple)),
        ]

        #without updating get_fields, the admin form will display w/o any new fields
        #without updating base_fields or declared_fields, django will throw an error: django.core.exceptions.FieldError: Unknown field(s) (test) specified for MyModel. Check fields/fieldsets/exclude attributes of class MyModelAdmin.

        for f in new_dynamic_fields:
            #`gf.append(f[0])` results in multiple instances of the new fields
            gf = gf + [f[0]]
            #updating base_fields seems to have the same effect
            self.form.declared_fields.update({f[0]:f[1]})
        return gf

Ответ 4

Вы можете создавать динамические поля и набор полей с использованием метакласса формы. Пример кода приведен ниже. Добавьте логику цикла в соответствии с вашими требованиями.

class CustomAdminFormMetaClass(ModelFormMetaclass):
    """
    Metaclass for custom admin form with dynamic field
    """
    def __new__(cls, name, bases, attrs):
        for field in get_dynamic_fields: #add logic to get the fields
            attrs[field] = forms.CharField(max_length=30) #add logic to the form field
        return super(CustomAdminFormMetaClass, cls).__new__(cls, name, bases, attrs)


class CustomAdminForm(six.with_metaclass(CustomAdminFormMetaClass, forms.ModelForm)):
    """
    Custom admin form
    """

    class Meta:
        model = ModelName
        fields = "__all__" 


class CustomAdmin(admin.ModelAdmin):
    """
    Custom admin 
    """

    fieldsets = None
    form = CustomAdminForm

    def get_fieldsets(self, request, obj=None):
        """
        Different fieldset for the admin form
        """
        self.fieldsets = self.dynamic_fieldset(). #add logic to add the dynamic fieldset with fields
        return super(CustomAdmin, self).get_fieldsets(request, obj)

    def dynamic_fieldset(self):
        """
        get the dynamic field sets
        """
        fieldsets = []
        for group in get_field_set_groups: #logic to get the field set group
            fields = []
            for field in get_group_fields: #logic to get the group fields
                fields.append(field)

            fieldset_values = {"fields": tuple(fields), "classes": ['collapse']}
            fieldsets.append((group, fieldset_values))

        fieldsets = tuple(fieldsets)

        return fieldsets

Ответ 5

Принятый ответ выше работал в более ранних версиях django, и как я это делал. Это теперь сломано в более поздних версиях django (я нахожусь на 1.68 в данный момент, но даже это уже старое).

Причина, по которой она теперь нарушена, состоит в том, что любые поля в пределах полей, возвращаемые из ModelAdmin.get_fieldsets(), в конечном счете передаются как параметр fields = в modelform_factory(), что даст вам ошибку, потому что поля в вашем списке не существуют (и не будут существовать до тех пор, пока ваша форма не будет создана и не будет вызвана ее __ init __).

Чтобы исправить это, мы должны переопределить ModelAdmin.get_form() и предоставить список полей, которые не содержат никаких дополнительных полей, которые будут добавлены позже. Поведение get_form по умолчанию - это вызов get_fieldsets() для этой информации, и мы должны предотвратить это:

# CHOOSE ONE
# newer versions of django use this
from django.contrib.admin.utils import flatten_fieldsets
# if above does not work, use this
from django.contrib.admin.util import flatten_fieldsets

class MyModelForm(ModelForm):
  def __init__(self, *args, **kwargs):
      super(MyModelForm, self).__init__(*args, **kwargs)
      # add your dynamic fields here..
      for fieldname in ('foo', 'bar', 'baz',):
          self.fields[fieldname] = form.CharField()

class MyAdmin(ModelAdmin): 
   form = MyModelForm

    fieldsets = [
       # here you put the list of fieldsets you want displayed.. only
       # including the ones that are not dynamic
    ]

    def get_form(self, request, obj=None, **kwargs):
        # By passing 'fields', we prevent ModelAdmin.get_form from
        # looking up the fields itself by calling self.get_fieldsets()
        # If you do not do this you will get an error from 
        # modelform_factory complaining about non-existent fields.

        # use this line only for django before 1.9 (but after 1.5??)
        kwargs['fields'] =  flatten_fieldsets(self.declared_fieldsets)
        # use this line only for django 1.9 and later 
        kwargs['fields'] =  flatten_fieldsets(self.fieldsets)

        return super(MyAdmin, self).get_form(request, obj, **kwargs)

    def get_fieldsets(self, request, obj=None):
        fieldsets = super(MyAdmin, self).get_fieldsets(request, obj)

        newfieldsets = list(fieldsets)
        fields = ['foo', 'bar', 'baz']
        newfieldsets.append(['Dynamic Fields', { 'fields': fields }])

        return newfieldsets

Ответ 6

Ответ Stephan является изящным, но когда я использовал in dj1.6, он требовал, чтобы поле было кортежем. Полное решение выглядело так:

class ProductForm(ModelForm):
    foo = CharField(label='foo')


class ProductAdmin(admin.ModelAdmin):
    form = ProductForm
    def get_fieldsets(self, request, obj=None):
        fieldsets = super(ProductAdmin, self).get_fieldsets(request, obj)
        fieldsets[0][1]['fields'] += ('foo', ) 
        return fieldsets

Ответ 7

не уверен, почему это не работает, но может ли возможное обходное решение определить статическое поле (в форме) и затем переопределить его в __init__?

Ответ 8

Я долгое время не мог решить проблему с динамическим добавлением полей. Решение "little_birdie" действительно работает. Спасибо, Берди)) Единственный нюанс: "Self.declared_fieldsets" следует заменить на "self.fieldsets".

#kwargs['fields'] =  flatten_fieldsets(self.declared_fieldsets)
kwargs['fields'] =  flatten_fieldsets(self.fieldsets)

Я использовал версию 1.10. Возможно, что-то изменилось.

Если кто-то найдет еще более простое и элегантное решение, покажите здесь.

Спасибо всем)))