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".
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.
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.
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>
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.
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)
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.)
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. 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 ** 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.
If you also check the first player list page, 13 queries have been issued.
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.
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).
-** 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