[PYTHON] Getting started and using sample class-based generic views in Django

If you have any mistakes or misunderstandings, please kindly point them out.

Why write this article

Django has a powerful template language.

It is the View that supplies the data to the template. And there's also a quick way to code it. That is the "Generic View".

It seems that long ago I used function-based generic views, but now I mainly use class-based views. The current Django Japanese translation docs are up to 1.4, but there are good docs for class-based generic views.

http://docs.djangoproject.jp/en/latest/topics/class-based-views.html

However, there were fewer articles explaining class-based generic views than I expected, so I decided to write them down.

Content of this article

--How to use class-based generic views in Django

What is a generic view

The view is the controller in Rails. Django seems to call it view because it is based on the idea of MTV (Model, Template, View).

It is responsible for supplying the data to the template, but most websites

--List of specific conditions --Narrow down and display articles from DB --Display a single article

It has a function called. The process is pre-defined in Django so you don't have to write similar code over and over again.

They are called "generic views", "function-based generic views" if provided by a function, and "class-based generic views" if provided by a class.

In the general view, the appropriate default value is set according to the model, form, etc. Therefore, the advantage is that the amount written by the programmer is very small.

In this article, I've written down about "class-based generic views".

Class-based generic views define similar processing for each class. We can inherit from that class, modify class variables if needed, and override methods to insert our own processing.

How to use class-based generic view

First step

** This is the first step, so let's focus on how to write it. ** **

Generic views are mainly used in views.py etc. Also, link the URL with the function ʻas_view () in ʻurls.py.

Let's write a sample. Suppose you created a project with django-admin etc. and created one application as manager.py startapp foo.

Well, I will write.

foo/views.py


from django.views.generic import TemplateView

class SampleTemplateView(TemplateView):
    template_name = "index.html"

It is now ready for use. You can see that the amount of description is very small.

Here, TemplateView is a generic view. Views are prepared for each purpose in django.views.generic etc., so ʻimport` and inherit.

URL is

urls.py


from foo.views import SampleTemplateView

urlpatterns = [
    url(r'^sample$', SampleTemplateView.as_view()),
]

If so, SampleTemplateView will handle it nicely when accessing / sample.

In this way, you can drastically reduce the amount of code you write to views.py. As the name implies, TemplateView is intended to be drawn using a template. If you use another general-purpose view instead of TemplateView, you can display the list without writing the code to get the data from the DB, or you can output the data after narrowing down by specific conditions.

I want to pass a variable I set to the template

In the first step, I could only view the template. However, you will usually want to display the data read from the DB.

In such a case, override the method defined in the generic view.

The method I often override is get_context_data.

Use it as follows.

views.py


from django.views.generic import TemplateView


class SampleTemplate(TemplateView):

    template_name = "index.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs) #First call the inherited method
        context["foo"] = "bar"
        return context

Use like.

In the case of the above code, the template allows you to use a variable called foo. If you use {{foo}} in the template, it will be bar.

If the file name of the template is ʻindex.html` etc.

index.html


<div class="sample">{{ foo }}</div>

given that,

index.html


<div class="sample">bar</div>

It means that.

In other words, if you describe the process you want to insert in this method and put it in the return value of get_context_data, you can use it in the template.

This chapter summary

Override the method in this way and add the processing you need to the generic view. This is the basic usage.

There are many useful general-purpose views such as ListView for displaying a list and DetailView for creating individual pages, so I will introduce them.

Class-based generic view types (partial)

** Note: Not all are presented here **

https://github.com/django/django/blob/master/django/views/generic/init.py

If you look at, many more generic views are defined, such as RedirectView and FormView.

TemplateView

As the name implies, TemplateView is a general-purpose view that displays something by specifying a template. Therefore, you must specify the template name.

Also, since it calls get_context_data, it overrides and sets the necessary data. Of course, it is out of the control of the general view, so you need to do the count and filtering yourself.

views.py


from django.views.generic import TemplateView


class MyTemplateView(TemplateView):

    template_name = "index.html"  #Mandatory

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        #Do various things
        return context

The author mainly uses it on the top page of the site.

ListView

https://docs.djangoproject.com/ja/1.9/ref/class-based-views/generic-display/#listview

As the name suggests, ListView is used when creating list pages.

If you simply create a list of target models,

views.py


from django.views.generic import ListView
from foo.model import MyModel

class MyListView(ListView):

    model = MyModel

It is defined as. model = MyModel is the model that creates the list.

The template would look like this.

mymodel/mymodel_list.html


<ul>
  {% for item in object_list %}
  <li>{{ item }}</li>
  {% endfor %}
</ul>

Even if there is a lot of data in the DB, ListView will split the list by 10 by default </ s> (changed as pointed out in the comment) You can control the number of items output on one page with. The number to divide is a variable called paginate_by.

In other words, if you want to divide 5 cases at a time

views.py


