[PYTHON] [Django 2.2] Comment affichez-vous la destination de la relation dans Django? [Affichage de liste]

introduction

Bonjour. Je m'appelle Ragnar et j'ai 1,5 mois d'expérience à Django (en pratique). Quand j'ai commencé Django, j'ai fait quelques tutoriels, mais quand il s'agissait de le faire, il y avait beaucoup de nouveaux concepts et je ne savais pas quoi faire.

La première chose qui est restée bloquée a été la relation. Avant de traiter avec Django, j'ai écrit SQL et utilisé DB, donc je ne comprends pas bien le concept de Queryset, puis-je obtenir des données avec ça? Comment c'est? C'était dans un état.

Cette fois, c'est un article pour ceux qui "j'ai essayé de toucher Django au tutoriel mais je n'ai pas bien compris".

environnement

Modèle prérequis

Cette fois, nous supposerons un site où vous pouvez voir une liste des équipes de football, des joueurs et des informations sur les joueurs. J'ai créé un tableau (modèle) des équipes, des positions et des joueurs.

sample/models.py


from django.db import models

FOOT_CHOICES = (
    ('right', 'Pied droit'),
    ('left', 'pied gauche'),
    ('both', 'Les deux pieds'),
)


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

    def __str__(self):
        return self.name

    class Meta:
        #Le nom affiché sur le site d'administration de Django
        verbose_name = "équipe"
        verbose_name_plural = "équipe"


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)
    #Les athlètes appartiennent à un seul club. Plusieurs joueurs appartiennent à l'équipe. 1 à n structure
    #Les joueurs peuvent ne pas être affiliés. sur_delete=models.SET_NULL
    team = models.ForeignKey(Team, null=True, on_delete=models.SET_NULL, related_name='player')
    #Les athlètes peuvent prendre plusieurs positions. Il y a plusieurs joueurs dans une même position. structure n à n
    position = models.ManyToManyField(Position, related_name='player')

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "joueur"
        verbose_name_plural = "joueur"

Enregistrez le modèle afin de pouvoir enregistrer les données depuis le site d'administration de Django. (J'ai entré les données correctement.)

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)

Nous avons également introduit django-debug-toolbar pour vérifier les requêtes. Il n'est pas exagéré de dire que c'est indispensable pour le développement de Django, donc si vous ne l'avez pas inclus, veuillez le faire maintenant.

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')  #Cette application
]

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

ListView Django a une application Web appelée Generic View. Recevoir la méthode GET et afficher la page, recevoir la méthode POST, valider le formulaire, mettre à jour l'enregistrement, supprimer, etc. Il existe une classe au comportement très général.

Cette fois, nous résumerons l'affichage de la liste et la gestion des relations à l'aide de ListView, qui est spécialisé pour l'affichage de liste d'enregistrements.

Afficher la liste des joueurs

Tout d'abord, retirons les noms de tous les joueurs.

sample/views.py


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


class PlayerListView(ListView):
    model = SoccerPlayer  #Modèle pour afficher une liste
    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>Nom</th>
      </tr>
      {% for player in soccerplayer_list %}
      <tr>
        <td>{{ player.name }}</td>
      </tr>
      {% endfor %}
    </table>
  </body>
</html>

La première chose à retenir dans ListView est que View passe un objet à Template avec le nom ** [nom du modèle (inférieur)] _list **. (Vous pouvez également le spécifier vous-même.) Vous pouvez le récupérer avec for dans le modèle et le nom avec ** [variables récupérées avec for] .name **. ** name ** est le champ du modèle défini par model. Je pense que je l'ai probablement fait dans des tutoriels.

Si vous accédez à http: // localhost: 8000 / sample / list /, vous pouvez voir que la liste peut être affichée. スクリーンショット 2019-12-16 23.16.56.png


Extraction de valeurs liées par Foreignkey

Ensuite, sortons l'équipe associée au joueur. Même si vous dites la destination de la relation, dans le cas des relations OnetoOneField (1 à 1) et Foreignkey (1 à n), c'est la même chose que l'écriture normale. Spécifiez simplement le nom du champ de la destination de la relation comme dans le cas du nom.

templates/player_list.html


<!DOCTYPE html>
<html>
  <head>
    <title>sample</title>
  </head>
  <body>
    <table>
      <tr>
        <th>Nom</th>
        <th>équipe</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 obtiendra également l'enregistrement associé à l'objet Player, même si vous ne le spécifiez pas. Au début, je n'ai pas compris ce sentiment et j'ai eu du mal à m'y habituer.


Récupérer des valeurs liées entre elles dans ManytoManyField

Vient ensuite le cas de ManytoManyField, une connexion n-to-n. Puisqu'il va de n à n, plusieurs enregistrements sont récupérés. Dans ce cas, il y a plusieurs positions par joueur. Contrairement à 1 à n etc., il ne peut pas être obtenu avec player.position. Vous devez le créer player.position.all et le récupérer avec for.

templates/player_list.html


<!DOCTYPE html>
<html>
  <head>
    <title>sample</title>
  </head>
  <body>
    <table>
      <tr>
        <th>Nom</th>
        <th>équipe</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>

(Les joueurs que vous mettez sont trop adaptés et il y a peu de joueurs à plusieurs positions) スクリーンショット 2019-12-16 23.45.25.png

En cas de référence inverse

Ensuite, essayons la soi-disant référence inversée, Team-> Player.

Dans le cas de la référence inverse, contrairement au modèle précédent, le champ n'est pas défini dans le modèle. Cependant, bien qu'il ne soit pas inclus dans le champ modèle, il est correctement référencé. Pour être clair et facile à comprendre, il est préférable de définir related_name.

