"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

django2.0でbootstrap4を使うための設定と、ドラッグ&ドロップのjavaScript実装など。

今回は、「django2.0」と「bootstrap4」を使った入力画面を作ってみます。

サンプルとして、ドラッグ&ドロップでファイル名を取得するタイプの入力画面を作ってみます。

f:id:arakan_no_boku:20190320212948j:plain

 

 

django2.0とbootstrap4を使うための設定

 

djangoをインストールして、新規プロジェクトを作成します。

以下の記事の手順でしました。

arakan-pgm-ai.hatenablog.com

 

djangoのプロジェクトはdj_imgという名前でstartprojectしました。

f:id:arakan_no_boku:20190113104125j:plain

自動生成したフォルダに、「static」と「views」フォルダを追加してます。

さらに、「static」フォルダの下には「css」「images」「js」フォルダを作ります。

静的ファイルアクセスができるようにする手順などは、こちらを使いました。

arakan-pgm-ai.hatenablog.com

viewsフォルダの下には、HTMLファイルを置きます。

さて、プロジェクトを生成して、必要なフォルダは用意できました。 

 

bootstrap4を使うための追加モジュールインストール

 

djangoでbootstrapを使うには、django-bootstrap4というモジュールが必要です。

インストールの方法はこうです。

pip install django-bootstrap4

あと、djangoの組み込みのformのclassにbootstrapのものを適用するには、django-widget-tweaksというモジュールが必要です。

インストールの方法はこうです。

pip install django-widget-tweaks

両方共、はいってなければインストールしときます。

 

settings.pyの設定変更

 

そのフォルダ構成と追加インストールしたモジュールを有効にするために「dj_img\settings.py」は以下の赤字部分を変更してます。

INSTALLED_APPS = [

    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'bootstrap4',
    'widget_tweaks',

]

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            os.path.join(BASE_DIR, 'views'),
        ],
        'APP_DIRS': True,
        'OPTIONS': {
        'context_processors': [
        'django.template.context_processors.debug',
        'django.template.context_processors.request',
        'django.contrib.auth.context_processors.auth',
        'django.contrib.messages.context_processors.messages',
        ],
    },
},
]

LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]

INSTALLED_APPS に追加した「bootstrap4」「'widget_tweaks」が、見た通り、bootstrap4をdjangoで使うための設定です。

 

bootstrap4のテンプレートHTMLの作成

 

毎回共通の定義を書くのは面倒なので、bootstrapの以下のページの「Starter Templete」を元に使いまわし用のbase.htmlを書きます。 

getbootstrap.com

あと、bootstrapの各部品の書き方は、こちらのチートシートから。

hackerthemes.com

ほぼ、Starter Templeteのままのところへ、あとで処理を差し込むブロックを書きます。

Djangoのテンプレートは、任意の場所に{% block xxxxx %}{% endblock %}を置いて、実態HTMLでは、そのブロックの中に差し込む処理だけを書くというのが基本なので。

 

base.htmlのソースコード

 

<!doctype html>
<html lang="jp">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>

	{% block header %}
	{% endblock %}
    <title>{% block title %}デモ用テンプレート{% endblock %}</title>
  </head>
  <body>
	{% block content %}
	{% endblock %}
    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>
  </body>
</html>

今回用意するblockは3つです。

header要素を差し込むブロック

{% block header %}{% endblock %}

titleのブロック

{% block title %}デモ用テンプレート{% endblock %} 

 bodyのブロック

{% block content %}{% endblock %}

まあ、最低限な感じです。

ここまでで、django2.0とbootstrap4を使う準備はできました。

 

今回作る画面とざっくりした仕様

 

今回作成する画面のイメージはこんな感じです。

f:id:arakan_no_boku:20190118011333j:plain

エクスプローラから評価対象の画像をドラッグ&ドロップして、画像ファイル名をテキストボックスに表示し、「画像を分類する」ボタンを押したら、裏で処理が動いて結果を表示する。

そんなシンプルなデモにします。

画像ファイルはtensorflow/kerasを使った判別で、何の画像かを表示するようなデモ画面を想定してますが、今回は、その判別処理などの処理は実装せず、とりあえず固定テキストを返すような形のモックにしています。

 

 入力画面のHTML

 

テンプレートを利用するHTML・・つまり、画面本体です。

