[PYTHON] TemplateView patterns you'll want to learn first in Django

TemplateView

If you create a Web service, most of the server processing will load an HTML template and apply display variables to it.

That's why Django provides TemplateView (django.views.generic.base.TemplateView). Although it is a simple class, it is a very convenient view class that contains the functions required for page generation.

If you create a view in class-based view format, most of the views HTTP GET from the browser will be written in TemplateView.

In the case of POST, I think that there are many processes such as using FormView, processing with raw view and returning HttpResponseRedirect. (It depends on the service content)

TemplateView can be written in various patterns due to the language characteristics of Python. Which one is better is on a case-by-case basis, but I will write some of the methods I often write.

Prerequisites (environment)

+ manage.py
+ myapp
  + models.py
  + urls.py
  + views.py
  + templates
    + myapp
      + index.html

Suppose you have a directory structure like this.

Also assume that settings.INSTALLED_APPS contains myapp and settings.TEMPLATE_LOADERS contains django.template.loaders.app_directories.Loader.

1. A pattern that gives an argument to as_view with urls

Basic

Not limited to TemplateView, it is a function of View ( django.views.generic.base.View), but it has a function to automatically store the keyword argument given to the constructor in the instance variable. When converting to a view function with ʻas_view (), all the arguments of ʻas_view () are usually the constructor arguments of the view class.

And because TemplateView uses the instance variable template_name as a template by default,

urls.py



