【Django】ListViewのQuerySetを動的に変更する

※本サイトにはプロモーション・広告が含まれています。

(最終更新月:2023年1月)

✔このような方へ向けて書かれた記事となります

「DjangoのListViewで一覧表示を動的に変更したい」

「ListViewのget_querysetメソッドの使い方がわからない」

「実例を元に自分のアプリ開発の参考にしたい」

✔当記事を通じてお伝えすること

  • ListViewの基本
  • QuerySetを動的に変更するためのメソッド
  • 日報アプリへの実装方法・具体例

当記事では、ListViewでQuerySetを動的に変更する方法を中心に解説していきます。

日報アプリ開発を通じた具体例もご覧になれるので、とくに初心者の方は必見です。

ぜひ最後までご覧ください。

✔YouTube解説動画

当記事の内容は動画を見ながら進めると、約15分程度で完了します。

動画ならではの情報も解説しているので、記事と一緒にご覧ください。

動画の概要欄には、単元ごとのコードを紹介しているgithubページも載せています。

筆者プロフィール

筆者プロフィールアイコン

【現職】プロダクトマネージャー

【副業】ブログ(月間20万PV)/YouTube/Web・アプリ制作

「プログラミング × ライティング × 営業」の経験を活かし、30後半からのIT系職へシフト。現在はプロダクトマネージャーとして、さまざまな関係者の間に入り奮闘してます。当サイトでは、実際に手を動かせるWebアプリの開発を通じて、プログラミングはもちろん、IT職に必要な情報を提供していきます。

【当ブログで紹介しているサイト】

当サイトチュートリアルで作成したデモ版日報アプリ

Django × Reactで開発したツール系Webアプリ

✔人に見せても恥ずかしくないコードを書こう

「リーダブルコード」は、わかりやすく良いコードの定義を教えてくれる本です。

  • 見るからにきれいなコードの書き方
  • コードの分割方法
  • 変数や関数の命名規則

エンジニアのスタンダートとすべき基準を一から解説しています。

何回も読むのに値する本なので、ぜひ手にとって読んでみてください。

ListViewの基本

当記事はDjangoのリストビューについて、基本事項を理解いただいている前提でお伝えしていきます。

なぜなら、ListViewを使うだけにしては、少し高度な内容になっているからです。

もしListViewの基本がわからないのであれば、以下の記事を読んだ上でお進みいただければ、スムーズに理解いただけます。

ListViewでQuerySetを動的に変更するために

ListViewでQuerySetを動的に変更するためには、モデルクラスを変数に格納する方法は使えません。

理由としては、変数に格納する値は一意となり、変更できないから。

以下のメソッド内でおこなう必要があります。

get_queryset

概要は以下の記事でもご説明してますので、ぜひご覧ください。

今回行うこと

当記事を通じて、日報に公開、非公開、という機能を追加します

そのうえで、リストビューで表示する日報は、

  • 自身の作成したすべての日報(公開、非公開は問わない)
  • 他人の作成した公開されている日報

の2種類となります

機能を実装するためには、下記の流れでそれぞれ変更を加えていく必要あります

  1. (models.py) publicフィールドの追加
  2. (forms.py) チェックボックスのため、Bootstrapの別クラス「form-check-input」を追加
  3. (views.py) 正しく表示するためのQuerySetを返す
  4. (views.py) QuerySetを新しい順に並びかえる
  5. (nippo-list.html) HTMLテンプレートで、作成者情報、作成日時を追加で表示
  6. (test.py) 自動テストの追加

models.py

NippoModelクラスに「public」という新たなフィールドを追加します。

class NippoModel(models.Model):
    ...
    public = models.BooleanField(default=False, verbose_name="公開する")
    ...

デフォルトは「False」となり、公開するときは自身で「True」へ変更します。

マイグレーションを忘れずに、次へ進みましょう。

forms.py

__init__関数内で、フィールドのforループを変更しました。

