BFT名古屋 TECH BLOG

日々の業務で得た知識を所属するエンジニアたちがアウトプットしていきます。

【Django】撮影した画像を表示するWebアプリケーションを作る(2/2)

はじめに


こんにちは!
株式会社BFT名古屋支店・インフラ女子(?)のやまぐちです。

pythonフルスタックフレームワークDjango」を使ってアプリ開発をしてみたい!と思い、勉強がてら以下の流れで記事を投稿していく予定です。

☆☆☆☆☆☆☆☆☆☆☆☆
撮影した画像を表示するWebアプリケーションを作る(1/2)
撮影した画像を表示するWebアプリケーションを作る(2/2) ★今ココ
Apache + Gunicorn + Django でWebアプリケーションを公開する(1/2) ※作成中
Apache + Gunicorn + Django でWebアプリケーションを公開する(2/2) ※作成中
AWSでCICDを実装する ※作成中
☆☆☆☆☆☆☆☆☆☆☆☆

撮影した画像を表示するWebアプリケーションをDjangoで作る



今回作成したアプリのフォルダ構成と全体像です。細かく見ていきましょう。

① urls.py(「CondoruPrj」)

DjangoではアクセスするURLに対してviews.pyに定義されているどの関数を返すかを「urls.py」に記載します。

http://<ホスト名/IPアドレス>:8000/webapp」というパスにアクセスしたら、写真の一覧を表示するページを返したいので、
① デフォルトアプリケーション「CondoruPrj」の「urls.py」に今回作成したアプリケーション「weapp」の「urls.py」を見るように指示します。

from django.urls import path, include

urlpatterns = [
    path('webapp/', include('webapp.urls')),
]

② urls.py(「webapp」)

