Несколько классов форм в джинго общих (классных) представлениях

Я хотел бы использовать общие представления класса django 1.3 для классов, но иногда приходится управлять несколькими классами форм в одной форме. Однако, похоже, что существующие представления, основанные на FormMixin, предполагают один класс формы.

Возможно ли это с помощью общих представлений и как я могу это сделать?

EDIT: для уточнения, у меня есть одна форма, но более одного (на основе ModelForm). Например, в примере inline_formset в django docs я хотел бы представить страницу, где автор и его книги могут быть отредактированы сразу в одной форме:

author_form = AuthorForm(request.POST, instance = author)
books_formset = BookInlineFormSet(request.POST, request.FILES, instance=author)

Ответы

Ответ 1

Перед аналогичной проблемой я пришел к выводу, что это невозможно.

Хотя наличие нескольких форм на странице само по себе оказалось ошибкой дизайна, представляя всевозможные проблемы. Например, пользователь заполняет две формы, клики представляют один из них и теряет данные от другого. Обходной путь требует сложного контроллера, который должен знать состояние всех форм на странице. (См. Также здесь для обсуждения связанных проблем.)

Если наличие нескольких форм на странице не является вашим точным требованием, я бы предложил посмотреть альтернативные решения.

Например, обычно можно показывать пользователю только одну редактируемую форму за раз.

В моем случае я переключился на django-formwizard (не на django.contrib, который немного устарел и, похоже, сейчас находится под редизайном, но этот Обновление: начиная с версии 1.4 Django, приложение django-formwizard будет доступно в django.contrib, заменив старый formwizard. Он уже находится в trunk, см. документы). Для пользователя я сделал так, чтобы на странице было всего несколько форм, но только одно редактируется. И пользователь должен был заполнять формы в заранее определенном порядке. Это упростило работу с несколькими формами.

В противном случае, если формы действительно должны быть представлены сразу, может иметь смысл объединить их в один.


ОБНОВЛЕНИЕ (после уточнения):

Нет, вы не можете обращаться с наборами форм, используя общий FormView. Хотя ваш пример выглядит довольно просто: я думаю, он очень похож на этот пример в Django docs на formsets. Он имеет дело с двумя наборами форм, и вам просто нужно заменить его на форму (я думаю, вам все равно нужно указать префикс, чтобы избежать возможных столкновений атрибутов id).

Короче говоря, в вашем случае я бы использовал подкласс django.views.generic.base.View и переопределял методы get() и post() для обработки форм и форм, подобных приведенному выше примеру из Django docs.

В этом случае, я думаю, это прекрасно, чтобы представить как форму, так и форму для редактирования - с помощью одной кнопки, чтобы отправить их оба.

ДРУГОЕ ОБНОВЛЕНИЕ:

В активном недавнем билете в Django trac, #16256 Больше представлений на основе классов: производные обобщенные виды на основе форм. Если все пойдет хорошо, в Django будут добавлены новые общие представления: FormSetsView, ModelFormSetsView и InlineFormSetsView. В частности, последний имеет возможность показать и обработать модель с помощью встроенных форм.

Ответ 2

Представление полей из двух моделей на одной странице просмотра

Вы должны расширить класс django.views.generic.View и переопределить методы get (request) и post (request).

Вот как я это сделал.

Я использую Django 1.11.

Так моя форма (состоящая из двух форм) выглядит так: Форма регистрации пользователя

Мой класс Вид, который отображает мои две формы:

from django.views.generic import View

class UserRegistrationView(View):
    # Here I say which classes i'm gonna use
    # (It not mandatory, it just that I find it easier)
    user_form_class = UserForm
    profile_form_class = ProfileForm
    template_name = 'user/register.html'

    def get(self, request):
        if request.user.is_authenticated():
            return render(request, 'user/already_logged_in.html')
        # Here I make instances of my form classes and pass them None
        # which tells them that there is no additional data to display (errors, for example)
        user_form = self.user_form_class(None)
        profile_form = self.profile_form_class(None)
        # and then just pass them to my template
        return render(request, self.template_name, {'user_form': user_form, 'profile_form': profile_form})

    def post(self, request):
        # Here I also make instances of my form classes but this time I fill
        # them up with data from POST request
        user_form = self.user_form_class(request.POST)
        profile_form = self.profile_form_class(request.POST)

        if user_form.is_valid() and profile_form.is_valid():
            user = user_form.save(commit=False)
            user_profile = profile_form.save(commit=False)

            # form.cleaned_data is a dictionary which contains data validated
            # by fields constraints (Say we have a field which is a number. The cleaning here would 
            # be to just convert a string which came from the browser to an integer.)
            username = user_form.cleaned_data['username']
            password = user_form.cleaned_data['password']

            # This will be clarified later 
            # You can save each object individually if they're not connected, as mines are (see class UserProfile below)
            user.set_password(password)
            user.userprofile = user_profile
            user.save()

            user = authenticate(username=username, password=password)

            if user is not None:
                if user.is_active:
                    login(request, user)
                return redirect('user:private_profile')

        # else: # form not valid - each form will contain errors in form.errors
        return render(request, self.template_name, {
            'user_form': user_form,
            'profile_form': profile_form
        })

