djangoクエリ結果をページングする方法

白蛇くん
djangoチュートリアルでページネーションのコードを書き、動作確認もできたのですが、クエリ結果を反映した場合のページネーションができないで困っています。
ヒロヤン
ページングはdjangoのチュートリアルを見れば簡単に作成できるのですがクエリでパラメーターを設定してからのページネーションは少しばかり複雑でコードを追加で書く必要があります。

前提条件

・django2.2/python3.8

・models、formsの基礎知識がある

・getとpostの理解ができている

・djangoのテンプレートの理解ができている

クエリ後のページングの問題点

クエリでのパラメーターをそのまま引き継いでページングをしてくれれば良いのですが、そんな都合良くdjangoさんは動いてくれません。

今回はクエリでパラメーターを検索したあともきちんとパラメーターを引き継いで2ページ目へと画面遷移できるコードを書いてみます。

チュートリアルのページング

ページングに関してはdjangoチュートリアルに習ってコードを作成すればすんなりと動作はします。

djangoチュートリアルのページネーションに書かれたページへ飛ぶ

上記のチュートリアルを理解した上でコードを下記のように書きました。

formに対してバリデーションをかけていますが、こちらのコードでまずはページング処理がきちんと行われます。

views.py

from django.views.decorators.http import require_http_methods
from django.core.paginator import Paginator, PageNotAnInteger,EmptyPage

@require_http_methods(['GET'])
def inquiry_list(request):
    qs = Inquiry.objects.order_by('-updated_at')
    form = InquiryFindForm(request.GET)
    if not form.is_valid():
        return render(request, 'inquiry_apps/inquiry_list.html', {'form': form})

    if form.is_valid():
        id_int = form.cleaned_data['id']
        email_str = form.cleaned_data['email']
        page_int = form.cleaned_data['page']
        word_str = form.cleaned_data['word']

    if email_str is not None:
        qs = qs.filter(email__contains=email_str)

    if id_int is not None:
        qs = qs.filter(id=id_int)

    if word_str is not None:
        qs = qs.filter(Q(subject__contains=word_str) |
                        Q(message__contains=word_str))

    paginator = Paginator(qs, 5)
    try:
        inquiries_page = paginator.page(page_int)
    except EmptyPage:
        raise Http404('Page does not exist')


    context = {
        'form': form,
        'search_email': email_str,
        'inquiries_page': inquiries_page,
    }

    template = loader.get_template('inquiry_apps/inquiry_list.html')
    return HttpResponse(template.render(context, request))

このviewsに対応するformが下記。InquiryFindFormになります。

form.py

from django import forms

class InquiryFindForm(forms.Form):
    id = forms.IntegerField(required=False,
                                min_value=1,
                                max_value=100000)
    email = forms.CharField(required=False,
                                max_length=255)
    page = forms.IntegerField(required=False,
                                max_value=10000,
                                min_value=1,)
    word = forms.CharField(required=False,
                            max_length=1000)
    
    def clean(self):
        cleaned_data = super().clean()
        return cleaned_data

    def clean_id(self):
        find_id = self.cleaned_data['id']
        return find_id
    
    def clean_email(self):
        email = self.cleaned_data['email']
        return email

    def clean_page(self):
        page =self.cleaned_data['page']
        if page == None:
            page = 1
        return page
    
    def clean_word(self):
        word = self.cleaned_data['word']
        return word

modelsのコードも念のため記述しておきます

model.py

from django.db import models

class Inquiry(models.Model):
    class Meta:
        db_table = 'inquiry'
    name = models.CharField(verbose_name='name',
                                max_length=255)
    subject = models.CharField(verbose_name='subject',
                                max_length=255)
    message = models.CharField(verbose_name='message',
                                max_length=500)
    email = models.EmailField(verbose_name='email')
    created_at = models.DateTimeField(verbose_name='created_at',
                                        auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name='updated_at',
                                        auto_now_add=True)
    
    class InquiryStatus():
        Pending = 0
        Ignore = 1
        Completed = 2
    
    INQUIRY_STATUS_CHOICES = [
        (InquiryStatus.Pending, 'Pending'),
        (InquiryStatus.Ignore, 'Ignore'),
        (InquiryStatus.Completed, 'Completed'),
    ]

    inquiry_status = models.IntegerField(
        verbose_name='inquiry_status',
        choices=INQUIRY_STATUS_CHOICES,
        default=0
    )

    def __str__(self):
        return self.subject

所々、djangoのテンプレートがありますがhtmlはこんな感じです

{% extends 'inquiry_apps/base.html' %}
{% block page_title %}Inquiry list{% endblock %}

{% block header %}
{% endblock %}

{% block content %}
<h1>Inquiry list</h1>
<form method="get">
    <ul class="search_inquiry_form">
        <li>
            <label for="id">Id:</label>
            <!-- <input type="number" name="id" min="1"> -->
            <span class="input_field">{{ form.id }}{{ form.id.errors }}</span>
        </li>
        <li>
            <label for="">Email:</label>
            <!-- <input type="text" name="email"> -->
            <span class="input_field">{{ form.email }}{{ form.email.erros }}</span>
        </li>
        <li>
            <label for="">Word:</label>
            <!-- <input type="text" name="message"> -->
            <span class="input_field">{{ form.word }}{{ form.word.erros }}</span>
        </li>
        <li>
            <label for=""></label>
            <span>※Word can find subject and message</span>
        </li>
        <li>
            <label for=""></label>
            <button type="submit" class="btn">Search</button>
        </li>

    </ul>
