unpocoプログラミング

ゲームライフを快適にするためにプログラミングを勉強します。間違ってたら教えてください。

【Django】GAEにデプロイ

更新をサボりデプロイについて調べていました。
調べれば調べるほど難しく、きちんと理解してから実施するとなるとかなり時間がかかってしまいそうなので、とりあえずやったれの精神でデプロイしてみた。

GCP

Google Cloud Platformのアカウント作成

無かったら作る。 console.cloud.google.com

プロジェクト作成

今回作ったプロジェクトの名前は「todoapp」
プロジェクトIDをメモしておく。

https://i.imgur.com/kZLa57s.png

Google Cloud SDKをインストール

インストールする。 cloud.google.com

Django

settings.py

ALLOWED_HOSTS = ['*']

プロジェクトフォルダ直下にapp.yamlを作成

runtime: python37
entrypoint: gunicorn -b :$PORT [プロジェクト名].wsgi:application

handlers:
- url: .*
  script: auto

プロジェクトフォルダ直下にrequirements.txtを作成

pip freeze > requirements.txt で作るのが簡単。

GCloud

djangoのプロジェクトフォルダにcdしておく。

認証

gcloud auth login

ブラウザが立ち上がるのでGCPのプロジェクトを作成したアカウントを選択する。

デプロイ

事前にメモしておいたプロジェクトIDはここで使う。

gcloud app deploy --project [プロジェクトID]

アプリを置く場所を聞かれる。東京は「2」

 [1] asia-east2    (supports standard and flexible)
 [2] asia-northeast1 (supports standard and flexible)
 [3] asia-northeast2 (supports standard and flexible)
 [4] asia-south1   (supports standard and flexible)
 [5] australia-southeast1 (supports standard and flexible)
 [6] europe-west   (supports standard and flexible)
 [7] europe-west2  (supports standard and flexible)
 [8] europe-west3  (supports standard and flexible)
 [9] europe-west6  (supports standard and flexible)
 [10] northamerica-northeast1 (supports standard and flexible)
 [11] southamerica-east1 (supports standard and flexible)
 [12] us-central    (supports standard and flexible)
 [13] us-east1      (supports standard and flexible)
 [14] us-east4      (supports standard and flexible)
 [15] us-west2      (supports standard and flexible)
 [16] cancel

デプロイ完了

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default

To view your application in the web browser run:
  $ gcloud app browse --project=[プロジェクトID]

上のコマンドを叩くとログを確認できる。
下のコマンドを叩くとブラウザが立ち上がり、デプロイしたアプリのページが開く。

ちゃんとデプロイされました

トップページだけ。
スマホからも見られてちょっと感動した。 https://i.imgur.com/1AIb2Lt.png

参考

GAEにDjangoアプリをデプロイしてみよう! | Udemy

https://cloud.google.com/appengine/docs/standard/python3/runtime

https://cloud.google.com/appengine/docs/standard/python3/config/appref

次は

参考ページをもとにデータベース連携をやってみる。
 

おわり

【Django】画像に使われている色を調べるやつを作った

できた。

画面

トップ

https://i.imgur.com/J5oh0jh.png

アップロード画面

https://i.imgur.com/MSpjxom.png

詳細画面(例1)

https://i.imgur.com/spsdiWj.png

詳細画面(例2)

https://i.imgur.com/Yl4MofC.png

コード(一部)

models.py

from django.db import models

class ImageFile(models.Model):
    """画像"""
    name = models.CharField("名前", max_length=20)
    file = models.ImageField("画像")

    def __str__(self):
        return self.name

views.py

背景色設定に使うためmost_common()ではhex値を返すように変えた。

from collections import Counter
import os

from PIL import Image

from django.conf import settings
from django.urls import reverse_lazy
from django.views import generic

from .models import ImageFile
from .forms import UploadForm

class IndexView(generic.ListView):
    model = ImageFile


class UploadView(generic.CreateView):
    model = ImageFile
    form_class = UploadForm
    template_name = "imgcolor/upload.html"
    success_url = reverse_lazy("imgcolor:index")


