Несколько моделей в одной модели Django ModelForm?

99

Можно ли включить несколько моделей в одну ModelFormв django? Я пытаюсь создать форму редактирования профиля. Поэтому мне нужно , чтобы включить некоторые поля из модели пользователя и модели UserProfile. В настоящее время я использую 2 такие формы

class UserEditForm(ModelForm):

    class Meta:
        model = User
        fields = ("first_name", "last_name")

class UserProfileForm(ModelForm):

    class Meta:
        model = UserProfile
        fields = ("middle_name", "home_phone", "work_phone", "cell_phone")

Есть ли способ объединить их в одну форму или мне просто нужно создать форму и обрабатывать загрузку базы данных и сохранение себя?

Джейсон Уэбб
источник
Возможный дубликат Django: несколько моделей в одном шаблоне с использованием форм
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

Ответы:

93

Вы можете просто показать обе формы в шаблоне внутри одного <form>элемента html. Затем просто обработайте формы отдельно в представлении. Вы по-прежнему сможете использовать form.save()и не будете обрабатывать загрузку и сохранение базы данных самостоятельно.

В этом случае он вам не понадобится, но если вы собираетесь использовать формы с одинаковыми именами полей, посмотрите prefixkwarg для форм django. (Я ответил на вопрос об этом здесь ).

Зак
источник
Это хороший совет, но есть случаи, когда он не применим, например. нестандартная модельная форма для набора форм.
Wtower
8
Что было бы простым способом сделать представление на основе классов способным отображать более одной формы и шаблон, который затем объединяет их в один и тот же <form>элемент?
jozxyqk
1
Но как? Обычно для FormViewa form_classназначается только один .
erikbwork 07
@erikbwork В этом случае вам не следует использовать FormView. Просто TemplateViewсоздайте подкласс и реализуйте ту же логику, что и FormView, но с несколькими формами.
швабра
10

Вы можете попробовать использовать следующие фрагменты кода:

class CombinedFormBase(forms.Form):
    form_classes = []

    def __init__(self, *args, **kwargs):
        super(CombinedFormBase, self).__init__(*args, **kwargs)
        for f in self.form_classes:
            name = f.__name__.lower()
            setattr(self, name, f(*args, **kwargs))
            form = getattr(self, name)
            self.fields.update(form.fields)
            self.initial.update(form.initial)

    def is_valid(self):
        isValid = True
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            if not form.is_valid():
                isValid = False
        # is_valid will trigger clean method
        # so it should be called after all other forms is_valid are called
        # otherwise clean_data will be empty
        if not super(CombinedFormBase, self).is_valid() :
            isValid = False
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            self.errors.update(form.errors)
        return isValid

    def clean(self):
        cleaned_data = super(CombinedFormBase, self).clean()
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            cleaned_data.update(form.cleaned_data)
        return cleaned_data

Пример использования:

class ConsumerRegistrationForm(CombinedFormBase):
    form_classes = [RegistrationForm, ConsumerProfileForm]

class RegisterView(FormView):
    template_name = "register.html"
    form_class = ConsumerRegistrationForm

    def form_valid(self, form):
        # some actions...
        return redirect(self.get_success_url())
Мяо ЧжиЧенг
источник
Похоже, это нельзя использовать в админке из-за некоторых явных проверок:admin.E016) The value of 'form' must inherit from 'BaseModelForm'.
WhyNotHugo
Как я могу его использовать UpdateView?
Павел Шлепнев,
4

У меня и у erikbwork была проблема, заключающаяся в том, что в общее представление на основе классов можно включить только одну модель. Я нашел такой же подход, как Мяо, но более модульный.

Я написал Mixin, чтобы вы могли использовать все общие представления на основе классов. Определите модель, поля, а теперь еще и child_model и child_field - а затем вы можете заключить поля обеих моделей в тег, как описывает Зак.

