0
1

More than 1 year has passed since last update.

Djangoで子テーブルのデータをテンプレートで表示させる方法

Last updated at Posted at 2023-04-29

はじめに

日記アプリなどのアプリケーションで、ユーザーのプロフィールページにそのユーザが投稿した日記を表示させたい場合など、小テーブルのデータをテンプレートに表示させる方法を紹介します。

サンプルケース

今回はマイページで自分が投稿した記事をテンプレートに表示させるケースを想定します。

また、CustomUserとArticleの2つのモデルを用意し、CustomUserを親テーブル、Articleモデルを子テーブルとして、一対多の関係でリレーションシップを取ります。

スクリーンショット 2023-04-28 4.41.01.png

accounts/models.py
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'
article/model.py
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を使用しました。

article/views.py
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で指定した値を入れます。

テンプレートの作成

次にテンプレートの作成をします。

mypage.html

                                    --中略--

{% 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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1