Django: Admin inline формирует исходные данные для каждого экземпляра
Я много читаю, но, похоже, не могу найти решение этой проблемы.
Я пишу приложение на Django, я все еще пишу на стороне администратора.
У меня есть модель под названием "Среды" и модель "Серверы", существует связь ForeignKey между серверами и средами, например, в данной среде имеется несколько серверов.
При изменении формы "Добавить" для сред в интерфейсе администратора я использую встроенную форму, чтобы иметь возможность визуализировать список серверов, которые будут связаны с окружающей средой, что-то вроде этого:
class ServerInline(admin.TabularInline):
model = Server
extra = 39
class EnvironmentAdmin(admin.ModelAdmin):
inlines = [ServerInline]
Довольно просто, верно?
То, что я хотел бы сделать, это предварительно заполнить встроенные формы Серверов значениями по умолчанию, я смог предварительно заполнить их тем же значением, выполнив это:
class ServerInlineAdminForm(forms.ModelForm):
class Meta:
model = Server
def __init__(self, *args, **kwargs):
super(ServerInlineAdminForm, self).__init__(*args, **kwargs)
self.initial['name']='Testing'
class ServerInline(admin.TabularInline):
form = ServerInlineAdminForm
model = Server
extra = 39
class EnvironmentAdmin(admin.ModelAdmin):
inlines = [ServerInline]
Но это не то, что я хочу, я хотел бы иметь возможность инициализировать 39 экземпляров формы сервера с 39 различными значениями, которые у меня есть в списке. Что было бы лучшим способом сделать это??
Спасибо!
8 ответов
Я понял, что решил проблему сам и не ответил здесь.
В итоге я переопределил метод save_model класса Environment вместо использования форм администратора.
Я объясню немного лучше:
У меня есть объект среды и объект сервера. Среда имеет ряд серверов, которые связаны с ней через внешний ключ в объекте сервера. Моей целью было заполнение серверов, связанных со средой, в процессе создания среды. Чтобы сделать это, я переопределил метод save_model для объекта Environment, сделав obj.save() и AFTERWARDS, создав объекты Server, которые указывают на эту среду, а затем снова obj.save(). Почему потом? Потому что я не могу связать новый созданный сервер со средой, которая еще не существует. Дайте мне знать, если кто-то заинтересован в фактическом коде.
Вот моя реализация, спасибо Стивену за идею.
Все в admin.py:
class SecondaryModelInline(admin.ModelAdmin):
model = SecondaryModel
formset = SecondaryModelInlineFormSet
def get_formset(self, request, obj=None, **kwargs):
formset = super(SecondaryModelInline, self).get_formset(request, obj, **kwargs)
formset.request = request
return formset
def get_extra(self, request, obj=None, **kwargs):
extra = super(SecondaryModelInline, self).get_extra(request, obj, **kwargs)
something = request.GET.get('something', None)
if something:
extra = ... figure out how much initial forms there are, from the request ...
return extra
Где-то раньше, также в admin.py, это:
class SecondaryModelInlineFormSet(forms.models.BaseInlineFormSet):
model = SecondaryModel
def __init__(self, *args, **kwargs):
super(SecondaryModelInlineFormSet, self).__init__(*args, **kwargs)
if self.request.GET.get('something', None):
# build your list using self.request
self.initial=[{'field_a': 'A', ...}, {}... ]
Не знаю точно, почему вы хотите это сделать, но, возможно, вы могли бы создать набор моделей:
from django.forms.models import BaseModelFormSet
class ServerFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(ServerFormSet, self).__init__(*args, **kwargs)
self.initial = [{ 'name': 's1', }, {'name': 's2'},] # supply your list here
и установите это на своем встроенном:
class ServerInline(admin.TabularInline):
form = ServerInlineAdminForm
model = Server
extra = 39
formset = ServerFormSet
Я не пробовал это.
См.: https://docs.djangoproject.com/en/dev/ref/contrib/admin/.
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/
https://docs.djangoproject.com/en/dev/topics/forms/formsets/
The ModelAdmin
есть методget_formset_kwargs
который можно использовать именно для этого сценария с очень небольшим количеством строк кода:
class ServerInline(admin.TabularInline):
model = Server
class EnvironmentAdmin(admin.ModelAdmin):
inlines = [ServerInline]
def get_formset_kwargs(self, request, obj, inline, prefix):
formset_params = super().get_formset_kwargs(request, obj, inline, prefix)
if not obj.id and isinstance(inline, ServerInline):
formset_params |= {"initial": [
{"name": f"Testing {i}"} for i in range(39)
]}
return formset_params
Обратите внимание: оператор слияния dict|
доступен только начиная с Python 3.9. Использоватьdict.update()
на старых версиях.
Я не пробовал этого, но так как сгенерированные дополнительные формы являются основными наборами форм Django, вам нужно привязать исходные данные к набору форм, что объясняется здесь в документации.
Я только что прочитал документы, и похоже, что вы можете определить свой собственный набор форм внутри своего inlineadmin, а затем, как уже упоминалось выше, предварительно заполнить набор форм данными из вашего списка. Я думаю, что вы могли бы достичь этого, поместив код предварительной численности в метод init вашего класса.
Я знаю, что это не очень подробное объяснение, но я нашел вопрос интересным, посмотрел документы и подумал, может быть, я смогу указать вам правильное направление, что попробовать дальше.
Ну, я хотел прокомментировать ответ frnhr, но мне не хватило репутации, поэтому:
Ответ сработал для меня, мне просто нужно было перебрать формы в наборе форм и установить исходные данные для каждой из них:
class SecondaryModelInlineFormSet(forms.models.BaseInlineFormSet):
model = SecondaryModel
def __init__(self, *args, **kwargs):
super(SecondaryModelInlineFormSet, self).__init__(*args, **kwargs)
if self.request.GET.get('something', None):
# build your list using self.request
for form in self:
form.initial = {'field_a':'A',...} #This is what I changed
self.initial=[{'field_a': 'A', ...}, {}... ]
У меня сработало в случае предопуляции user
от request.user
за StackedInline
а также TabularInline
,
def save_formset(self, request, form, formset, change):
for form in formset.forms:
form.instance.user = request.user
formset.save()
Добавления элементов во встроенный класс, как предполагают все остальные ответы, будет недостаточно для фактического сохранения элементов.
Django игнорирует исходные элементы , которые не были изменены пользователями, поэтому вам нужно добавить дополнительную логику в ваш файл.InlineFormSet
для сохранения исходных данных:
from django.contrib import admin
from django.forms import BaseInlineFormSet
class InitialElementsInlineFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.initial_extra is not None:
self.extra = len(self.initial_extra)
def save_new_objects(self, commit=True):
super().save_new_objects(commit)
for form in self.extra_forms:
if not form.has_changed() and form.initial in self.initial_extra:
instance = form.instance
instance.__dict__.update(form.initial)
setattr(instance, self.fk.name, self.instance)
self.new_objects.append(instance)
if commit:
instance.save()
return self.new_objects
Этот класс Formset делает две вещи:
- в
__init__
мы устанавливаем исходный номер формы таким же, как и исходные элементы, поэтому все они отображаются. - Когда форма отправляется, она сохраняет элементы, которые не были изменены. Будьте осторожны: если вы не указали все обязательные поля в
initial
, ему не удастся создать объект, что приведет к ответу «Ошибка сервера».
После того, как вы создали этот класс, вы сможете использовать его в своих моделях администрирования следующим образом:
class MyModelInline(admin.TabularInline):
model = MyModel
formset = InitialElementsInlineFormSet
def get_initial(self, request):
data = [
{
"field_a": "example",
"user_id": request.user.id,
"favorite_number": i,
}
for i in range(3)
]
return data
class ParentAdmin(admin.ModelAdmin):
inlines = [MyModelInline]
def get_formset_kwargs(self, request, obj, inline, prefix):
formset_params = super().get_formset_kwargs(request, obj, inline, prefix)
if isinstance(inline, MyModelInline) and obj.pk is None:
formset_params.update(initial=inline.get_initial(request))
return formset_params
Переопределить класс Inline'get_initial
метод для заполнения форм, которые вы хотите видеть при создании нового объекта. Вы можете получить доступ к пользовательскому объекту, а также к данным родительского объекта, используяrequest
объект.
вParentAdmin
классе, мы следим за тем, чтобы мы заполняли только начальное ключевое слово для нашегоInline
сорт. Проверив, еслиobj.pk is None
, мы гарантируем, что это произойдет только во время созданияParent
объект. Вам не нужно изменять эту часть.