sample/models.py


#Partiellement omis
class Team(models.Model):
    name = models.CharField(max_length=40)
    country = models.CharField(max_length=40)
    #l'équipe n'a pas de champ de joueur!

class SoccerPlayer(models.Model):
    name = models.CharField(max_length=40)
    foot = models.CharField(max_length=5, choices=FOOT_CHOICES)
    # related_En cas de référence inversée avec nom, précisez la phrase à utiliser
    team = models.ForeignKey(Team, null=True, on_delete=models.SET_NULL, related_name='player')
    position...

Lorsque vous obtenez (Player from Team) de l'opposé, vous pouvez l'obtenir en utilisant le nom défini dans ce related_name. Ici, puisque "player" est spécifié, le record du joueur associé à cette équipe peut être obtenu avec "team.player.all".

sample/views.py


#ajouter à
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'),
]

Si vous l'obtenez avec team.player.all, vous pouvez le retirer un par un avec for, ce qui est le même qu'avant.

templates/team_list.html


<!DOCTYPE html>
<html>
  <head>
    <title>sample</title>
  </head>
  <body>
    <table>
      <tr>
        <th>Nom</th>
        <th>Nombre de personnes</th>
        <th>joueur</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>

Si vous accédez à http: // localhost: 8000 / sample / list / team /, vous pouvez voir que les joueurs associés à chaque équipe ont été acquis. (Si vous ne comprenez pas le football, cela ne vous viendra peut-être pas. Je suis désolé.) スクリーンショット 2019-12-17 0.27.10.png

Vérifiez le SQL émis

Sur la page de la liste des équipes, utilisez django-debug-toolbar pour vérifier le SQL. Si vous cliquez sur le panneau SQL de django-debug-toolbar affiché sur le côté, cela ressemble à ceci. スクリーンショット 2019-12-17 0.33.10.png C'est petit et déroutant, mais 13 SQL sont en cours d'exécution pour afficher les informations sur cette page. Ce phénomène est dû au fait que la boucle du modèle obtient à chaque fois les informations de la destination de la relation. (C'est ce qu'on appelle le problème N + 1) À ce rythme, à mesure que le nombre d'enregistrements augmente, le nombre d'exécutions SQL lors du chargement de la page augmente, ce qui entraîne une page extrêmement lourde.

Pour résoudre ce problème, utilisez une méthode appelée prefetch_related.

prefetch_related et select_related

Tout simplement prefetch_related récupère les enregistrements de la base de données à l'avance et les associe chacun à python (Django) select_related est obtenu par JOIN à partir de DB C'est une méthode appelée. (Je m'excuse si je me trompe.)

Dans le cas de ListView, View a une méthode appelée * get_queryset *, qui peut être réécrite.

sample/views.py


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

    def get_queryset(self):
        #Méthode pour obtenir le jeu de requêtes à afficher dans View
        qs = self.model.objects.prefetch_related('player')  # team ->Puisqu'il s'agit d'une référence inverse du joueur, liée_Nom d'utilisation
        return qs

En faisant cela, le nombre d'exécutions SQL a été réduit à deux. スクリーンショット 2019-12-17 0.51.25.png

Si vous consultez également la première page de la liste des joueurs, 13 requêtes ont été émises. スクリーンショット 2019-12-17 0.55.26.png

Cette page récupère l'équipe et la position du joueur. Même dans ce cas, vous pouvez connecter prefetch_related et 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

Cela a réduit le nombre de requêtes émises à 2. スクリーンショット 2019-12-17 0.59.54.png

Si vous vérifiez le SQL et voyez une notation telle que «6 requêtes similaires» ou «4 doublons», n'oubliez pas d'utiliser prefetch_related (select_related).

Résumé

J'espère que ça t'as aidé. Personnellement, j'aimerais augmenter la production de Django, qui aurait moins d'informations japonaises.

Recommended Posts

[Django 2.2] Comment affichez-vous la destination de la relation dans Django? [Affichage de liste]
Comment faire des événements envoyés par le serveur dans Django
Python - Comment diviser une liste en morceaux de taille égale en Python?
Si vous écrivez View decorator dans urls.py dans Django, la liste sera plus élevée.
Comment utiliser le bootstrap dans la vue de classe générique Django
Comment télécharger des fichiers dans la vue de classe générique Django
Comment collectez-vous les informations?
[Python] Comment faire PCA avec Python
Comment refléter CSS dans Django
Combien de types de Python avez-vous dans votre Windows 10? J'avais 5 types.
Pour trouver le nom de la vue avec l'espace de noms à partir de l'URL (path_info) dans Django
Comment effectuer un traitement arithmétique avec le modèle Django
Lorsque base.html ne peut pas être appelé dans Django
Comment supprimer l'erreur d'affichage dans matplotlib
Django ~ Affichons-le sur le navigateur ~
Comment faire R chartr () en Python
Afficher une liste d'alphabets en Python 3
Comment supprimer des sessions expirées dans Django
Comment afficher la table quatre-vingt-dix-neuf en python
Ajax dans Django (en utilisant la vue de classe générique)
Comment afficher Hello World en python
Comment afficher les images dans l'administration de Django
Comment identifier de manière unique la source d'accès dans la vue de classe générique Django
Si vous souhaitez afficher la valeur à l'aide des choix du modèle dans le modèle Django
Qu'est-ce que vous aimez dans la conversion d'un tableau (liste) en chaîne?