Динамические поля в Django Admin - PullRequest
15 голосов
/ 04 ноября 2011

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

Связанный с постом 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)

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

Ответы [ 8 ]

16 голосов
/ 05 ноября 2011

Вот решение проблемы. Благодаря 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

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

6 голосов
/ 04 марта 2016

Это работает для добавления динамических полей в 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
5 голосов
/ 20 августа 2015

Принятый ответ выше работал в старых версиях 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
5 голосов
/ 04 ноября 2011

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

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

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

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

4 голосов
/ 03 августа 2015

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

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
2 голосов
/ 31 июля 2014

Ответ Стефана изящен, но когда я использовал в 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
0 голосов
/ 12 марта 2017

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

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

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

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

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

0 голосов
/ 04 ноября 2011

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

...