【初めてのDjango】(発展編 #04)ListViewを用いてモデル情報を描写する

本記事では「初めてのDjango(発展編)」として、初めてDjangoというフレームワークを使用してWebアプリケーションを作ってみたい!という方向けに、簡単なアプリケーションを作成しながら基本的な知識をご紹介していきます。

本シリーズの全容はこのようになっております。

今回の内容

・モデルの定義
・LoginRequiredMixinクラス、ListViewクラスの使い方
・管理サイトでデータを登録する
・データベースの情報を使用して描写する

こちら(Github)のコードの続きとして作成しますので、本記事単体でご覧になる方はぜひご参考にしてください。

目次

何を実装するか

現在作成しているカフェのHPにて、メニューを一覧として表示しているページがあります。

このメニューたちは、ビューに手打ちで記述したオブジェクト情報を基に作成されています。

class MenuView(generic.TemplateView):
    template_name = "menu.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['menu_items'] = [
            {
                'name': 'ブレンドコーヒー', 
                'price': 420, 
                'description': '当店おすすめの一杯です。\n迷った場合はぜひこちらをご堪能ください。', 
                'img_path': 'brend-coffee.jpg'
            }, 
            {
                'name': 'カフェオレ', 
                'price': 500, 
                'description': '産地直送の贅沢ミルクを使用しています。\nオリジナルのラテアートも自慢です。', 
                'img_path': 'cafe-au-lait.jpg'
            }, 
            {
                'name': '紅茶', 
                'price': 480, 
                'description': '圧倒的な茶葉使用。\n\n故郷でもない外国の景色に想いを馳せることができます。', 
                'img_path': 'black-tea.jpg'
            }, 
        ]

        return context

したがって、メニューを追加したい場合はこの views.py に追加していく必要があります。このままだと、このHPの管理者以外はメニューを追加することはできません。(カフェのHPなので管理者以外編集する必要はないのでは、、、というのは無視してください笑)

本記事からは「本サイトに会員登録したユーザーがお好きなメニューを投稿でき、さらにそのメニューがサイトに表示される」という機能を実装していきます。

会員限定機能を実装できます。

こんな感じです。

ユーザーが投稿して、さらにそれを表示したり削除したり修正したり、、、までを紹介すると膨大な量になってしまいます。なので本記事は「管理サイトでデータベースに追加したメニューをテンプレートで描写させる」までを解説させていただきます。

メニューが表示されるまでの流れ

ユーザーがメニューを投稿し、そのメニューが表示されるまでの流れは下記のようになっております。

  1. ログイン済みユーザーが、サイト上の「投稿画面」でメニューを投稿。
  2. 投稿したメニューがデータベースに保存される。
  3. 「メニュー」画面をユーザーがリクエスト。
  4. ビュー(views.py)がモデル(models.py)に、データベースからメニューの抽出をお願い。
  5. モデル(models.py)がデータベースからメニューを抽出し、ビュー(views.py)に渡す。
  6. ビュー(views.py)が受け取ったメニューをテンプレートに渡し、ブラウザに描写させる。

図で表すとこのようになります。

流れがあらかた分かったとこで、次から実際に機能を追加していきます!

会員用機能の実装

では実際に会員用機能を実装するまでの流れをご紹介します。

メディアファイルを扱えるようにする

本サイトだけではなく、日記アプリやSNSなどでも画像ファイルを扱います。これらの画像は、Webアプリケーションを通してサイト上にアップロードされます。このようなファイルをメディアファイルといいます。

「static/」ディレクトリにあるファイルたちは、Webアプリケーションを通さずに最初から用意しておくことのできるファイルであり、メディアファイルとは異なります。

Pillowのインストール

ターミナル上で下記のコマンドを実行し、Pillowをインストールしましょう。

きちんと仮想環境に入っていることを確認しましょう!

(venv_cafe)$ pip install Pillow

プロジェクト設定ファイルの修正

プロジェクトの設定ファイルである settings.py に下記の内容を追加し、メディアファイルを扱えるようにしましょう。

# 開発環境におけるメディアファイルの配置場所(「media/」はGithub管理する必要なし)
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

# 画像を配信するURLのホスト名以下を指定
# 例えば「hoge.png」なら、'https://<ホスト名>/media/hoge.png'で配信される
MEDIA_URL = '/media/'