</form>


<hr>
{% for num in inquiries_page.paginator.page_range %}
{% if inquiries_page.number == num %}
<span>{{ num }}</span>
{% else %}
<a href="?email={{ search_email }}&page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}

<table class="inquiry_list_table">
    <thead>
        <tr>
            <td>Id</td>
            <td>Name</td>
            <td>Subject</td>
            <td>Message</td>
            <td>Email</td>
            <td>Updated</td>
            <td>Detail</td>
            <td>Status</td>
        </tr>
    </thead>
    <tbody>
        {% for inquiry in inquiries_page %}
        <tr>
            <td>{{ inquiry.id }}</td>
            <td><div class="container"><span>Name</span><span>{{ inquiry.name }}</span></div></td>
            <td><div class="container"><span>Subject</span><span>{{ inquiry.subject }}</span></div></td>
            <td><div class="container"><span>Message</span><span>{{ inquiry.message }}</span></div></td>
            <td><div class="container"><span>Email</span><span>{{ inquiry.email }}</span></div></td>
            <td><div class="container"><span>Updated</span><span>{{ inquiry.updated_at|date:"Y-m-j H:i " }}</span></div></td>
            <td><div class="container"><span>Detail</span><span><a href="{% url 'inquiry_apps:comment_list' inquiry.id %}">detail</a></span></div></td>
            <td>
                <div class="container"><span>Status</span><span>
                {% if inquiry.inquiry_status == inquiry.InquiryStatus.Pending %}
                Penging
                {% elif inquiry.inquiry_status == inquiry.InquiryStatus.Ignore %}
                Ignore
                {% else %}
                Competed
                {% endif %}
                </span></div>
            </td>
        </tr>
        {% endfor %}
    </tbody>
</table>



{% endblock %}

クエリ後が引き継がれるようにviewsに設定

クエリを引き継がせるには、新たに_some_page_hrefという内部関数を作成します。明示的に内部関数は先頭にアンダーバーをつけます。

_some_page_href関数で返される戻り値( return ‘?’ + ‘&’.join(params) が、クエリで検索語の文字列をページングを実行しても引き継がれるようにしています。

views.py


def _some_page_href(id, email, current_page, word):
    params = []
    if id:
        params.append('id=%s' % urllib.parse.quote(str(id)))
    if email:
        params.append('email=%s' % urllib.parse.quote(email))
    if current_page:
        params.append('page=%s' % urllib.parse.quote(str(current_page)))
    if word:
        params.append('word=%s' % urllib.parse.quote(word))
    return '?' + '&'.join(params)

@require_http_methods(['GET'])
def inquiry_list(request):
    qs = Inquiry.objects.order_by('-updated_at')
    form = InquiryFindForm(request.GET)
    if not form.is_valid():
        return render(request, 'inquiry_apps/inquiry_list.html', {'form': form})

    if form.is_valid():
        id_int = form.cleaned_data['id']
        email_str = form.cleaned_data['email']
        page_int = form.cleaned_data['page']
        word_str = form.cleaned_data['word']

    if email_str is not None:
        qs = qs.filter(email__contains=email_str)

    if id_int is not None:
        qs = qs.filter(id=id_int)

    if word_str is not None:
        qs = qs.filter(Q(subject__contains=word_str) |
                        Q(message__contains=word_str))

    # making next page parameter
    next_page_href = _some_page_href(id_int, email_str, page_int+1, word_str)
    # making prev page parameter
    prev_page_href = _some_page_href(id_int, email_str, page_int-1, word_str)

    paginator = Paginator(qs, 5)
    try:
        inquiries_page = paginator.page(page_int)
    except EmptyPage:
        raise Http404('Page does not exist')


    context = {
        'form': form,
        'inquiries_page': inquiries_page,
        'search_email': email_str,
        'next_page_href': next_page_href,
        'prev_page_href': prev_page_href,
    }

htmlには以下のテンプレートを追記します。

{% if inquiries_page.has_previous %}
<a href="{{ prev_page_href }}">◀︎Prev</a>
{% endif %}
{% for num in inquiries_page.paginator.page_range %}
{% if inquiries_page.number == num %}
<span>{{ num }}</span>
{% else %}
<a href="?email={{ search_email }}&page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
{% if inquiries_page.has_next %}
<a href="{{ next_page_href }}">Next▶︎</a>
{% endif %}

スクールを利用して本格的に学ぶ

いかがでしたでしょうか?

今回10人中9人が挫折すると言われるプログラミングを、半年間もの間頑張れ、結果、今はPythonエンジニアとして働く事ができているのも、プログラミングスクールを利用して自ら目標を設定して講師の言う通りにひたすら打ち込んだまでです。

挫折率が高いプログラミングこそお金を払ってメンターを付けて、道を見失わないように環境を構築する必要があるのではないでしょうか。

結局一人だとどうしてもだらけてしまいます。

これはダイエットで自分一人では痩せられないけど、トレーナーを付けて否が応でもせざるを得ない環境を作ると一緒ですね。

ヒロヤンもプログラミング勉強開始直後はあれこれ悩みましたが、悩むよりも手っ取り早くテックキャンプやDMM Web Campのスクールに登録した方が最短ルートで勉強できるのではないかと考えました。

テックキャンプで無料相談を受ける

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です