はじめに
日記アプリなどのアプリケーションで、ユーザーのプロフィールページにそのユーザが投稿した日記を表示させたい場合など、小テーブルのデータをテンプレートに表示させる方法を紹介します。
サンプルケース
今回はマイページで自分が投稿した記事をテンプレートに表示させるケースを想定します。
また、CustomUserとArticleの2つのモデルを用意し、CustomUserを親テーブル、Articleモデルを子テーブルとして、一対多の関係でリレーションシップを取ります。
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import gettext_lazy as _
class CustomUser(AbstractUser):
"""拡張ユーザーモデル"""
username = models.CharField(
_("username"),
max_length=30,
help_text='Required 30 characters or fewer.',
unique=True,
error_messages={
'unique': _("This Username already exists."),
},)
email = models.EmailField(
_('email'),
unique=True,
error_messages={
'unique': _("A user with that email address already exists."),
},)
fb_link = models.URLField(verbose_name='Facebook Link', null=True, blank=True)
ig_link = models.URLField(verbose_name='Instagram Link', null=True, blank=True)
tw_link = models.URLField(verbose_name='Twitter Link', null=True, blank=True)
bg_image = models.ImageField(verbose_name='Backgroung Image', null=True, blank=True, upload_to='bgimage/')
icon = models.ImageField(verbose_name='Icon Image', null=True, blank=True, upload_to='icon/')
profession = models.CharField(verbose_name='Profession', null=True, blank=True, max_length=20)
introduction = models.TextField(verbose_name='Introduction', null=True, blank=True, max_length=500)
class Meta:
verbose_name_plural = 'CustomUser'
from django.db import models
from accounts.models import CustomUser
from mdeditor.fields import MDTextField
class Article(models.Model):
"""記事タグ"""
tag_choices = (
('ELECTRONICS','ELECTRONICS'),
('INSTALLATION','INSTALLATION'),
('SERVICES','SERVICES'),
('CRAFT','CRAFT')
)
post_user = models.ForeignKey(CustomUser, verbose_name='Post User', on_delete=models.CASCADE, related_name='name',)
title = models.TextField(verbose_name='title', max_length=50,)
content = MDTextField()
photo = models.ImageField(verbose_name='photo', null=True, blank=True,)
thumbnail = models.ImageField(verbose_name='thumbnail', null=True, blank=True,)
tag = models.CharField(verbose_name='article tag', choices=tag_choices, max_length=30,)
created_at = models.DateField(verbose_name='created_at', auto_now_add=True,)
view_count = models.PositiveIntegerField(verbose_name='view count', default=0,)
親テーブルには子テーブルのフィールドが定義されていません。一方で参照はされており、それを利用するために、related_nameをmodels.pyのリレートフィールドに定義する必要があり、このrelated_nameで設定した名前を使用して取得することが出来ます。今回はrelated_name='name'と指定しているため、例えばテンプレート側では{{ 変数.name.all }}で紐付く子テーブルの全レコードを取得できるようになります。
ビューの作成
マイページの表示はListlViewを継承したクラスビューで作成します。クラス変数のmodelにはCustomUserを指定しており、常に表示されるレコードが1つ(マイページへアクセスするログインユーザー)ですが、紐付くArticleモデルのレコードが0以上の複数であるため、単一のオブジェクトを返すことが期待されているDetailViewを使わずにListViewを使用しました。
from django.views import generic
from django.contrib.auth.mixins import LoginRequiredMixin
from accounts.models import CustomUser
from .models import Article
class MyPageView(LoginRequiredMixin, generic.ListView):
template_name = 'mypage.html'
model = CustomUser
def get_queryset(self):
articles = CustomUser.objects.filter(username=self.request.user).prefetch_related('name')
return articles
ここで注意したいのが、get_querysetメソッドのオーバーライドです。prefetch_related()メソッドは、リレーション関係があるレコード抽出のクエリの最適化を行うメソッドです。models.pyのrelated_nameで指定した値を入れます。
テンプレートの作成
次にテンプレートの作成をします。
--中略--
{% block contents %}
{% for s in customuser_list %}
<section>
<div class="container">
<!-- Profile Block -->
<div class="row">
<div class="col-md-4 mx-auto">
<div class="u-pull-half text-center">
{% if s.icon %}
<img class="img-fluid u-avatar u-box-shadow-lg rounded-circle mb-3" width="200" height="auto" src="{{ s.icon.url }}" alt="Image Description">
{% else %}
<img class="img-fluid u-avatar u-box-shadow-lg rounded-circle mb-3" width="200" height="auto" src="{% static 'img/default_icon.png' %}" alt="Image Description">
{% endif %}
</div>
</div>
</div>
<!-- End Profile Block -->
<!-- About -->
<div class="row u-content-space-bottom">
<div class="col-lg-6 mb-5 mb-lg-5 pl-lg-5 mx-auto">
{% if s.introduction %}
<h4 class="mb-3">About me</h4>
<p>{{ s.introduction }}</p>
{% endif%}
</div>
</div>
<!-- End About -->
</div>
</section>
<!-- End About Section -->
<!-- Portfolio -->
<section class="u-content-space">
<div class="container">
<header class="text-center w-md-50 mx-auto mb-8">
<h2 class="h1">Prototyping Works</h2>
</header>
<!-- Work Content -->
<div class="js-shuffle u-portfolio row no-gutters mb-6">
{% for article in s.name.all %}
<figure class="col-sm-6 col-md-4 u-portfolio__item" data-groups='["its-illustration"]'>
<img class="u-portfolio__image" src="{{ article.thumbnail.url }}" alt="Image Description">
<figcaption class="u-portfolio__info">
<h6 class="mb-0">{{ article.title }}</h6>
<small class="d-block">Branding</small>
</figcaption>
<a class="js-popup-image u-portfolio__zoom" href="assets/img-temp/portfolio/img1.jpg">Zoom</a>
</figure>
{% endfor %}
<!-- sizer -->
<figure class="col-sm-6 col-md-4 u-portfolio__item shuffle_sizer"></figure>
</div>
<!-- End Work Content -->
</div>
</section>
<!-- End Portfolio -->
{% endfor %}
{% endblock %}
ListViewではビューからテンプレートにはmodel名(小文字)]_listという名前でオブジェクトが渡されます。今回は{% for s in customuser_list %}でレコードをそれぞれ取り出し、{{ s.フィールド名 }}で各フィールドのデータを取り出しています。また、子テーブルのデータの取り出しには、{% for article in s.name.all %}でレコードをそれぞれ取り出し、{{ article.フィールド名 }}で子テーブルの各フィールドのデータを取り出しています。
参考
・Djangoでデータを取得してみる(外部キー)
https://yk5656.hatenablog.com/entry/20210410/1617980400
・Django 1対多(one2many)でリレーションされた子テーブルの集計値を取得する
https://igreks.jp/dev/django-1%E5%AF%BE%E5%A4%9Aone2many%E3%81%A7%E3%83%AA%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%95%E3%82%8C%E3%81%9F%E5%AD%90%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%AE%E9%9B%86%E8%A8%88/
・【Django2.2】Djangoでリレーション先はどう表示するの? 【ListView編】
https://qiita.com/ragnar1904/items/1afaeb6601cc490fb70a
