Динамические поля в 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. Возможно, что-то изменилось.
Если кто-то найдет еще более простое и элегантное решение, покажите здесь.
Спасибо всем)))