ドラッグ&ドロップの処理は、ここにJavaScriptで書いてます。

{% extends 'base.html' %}
{% load static %}
{% load bootstrap4 %}
{% load widget_tweaks %}

{% block header %}
<link rel="stylesheet" href="{% static "css/dj_img.css" %}"></link>
{% endblock %}

{% block title %}
  サンプルその1
{% endblock %}

{% block content %}
<div class="container">

    <form action="" method="post">{% csrf_token %}
        {% if textone %}
        <h1>ドロップされた画像</h1>
        <div class="form-group row my-4">         
     		<div class="col-lg-10">       
	            <img src="{{ textone | safe }}" />
	        </div>    
        </div>    
        <div class="form-group row my-4">
            <label class="col-lg-2 col-form-label"><h2>{{cnamelabel}}</h2></label>
    		<div class="col-lg-8">       
            	<h2>{{ cname }}</h2>
            </div>	
        </div>
        <a href="{% url 'demo02' %}"><h2>もう一回</h2></a>
        {% else %}
        <h1>サンプル</h1>
    	<img src="{% static "images/test.jpg" %}" />
        <div class="form-group row my-4">  
        	<div id="dragandrophandler" class="border col-lg-9">ここに画像をドロップ</div>
        </div>
        <div class="form-group row my-4">         
            <label class="col-lg-2 col-form-label"><h4>{{form.textone.label}}</h4></label>
    		<div class="col-lg-8">       
	            {{form.textone|add_class:"form-control bg-warning"}}
    		</div>
        </div>
        <div class="form-group row my-4">         
    		<div class="col-lg-2">       
	            <button type="submit" class="btn btn-primary">画像を分類する</button>
    		</div>
    		<div class="col-lg-8"></div>       
        </div>    
        {% endif %} 
    </form>
</div>  
<script>
var obj = $("#dragandrophandler");
obj.on('dragenter', function (e) 
{
    e.stopPropagation();
    e.preventDefault();
    $(this).css('border', '2px solid #0B85A1');

});
obj.on('dragover', function (e) 
{
     e.stopPropagation();
     e.preventDefault();
     $("#dragandrophandler").css('background-color', '#ffffe0');
});
obj.on('drop', function (e) 
{
     $(this).css('border', '2px dotted #0B85A1');
     e.preventDefault();
     var files = e.originalEvent.dataTransfer.files;
  
     //We need to send dropped files to Server
     $("#dropedfile").val(files[0].name);
     $("#dragandrophandler").css('background-color', '#ffffff');

});
$(document).on('dragenter', function (e) 
{
    e.stopPropagation();
    e.preventDefault();
    $("#dragandrophandler").css('background-color', '#ffffff');

});
$(document).on('dragover', function (e) 
{
  e.stopPropagation();
  e.preventDefault();
  obj.css('border', '2px dotted #0B85A1');
});
$(document).on('drop', function (e) 
{
    e.stopPropagation();
    e.preventDefault();
});

</script>  
{% endblock %}

補足します。

頭の方に書いた部分は、機能を利用するための決まり文句です。

{% extends 'base.html' %}

上記に定義したbase.htmlの読み込みです。

{% load static %}

静的ファイルのアクセスを可能にします。

{% load bootstrap4 %}

django-bootstrapを有効にします。

{% load widget_tweaks %} 

widget_tweaksを有効にします。

後半の<script></script>タグの中身がドラッグ&ドロップ処理です。

基本、dropやdragoverなどのイベントリスナを仕掛けているだけなので、下手な説明より見ればわかる類のソースかなと思っています。

 

ドラッグ&ドロップエリアの静的CSS

 

{% block header %}の部分には、静的CSSファイルの定義です。

各タグのclass定義は、すべて「bootstrap」のクラスなのですが、ファイルをドロップするDIVタグだけはIDの値で独自制御したかったので、以下のCSSを書いてます。

#dragandrophandler
{
border:2px dotted #0B85A1;
width:500px;
height:100px;
color:#92AAB0;
text-align:left;vertical-align:middle;
padding:10px 10px 10 10px;
margin-bottom:10px;
font-size:200%;
}

 

block contentの内容(HTMLの補足説明)

 

あと、{% block content %}の中身です。

body部ですね。

