Кілька моделей в одному django ModelForm?


96

Чи можливо ModelFormв джанго включити декілька моделей ? Я намагаюся створити форму для редагування профілю. Тому мені потрібно включити деякі поля з моделі користувача та моделі 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")

Чи є спосіб об'єднати їх в одну форму чи мені просто потрібно створити форму та обробити завантаження та збереження db?


Відповіді:


92

Ви можете просто показати обидві форми в шаблоні всередині одного <form>елемента html. Потім просто обробляйте форми окремо у вікні. Ви все одно зможете користуватися form.save()і не потрібно обробляти завантаження та збереження db.

У цьому випадку вам це не потрібно, але якщо ви будете використовувати форми з однаковими іменами полів, загляньте в prefixквард для форм джанго. (Я відповів на запитання про це тут ).


Це хороша порада, але є випадки, коли це не застосовується, наприклад. спеціальна форма моделі для набору форм.
Wtower

8
Який був би простий спосіб зробити перегляд на основі класу, здатний відображати більше однієї форми та шаблону, який потім об'єднує їх у один і той же <form>елемент?
jozxyqk

1
Але як? Зазвичай у FormViewєдиного є form_classпризначений один .
erikbwork

@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?
Павло Шлепнев

3

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

Я написав 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

Зроблено. Надія, що комусь допомагає.


У цьому save_child_form.course_key = self.object, що є .course_key?
Адам Старр

Я думаю, що course_key - це пов’язана модель, в моєму випадку це "користувач", як у UserProfile.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

На жаль, при збереженні він все одно вийде з ладу у двох місцях, в якому само.об'єкт ще не існує у form_valid, тому він викидає AttributeError, а іншого примірника місця немає. Я не впевнений, що це рішення було повністю протестовано перед публікацією, тому, можливо, буде краще перейти з іншою відповіддю за допомогою CombinedFormBase.
robvdl

1
Що таке model_forms?
Бабуся

2

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


1
Вбудовані набори форм використовуються, коли вам потрібно працювати з відносинами "один до багатьох". Наприклад, компанія, де ви додаєте співробітників. Я намагаюся поєднати 2 таблиці в одну єдину форму. Це стосунки один до одного.
Джейсон Вебб

Використання набору форматів Inline працювало б, але, швидше за все, ідеально. Ви також можете створити Модель, яка обробляє відношення для вас, а потім використовувати єдину форму. Працювати лише одна сторінка з двома формами, як це пропонується в stackoverflow.com/questions/2770810/… .
Джон Персіваль Хакворт

2

Ви можете перевірити мою відповідь тут щодо подібної проблеми.

У ньому йдеться про те, як поєднати реєстрацію та профіль користувача в одній формі, але її можна узагальнити до будь-якої комбінації ModelForm.


0

Я використовував Джанго betterforms «S різноманітний і MultiModelForm в моєму проекті. Код можна вдосконалити. Наприклад, це залежить від django.six, який не підтримується 3. +, але все це можна легко виправити

Це питання вже кілька разів виникало в StackOverflow, тому я думаю, що настав час знайти стандартизований спосіб впоратися з цим.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.