ルーティングの追加

開発サーバーでメディアファイルを配信するには、プロジェクト側の urls.py にメディアファイル配信用のルーティングを追加する必要があります。下記の内容を urls.py に追加してください。

# ... 略 ...
from django.contrib.staticfiles.urls import static

from . import settings

# ... 略 ...

# 開発サーバーでメディアを配信できるように
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

static()メソッドの引数に、先ほど settings.py で設定した「MEDIA_URL」「MEDIA_ROOT」を渡しています。

メニューのモデルを定義する

ユーザーが投稿するカフェのメニューのモデルを定義しましょう!(※「モデルとは?」という方はこちらをご覧ください)

モデルには、メニュー表示に必要な

  • 写真
  • 商品名
  • 値段
  • 説明

を投稿できるようにしましょう。models.py を下記のように編集しましょう。

from django.db import models


class CafeMenu(models.Model):
    """カフェのメニューのモデル"""

    photo = models.ImageField(verbose_name='写真', blank=True, null=True)
    name = models.CharField(verbose_name='商品名', max_length=255, default='商品名')
    price = models.PositiveIntegerField(verbose_name='値段')
    description = models.CharField(verbose_name='説明', max_length=255)

    class Meta:
        # 管理画面での表示。verbose_name = ''だと s がつく
        verbose_name_plural = 'Cafe_App'
    
    # 管理画面でレコードを判明できるように表示されるものを設定.
    def __str__(self):
        return self.name

各項目のフィールドの型とオプションを定義しています。先ほどインストールした「Pillow」は、ここで登場する ImageField を使用するためのものでした(8行目)。

補足
verbose_name

アプリケーションの管理画面上でGUIを用いてレコードを追加することができるのですが、その項目名を verbose_name によって設定しています。

verbose_name_plural

こちらも管理画面での表示に関する設定です。

このように管理画面でのテーブル名の表示に紐付いています。

def __str__(self)

こちらも管理画面での表示に関する設定で、レコード名をどのように表示させるかの設定をしています。

今回は「return self.name」としているので、商品名をラベルとして設定しています。もしこの設定を行わない場合、下記のようにラベルだけでは中身が分かりません。

詳しいフィールドの種類などはこちらに分かりやすくまとめられているので、引用させていただきます。

メニューを作成するだけではこれで良さそうですが、ログインしているユーザーが投稿したメニューのみを表示させたい場合、以上の項目だけで足りるでしょうか?

実は上記の項目だけでは足りず、認証機能を実装した時に使用したカスタムユーザーモデルを使用し、データベース上のデータから「誰が投稿したものか?」で抽出する必要があります。

またメニューを作成した日時でソートするために「作成日時」「更新日時」の項目も追加します。

from accounts.models import CustomUser
from django.db import models


class CafeMenu(models.Model):
    """カフェのメニューのモデル"""

    # カスタムユーザーモデルから引っ張ってくる.
    user = models.ForeignKey(CustomUser, verbose_name='ユーザー', on_delete=models.PROTECT)
    photo = models.ImageField(verbose_name='写真', blank=True, null=True)
    name = models.CharField(verbose_name='商品名', max_length=255, default='商品名')
    price = models.PositiveIntegerField(verbose_name='値段')
    description = models.CharField(verbose_name='説明', max_length=255)
    created_at = models.DateTimeField(verbose_name='作成日時', auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)

    class Meta:
        # 管理画面での表示名を指定。
        verbose_name_plural = 'Cafe_App'
    
    # 管理画面でレコードを判明できるように表示されるものを設定.
    def __str__(self):
        return self.name

ハイライト部分を追加しましょう。

管理サイトにメニューモデルを登録する

作成したCafeMenuモデルを管理サイトで編集できるように、admin.py を下記のように編集します。

from django.contrib import admin

from .models import CafeMenu

admin.site.register(CafeMenu)

メニュー一覧を表示するページの作成

続いて、ユーザーが投稿したメニューを表示させるためのページを作成していきます。

ルーティング設定

下記のようにapp_cafeアプリケーションの urls.py を修正します。

from django.urls import path
from . import views