class ChildModelFormMixin: 
    ''' extends ModelFormMixin with the ability to include ChildModelForm '''
    child_model = ""
    child_fields = ()
    child_form_class = None

    def get_child_model(self):
        return self.child_model

    def get_child_fields(self):
        return self.child_fields

    def get_child_form(self):
        if not self.child_form_class:
            self.child_form_class = model_forms.modelform_factory(self.get_child_model(), fields=self.get_child_fields())
        return self.child_form_class(**self.get_form_kwargs())

    def get_context_data(self, **kwargs):
        if 'child_form' not in kwargs:
            kwargs['child_form'] = self.get_child_form()
        return super().get_context_data(**kwargs)

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        child_form = self.get_child_form()

        # check if both forms are valid
        form_valid = form.is_valid()
        child_form_valid = child_form.is_valid()

        if form_valid and child_form_valid:
            return self.form_valid(form, child_form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form, child_form):
        self.object = form.save()
        save_child_form = child_form.save(commit=False)
        save_child_form.course_key = self.object
        save_child_form.save()

        return HttpResponseRedirect(self.get_success_url())

Пример использования:

class ConsumerRegistrationUpdateView(UpdateView):
    model = Registration
    fields = ('firstname', 'lastname',)
    child_model = ConsumerProfile
    child_fields = ('payment_token', 'cart',)

Или с помощью ModelFormClass:

class ConsumerRegistrationUpdateView(UpdateView):
    model = Registration
    fields = ('firstname', 'lastname',)
    child_model = ConsumerProfile
    child_form_class = ConsumerProfileForm

Выполнено. Надеюсь, это кому-то поможет.

LGG
источник
В этом save_child_form.course_key = self.objectчто .course_key?
Adam Starrh
Я думаю, что course_key - это связанная модель, в моем случае это «пользователь», как в UserProfile.user, который является обратной ссылкой, возможно, это имя поля следует настраивать, если оно должно быть повторно используемым миксином. Но у меня все еще есть другая проблема, когда дочерняя форма фактически не заполняется исходными данными, все поля из User предварительно заполнены, но не для UserProfile. Возможно, мне придется сначала это исправить.
robvdl
Проблема, почему дочерняя форма не заполняется, заключается в том, что в методе get_child_form он вызывает, return self.child_form_class(**self.get_form_kwargs())но получает неправильный экземпляр модели kwargs['instance'], например, экземпляр - это основная модель, а не дочерняя модель. Чтобы исправить это, вам нужно сначала сохранить kwargs в переменной, а kwargs = self.get_form_kwargs()затем обновить kwargs['initial']правильный экземпляр модели перед вызовом return self.child_form_class(**kwargs). В моем случае это было, kwargs['instance'] = kwargs['instance'].profileесли в этом есть смысл.
robvdl
К сожалению, при сохранении он все равно выйдет из строя в двух местах, в одном из которых self.object еще не существует в form_valid, поэтому он выдает AttributeError, а другого экземпляра места нет. Я не уверен, что это решение было полностью протестировано перед публикацией, поэтому, возможно, будет лучше использовать другой ответ, используя CombinedFormBase.
robvdl
1
Что есть model_forms?
Granny Aching
2

Вам, вероятно, стоит взглянуть на встроенные наборы форм . Встроенные наборы форм используются, когда ваши модели связаны внешним ключом.

Джон Персиваль Хакворт
источник
1
Встроенные наборы форм используются, когда вам нужно работать с отношением «один ко многим». Например, компания, в которую вы добавляете сотрудников. Я пытаюсь объединить 2 таблицы в одну форму. Это отношения один на один.
Джейсон Уэбб
Использование встроенного набора форм будет работать, но, скорее всего, не идеально. Вы также можете создать модель, которая обрабатывает отношение за вас, а затем использовать единую форму. Будет работать только одна страница с 2 формами, как предлагается в stackoverflow.com/questions/2770810/… .
Джон Персиваль Хакворт,
2

Вы можете проверить мой ответ здесь на аналогичную проблему.

В нем рассказывается о том, как объединить регистрацию и профиль пользователя в одну форму, но его можно обобщить на любую комбинацию ModelForm.

Митар
источник
0

Я использовал Джанго betterforms «S многообразный и MultiModelForm в моем проекте. Однако код можно улучшить. Например, он зависит от django.six, который не поддерживается в 3. +, но все это легко исправить.

Этот вопрос несколько раз появлялся в StackOverflow, поэтому я думаю, что пора найти стандартизированный способ справиться с этим.

J Eti
источник