class MyListView(ListView):
    model = MyModel
    paginate_by = 5

Is defined as.

This is a setting that if there are 100 data items, only 5 items will be displayed on one page.

Also, sometimes you may want to change the sort order of the data to be acquired in advance, or filter and control it. In that case, override the get_queryset method.

views.py


class MyListView(ListView):
    model = MyModel
    paginate_by = 5

    def get_queryset(self):
        return MyModel.objects.filter(some_column=foo)

To do. Then it will issue the query defined by get_queryset and store the data in ʻobject_list`.

If you want to limit the number of items to be extracted in the list, specify here.

The designation is a normal slice.

views.py



def get_queryset(self, queryset=None):
    return MyModel.objects.all()[:10]

With this, even if there are 100 data items, only 10 items will be extracted.

This writing can be a little shorter.

views.py


class MyListView(ListView):
    model = MyModel
    paginate_by = 5
    queryset = MyModel.objects.filter(some_column=foo)

Whichever you use, the result will not change.

If you want to issue queries dynamically, use the former, and if you always want the same result, use the latter.

Of course, you can pass data from other tables to the template by overriding the get_context_data method.

views.py


class MyListView(ListView):
    model = MyModel
    paginate_by = 5

    def get_queryset(self):
        return MyModel.objects.filter(some_column=foo)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**data)
        context["bar"] = OtherModel.objects.all()  #Get data from other models
        return context

However, in this case, ʻOtherModel is not under the control of the generic view, so context ["bar"] stores the entire number of ʻOtherModel.

DetailView

https://docs.djangoproject.com/ja/1.9/ref/class-based-views/generic-display/#detailview

As the name implies, DetailView is a general-purpose view for individual detail pages. Acquires data for a single row of records.

views.py



from django.views.generic import DetailView
from mymodel.model import MyModel


class MyDetailView(DetailView):
    model = MyModel

URL is

urls.py


urlpatterns = [
    url(r'^(?P<pk>\d+)$', MyDetailView.as_view()),
]

will do. pk is the primary key, but DetailView uses this name to identify the record. You can change it, but if you don't need it, you can leave it as it is.

CreateView / UpdateView

https://docs.djangoproject.com/ja/1.9/ref/class-based-views/generic-editing/#django.views.generic.edit.CreateView

CreateView is a view that provides a form to add a new record. ʻUpdateView`, as you might expect, is a view that provides a form to update existing data.

views.py


from django.views.generic.edit import CreateView

class MyCreateView(CreateView):
    model = MyModel
    fields = ("name", )  #List or tuple

Define it like this. It's not much different from other generic views. However, you need to define a variable called fields in CreateView and ʻUpdateView. This means that when data is entered from the form, it will be ignored except for the fields defined in fields`. Is it something like Rails' Strong Parameters?

