Подтвердить что ты не робот

Django объединяет DetailView и FormView

У меня есть представление, где мне нужно отображать информацию об определенном экземпляре модели, поэтому я использую DetailView. Мне также нужен тот же вид для обработки обычной формы (а не образцовой формы), отображающей форму на GET и проверяя ее на POST. Для этого я пытаюсь использовать FormView, однако комбинация обоих представлений не работает:

class FooView(FormView, DetailView):
    # configs here

В GET (для простоты вопроса я покажу только проблему с GET, так как POST имеет другую проблему), она не работает, потому что форма никогда не добавляется в контекст. Причина связана с порядком разрешения метода для этого класса:

>>> inspect.getmro(FooView)
(FooView,
 django.views.generic.edit.FormView,
 django.views.generic.detail.DetailView,
 django.views.generic.detail.SingleObjectTemplateResponseMixin,
 django.views.generic.base.TemplateResponseMixin,
 django.views.generic.edit.BaseFormView,
 django.views.generic.edit.FormMixin,
 django.views.generic.detail.BaseDetailView,
 django.views.generic.detail.SingleObjectMixin,
 django.views.generic.base.ContextMixin,
 django.views.generic.edit.ProcessFormView,
 django.views.generic.base.View,
 object)

В рамках запроса Django должен получить форму и добавить ее в контекст. Это происходит в ProcessFormView.get:

def get(self, request, *args, **kwargs):
    """
    Handles GET requests and instantiates a blank version of the form.
    """
    form_class = self.get_form_class()
    form = self.get_form(form_class)
    return self.render_to_response(self.get_context_data(form=form))

Однако первый класс с MRO, который имеет GET, является BaseDetailView:

def get(self, request, *args, **kwargs):
    self.object = self.get_object()
    context = self.get_context_data(object=self.object)
    return self.render_to_response(context)

Как вы видите, BaseDetailView.get никогда не вызывает super, поэтому ProcessFormView.get никогда не будет вызываться, поэтому форма не будет добавлена ​​в контекст. Это можно устранить, создав представление mixin, где все эти нюансы для GET и POST можно позаботиться, однако я не считаю, что это чистое решение.

Мой вопрос: есть ли способ выполнить то, что я хочу, с реализацией CBV по умолчанию Django без создания каких-либо миксинов?

4b9b3361

Ответ 1

Одним из решений было бы использовать mixins, как указано выше в комментариях.

Другой подход состоит в том, чтобы иметь два отдельных вида: один a DetailView, а другой a FormView. Затем в шаблоне для первого отобразится тот же самый вид, который вы используете в последнем, за исключением того, что вы не оставите атрибут action пустым - вместо этого установите его в URL-адрес для FormView. Что-то вроде этого (будьте осторожны с любыми ошибками, поскольку я пишу это без какого-либо тестирования):

В views.py:

class MyDetailView(DetailView):
    model = MyModel
    template_name = 'my_detail_view.html'

    def get_context_data(self, **kwargs):
        context = super(MyDetailView, self).get_context_data(**kwargs)
        context['form'] = MyFormClass
        return context

class MyFormView(FormView):
    form_class = MyFormClass
    success_url = 'go/here/if/all/works'

В my_detail_view.html:

<!-- some representation of the MyModel object -->

<form method="post" action="{% url "my_form_view_url" %}">

{{ form }}

</form>

В urls.py:

# ...
url('^my_model/(?P<pk>\d+)/$', MyDetailView.as_view(), name='my_detail_view_url'),
url('^my_form/$', require_POST(MyFormView.as_view()), name='my_form_view_url'),
# ...

Обратите внимание, что декоратор require_POST не является обязательным, если вы не хотите, чтобы MyFormView был доступен GET, и хотите, чтобы он обрабатывался только при отправке формы.

Ответ 2

Django также имеет довольно длинную документацию об этой проблеме.

https://docs.djangoproject.com/en/1.8/topics/class-based-views/mixins/#using-formmixin-with-detailview

Они советуют сделать 2 разных представления и просмотреть подробный вид в форме просмотра сообщения.

В настоящее время я вижу, может ли этот хак работать:

class MyDetailFormView(FormView, DetailView):
    model = MyModel
    form_class = MyFormClass
    template_name = 'my_template.html'

    def get_context_data(self, **kwargs):
        context = super(MyDetailFormView, self).get_context_data(**kwargs)
        context['form'] = self.get_form()
        return context

    def post(self, request, *args, **kwargs):
        return FormView.post(self, request, *args, **kwargs)

Ответ 3

В Django. В примере из lightbird они используют библиотеку MCBV для смешивания общих представлений:

В моих руководствах руководств будет использована библиотека классов, основанная на модифицированных общих представлениях Django; библиотека называется MCBV (M означает модульную), и основное отличие по сравнению с общими CBV заключается в том, что ее можно легко смешивать и сопоставлять множественные общие представления (например, ListView и CreateView, DetailView и UpdateView и т.д.).

Вы можете прочитать объяснение здесь: helper-functions

И используйте его для смешивания FormView и DetailView или любого другого

Код: MCBV

Ответ 4

Используя FormMixin

views.py

from django.contrib.auth import get_user_model
from django.core.urlresolvers import (
    reverse_lazy
    )
from django.http import Http404
from django.shortcuts import (
    render,
    redirect
    )
from django.views.generic import (
    DetailView,
    FormView,
    )
from django.views.generic.edit import FormMixin    

from .forms import SendRequestForm


User = get_user_model()  


class ViewProfile(FormMixin, DetailView):

    model = User
    context_object_name = 'profile'
    template_name = 'htmls/view_profile.html'
    form_class = SendRequestForm

    def get_success_url(self):
        return reverse_lazy('view-profile', kwargs={'pk': self.object.pk})

    def get_object(self):
        try:
            my_object = User.objects.get(id=self.kwargs.get('pk'))
            return my_object
        except self.model.DoesNotExist:
            raise Http404("No MyModel matches the given query.")

    def get_context_data(self, *args, **kwargs):
        context = super(ViewProfile, self).get_context_data(*args, **kwargs)
        profile = self.get_object()
        # form
        context['form'] = self.get_form()
        context['profile'] = profile
        return context

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)    

    def form_valid(self, form):
    #put logic here
        return super(ViewProfile, self).form_valid(form)

    def form_invalid(self, form):
    #put logic here
        return super(ViewProfile, self).form_invalid(form)

forms.py

from django import forms 

class SendRequestForm(forms.Form):

    request_type = forms.CharField()

    def clean_request_type(self):
        request_type = self.cleaned_data.get('request_type')
        if 'something' not in request_type:
            raise forms.ValidationError('Something must be in request_type field.')
        return request_type

urls.py

urlpatterns = [
    url(r'^view-profile/(?P<pk>\d+)', ViewProfile.as_view(), name='view-profile'),
]

шаблон

username -{{object.username}}
id -{{object.id}}
<form action="{% url 'view-profile' object.id %}" method="POST">
    {% csrf_token %}
    {{form}}
    <input type="submit" value="Send request">
</form>

Ответ 5

Я выполнил свое решение с помощью ModelForms и что-то вроде этого: По методу get_context_data моего DetailView я сделал:

form = CommentForm(
        instance=Comment(
            school=self.object, user=self.request.user.profile
        )
    )
    context['form'] = form

И мой FormView был похож:

class SchoolComment(FormView):
form_class = CommentForm

def get_success_url(self):
    return resolve_url('schools:school-profile', self.kwargs.get('pk'))

def form_valid(self, form):
    form.save()
    return super(SchoolComment, self).form_valid(form)