urlpatterns = [
    url(r'^myapp/$', TemplateView.as_view(template_name='myapp/index.html'),
        name='myapp-index'),
    ...

If you write this, when you request myapp / in the browser, the contents of myapp / index.html will be evaluated and output to the browser.

ʻAs_view ()At runtime, a decorator-wrapped view function is generated, but the keyword arguments are bound within it, and when the user actually requests it,TemplateView (template_name ='myapp / index." The html') `equivalent construct process works, and the get method works to create the response.

application

TemplateView, or ContextMixin, is a feature of the instantiated view class as a template argument named view.

urls.py



urlpatterns = [
    url(r'^myapp/a/$', TemplateView.as_view(template_name='myapp/index.html', mode='a'),
        name='myapp-a'),
    url(r'^myapp/b/$', TemplateView.as_view(template_name='myapp/index.html', mode='b'),
        name='myapp-b'),   ...

Keep it in urls like this and in the template

myapp/index.html



{% if view.mode == 'a' %}
Mode A display
{% elif view.mode == 'b' %}
Mode B display
{% endif %}

You can also branch the display in this way.

Also, as a function of View, request, kwargs, etc. are automatically added to the instance argument, so

urls.py



urlpatterns = [
    url(r'^myapp/(?P<mode_name>\w+)/$', TemplateView.as_view(template_name='myapp/index.html'),
        name='myapp-index'),
    ...

myapp/index.html


{{ view.kwargs.mode_name }}mode<br />
Hello! {{ view.request.user.username }}

This kind of display can be achieved with just a template without writing any Python code other than urls.

2. Pattern to override the get method

A pattern that creates a template context within get without calling the parent get

This pattern is often used when you want to write view processing in a procedural manner.

myapp/views.py


class IndexView(TemplateView):
    template_name = 'myapp/index.html'

    def get(self, request, **kwargs):
Procedural processing...
        context = {
            'items': ....
        }
        return self.render_to_response(context)

The format is intuitive and easy to understand.

When it comes to complicated processing, the method becomes long vertically, so in such a case it is better to send the processing to another class. When dealing with models, I think it usually works well if you put the method in the model or in the model manager (MyAppModel.objects ← this guy).

For an example of extending the model manager and spawning methods, you might find it helpful to see AbstractUser in django.contrib.auth.models using UserManager as a manager.

By the way, if you write as shown above, you will not be able to refer to the view instance in view from the template, so if you want to refer to view from the template

    context = {
        'view': self,
        'items': ....
    }

You can also explicitly include the view like this.

Personally, when I want to override the TemplateView get method, I think I should calm down and think about what I really need. When acquiring a model instance, overriding get_context_data described later or creating a method in view can make each process sparse and reduce the scope of variables, so the risk of side effects of the process can be reduced.

Pattern to call parent get

myapp/views.py


class IndexView(TemplateView):
    template_name = 'myapp/index.html'

    def get(self, request, **kwargs):
        if XXX:
            return HttpResponseBadRequest(...)
        return super().get(request, **kwargs)
        # python2: return super(IndexView, self).get(request, **kwargs)

This is a more familiar form in object-oriented languages. It's not a pattern to mess with the template context, so it has limited capabilities and limited uses. It is often used when you want to write only the process that returns an error to the request.

3. Patterns that override get_context_data

If overriding get doesn't feel like the logic gets dirty, it's a good idea to override the ContextMixin method get_context_data.

myapp/views.py


class IndexView(TemplateView):
    template_name = 'myapp/index.html'

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['items'] = ...
        ctx[xxx] = ...
        return ctx

It looks like this.

If you want to create a base class that extends TemplateView and then extend it (inherit in multiple stages) to create an actual view class, it is simpler to write to override get_context_data rather than override get. I feel like I can read it.

Now, unlike when you override get, it looks like you can't access the request argument, but because of the View feature, request is an instance variable, so

myapp/views.py


class IndexView(TemplateView):
    template_name = 'myapp/index.html'

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['authenticated'] = self.request.user.is_authenticated
        ctx[xxx] = self.request.session.get(...)
        return ctx

In this way, you can handle requests without any problems.

The kwargs argument of get_context_data contains the content that matches the placeholder of the named regular expression specified in urls.py, but this content is also included in self.kwargs and can be accessed, so it is a bit verbose. I feel like it.

4. And cached_property

The View class works very well with cached_property (django.utils.functional.cached_property) because it is instantiated on request.

myapp/views.py


class IndexView(TemplateView):
    template_name = 'myapp/index.html'

    @cached_property
    def items(self):
        """List of possessed items"""
        return Item.objects.filter(user=self.request.user)

    @cached_property
    def power_total(self):
        """Total power"""
        return sum(i.power for i in self.items)

    @cached_property
    def power_average(self):
        """Power average"""
        return self.power_total / len(self.items)

    def _get_special_items(self):
        """Generator that extracts only special items from items"""
        for item in self.items:
            if item.is_not_special:
                continue
            if ....:
                continue
            yield item

    @cached_property
    def special_power_total(self):
         """Total power of special items"""
         return sum(i.power for i in self._get_special_items())

    @cached_property
    def special_power_rate(self):
         """Total power ratio of special items and all items"""
         return self.special_power_total / self.power_total

myapp/index.html


Total power: {{ view.power_total }}
Total special power: {{ view.special_power_total }}
Special rate: {{ view.special_power_rate }}

Well, in this example, it is said that ** Create a separate class that summarizes Items without writing it in view! **, but you can write it like this as an example.

Using cached_property guarantees that heavy processing will only run once in the view without any special awareness, so it has good visibility and processing speed. You don't have to worry about the hierarchical relationship of processing.

For example, in this example, the items method has @cached_property, so the SQL to search the Item model is issued only once, and the process of summing the sum of the powers of all items is done only once. I understand.

I think it's a Python-like way of writing code, but what about it?

Caching HTML generation results

A good way to cache HTML generation results in a common cache is to use the django.views.decorators.cache_page decorator, as the Django documentation says.

You can decorate the result of as_view

urls.py



urlpatterns = [
    url(r'^myapp/(?P<mode_name>\w+)/$',
        cache_page(3600)(TemplateView.as_view(template_name='myapp/index.html')),
        name='myapp-index'),
    ...

Loose

views.py


class IndexView(TemplateView):
    template_name = 'myapp/index.html'
    ....

index_view = cache_page(3600)(IndexView.as_view())

urls.py


urlpatterns = [
    url(r'^myapp/(?P<mode_name>\w+)/$',
        'index_view',
        name='myapp-index'),
    ...

I think it's a pattern that I often use. login_required is the same.

views.py


class MyPageView.as_view(TemplateView):
    template_name = 'mypage/index.html'
    ....

mypage_view = login_required(MyPageView.as_view())

If you use django.utils.decorators.method_decorator, it will transform the decorator function so that it can be applied to bound methods.

views.py


class IndexView(TemplateView):
    template_name = 'myapp/index.html'
    
    @method_decorator(cache_page(3600))
    def get(....):
        ....

If you want to decorate the get method, this is also a good idea.

If you don't like overriding the get method, you can do it inside the overridden as_view.

views.py


class IndexView(TemplateView):
    template_name = '...'

    @classonlymethod
    def as_view(cls, **initkwargs):
        return cache_page(3600)(
            super().as_view(**initkwargs)

Cases where you want to handle HTTP POST

In a web app that returns HTML, if you're about to write a def post (...) in aTemplateView, let's calm down.

Perhaps FormView is better than TemplateView.

FormView has a feature that works with Django forms to "prompt you to re-enter while displaying an error display when there is an error in the form". "If there is no error in the form, perform ◯◯ processing" is also prepared as a template method. Why don't you consider it?

Postscript CRUD generic view

I wrote about CRUD generic view on my blog. Easy to use Django's CRUD generic views (ListView, DetailView, CreateView, UpdateView, DeleteView)

Recommended Posts

TemplateView patterns you'll want to learn first in Django
I want to pin Datetime.now in Django tests
[Django] I want to log in automatically after new registration
Errors related to memcached in django
How to reflect CSS in Django
I want to scrape images to learn
I want to use the Django Debug Toolbar in my Ajax application
I want to print in a comprehension
How to delete expired sessions in Django
[Django] Want to customize your admin page?
Django non-logged-in users want to transition to login.html
I want to embed Matplotlib in PySimpleGUI
How to do Server-Sent Events in Django
How to convert DateTimeField format in Django
If you want to display values using choices in a template in a Django model
[Django memo] I want to set the login user information in the form in advance
The first thing to check when a No Reverse Match occurs in Django
Scraping in Python-Introduction to Scrapy First 2nd Step
First steps to try Google CloudVision in Python
I want to do Dunnett's test in Python
Dynamically add fields to Form objects in Django
How to implement Rails helper-like functionality in Django
How to reflect ImageField in Django + Docker (pillow)
How to run some script regularly in Django
I want to store DB information in list
How to write regular expression patterns in Linux
I want to merge nested dicts in Python
Pass login user information to view in Django
I implemented Google's Speech to text in Django
How to create a Rest Api in Django
I want to display the progress in Python!
I want to upload a Django app to heroku
I want to create a lunch database [EP1] Django study for the first time
I want to create a lunch database [EP1-4] Django study for the first time
I want to get an error message in Japanese with django Password Change Form