What sets CreateView and ʻUpdateViewapart from other generic views is that they" create a form ". That is, it creates a variable calledform instead of ʻobject or ʻobject_list`.

The form variable contains data for creating a form, making it easy to create a form.

Here's a template that uses the form variable.

<form method="post">
    {% csrf_token %}
    <table>
        {{ form }}
    </table>
    <input type="submit">
</form>

You have to write the HTML tag <form> </ form> yourself, but the form fields will be generated for you. {{form}} outputs a field using a table by default. If you want to make a field using the <p> </ p> tag, use {{form.as_p}}.

{% csrf_token%} is a CSRF measure. Required if you've created a POST form with Django.

https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B5%E3%82%A4%E3%83%88%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%AA

http://docs.djangoproject.jp/en/latest/ref/contrib/csrf.html

You can also use form objects such as ModelForm.

In this way, you can easily leverage your existing form classes.

forms.py


from django import forms
from myapp.model import MyModel

class MyModelForm(forms.ModelForm):
    class Meta:
        model = MyModel
        fields = ("name", )

views.py


from forms import MyModelForm

class MyCreateView(CreateView):
    form_class = MyModelForm

Validation is also an important feature of these forms. Even if you submit the form, if you get an error in validation and it is not added / updated, you may be confused if you do not notify the user.

Therefore, the methods form_valid and form_invalid are defined in these general-purpose views, and you can perform the necessary processing depending on whether the validation has passed or not.

For example, it's common to tell users if the form they submitted was saved successfully. At that time, Django uses the message framework.

http://docs.djangoproject.jp/en/latest/ref/contrib/messages.html

views.py


from django.views.generic.edit import CreateView
from django.contrib import messages  #Message framework

from myapp.models import MyModel


class MyCreateView(CreateView):
    model = MyModel

    def form_valid(self, form):
        '''When passed validation'''
        messages.success(self.request, "Saved")
        return super().form_valid(form)

    def form_invalid(self, form):
        '''When validation fails'''
        message.warning(self.request, "Could not save")
        return super().form_invalid(form)

Here, we have added our own messages to the form_valid method, which is called when validation is successful, and the form_invalid, which is called when validation is unsuccessful.

By doing this, you can add your own processing, so in addition to the above example, you can insert processing such as notifying the administrator by email when an error occurs.

When you're done with your own processing, call the inheriting method and return the return value.

By the way, to display the message, do as follows. The content of the document is as it is.

{% if messages %}
<ul class="messages">
    {% for message in messages %}
    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
    {% endfor %}
</ul>
{% endif %}

DeleteView

As the name implies, DeleteView deletes a record. The template is (AppName) / (AppName) _confirm_delete.html.

views.py


from django.views.generic.edit import DeleteView
from myapp.models import MyModel


class MyDeleteView(DeleteView):
    model = MyModel

The template in this case will be a confirmation form, "Are you sure you want to delete it?"

Perhaps you don't really want to delete it, you just want to turn on the delete flag. In such a case, it is better to override the delete method. However, I haven't tried it because I delete it obediently.

Common Items

In addition to the above-mentioned processing, the class-based general-purpose view can freely override the template file name and variable name. In addition, the method is also common, so I will introduce some. I think it's usually best to use the default value and override it only when needed.

Default variable

As you may have already noticed, generic views have some regularity. Overriding get_context_data, doing the necessary processing, and setting it in a variable was introduced in the first step. If you want to pass data to a template, this should be enough.

So, on the contrary, when the generic view automatically retrieves the data, what variables are set in the data and what data is set in the data?

In the class-based general-purpose view, data is automatically acquired from the DB according to its purpose (inheriting class). Set from which table to get the data like model = MyModel.

views.py


class MyListView(ListView):
    model = MyModel  #Get a list of MyModels automatically

By doing this, you will get the data from MyModel. The result etc. is to be set in a variable with a fixed name. ʻObject if there is only one record such as an individual page. If there is more than one, ʻobject_list.

The template would look like this:

Individual pages etc.

{{ object }}

If there is more than one

{% for item in object_list %}
  {{ item }}
{% endfor %}

However, I can't tell what this means by looking at the template. You may want to make the variable name more descriptive. In such a case, assign the variable name to context_object_name.

views.py


class MyBookList(ListView):
    model = Books
    context_object_name = "my_books"  #Specify the variable name on this line

This way, in the template

<ul>
    {% for book in my_books %}
    <li>{{ book.name }}</li>
    {% endfor %}
</ul>

You can call it like this. Now that you know what the variable name refers to, it's easy to see what the variable means just by reading the template.

Specifying the file to use as a template

views is a controller. Therefore, pass the data to the appropriate template. The template can be specified explicitly, but it has a default template name. that is,

(ModelName)/(ModelName)_(ViewName).html

That is.

They are,

  • AppName
  • The name of the application created by manage.py startapp etc.
  • ViewName
  • Unique names such as list for ListView and detail for DetailView

It is supposed to be. That is, using ListView with myapp will look for myapp / myapp_list.html by default.

If you want to change this

views.py


class MyListView(ListView):
    model = MyModel
    template_name = "my_list_view.html"  #Template specification on this line

To do.

Digression

If you are using a generic view, you will want to define the same variable in every view. For example, "site name". It's not good to define it in all views, so I wonder if I should create a common class and inherit it.

base_view.py


from django.views.generic import ListView

from myapp.models import MyModel


#Please inherit object without inheriting Mixin etc.
class MyCommonView(object):

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["SITE_NAME"] = "SITE-NAME"
        return context


class MyListView(ListView, MyCommonView):
    model = MyModel

Now, if you want to change the site name, you only have to change it in one place ...? That would be one way, but I think it's better to use the Context Processor obediently.

Afterword

I'm tired.

I hope it will be of some help to those who use Django.

For the time being

  • Django 1.9
  • python 3.5.1

I have confirmed the operation with, but please kindly tell me if there is something wrong.

Recommended Posts

Getting started and using sample class-based generic views in Django
Ajax in Django (using generic class view)
Getting Started with Django 1
Getting Started with Django 2
Sample of getting module name and class name in Python
Overview of class-based generic views and inherited class relationships
Django Getting Started: 2_ Project Creation
Django Getting Started: 1_Environment Building
Getting Started with Python Django (1)
Django Getting Started: 4_MySQL Integration
Django Getting Started: 3_Apache integration
Getting Started with Python Django (4)
Getting Started with Python Django (3)
Getting Started with Python Django (6)
Getting Started with Django with PyCharm
Getting Started with Python Django (5)
Getting Started with Heroku-Viewing Hello World in Python Django with Raspberry PI 3
Flow of getting the result of asynchronous processing using Django and Celery
Deploy Django in 3 minutes using docker-compose
django class-based views API class diagram
Notes using cChardet and python3-chardet in Python 3.3.1.
Information recording memo using session in Django
CSS environment created in 10 minutes using Django