なぜなら今までの記述ですと、すべてのフォームに「form-control」クラスが適用されてしまうから。

class NippoModelForm(forms.ModelForm):

    class Meta:
        model = NippoModel
        exclude = ["user"]

    def __init__(self, *args, **kwargs):
        for key, field in self.base_fields.items():
            if key != "public":
                field.widget.attrs["class"] = "form-control"
            else:
                field.widget.attrs["class"] = "form-check-input"
        super().__init__(*args, **kwargs)

「public」フィールドには「form-check-input」というクラスを設定しましょう

views.py

get_queryset関数で、ログインユーザーごとの「object_list」を返すよう定義します。

class NippoListView(ListView):
    template_name = "nippo/nippo-list.html"
    model = NippoModel

    def get_queryset(self):
        qs = NippoModel.objects.all()
        if self.request.user.is_authenticated:
            qs = qs.filter(Q(public=True)|Q(user=self.request.user))
        else:
            qs = qs.filter(public=True)

        qs = qs.order_by("-timestamp")
        return qs
  • ログインユーザー:自分の日報、もしくは他人の公開済みの日報
  • ゲストユーザー:公開済みの日報のみ

Qオブジェクトについてはこちら。

また、新しいものから表示されるよう「order_by」メソッドでの記述も追加しています。

nippo-list.html

forループ内のみ変更、抜粋しています。

{% for obj in object_list %}
    <div class="card my-3 mx-auto" style="max-width:700px;">
        <div class="card-header">
            {{ obj.timestamp|date:"Y年n年j日" }}
        </div>
        <div class="card-body">
            <h5 class="card-title">
            {% if obj.user == request.user %}
                <a href={% url 'nippo-update' obj.pk %}>
                    {{ obj.title}}<span style="font-size:10px;">(編集)</span>
            {% else %}
                <a href={% url 'nippo-detail' obj.pk %}>
                    {{ obj.title}}
            {% endif %}
                </a>
            </h5>
            <p class="card-text">
                {{ obj.content }}
            </p>
        </div>
        <div class="card-footer">
        by 
        {% if obj.user == request.user %}
            <span class="badge bg-primary">あなた</span>
        {% else %}
            <span class="badge bg-secondary">{{ obj.user.profile.username }}</span>
        {% endif %}                 
        </div>
    </div>
{% endfor %}

カードのヘッダーやフッターを追加。

DateField「timestamp」には、テンプレートフィルター「date」を使うことで年月等を任意に表示することができます。

tests.py

今まで作成した機能で、ログインしている場合としていない場合でリストビューの表示がかわるはず

渡される「object_list」の数によりきちんと動作しているかを判断するテスト関数になります

def test_listview_with_anonymous(self):
    url = reverse("nippo-list")
    response = self.client.get(url)
    object_list = response.context_data["object_list"]
    self.assertEqual(len(object_list), 0)

def test_listview_with_own_user(self):
    url = reverse("nippo-list")
    self.client.login(email=self.email, password=self.password)
    response = self.client.get(url)
    object_list = response.context_data["object_list"]
    self.assertEqual(len(object_list), 1)

まとめ

当記事の内容をまとめます。

  • QuerySetを動的に変更したいならget_querysetを使う
  • 新しい機能を追加したら、合わせてテストプログラムを作ると便利

直接アクセスをして試したいのなら、Google Chromeなどの「シークレットウィンドウ」を使うと便利です。

シークレットウィンドウでは、現ブラウザのログイン状況は共有されません

Chromeブラウザで右上の○が3つ並んでる箇所をクリックすると、新しいシークレットウィンドウを出せます。

ぜひご活用ください。

当記事の結果としては、私の画面ではこんなふうになりました。

さて、次回のチュートリアルでは、ファイル数やインポート数が増えてきてしまったので、自作ファイルをインポートするモジュール化などの方法をお伝えしていきます。

タイトルとURLをコピーしました