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 без создания каких-либо mixins?
5 ответов
одним из решений было бы использовать mixins, согласно комментарию limelights выше.
другой подход состоит в том, чтобы иметь два отдельных взгляды, один DetailView
и 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
и хотите, чтобы он обрабатывался только при отправке формы.
Django также имеет довольно длинную документацию об этой проблеме.
они советуют сделать 2 разных вида, и у подробного вида обратитесь к виду формы на post.
в настоящее время я вижу, может ли этот хак работать:
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)
С помощью 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>
в Django на примере lightbird они используют библиотеку MCBV для смешивания общих представлений:
мой гид по учебники будут использовать библиотеку классов на основе представлений, основанных на модифицированных Джанго универсальный вид; библиотека называется MCBV (м-модульная) и главное отличие по сравнению с generic CBVs, что можно смешивать и сочетать несколько общих взглядов легко (например, ListView и CreateView, DetailView и UpdateView и т. д.)
вы можете следуйте объяснению здесь:хелпер-функции
и использовать его для смешивания FormView и DetailView, или что угодно
код: MCBV
Я выполнил свое решение с помощью 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)