J'ai une vue où j'ai besoin d'afficher des informations sur une instance de modèle donnée et j'utilise donc une variable DetailView
. J'ai également besoin de cette même vue pour gérer un formulaire standard (pas un formulaire modèle), affichant le formulaire sur GET
et le validant sur POST
. Pour ce faire, j'essaie d'utiliser une FormView
mais la combinaison des deux classes de vues ne fonctionne pas:
class FooView(FormView, DetailView):
# configs here
Dans GET
(pour simplifier la question, je ne montrerai que le problème avec GET
car POST
a un problème différent), cela ne fonctionne pas car le formulaire n'est jamais ajouté au contexte. La raison en est liée à l'ordre de résolution de la méthode pour cette classe:
>>> 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)
Dans la demande, Django doit récupérer le formulaire et l'ajouter au contexte. Cela se passe dans 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))
Cependant, la première classe avec le MRO qui a défini get
est 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)
Comme vous pouvez le voir, le BaseDetailView.get
n'appelle jamais super
et donc le ProcessFormView.get
ne sera jamais appelé, de sorte que le formulaire ne sera pas ajouté au contexte. Cela peut être corrigé en créant une vue mixte où toutes ces nuances pour GET
et POST
peuvent être prises en charge, mais je ne pense pas que ce soit une solution propre.
Ma question: est-il possible d'accomplir ce que je veux avec l'implémentation CBV par défaut de Django sans créer de mixins?
Une solution consisterait à utiliser mixins, comme indiqué dans le commentaire de limelights ci-dessus.
Une autre approche consiste à avoir deux vues distinctes, l’une a DetailView
et l’autre a FormView
. Ensuite, dans le modèle pour le premier, affichez le même formulaire que celui que vous utilisez dans le dernier, à ceci près que vous ne laisserez pas l'attribut action
vide. Réglez-le plutôt sur l'URL de FormView
. Quelque chose dans le sens de ceci (s'il vous plaît méfiez-vous des erreurs que j'écris ceci sans aucun test):
Dans 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'
Dans my_detail_view.html
:
<!-- some representation of the MyModel object -->
<form method="post" action="{% url "my_form_view_url" %}">
{{ form }}
</form>
Dans 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'),
# ...
Notez que le décorateur require_POST
est facultatif, dans le cas où vous ne souhaitez pas que MyFormView
soit accessible par GET
et que vous ne souhaitiez le traiter que lorsque le formulaire est soumis.
Django a également une documentation assez longue sur ce problème.
Ils conseillent de créer 2 vues différentes et de faire en sorte que la vue de détail se réfère à la vue de formulaire sur le post.
Je vois actuellement si ce hack pourrait fonctionner:
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)
En utilisant 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'),
]
modèle
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>
Dans Django By Example de lightbird, ils utilisent une bibliothèque, MCBV, pour mélanger des vues génériques:
Les tutoriels de mon guide utiliseront une bibliothèque de vues basées sur des classes basées sur des vues génériques Django modifiées; la bibliothèque est appelée MCBV (M = modulaire) et la principale différence par rapport aux CBV génériques est qu’il est possible de mélanger et de faire correspondre facilement plusieurs vues génériques (par exemple, ListView et CreateView, DetailView et UpdateView, etc.)
Vous pouvez suivre l'explication ici: helper-functions
Et utilisez-le pour mélanger FormView et DetailView, ou autre chose
Code: MCBV
J'ai effectué ma solution en utilisant ModelForms et quelque chose comme ceci: Sur la méthode get_context_data de mon DetailView j'ai créé:
form = CommentForm(
instance=Comment(
school=self.object, user=self.request.user.profile
)
)
context['form'] = form
Et mon FormView était comme:
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)