diff options
Diffstat (limited to 'docs/topics/class-based-views')
| -rw-r--r-- | docs/topics/class-based-views/generic-display.txt | 54 | ||||
| -rw-r--r-- | docs/topics/class-based-views/generic-editing.txt | 39 | ||||
| -rw-r--r-- | docs/topics/class-based-views/index.txt | 17 | ||||
| -rw-r--r-- | docs/topics/class-based-views/intro.txt | 54 | ||||
| -rw-r--r-- | docs/topics/class-based-views/mixins.txt | 48 |
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) |
