[PYTHON] [Django 2.2] How do you display relation destinations in Django? [List View]

Introduction

Hello. My name is ragnar and I have 1.5 months of experience with Django (in practice). When I started Django, I did some tutorials, but when it came to actually making it, there were a lot of new concepts and I wasn't sure what to do.

The first thing that got stuck was the relationship. Before dealing with Django, I wrote SQL and operated DB, so I don't understand the concept of Queryset well, can I get data with this? How is it? It was in a state.

This time, it's an article for people who "tried Django to the tutorial but didn't understand it well".

environment

Prerequisite model

This time, we will assume a site where you can see a list of soccer teams, players, and player information. We have created a table (model) of teams, positions and players.

sample/models.py


from django.db import models

FOOT_CHOICES = (
    ('right', 'Right foot'),
    ('left', 'left foot'),
    ('both', 'Both feet'),
)


class Team(models.Model):
    name = models.CharField(max_length=40)
    country = models.CharField(max_length=40)

    def __str__(self):
        return self.name

    class Meta:
        #The name you see on the Django admin site
        verbose_name = "team"
        verbose_name_plural = "team"


class Position(models.Model):
    name = models.CharField(max_length=20)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "position"
        verbose_name_plural = "position"


class SoccerPlayer(models.Model):
    name = models.CharField(max_length=40)
    foot = models.CharField(max_length=5, choices=FOOT_CHOICES)
    #Athletes belong to one club. Multiple players belong to the team. 1 to n structure
    #Players may be independent. on_delete=models.SET_NULL
    team = models.ForeignKey(Team, null=True, on_delete=models.SET_NULL, related_name='player')
    #Athletes may take multiple positions. There are multiple players in one position. n to n structure
    position = models.ManyToManyField(Position, related_name='player')

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "player"
        verbose_name_plural = "player"

Register your model so that you can register your data from the Django admin site. (I entered the data appropriately.)

sample/admin.py


from django.contrib import admin

from . import models

admin.site.register(models.Position)
admin.site.register(models.SoccerPlayer)
admin.site.register(models.Team)

We've also introduced django-debug-toolbar to check your queries. It's no exaggeration to say that it's indispensable for Django development, so if you haven't included it, this is the time.

config/urls.py


from django.contrib import admin
from django.urls import path, include
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('sample/', include('sample.urls'), name='sample')  #This app
]

if settings.DEBUG:
    import debug_toolbar
    urlpatterns = [
        path('__debug__/', include(debug_toolbar.urls)),
    ] + urlpatterns

ListView Django has a web application called Generic View. Receive GET method and display page, receive POST method, validate form, update record, delete ... etc. There is a class with very general behavior.

This time, we will summarize the list display and the handling of relations using ListView, which is specialized for list display of records.

Display player list

First, let's take out the names of all the players.

sample/views.py


from django.views.generic import ListView
from .models import SoccerPlayer


class PlayerListView(ListView):
    model = SoccerPlayer  #Model to display a list
    template_name = 'player_list.html'  # template


player_list = PlayerListView.as_view()

sample/urls.py


from django.urls import path
from . import views

app_name = 'sample'
urlpatterns = [
    path('list/', views.player_list, name='player_list'),
]

templates/player_list.html


<!DOCTYPE html>
<html>
  <head>
    <title>sample</title>
  </head>
  <body>
    <table>
      <tr>
        <th>name</th>
      </tr>
      {% for player in soccerplayer_list %}
      <tr>
        <td>{{ player.name }}</td>
      </tr>
      {% endfor %}
    </table>
  </body>
</html>

The first thing to remember in ListView is that View passes an object to Template with the name ** [model name (lowercase)] _list **. (You can also specify it yourself.) You can retrieve this with for in the template and ** [variable retrieved with for] .name ** to retrieve the name. ** name ** is the field of model defined by model. I think I've probably done it in tutorials.

If you access http: // localhost: 8000 / sample / list /, you can see that the list can be displayed. スクリーンショット 2019-12-16 23.16.56.png


Extraction of values linked by Foreignkey

Next, let's take out the team associated with the player. Even if you say the relationship destination, in the case of the OnetoOneField (1 to 1) and Foreign key (1 to n) relationship, it is the same as the usual writing style. Just specify the field name of the relation destination as in the case of name.

templates/player_list.html


<!DOCTYPE html>
<html>
  <head>
    <title>sample</title>
  </head>
  <body>
    <table>
      <tr>
        <th>name</th>
        <th>team</th>
      </tr>
      {% for player in soccerplayer_list %}
      <tr>
        <td>{{ player.name }}</td>
        <td>{{ player.team }}</td> 
      </tr>
      {% endfor %}
    </table>
  </body>
</html>

スクリーンショット 2019-12-16 23.30.16.png

Django will also get the record associated with the Player object, even if you don't specify it. At first I didn't understand this feeling and had a hard time getting used to it.


Retrieving values tied together in ManytoManyField

Next is the case of ManytoManyField, an n-to-n connection. Since it is n to n, multiple records are retrieved. In this case, there are multiple positions per player. Unlike 1 to n etc., it cannot be obtained with player.position. You need to make it player.position.all and retrieve it with for.

templates/player_list.html


<!DOCTYPE html>
<html>
  <head>
    <title>sample</title>
  </head>
  <body>
    <table>
      <tr>
        <th>name</th>
        <th>team</th>
        <th>position</th>
      </tr>
      {% for player in soccerplayer_list %}
      <tr>
        <td>{{ player.name }}</td>
        <td>{{ player.team }}</td> 
        <td>
          {% for position in player.position.all %}
          {{ position.name }}
          {% endfor %}
        </td>
      </tr>
      {% endfor %}
    </table>
  </body>
