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объект. Вам не нужно изменять эту часть.

Другие вопросы по тегам