У меня есть модели User и UserProfile. User django.contrib.auth.models.User и UserProfile выглядит следующим образом:

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    friends = models.ManyToManyField('self', null=True, blank=True)
    address = models.CharField(max_length=100, default='Some address 42')

    def get_absolute_url(self):
        return reverse('user:public_profile', kwargs={'pk': self.pk})

    def __str__(self):
        return 'username: ' + self.user.username + '; address: ' + self.address

    @receiver(post_save, sender=User) # see Clarification 1 below
    def create_user_profile(sender, instance, created, **kwargs):
        if created: # See Clarification 2 below
            UserProfile.objects.create(user=instance, address=instance.userprofile.address)

    @receiver(post_save, sender=User)
    def update_user_profile(sender, instance, **kwargs):
        instance.userprofile.save()

Разъяснение 1: @receiver (post_save, sender = Пользователь)

  • когда пользователь сохраняется (я написал user.save() где-то (пользователь является экземпляром класса User)) UserProfile также будет сохранен.

Разъяснение 2:, если создано: (пояснение из класса View)

  • Если пользователь СОЗДАН, создайте UserProfile                   где user = Пользовательский экземпляр, который только что был отправлен через UserForm

  • адрес собирается из ProfileForm и добавляется к экземпляру пользователя перед                   вызов user.save()

У меня две формы:

UserForm:

class UserForm(forms.ModelForm):
    password = forms.CharField(widget=forms.PasswordInput(render_value=True), required=True)
    password_confirmation = forms.CharField(widget=forms.PasswordInput(render_value=True), required=True)

    first_name = forms.CharField(required=True)
    last_name = forms.CharField(required=True)

    class Meta:
        model = User
        fields = ('email', 'username', 'first_name', 'last_name', 'password', 'password_confirmation')

    def clean(self):
        cleaned_data = super(UserForm, self).clean()
        password = cleaned_data.get("password")
        password_confirmation = cleaned_data.get("password_confirmation")

        if password != password_confirmation:
            self.fields['password'].widget = forms.PasswordInput()
            self.fields['password_confirmation'].widget = forms.PasswordInput()

            self.add_error('password', "Must match with Password confirmation")
            self.add_error('password_confirmation', "Must match with Password")
            raise forms.ValidationError(
                "Password and Password confirmation do not match"
            )

ProfileForm:

class ProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile
        fields = ('address',)

Надеюсь, что я хорошо понял ваш вопрос и что это поможет вам (и другим придет).:)

Ответ 3

Одним из принципов django является то, что вы можете создать одну большую форму из нескольких более мелких форм, только с одной кнопкой отправки. Вот почему <form> -tags не создаются самим django.

Проблема с общими представлениями, основанными на классе или нет, и несколькими подобными формами в фоновом режиме, конечно же, является пределом неба. Формы могут быть связаны как-то: "материнская" форма и дополнительные факультативные данные, которые зависят от данных в матери (onetoone, скажем). Тогда есть модель, которая подключена к нескольким другим моделям через внешние ключи и/или промежуточные таблицы, где вы будете использовать form + formsets. Тогда есть все-форм-тип страницы, как в администраторе, когда вы создаете некоторые поля, редактируемые непосредственно в виде списка. Каждый из них представляет собой различные типы представлений с несколькими формами, и я не думаю, что было бы полезно сделать один общий вид, который охватывал бы все случаи.

Если у вас есть модель "мать", вы можете использовать стандартные UpdateView или CreateView и добавлять методы для дополнительных форм, вызываемых из get() и post(), после кода, который имеет дело с материнская модель. Например, в form_valid(), если матерная форма действительна, вы можете обрабатывать другие формы. У вас будет pk матери, которую вы затем используете для подключения данных в других формах.