app_name = 'cafe_app'
urlpatterns = [
    path('', views.IndexView.as_view(), name="index"), 
    path('menu', views.MenuView.as_view(), name="menu"), 
    path('contact', views.ContactView.as_view(), name="contact"), 
    path('original-menu-list', views.OriginalMenuList.as_view(), name="original_menu_list"), 
]

https://<ホスト名>/original-menu-list/」にアクセスがあった際に、OriginalMenuListビューに処理をお願いしています。

ビューの作成

views.py に、下記のように OriginalMenuListビューを追加します。

# ... 略 ...
from django.contrib.auth.mixins import LoginRequiredMixin

from .models import CafeMenu

# ... 略 ...

class OriginalMenuList(LoginRequiredMixin, generic.ListView):
    model = CafeMenu
    template_name = 'original_menu_list.html'

    def get_queryset(self):
        # ログインユーザーに基づいたメニューを抽出している.さらに作成日時の新しい順でソート.
        menus = CafeMenu.objects.filter(user=self.request.user).order_by('-created_at')
        return menus

ここで作成したOriginalMenuListビューでは、2つのクラスを継承しています。

  • LoginRequiredMixin
  • ListView

LoginRequiredMixinクラスを継承することで、ログイン状態でないとOriginalMenuListビューにアクセスできないようにしています。

ListViewはDjangoの標準ビューの1つで、モデルとテンプレート名を指定するだけで、そのモデルに紐づいたデータベースのデータをオブジェクトとしてテンプレートに渡し、描写することができます。

# このモデルのデータを
model = CafeMenu
# ここに渡す
template_name = 'original_menu_list.html'

しかしモデルとテンプレートを指定しただけでは、データベースの情報全てがテンプレートに渡ってしまいます。今回はユーザーが投稿したメニューのみを抽出したいので、get_queryset()メソッドをオーバーライドし、抽出結果をテンプレートに渡すようにしています。

def get_queryset(self):
    # ログインユーザーに基づいたメニューを抽出している.さらに作成日時の新しい順で.
    menus = CafeMenu.objects.filter(user=self.request.user).order_by('-created_at')
    return menus

具体的には、filter()メソッドで抽出を行い、order_by()メソッドでデータのソートを行なっています。

テンプレートの作成

OriginalMenuListビューから受け取ったオブジェクトを使用して、メニューを描写するテンプレートを作成します。

「cafe_app/templates/」ディレクトリに original_menu_list.html を新規作成し、下記のように編集しましょう。

{% extends 'base.html' %}

{% load static %}

{% block title %}オリジナルメニュー{% endblock title %}

{% block contents %}
<section class="sectionMenu">
    <div class="wrapperHeader">
        <h2>ORIGINAL MENU</h2>
    </div>
    <div class="wrapperMenu">
        <ul class="ulMenu">
            {% for menu in object_list %}
                <li class="menuContent">
                    <img src="{{ menu.photo.url }}" alt="{{ menu.name }}">
                    <h3>{{ menu.name }}</h3>
                    <p class="price">{{ menu.price }}円</p>
                    <p class="description">{{ menu.description }}</p>
                </li>
            {% empty %}
                <p>メニューがありません</p>
            {% endfor %}
        </ul>
    </div>
    <a class="backToHome" href="{% url 'cafe_app:index' %}"><<<ホームに戻る</a>
</section>
{% endblock contents %}

ここでキーとなるポイントをハイライトしています。

ListViewから、オブジェクトのリストを「object_list」という変数名で渡されます。さらにそのオブジェクトを{% for menu in object_list %}として1つずつ取り出し、各レコードの値を取り出しています。

{% empty %}以下は、このリストが空だったときの内容を記述しています。

オリジナルメニューページへのリンクを貼る

上記で作成したオリジナルメニューページへのリンクをトップページに貼ります。index.html のナビゲーション部分に下記の一行を追加します。

<nav>
    <ul>
        <!-- ... 略 ... -->
        <li><a href="{% url 'cafe_app:original_menu_list' %}">ORIGINAL</a></li>
        <!-- ... 略 ... -->
    </ul>
</nav>

マイグレーションを行う

ここまで作業が完了した時点でマイグレーションを行いましょう。(マイグレーションについてはこちらで解説しております。)

(venv_cafe)$ python manage.py makemigrations
(venv_cafe)$ python manage.py migrate

管理サイトからメニューデータを登録する

