summaryrefslogtreecommitdiff
path: root/docs/topics/class-based-views
diff options
context:
space:
mode:
authordjango-bot <ops@djangoproject.com>2023-02-28 20:53:28 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-03-01 13:03:56 +0100
commit14459f80ee3a9e005989db37c26fd13bb6d2fab2 (patch)
treeeb62429ed696ed3a5389f3a676aecfc6d15a99cc /docs/topics/class-based-views
parent6015bab80e28aef2669f6fac53423aa65f70cb08 (diff)
Fixed #34140 -- Reformatted code blocks in docs with blacken-docs.
Diffstat (limited to 'docs/topics/class-based-views')
-rw-r--r--docs/topics/class-based-views/generic-display.txt54
-rw-r--r--docs/topics/class-based-views/generic-editing.txt39
-rw-r--r--docs/topics/class-based-views/index.txt17
-rw-r--r--docs/topics/class-based-views/intro.txt54
-rw-r--r--docs/topics/class-based-views/mixins.txt48
5 files changed, 132 insertions, 80 deletions
diff --git a/docs/topics/class-based-views/generic-display.txt b/docs/topics/class-based-views/generic-display.txt
index cc8d9e21b0..fd3f3c78c9 100644
--- a/docs/topics/class-based-views/generic-display.txt
+++ b/docs/topics/class-based-views/generic-display.txt
@@ -73,6 +73,7 @@ We'll be using these models::
# models.py
from django.db import models
+
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
@@ -87,18 +88,20 @@ We'll be using these models::
def __str__(self):
return self.name
+
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
- headshot = models.ImageField(upload_to='author_headshots')
+ headshot = models.ImageField(upload_to="author_headshots")
def __str__(self):
return self.name
+
class Book(models.Model):
title = models.CharField(max_length=100)
- authors = models.ManyToManyField('Author')
+ authors = models.ManyToManyField("Author")
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
publication_date = models.DateField()
@@ -108,6 +111,7 @@ Now we need to define a view::
from django.views.generic import ListView
from books.models import Publisher
+
class PublisherListView(ListView):
model = Publisher
@@ -118,7 +122,7 @@ Finally hook that view into your urls::
from books.views import PublisherListView
urlpatterns = [
- path('publishers/', PublisherListView.as_view()),
+ path("publishers/", PublisherListView.as_view()),
]
That's all the Python code we need to write. We still need to write a template,
@@ -181,9 +185,10 @@ specifies the context variable to use::
from django.views.generic import ListView
from books.models import Publisher
+
class PublisherListView(ListView):
model = Publisher
- context_object_name = 'my_favorite_publishers'
+ context_object_name = "my_favorite_publishers"
Providing a useful ``context_object_name`` is always a good idea. Your
coworkers who design templates will thank you.
@@ -208,15 +213,15 @@ you can override it to send more::
from django.views.generic import DetailView
from books.models import Book, Publisher
- class PublisherDetailView(DetailView):
+ class PublisherDetailView(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books
- context['book_list'] = Book.objects.all()
+ context["book_list"] = Book.objects.all()
return context
.. note::
@@ -252,9 +257,9 @@ specify the list of objects using the ``queryset`` argument::
from django.views.generic import DetailView
from books.models import Publisher
- class PublisherDetailView(DetailView):
- context_object_name = 'publisher'
+ class PublisherDetailView(DetailView):
+ context_object_name = "publisher"
queryset = Publisher.objects.all()
Specifying ``model = Publisher`` is shorthand for saying ``queryset =
@@ -271,9 +276,10 @@ with the most recent first::
from django.views.generic import ListView
from books.models import Book
+
class BookListView(ListView):
- queryset = Book.objects.order_by('-publication_date')
- context_object_name = 'book_list'
+ queryset = Book.objects.order_by("-publication_date")
+ context_object_name = "book_list"
That's a pretty minimal example, but it illustrates the idea nicely. You'll
usually want to do more than just reorder objects. If you want to present a
@@ -282,11 +288,11 @@ list of books by a particular publisher, you can use the same technique::
from django.views.generic import ListView
from books.models import Book
- class AcmeBookListView(ListView):
- context_object_name = 'book_list'
- queryset = Book.objects.filter(publisher__name='ACME Publishing')
- template_name = 'books/acme_list.html'
+ class AcmeBookListView(ListView):
+ context_object_name = "book_list"
+ queryset = Book.objects.filter(publisher__name="ACME Publishing")
+ template_name = "books/acme_list.html"
Notice that along with a filtered ``queryset``, we're also using a custom
template name. If we didn't, the generic view would use the same template as the
@@ -331,7 +337,7 @@ Here, we have a URLconf with a single captured group::
from books.views import PublisherBookListView
urlpatterns = [
- path('books/<publisher>/', PublisherBookListView.as_view()),
+ path("books/<publisher>/", PublisherBookListView.as_view()),
]
Next, we'll write the ``PublisherBookListView`` view itself::
@@ -341,12 +347,12 @@ Next, we'll write the ``PublisherBookListView`` view itself::
from django.views.generic import ListView
from books.models import Book, Publisher
- class PublisherBookListView(ListView):
- template_name = 'books/books_by_publisher.html'
+ class PublisherBookListView(ListView):
+ template_name = "books/books_by_publisher.html"
def get_queryset(self):
- self.publisher = get_object_or_404(Publisher, name=self.kwargs['publisher'])
+ self.publisher = get_object_or_404(Publisher, name=self.kwargs["publisher"])
return Book.objects.filter(publisher=self.publisher)
Using ``get_queryset`` to add logic to the queryset selection is as convenient
@@ -359,11 +365,12 @@ use it in the template::
# ...
+
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in the publisher
- context['publisher'] = self.publisher
+ context["publisher"] = self.publisher
return context
.. _generic-views-extra-work:
@@ -380,11 +387,12 @@ using to keep track of the last time anybody looked at that author::
# models.py
from django.db import models
+
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
- headshot = models.ImageField(upload_to='author_headshots')
+ headshot = models.ImageField(upload_to="author_headshots")
last_accessed = models.DateTimeField()
The generic ``DetailView`` class wouldn't know anything about this field, but
@@ -397,8 +405,8 @@ custom view::
from books.views import AuthorDetailView
urlpatterns = [
- #...
- path('authors/<int:pk>/', AuthorDetailView.as_view(), name='author-detail'),
+ # ...
+ path("authors/<int:pk>/", AuthorDetailView.as_view(), name="author-detail"),
]
Then we'd write our new view -- ``get_object`` is the method that retrieves the
@@ -408,8 +416,8 @@ object -- so we override it and wrap the call::
from django.views.generic import DetailView
from books.models import Author
- class AuthorDetailView(DetailView):
+ class AuthorDetailView(DetailView):
queryset = Author.objects.all()
def get_object(self):
diff --git a/docs/topics/class-based-views/generic-editing.txt b/docs/topics/class-based-views/generic-editing.txt
index b40d306898..5841c703f6 100644
--- a/docs/topics/class-based-views/generic-editing.txt
+++ b/docs/topics/class-based-views/generic-editing.txt
@@ -23,6 +23,7 @@ Given a contact form:
from django import forms
+
class ContactForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
@@ -39,10 +40,11 @@ The view can be constructed using a ``FormView``:
from myapp.forms import ContactForm
from django.views.generic.edit import FormView
+
class ContactFormView(FormView):
- template_name = 'contact.html'
+ template_name = "contact.html"
form_class = ContactForm
- success_url = '/thanks/'
+ success_url = "/thanks/"
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
@@ -102,11 +104,12 @@ First we need to add :meth:`~django.db.models.Model.get_absolute_url()` to our
from django.db import models
from django.urls import reverse
+
class Author(models.Model):
name = models.CharField(max_length=200)
def get_absolute_url(self):
- return reverse('author-detail', kwargs={'pk': self.pk})
+ return reverse("author-detail", kwargs={"pk": self.pk})
Then we can use :class:`CreateView` and friends to do the actual
work. Notice how we're just configuring the generic class-based views
@@ -119,17 +122,20 @@ here; we don't have to write any logic ourselves:
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from myapp.models import Author
+
class AuthorCreateView(CreateView):
model = Author
- fields = ['name']
+ fields = ["name"]
+
class AuthorUpdateView(UpdateView):
model = Author
- fields = ['name']
+ fields = ["name"]
+
class AuthorDeleteView(DeleteView):
model = Author
- success_url = reverse_lazy('author-list')
+ success_url = reverse_lazy("author-list")
.. note::
We have to use :func:`~django.urls.reverse_lazy` instead of
@@ -154,9 +160,9 @@ Finally, we hook these new views into the URLconf:
urlpatterns = [
# ...
- path('author/add/', AuthorCreateView.as_view(), name='author-add'),
- path('author/<int:pk>/', AuthorUpdateView.as_view(), name='author-update'),
- path('author/<int:pk>/delete/', AuthorDeleteView.as_view(), name='author-delete'),
+ path("author/add/", AuthorCreateView.as_view(), name="author-add"),
+ path("author/<int:pk>/", AuthorUpdateView.as_view(), name="author-update"),
+ path("author/<int:pk>/delete/", AuthorDeleteView.as_view(), name="author-delete"),
]
.. note::
@@ -193,6 +199,7 @@ the foreign key relation to the model:
from django.contrib.auth.models import User
from django.db import models
+
class Author(models.Model):
name = models.CharField(max_length=200)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
@@ -210,9 +217,10 @@ to edit, and override
from django.views.generic.edit import CreateView
from myapp.models import Author
+
class AuthorCreateView(LoginRequiredMixin, CreateView):
model = Author
- fields = ['name']
+ fields = ["name"]
def form_valid(self, form):
form.instance.created_by = self.request.user
@@ -234,14 +242,16 @@ works with an API-based workflow as well as 'normal' form POSTs::
from django.views.generic.edit import CreateView
from myapp.models import Author
+
class JsonableResponseMixin:
"""
Mixin to add JSON support to a form.
Must be used with an object-based FormView (e.g. CreateView)
"""
+
def form_invalid(self, form):
response = super().form_invalid(form)
- if self.request.accepts('text/html'):
+ if self.request.accepts("text/html"):
return response
else:
return JsonResponse(form.errors, status=400)
@@ -251,14 +261,15 @@ works with an API-based workflow as well as 'normal' form POSTs::
# it might do some processing (in the case of CreateView, it will
# call form.save() for example).
response = super().form_valid(form)
- if self.request.accepts('text/html'):
+ if self.request.accepts("text/html"):
return response
else:
data = {
- 'pk': self.object.pk,
+ "pk": self.object.pk,
}
return JsonResponse(data)
+
class AuthorCreateView(JsonableResponseMixin, CreateView):
model = Author
- fields = ['name']
+ fields = ["name"]
diff --git a/docs/topics/class-based-views/index.txt b/docs/topics/class-based-views/index.txt
index 206cf0a006..ec126099f6 100644
--- a/docs/topics/class-based-views/index.txt
+++ b/docs/topics/class-based-views/index.txt
@@ -42,7 +42,7 @@ call itself::
from django.views.generic import TemplateView
urlpatterns = [
- path('about/', TemplateView.as_view(template_name="about.html")),
+ path("about/", TemplateView.as_view(template_name="about.html")),
]
Any arguments passed to :meth:`~django.views.generic.base.View.as_view` will
@@ -65,6 +65,7 @@ override the template name::
# some_app/views.py
from django.views.generic import TemplateView
+
class AboutView(TemplateView):
template_name = "about.html"
@@ -78,7 +79,7 @@ method instead, which provides a function-like entry to class-based views::
from some_app.views import AboutView
urlpatterns = [
- path('about/', AboutView.as_view()),
+ path("about/", AboutView.as_view()),
]
@@ -103,7 +104,7 @@ We map the URL to book list view in the URLconf::
from books.views import BookListView
urlpatterns = [
- path('books/', BookListView.as_view()),
+ path("books/", BookListView.as_view()),
]
And the view::
@@ -112,14 +113,19 @@ And the view::
from django.views.generic import ListView
from books.models import Book
+
class BookListView(ListView):
model = Book
def head(self, *args, **kwargs):
- last_book = self.get_queryset().latest('publication_date')
+ last_book = self.get_queryset().latest("publication_date")
response = HttpResponse(
# RFC 1123 date format.
- headers={'Last-Modified': last_book.publication_date.strftime('%a, %d %b %Y %H:%M:%S GMT')},
+ headers={
+ "Last-Modified": last_book.publication_date.strftime(
+ "%a, %d %b %Y %H:%M:%S GMT"
+ )
+ },
)
return response
@@ -142,6 +148,7 @@ asynchronous code using ``await``::
from django.http import HttpResponse
from django.views import View
+
class AsyncView(View):
async def get(self, request, *args, **kwargs):
# Perform io-blocking view logic using await, sleep for example.
diff --git a/docs/topics/class-based-views/intro.txt b/docs/topics/class-based-views/intro.txt
index 6b31180cf9..ab84c8c9ac 100644
--- a/docs/topics/class-based-views/intro.txt
+++ b/docs/topics/class-based-views/intro.txt
@@ -63,20 +63,22 @@ something like::
from django.http import HttpResponse
+
def my_view(request):
- if request.method == 'GET':
+ if request.method == "GET":
# <view logic>
- return HttpResponse('result')
+ return HttpResponse("result")
In a class-based view, this would become::
from django.http import HttpResponse
from django.views import View
+
class MyView(View):
def get(self, request):
# <view logic>
- return HttpResponse('result')
+ return HttpResponse("result")
Because Django's URL resolver expects to send the request and associated
arguments to a callable function, not a class, class-based views have an
@@ -94,7 +96,7 @@ or raises :class:`~django.http.HttpResponseNotAllowed` if not::
from myapp.views import MyView
urlpatterns = [
- path('about/', MyView.as_view()),
+ path("about/", MyView.as_view()),
]
@@ -116,6 +118,7 @@ and methods in the subclass. So that if your parent class had an attribute
from django.http import HttpResponse
from django.views import View
+
class GreetingView(View):
greeting = "Good Day"
@@ -131,7 +134,7 @@ Another option is to configure class attributes as keyword arguments to the
:meth:`~django.views.generic.base.View.as_view` call in the URLconf::
urlpatterns = [
- path('about/', GreetingView.as_view(greeting="G'day")),
+ path("about/", GreetingView.as_view(greeting="G'day")),
]
.. note::
@@ -185,16 +188,17 @@ A basic function-based view that handles forms may look something like this::
from .forms import MyForm
+
def myview(request):
if request.method == "POST":
form = MyForm(request.POST)
if form.is_valid():
# <process form cleaned data>
- return HttpResponseRedirect('/success/')
+ return HttpResponseRedirect("/success/")
else:
- form = MyForm(initial={'key': 'value'})
+ form = MyForm(initial={"key": "value"})
- return render(request, 'form_template.html', {'form': form})
+ return render(request, "form_template.html", {"form": form})
A similar class-based view might look like::
@@ -204,22 +208,23 @@ A similar class-based view might look like::
from .forms import MyForm
+
class MyFormView(View):
form_class = MyForm
- initial = {'key': 'value'}
- template_name = 'form_template.html'
+ initial = {"key": "value"}
+ template_name = "form_template.html"
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
- return render(request, self.template_name, {'form': form})
+ return render(request, self.template_name, {"form": form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
# <process form cleaned data>
- return HttpResponseRedirect('/success/')
+ return HttpResponseRedirect("/success/")
- return render(request, self.template_name, {'form': form})
+ return render(request, self.template_name, {"form": form})
This is a minimal case, but you can see that you would then have the option
of customizing this view by overriding any of the class attributes, e.g.
@@ -246,8 +251,8 @@ this is in the URLconf where you deploy your view::
from .views import VoteView
urlpatterns = [
- path('about/', login_required(TemplateView.as_view(template_name="secret.html"))),
- path('vote/', permission_required('polls.can_vote')(VoteView.as_view())),
+ path("about/", login_required(TemplateView.as_view(template_name="secret.html"))),
+ path("vote/", permission_required("polls.can_vote")(VoteView.as_view())),
]
This approach applies the decorator on a per-instance basis. If you
@@ -273,8 +278,9 @@ instance method. For example::
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
+
class ProtectedView(TemplateView):
- template_name = 'secret.html'
+ template_name = "secret.html"
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
@@ -283,9 +289,9 @@ instance method. For example::
Or, more succinctly, you can decorate the class instead and pass the name
of the method to be decorated as the keyword argument ``name``::
- @method_decorator(login_required, name='dispatch')
+ @method_decorator(login_required, name="dispatch")
class ProtectedView(TemplateView):
- template_name = 'secret.html'
+ template_name = "secret.html"
If you have a set of common decorators used in several places, you can define
a list or tuple of decorators and use this instead of invoking
@@ -293,14 +299,16 @@ a list or tuple of decorators and use this instead of invoking
decorators = [never_cache, login_required]
- @method_decorator(decorators, name='dispatch')
+
+ @method_decorator(decorators, name="dispatch")
class ProtectedView(TemplateView):
- template_name = 'secret.html'
+ template_name = "secret.html"
+
- @method_decorator(never_cache, name='dispatch')
- @method_decorator(login_required, name='dispatch')
+ @method_decorator(never_cache, name="dispatch")
+ @method_decorator(login_required, name="dispatch")
class ProtectedView(TemplateView):
- template_name = 'secret.html'
+ template_name = "secret.html"
The decorators will process a request in the order they are passed to the
decorator. In the example, ``never_cache()`` will process the request before
diff --git a/docs/topics/class-based-views/mixins.txt b/docs/topics/class-based-views/mixins.txt
index 4d622211d6..2603c7d447 100644
--- a/docs/topics/class-based-views/mixins.txt
+++ b/docs/topics/class-based-views/mixins.txt
@@ -229,8 +229,10 @@ We'll demonstrate this with the ``Author`` model we used in the
from django.views.generic.detail import SingleObjectMixin
from books.models import Author
+
class RecordInterestView(SingleObjectMixin, View):
"""Records the current user's interest in an author."""
+
model = Author
def post(self, request, *args, **kwargs):
@@ -241,7 +243,9 @@ We'll demonstrate this with the ``Author`` model we used in the
self.object = self.get_object()
# Actually record interest somehow here!
- return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk}))
+ return HttpResponseRedirect(
+ reverse("author-detail", kwargs={"pk": self.object.pk})
+ )
In practice you'd probably want to record the interest in a key-value
store rather than in a relational database, so we've left that bit
@@ -259,8 +263,12 @@ We can hook this into our URLs easily enough:
from books.views import RecordInterestView
urlpatterns = [
- #...
- path('author/<int:pk>/interest/', RecordInterestView.as_view(), name='author-interest'),
+ # ...
+ path(
+ "author/<int:pk>/interest/",
+ RecordInterestView.as_view(),
+ name="author-interest",
+ ),
]
Note the ``pk`` named group, which
@@ -313,6 +321,7 @@ Now we can write a new ``PublisherDetailView``::
from django.views.generic.detail import SingleObjectMixin
from books.models import Publisher
+
class PublisherDetailView(SingleObjectMixin, ListView):
paginate_by = 2
template_name = "books/publisher_detail.html"
@@ -323,7 +332,7 @@ Now we can write a new ``PublisherDetailView``::
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- context['publisher'] = self.object
+ context["publisher"] = self.object
return context
def get_queryset(self):
@@ -448,15 +457,17 @@ Our new ``AuthorDetailView`` looks like this::
from django.views.generic.edit import FormMixin
from books.models import Author
+
class AuthorInterestForm(forms.Form):
message = forms.CharField()
+
class AuthorDetailView(FormMixin, DetailView):
model = Author
form_class = AuthorInterestForm
def get_success_url(self):
- return reverse('author-detail', kwargs={'pk': self.object.pk})
+ return reverse("author-detail", kwargs={"pk": self.object.pk})
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
@@ -514,15 +525,17 @@ write our own ``get_context_data()`` to make the
from django.views.generic import DetailView
from books.models import Author
+
class AuthorInterestForm(forms.Form):
message = forms.CharField()
+
class AuthorDetailView(DetailView):
model = Author
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- context['form'] = AuthorInterestForm()
+ context["form"] = AuthorInterestForm()
return context
Then the ``AuthorInterestForm`` is a :class:`FormView`, but we have to bring in
@@ -536,8 +549,9 @@ is using on ``GET``::
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin
+
class AuthorInterestFormView(SingleObjectMixin, FormView):
- template_name = 'books/author_detail.html'
+ template_name = "books/author_detail.html"
form_class = AuthorInterestForm
model = Author
@@ -548,7 +562,7 @@ is using on ``GET``::
return super().post(request, *args, **kwargs)
def get_success_url(self):
- return reverse('author-detail', kwargs={'pk': self.object.pk})
+ return reverse("author-detail", kwargs={"pk": self.object.pk})
Finally we bring this together in a new ``AuthorView`` view. We
already know that calling :meth:`~django.views.generic.base.View.as_view()` on
@@ -562,8 +576,8 @@ behavior to also appear at another URL but using a different template::
from django.views import View
- class AuthorView(View):
+ class AuthorView(View):
def get(self, request, *args, **kwargs):
view = AuthorDetailView.as_view()
return view(request, *args, **kwargs)
@@ -593,18 +607,17 @@ For example, a JSON mixin might look something like this::
from django.http import JsonResponse
+
class JSONResponseMixin:
"""
A mixin that can be used to render a JSON response.
"""
+
def render_to_json_response(self, context, **response_kwargs):
"""
Returns a JSON response, transforming 'context' to make the payload.
"""
- return JsonResponse(
- self.get_data(context),
- **response_kwargs
- )
+ return JsonResponse(self.get_data(context), **response_kwargs)
def get_data(self, context):
"""
@@ -629,6 +642,7 @@ To use it, we need to mix it into a ``TemplateView`` for example, and override
from django.views.generic import TemplateView
+
class JSONView(JSONResponseMixin, TemplateView):
def render_to_response(self, context, **response_kwargs):
return self.render_to_json_response(context, **response_kwargs)
@@ -642,6 +656,7 @@ rendering behavior has been mixed in)::
from django.views.generic.detail import BaseDetailView
+
class JSONDetailView(JSONResponseMixin, BaseDetailView):
def render_to_response(self, context, **response_kwargs):
return self.render_to_json_response(context, **response_kwargs)
@@ -663,10 +678,13 @@ that the user requested::
from django.views.generic.detail import SingleObjectTemplateResponseMixin
- class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
+
+ class HybridDetailView(
+ JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView
+ ):
def render_to_response(self, context):
# Look for a 'format=json' GET argument
- if self.request.GET.get('format') == 'json':
+ if self.request.GET.get("format") == "json":
return self.render_to_json_response(context)
else:
return super().render_to_response(context)