</html>

(The players you put in are too suitable and there are few players in multiple positions) スクリーンショット 2019-12-16 23.45.25.png

In case of reverse reference

Next, let's try the so-called reverse reference, Team-> Player.

In the case of the reverse reference, unlike the previous pattern, the field is not defined in the model. However, although it is not included in the model field, it is properly referenced. To make it clear and easy to understand, it's a good idea to define related_name.

sample/models.py


#Partially omitted
class Team(models.Model):
    name = models.CharField(max_length=40)
    country = models.CharField(max_length=40)
    #team has no player field!

class SoccerPlayer(models.Model):
    name = models.CharField(max_length=40)
    foot = models.CharField(max_length=5, choices=FOOT_CHOICES)
    # related_In case of reverse reference with name, specify the phrase to be used
    team = models.ForeignKey(Team, null=True, on_delete=models.SET_NULL, related_name='player')
    position...

When you get (Player from Team) from the opposite, you can get it using the name set in this related_name. Here, since player is specified, the record of the player associated with that team can be obtained with team.player.all.

sample/views.py


#add to
from .models import Team

class TeamList(ListView):
    model = Team
    template_name = 'team_list.html'

sample/urls.py


from django.urls import path
from . import views

app_name = 'sample'
urlpatterns = [
    path('list/', views.player_list, name='player_list'),
    path('list/team/', views.team_list, name='team_list'),
]

If you get it with team.player.all, you can take it out one by one with for, which is the same as before.

templates/team_list.html


<!DOCTYPE html>
<html>
  <head>
    <title>sample</title>
  </head>
  <body>
    <table>
      <tr>
        <th>name</th>
        <th>Number of people</th>
        <th>player</th>
      </tr>
      {% for team in team_list %}
      <tr>
        <td>{{ team.name }}</td>
        <td>{{ team.player.all.count }}</td>
        <td>
          {% for player in team.player.all %} 
          {{ player }} 
          {% endfor %}
        </td>
      </tr>
      {% endfor %}
    </table>
  </body>
</html>

If you access http: // localhost: 8000 / sample / list / team /, you can see that the players associated with each team have been acquired. (If you don't understand soccer, it may not come to you. I'm sorry.) スクリーンショット 2019-12-17 0.27.10.png

Check the issued SQL

On the team list page, use django-debug-toolbar to check the SQL. If you click the SQL panel from the django-debug-toolbar displayed on the side, it looks like this. スクリーンショット 2019-12-17 0.33.10.png It's small and confusing, but 13 SQLs are being executed to display the information on this page. This phenomenon is because the loop in the template gets the information of the relation destination every time. (It's called the N + 1 problem) At this rate, as the number of records increases, the number of SQL executions when loading the page increases, and the page becomes extremely heavy.

To solve this, use a method called prefetch_related.

prefetch_related and select_related

--** prefetch_related ** if you want to fetch n tied records --** select_related ** if you want to get one tied record

Simply put prefetch_related fetches records from DB in advance and associates each with python (Django) select_related is obtained by JOIN from DB It is a method called. (I apologize if I'm wrong.)

In the case of ListView, View has a method called * get_queryset *, which can be rewritten.

sample/views.py


class TeamList(ListView):
    model = Team
    template_name = 'team_list.html'

    def get_queryset(self):
        #Method to get the query set to be displayed in View
        qs = self.model.objects.prefetch_related('player')  # team ->Since it is a reverse reference of player, related_use name
        return qs

By doing this, the number of SQL executions was reduced to two. スクリーンショット 2019-12-17 0.51.25.png

If you also check the first player list page, 13 queries have been issued. スクリーンショット 2019-12-17 0.55.26.png

This page gets the team and position from the player. Even in this case, it is okay to connect prefetch_related and select_related.

sample/views.py


class PlayerListView(ListView):
    model = SoccerPlayer
    template_name = 'player_list.html'

    def get_queryset(self):
        qs = self.model.objects.prefetch_related('position').select_related('team')
        return qs

This reduced the number of queries issued to 2. スクリーンショット 2019-12-17 0.59.54.png

If you check the SQL and see the notation such as 6 similar queries or 4 duplicates, don't forget to use prefetch_related (select_related).

Summary

-** 1 vs n ** and ** 1 vs 1 ** relationships can be retrieved using the field name -** n to n ** relations are retrieved with [field name] .all and retrieved one by one using for -In case of ** reverse reference **, retrieve using related_name set in the field Don't forget prefetch_related (select_related) when fetching the relation destination value with --for

I hope you find this helpful. Personally, I would like to increase the output of Django, which is said to have less Japanese information.

Recommended Posts

[Django 2.2] How do you display relation destinations in Django? [List View]
How to do Server-Sent Events in Django
Python --How do you split a list into evenly sized chunks in Python?
If you write the View decorator in urls.py in Django, the list will be higher.
How to use bootstrap in Django generic class view
How to upload files in Django generic class view
How do you collect information?
[Python] How to do PCA in Python
How to reflect CSS in Django
How many types of Python do you have in Windows 10? I had 5 types.
How to get a namespaced view name from a URL (path_info) in Django
How to do arithmetic with Django template
When you can't call base.html in Django
How to suppress display error in matplotlib
Django ~ Let's display it in the browser ~
How to do R chartr () in Python
Display a list of alphabets in Python 3
How to delete expired sessions in Django
How to display multiplication table in python
Ajax in Django (using generic class view)
How to implement Scroll View in pythonista 1
How to convert DateTimeField format in Django
How to display Hello world in python
How to view images in Django's Admin
How to uniquely identify the source of access in the Django Generic Class View
If you want to display values using choices in a template in a Django model
What do you like about how to convert an array (list) to a string?