Кілька моделей в одному 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.