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

Django

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

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

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

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

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

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

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

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

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

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

【著者プロフィール】

profile_icon
【Python歴】11年 x 【Django歴】10年
HP作成、社内システムの構築、コンサルティング

当ブログを通じて、Webアプリの書き方やアプリの公開方法までを解説しています。

☆日報アプリ「D-Repo」(デモ版)→こちら

※Djangoをベースに作成したアプリです。

☆便利ツールアプリ「Tool Station」→こちら

※Django REST frameworkとReactで作成しているアプリです。

✔YouTube解説動画

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

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

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

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つ並んでる箇所をクリックすると、新しいシークレットウィンドウを出せます。

ぜひご活用ください。

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

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

✔当ブログは以下のような方に向けて書かれています

「Djangoでのアプリ開発を学びたい!」

「Djangoで開発したアプリをWebで公開するにはどうするの?」

✔当ブログ掲載の記事

  • Djangoで作る日報アプリ開発
  • WebアプリをWeb上に公開する方法
  • Webアプリ開発に必要なそのほかの情報
【Django】チュートリアル|日報アプリの開発から公開まで
Djangoのチュートリアルをお探しですか?具体的に「手を動かして作ってみたい!」という方へ向けて、誰でもできる簡易的な日報アプリの開発を通じて、Djangoの様々な機能に触れていくシリーズとなっています。PythonでWebアプリを作りたい方、必見の記事となります!

ITCブログにご協力いただける方は、以下もご検討いただけると嬉しいです。

ITCへ投げ銭をする

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