テンプレート側でメニューを表示できるようになりましたが、肝心なメニューのデータがデータベースにありません。なので管理サイトを使用してデータを追加していきます。

アプリケーション上で追加したりする機能は次回以降で紹介させていただきます。

スーパーユーザーの作成

管理サイトでは、管理者がデータの作成や削除など、様々な機能をブラウザ上で行うことができます。しかし、管理サイトにアクセスするためには管理者権限を持つスーパーユーザーというアカウントを作成しなければなりません。逆にいうと、このスパーユーザーでなんでもできてしまうので、取り扱いには注意してください。

ターミナル上でプロジェクトのルートディレクトリ(manage.pyがあるとこ)に移動し、下記のコマンドを実行しましょう。

(venv_cafe)$ python manage.py createsuperuser --settings cafe.settings

するとスーパーユーザーの「ユーザー名」「メールアドレス」「パスワード」を尋ねられますので、任意の情報を入力しましょう。

ユーザー名: <任意のユーザー名>
メールアドレス: <任意のメールアドレス>
Password: <任意のパスワード>
Password (again): <任意のパスワード>
Superuser created successfully.

一般ユーザーアカウントを作成する

続いて、スーパーユーザーとは別に一般のアカウントを作成します。こちらはターミナルではなく、ローカルサーバーを立ち上げてブラウザで登録します。

「python manage.py runserver」でローカルサーバーを立ち上げます。

「http://127.0.0.1:8000/accounts/signup/」にアクセスし、流れに沿ってアカウントを作成しましょう!

メニューデータを登録する

管理サイトからメニューデータを登録します。ローカルサーバーを立ち上げた状態で「http://127.0.0.1:8000/admin/」にアクセスしましょう。

先ほど作成したスーパーユーザーの「ユーザー名」「パスワード」を入力し、ログインします。

管理サイトのトップページに移動します。さらに「Cafe_App」の行の「追加」をクリックします。

このように、モデルに定義したCafeMenuの各フィールドを入力するページが表示されますので、お好きな写真や商品名などを入力しましょう。

ただし、「ユーザー」は先ほど作成した一般ユーザーの方を選択してください。

全て入力したら「保存」をクリックしてメニューデータを登録しましょう。

メニュー一覧ページを確認する

管理サイトを使用してメニューをデータベースに登録できましたので、Webアプリケーション側で正しく表示できるか確認しましょう。

先ほど登録した一般ユーザーでログインした状態で http://127.0.0.1:8000/original-menu-list にアクセスしましょう。

このように管理サイトで登録したメニューが表示されていればOKです!!

この時点でのコード全体をGithubに載せております。確認用としてお使いいただければと思います。

まとめ

本セクションでは、会員機能としてデータベースの情報を抽出しテンプレートに渡し、描写するまでをご紹介しました。その中で、ログインしているユーザーのみがアクセスできるような「LoginRequiredMixin」クラスや、データベースからの情報をリストとして渡すことができる「ListView」について解説させていただきました。

しかし、現状ではデータの登録などを管理サイトで行なっている状況です。次回以降で、アプリケーションの機能として追加していく方法を解説していきます。

ご覧いただきありがとうございました!

案件、ありますか?

「メインの仕事があるけれど、週1、2日だけできる仕事ないかな、、、」

「ある程度スキルが身に付いてはきたけど、そのスキルを活用できる場が欲しい」

なんて悩みが以前はありました。

自分で仕事を探しに行くのも大事ですが、蛇の道は蛇。その道の人に頼むことで、自分だけでは見つからないような案件に携わることができます。

IT PRO パートナーズでは、簡単に無料でアカウントを登録でき、さらにはエージェントさんに希望の働き方・案件の種類を提示することでお仕事を紹介してくれます!

登録自体も非常に簡単で、「エージェントさんとの面談を希望する」という欄にチェックをするだけで、エージェントさんから直接連絡をいただくことができます。

驚くほど簡単で正直拍子抜けしてしまいました笑

もしお仕事探しに困っておりましたら、一度登録し案件を眺めてみることをおすすめします!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

勤めていた設計会社を退社し、フリーランスとして活動しています
また、趣味で主にpyhonを用いて機械学習を行なっています!
現在競艇の予測モデルの開発中です。

コメント

コメント一覧 (5件)

コメントする

目次