初期表示画面とsubmitボタンを押した後に表示する画面を両方書いてます。

 {% if textone %}から{% else %}までが、Submit後の結果表示画面。

Submitで呼ばれるメソッドの中で「textone」変数をセットするので、初期表示の時はtextoneは空になります。

だから、{% else %}より後ろが初期表示されるわけです。

最後にダダっと書いている<script></script>の間の処理は、ドラッグ&ドロップの処理です。

objがドロップするDivタグのエリアを指しているので、その領域にはいってきたり(dragenter)、領域内にいたり(dragover)、ドロップされたり(drop)の処理をイベントとして拾って処理をしてます。

ほぼほぼ、定型処理ですが、背景色を変えるなどして、ドロップ可能領域上にいるかどうかをわかりやすくするため、領域にはいってきたら薄い黄色の背景にして、でたら白に戻すみたいな感じにしてます。

ただ、objの中だけの処理しか書いていないと片手落ちなので、領域の外の動作も$(document)でドラッグ領域外のイベントの処理として書いてます。

 

ドラッグ&ドロップ処理はちょっと手抜き

 

ブラウザにエクスプローラからドラッグ&ドロップすると、ファイル名だけでフルパスはとれません。

なので。

今回のように、ドロップした画像ファイルを、裏で動くpythonプログラムで処理するには、ドロップされた画像ファイルを「upload」して、djangoから見えるフォルダに置いてやる処理が必要です。

ですが、用途として、ローカルで動かす簡単なデモ画面をスピーディに作るという点を重視しているので、そこはアップロード処理を実装する代わりに、画像ファイルをdjangoの管理下になるフォルダに事前においておいて、そこから選んでドロップする仕様にしてます。

まあ、手抜きなのですが・・。

ご容赦を。

 

Pythonのソースの修正

 

さて、HTMLまで準備できたので、pythonのソースを書いていきます。

 

forms.py

 

上記HTMLの以下の部分がテキスト入力フィールドになります。

{{form.textone|add_class:"form-control bg-warning"}}

 form.textone等の部分が、djangoの組み込みフォームで、add_class以降で、その入力フォームにbootstrapのCSSを適用しています。

このフォームの定義は、forms.pyで行います。

from django import forms

class UserForm(forms.Form):
     textone = forms.CharField(label='画像ファイル',widget=forms.TextInput(attrs={'id': 'dropedfile'}))

CharFieldがテキスト入力フィールドになります。 

JavaScriptでテキストボックスに値をセットするために、IDセレクタを使ってます。

${"#dropedfile")の部分です。

これを有効にするために、idをセットしています。

 

views.py

 

このフォームを使うメソッドはviews.pyに書きます。

from django.http.response import HttpResponse
from django.shortcuts import render, render_to_response
from datetime import datetime as dt
from . import forms
from django.template.context_processors import csrf


def demo(request):
    if request.method == 'POST':
         c = {
             'textone': '/static/images/' + request.POST["textone"],
             'cnamelabel':'この画像は',
             'cname':'あああああ(XXXXXXXXXXXXX)です。'
         }
    else:
        form = forms.UserForm(label_suffix=':')
        c = {'form': form}
        c.update(csrf(request))
    return render(request,'demo02.html',c)

そして、あとは呼び出すURLです。

cnamelabelとcnameは、評価結果を表示するエリアですが、そのロジックは次回に用意するので、とりあえず適当な文字列をセットしてます。

 

urls.py

 

urls.pyに追記します。

from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('demo/',views.demo,name='demo02'),
]

上記の最後の 「path('demo/',views.demo,name='demo02'),」が追加した部分です。

http://localhost:8000/demo で画面を呼び出し、views.pyのdemoメソッド、HTMLは「demo02.html」を使って表示する・・という意味です。

 

実行してみます

 

これで動くはずです。

まずはプロジェクトフォルダをカレントにして「python  manage.py runserver」ですね。

それで、「http://localhost:8000/demo」です。

f:id:arakan_no_boku:20190118011333j:plain

 適当な画像ファイルをドロップして、「画像を分類する」ボタンを押すと。

f:id:arakan_no_boku:20190119103853j:plain

おお。

まあ、OKじゃないですかね。

では、これをベースにVGG16の事前学習済モデルを利用した画像分類の処理を組み込んでいきます。

続きは別の回で。

arakan-pgm-ai.hatenablog.com

ではでは。