class DetailView(generic.DetailView):
    model = ImageFile

    def most_common(self, filename, n=5):
        im = Image.open(filename)
        x_len, y_len = im.size
        px = im.load()
        
        px_list = []
        for x in range(x_len):
            for y in range(y_len):
                px_list.append(px[x, y])
        
        counter = Counter(px_list)
        result = []
        for item in counter.most_common(n):
            hex_str = "#{:02x}{:02x}{:02x}".format(*item[0])
            result.append(hex_str)
        return result

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        filepath = os.path.join(settings.MEDIA_ROOT, context["imagefile"].file.name)
        context["colors"] = self.most_common(filepath)
        return context

upload.html(テンプレ)

{% extends 'imgcolor/base.html' %}

{% block title %}upload{% endblock %}

{% block content %}

<form action="" method="POST" enctype="multipart/form-data" class="mt-3">
    {{ form.as_p }}
    {% csrf_token %}
    <button type="submit" class="btn btn-primary">送信</button>
</form>

{% endblock %}

imagefile_detail.html(テンプレ)

白(#ffffff)の場合のみ枠線を引く。
spanにpx-5(パディング)を設定することで色を表示する領域を確保している。

{% extends 'imgcolor/base.html' %}

{% block title %}{{ imagefile.name }}{% endblock %}

{% block content %}
<div class="mt-4">
    <img src="{{ imagefile.file.url }}" alt="{{ imagefile.name }}" class="mb-4">
    <div class="row">
        <div class="col-md-4">
            {% for color in colors %}
                <p>
                    {% if color == "#ffffff" %}
                    <span style="background-color:{{ color }}" class="px-5 mr-3 border border-dark"></span>
                    {% else %}
                    <span style="background-color:{{ color }}" class="px-5 mr-3"></span>
                    {% endif %}
                    {{ color }}
                </p>
            {% endfor %}
        </div>
    </div>
</div>
{% endblock %}

参考

https://narito.ninja/blog/detail/92/

おわりに

先人の知恵を借りまくってはいるが大分慣れてきた感がある。
そろそろデプロイにも挑戦したい。(もちろん先人の知恵をフル活用する)
 
おわり

画像に使われている色を調べるやつを作る(準備)

https://i.imgur.com/7hN7n49.png

https://www.design-seeds.com/ から拝借。

こんな感じの「画像にどんな色が使われているか」みたいなやつを作る。

実現に必要そうなこと

たぶんこれだけ。

画像を1ピクセルずつ見る

色の数を数えるので1ピクセルずつ見る必要がある。

画像のアップロード

今回もDjangoで作ろうと思うのでファイルのアップロードを出来るようにしたい。

ドラッグアンドドロップでアップロード

これが理想。

Pillow

画像を1ピクセルずつ見るのにはPillowっていうライブラリを使えばよさそう。

pillow.readthedocs.io

さっそく試す

テストに使う画像(400x400)

https://i.imgur.com/2Lu7KyY.png

コード
from collections import Counter
from PIL import Image

def most_common(filename, n=5):
    im = Image.open(filename)
    print(im.format, im.size, im.mode)
    x_len, y_len = im.size
    px = im.load()
    
    px_list = []
    for x in range(x_len):
        for y in range(y_len):
            px_list.append(px[x, y])
    print(len(px_list))
    
    counter = Counter(px_list)
    return counter.most_common(n)

most_common("sample.png")
PNG (400, 400) RGB
160000
[((195, 195, 195), 63537),
 ((34, 177, 76), 34082),
 ((0, 0, 0), 20160),
 ((237, 28, 36), 12186),
 ((255, 242, 0), 11781)]

RGB値見てもいまいちピンと来ませんがおそらく合ってる。

次回

Djangoのアプリを作るところから始める。
アプリ名は何にしようか。
 
おわり

【Django】障害管理アプリを作る 11-エクスポートと詳細画面の移動をフィルタリングに対応させる

障害一覧のエクスポートと詳細画面の移動がフィルタリングに対応していなかったのを修正。

コード

views.py

globalを使う。

global my_queryset
my_queryset = None


def get_my_queryset():
    global my_queryset
    if my_queryset is None:
        my_queryset = Bug.objects.all()
    return my_queryset


def set_my_queryset(qs):
    global my_queryset
    my_queryset = qs
IndexViewのget_queryset()

returnする前にセットしておく。

    def get_queryset(self):
       (略)
        
        set_my_queryset(queryset)
        return queryset
DetailViewのget_context_data()
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        my_id = context["object"].id
        # qs = self.get_queryset()
        qs = get_my_queryset()
        
      (略)
export_bug()
def export_bug(request):
    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename="bugs.csv"'
    writer = csv.writer(response)
    # qs = Bug.objects.all()
    qs = get_my_queryset()
    for bug in qs:
        writer.writerow(
            [bug.pk, bug.system, bug.created_at, bug.discoverer, bug.kind,\
             bug.priority, bug.impact, bug.title, bug.body, bug.status,\
             bug.similar, bug.updated_at, bug.changer]
            )
    return response

おしまい

もっといい方法がありそうだが、やりたいことはこれで実現できた。
とりあえず障害管理アプリはこれで終わり。
 

おわり

【Django】障害管理アプリを作る 10-障害レポート作成機能を追加

詳細ページから該当障害のレポートを作成する機能を追加する。

テンプレート

bug_detail.html

画面下部に「レポート作成」ボタンを追加。

(略)

<tr>
    <th>類似障害</th>
    <td>
        {% for sim_bug in bug.similar.all %}
            <a href="{% url 'bagutta:detail' sim_bug.pk %}">{{ sim_bug }}</a><br>
        {% endfor %}
    </td>
</tr>
</table>
<div class="mb-3">
    <a class="btn btn-primary mr-3" href="{% url 'bagutta:update' bug.pk %}">更新画面</a>
    <a class="btn btn-primary" href="{% url 'bagutta:report' bug.pk %}">レポート作成</a>
</div>
{% endblock %}

コード

settings.py

1行追加。

REPORT_TEMPLATE = os.path.join(BASE_DIR, "report.txt")
レポートのテンプレ

「report.txt」という名前でプロジェクトフォルダ直下に配置。

■発生日時
$date

■システム
$system

■障害
$title

■対応経緯
$body

views.py

make_reportを追加。

import codecs
import shutil
import string
import tempfile

from django.conf import settings
from django.shortcuts import get_object_or_404

def make_report(request, pk):
    bug = get_object_or_404(Bug, pk=pk)
    context = {
        "date": bug.created_at,
        "system": bug.system,
        "title": bug.title.strip(),
        "body": bug.body.strip(),
    }
    
    with codecs.open(settings.REPORT_TEMPLATE, "r", "utf-8") as f:
        template = string.Template(f.read())
    report = template.substitute(context)
    # CRLFがLF2つに変わってしまうため置換しておく
    report = report.replace("\r\n", "\n")

    with tempfile.TemporaryFile("w+") as t:
        t.write(report)
        t.seek(0)
        response = HttpResponse(content_type='text/plain')
        response['Content-Disposition'] = 'attachment; filename="bug.txt"'
        shutil.copyfileobj(t, response)
    
    return response
参考

Djangoで、ファイルダウンロード

Content-Typeの一覧 - Qiita

urls.py

pathを追加。

from django.urls import path
from . import views

app_name = 'bagutta'

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('add/', views.AddView.as_view(), name='add'),
    path('update/<int:pk>', views.UpdateView.as_view(), name='update'),
    path('detail/<int:pk>', views.DetailView.as_view(), name='detail'),
    path('export/', views.export_bug, name='export'),
    path('report/<int:pk>/', views.make_report, name='report'),
]

問題

CRLFがLF2つに変わってしまう問題。
無理やり回避しているがどうにかならないものか。

 
おわり