アクセスしたパス(http://<ホスト名/IPアドレス>:8000/webapp)に対してviews.pyに定義されている「rec_filelist」関数を返すようにします。

from django.urls import path
from .views import rec_filelist

urlpatterns = [
    path('', rec_filelist, name='rec_filelist'),
]

③ views.py

ページングするための関数と実際にファイル一覧を取得する関数を記載します。ここでは撮影した画像を格納するパスを「/tmp/path/」として記載しています。

細かい記載内容の説明は以下を参照ください。

ページングについてはこちらの記事を参考にさせていただきました。
またファイル一覧から年月日&時間情報を取り出しHTMLへ渡す変数に格納する部分は以下の記事に詳細を記載しています。コードが長いのでよければこちらをご覧ください。

bftnagoya.hateblo.jp

import os
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.shortcuts import render


# ページング処理
def paginate_queryset(request, queryset, count):
    """
    Pageオブジェクトを返す

    引数:
    queryset -- ファイル更新日時で降順に並び替えられた画像ファイル名のリスト
    count    -- 1ページに表示する件数を指定する(rec_filelistで指定)

    返り値:
    page_obj -- ページングに必要な各種情報(全体のページ数、コンテンツの量、前後のページなど)

    HTMLでの書き方例:
        {% if page_obj.has_previous %}
          <a href="?page={{ page_obj.previous_page_number }}">Prev</a>
        {% endif %}

    また、page_obj.object_list で、count件数分の絞り込まれたquerysetが取得できる
    """

    paginator = Paginator(queryset, count)
    page = request.GET.get('page')
    try:
        page_obj = paginator.page(page)
    except PageNotAnInteger:
        page_obj = paginator.page(1)
    except EmptyPage:
        page_obj = paginator.page(paginator.num_pages)
    return page_obj

# urls.pyから呼び出される関数
# ファイル名の一覧取得
def rec_filelist(request):
    """
    /opt/CondorU/recognized/以下のjpgファイルを検索し、降順に格納、list.htmlに結果を返す

    引数:
    なし

    返り値:
    page_list -- count件数分の絞り込まれた画像ファイル名のリスト
    page_obj -- ページングに必要な各種情報(全体のページ数、コンテンツの量、前後のページなど)
    
    出力先:
    list.html
    """

    # 物体検出で描画された画像が格納されるディレクトリをpathに指定
    path = "/tmp/path/"
    content_cnt = "12"

    getdirsfiles = os.listdir(path=path)
    filesonly = [f for f in getdirsfiles if os.path.isfile(os.path.join(path, f))]

    # ファイル名は「yyyymmdd_HHMMSS.jpg」として保存される
    # "_"や".jpg"を取り除き、int型に変換して降順に並び替え、再度str型に戻す
    rm_char_files = [s.replace('_', '') for s in filesonly]
    rm_char_files = [s.replace('.jpg', '') for s in rm_char_files]
    to_int_filename = [int(i) for i in rm_char_files]
    dsc_to_int_filename = sorted(to_int_filename, reverse=True)
    dsc_to_str_filename = [str(s) for s in dsc_to_int_filename]
    
    # 初期化(cntは画像をモーダルウィンドウで表示するためのidとして使用)
    cnt = 0
    fixed_filelist = []

    # fixed_filelistに、0-番号、1-ファイル名、2-年、3-月、4-日、5-時、6-分、7-秒を格納
    for x in dsc_to_str_filename:
        yyyy = x[0:4]
        mm = x[4:6]
        dd = x[6:8]
        hh = x[8:10]
        min = x[10:12]
        sec = x[12:14]

        cnt += 1
        filename = yyyy + mm + dd  + '_' + hh + min + sec + '.jpg'
        fixed_filelist.append([cnt, filename, yyyy, mm, dd, hh, min, sec])

    
    page_obj = paginate_queryset(request, fixed_filelist, content_cnt)
    context = {
        'list_obj': page_obj.object_list,
        'page_obj': page_obj,
    }
    return render(request, 'list.html', context)

④ list.html

↑の最後のコードでlist.htmlに対してcontext変数を返しています。contextの中には何が入っているかというと「list_obj」と「page_obj」でその中にはそれぞれ…と辿っていくのも何とかできそうです。

まずは「page_obj」から。 やりたいのは以下の図の赤枠部分です。

「page.obj」はページングの部分に使用し、HTML側では以下のように記載します。いろいろ書いていますが、前へ の部分、数字の部分、次へ の部分があれば問題ありません。

<!-- ページング -->
<div class="mx-1 my-0">
    <ul class="pagination">
        <!-- 前へ の部分 -->
        {% if page_obj.has_previous %}
        <li class="page-item">
            <a class="page-link" href="?page={{ page_obj.previous_page_number }}">
                <span aria-hidden="true">&laquo;</span>
            </a>
        </li>
        {% endif %}

        <!-- 数字の部分 -->
        {% for num in page_obj.paginator.page_range %}
            {% if page_obj.number == num %}
                <li class="page-item active"><a class="page-link" href="#!">{{ num }}</a></li>
            {% else %}
                <li class="page-item"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
            {% endif %}
        {% endfor %}

        <!-- 次へ の部分 -->
        {% if page_obj.has_next %}
            <li class="page-item">
                <a class="page-link" href="?page={{ page_obj.next_page_number }}">
                    <span aria-hidden="true">&raquo;</span>
                </a>
            </li>
        {% endif %}
    </ul>
</div>

次に「list_obj」です。 「list_obj」には「page_obj」の中の「fixed_filelist」が入っているのでつまり「0-番号、1-ファイル名、2-年、3-月、4-日、5-時、6-分、7-秒」です。

大事なのは配列「list_obj」をforでまわしてfに格納し、f.番号で取り出して必要な場所で使用しているところでしょうか。

モーダル表示、という選択した画像をクリックすると拡大表示するための仕組みも実装しています。こんな感じです。

    {% for f in list_obj %}
    <div class="col col-3">
        <div class="my-3">
            <!--画像-->
            <a data-toggle="modal" data-target=".modal_{{ f.0 }}">
                <figure class="figure">
                    <img src="/static/{{ f.1 }}" class="img-thumbnail img-fluid shadow-sm" style="cursor:zoom-in">
                    <figcaption class="text-center h5 text-secondary">{{ f.2 }}/{{ f.3 }}/{{ f.4 }} {{ f.5 }}:{{ f.6 }}</figcaption>
                </figure>
            </a>
            <!--モーダル表示-->
            <div class="modal fade modal_{{ f.0 }}">
                <div class="modal-dialog modal-lg">
                    <div class="modal-content text-center text-white bg-secondary bg-opacity-75">
                        <div class="modal-header">
                            <h3 class="modal-title">☆--- {{ f.2 }}年{{ f.3 }}月{{ f.4 }}日 {{ f.5 }}時{{ f.6 }}分 ---*:.。☆</h3>
                        </div>
                        <div class="modal-body">
                            <img src="/static/{{ f.1 }}" width="100%" data-dismiss="modal">
                        </div>
                    </div>
                </div>
            </div>
        </div> 
    </div>      
    {% endfor %}

終わりに


Djangoフルスタックフレームワークと言われるだけあって、仕組みさえ覚えてしまえばとても簡単です。views.pyの書き方などは難しかったものの、開発部分・画面部分に集中できることがわかりました。

次はこれを開発環境へのせるための手順をまとめていきたいと思います。

ここまで読んでいただきありがとうございました~^ ^