【Django】既存のプロジェクトに画像加工アプリを追加してみた!

2023年07月31日 更新日:2023年08月19日

ドラ猫

前回の記事の続きニャ!


実行環境


Django (4.1.2)

Python (3.10.2)


ディレクトリ構成


プロジェクト名はmysiteで、ディレクトリ内のファイル名は省略しています。


mysite
├─blog
│  ├─migrations
│  │  └─__pycache__
│  ├─static
│  │  ├─css
│  │  └─images
│  ├─templates
│  │  └─blog
│  └─__pycache__
├─media
└─mysite


ブランチの作成


失敗したら大変なことになるのでgitのブランチ機能を利用します。

個人開発なので、簡単に言うとバックアップを取っていると考えて大丈夫です。

ブランチ名はnew_appとします。


ターミナル

git branch new_app
git checkout new_app


アプリ作成


startappコマンドでアプリを作成します。

アプリ名はimage_editorとします。


ターミナル

python manage.py startapp image_editor


settings.py


INSTALLED_APPSを編集します。


INSTALLED_APPS = [
    …,
    'image_editor.apps.ImageEditorConfig',
]


単に'image_editor'を追加するだけでもOKです。


models.py


ここからすこしややこしいです。

もとの画像をmediaのbeforeディレクトリに保存、加工後の画像をafterに保存することにします。


また、リサイズするときに縦幅と横幅を指定できるようにします。


from django.db import models
from PIL import Image
import os
from django.conf import settings

class Photo(models.Model):
    before = models.ImageField('画像ファイル', upload_to='image_editor/before')
    after = models.ImageField(upload_to='image_editor/after')
    width = models.IntegerField('幅(px)')
    height = models.IntegerField('高さ(px)')
    
    def editor(self):
        with Image.open(self.before) as img:
            new_img = img.resize((self.width, self.height), Image.LANCZOS)
        
        file_name = os.path.basename(os.path.basename(self.before.path))
        
        #new_imgは単なるバイナリデータ(0と1のデータ)ではない
        #ImageFieldに保存できるのは0と1で表された画像ファイル
        #したがって一時的にimage_editor/tempに保存
        temp_path = os.path.join(settings.MEDIA_ROOT, 'image_editor', 'temp', file_name)
        new_img.save(temp_path)
        
        self.after.save('after_'+file_name, open(temp_path, 'rb'))
        
        os.remove(temp_path)
        
        #インスタンスを保存
        self.save()


次にeditorメソッドについて説明します。


with Image.open(self.before) as img:は、imgという変数に画像ファイルが入っていることを表します。

img = Image.open(self.before)としても良いですが、その場合は最後にclose()を記述してください。

with文を使うとclose()を省略できます。


temp_pathという変数には/media/image_editor/temp/(画像ファイル名)という文字列を格納しています。

画像ファイルは通常0と1で構成されたバイナリデータとして保存されます。

しかし、Pillowのopen関数でファイルを開くと、単なる画像データではなくなってしまいます。

これではafterのImageFieldに格納できないのでnew_img.save(temp_path)で一時的にtempディレクトリに画像として保存しています。


open(temp_path, 'rb')はtemp_pathの画像をバイナリモード(0と1で表すモード)で開いています。


self.after.save('after_'+file_name, open(temp_path, 'rb'))は加工した画像ファイルをafter_(ファイル名)としてafterに保存するということです。


os.remove(temp_path)で一時的に保存したファイルを削除します。


最後にインスタンスを保存して終了です。


forms.py


from django import forms
from .models import Photo

class PhotoForm(forms.ModelForm):
    class Meta:
        model = Photo
        fields = ('before', 'width', 'height',)


ModelFormを使うことで、後ほど作成するform.htmlで自動的にフォームを作成してくれます。


views.py


from typing import Any
from django.db import models
from django.shortcuts import get_object_or_404, render
from django.views import generic
from django.urls import reverse
from .forms import PhotoForm
from .models import Photo

class CreateView(generic.CreateView):
    template_name = 'image_editor/form.html'
    model = Photo
    form_class = PhotoForm
    
    #formでPOSTされたときにform_validメソッドが呼び出される
    def form_valid(self, form):
        response = super().form_valid(form)
        self.object.editor()
        return response
    
    #フォームの内容をPhotoに登録したら呼び出される。
    def get_success_url(self):
        success_url = reverse('image_editor:after', kwargs={'pk': self.object.id})
        return success_url

class PhotoDetailView(generic.DetailView):
    template_name = 'image_editor/photo_detail.html'
    model = Photo


CreateViewでeditorメソッドを実行します。


form_validメソッドはformの内容に間違いがないかどうか判定してくれます。

これをオーバーライドして、フォームの内容が送信されたときにeditorメソッドを実行します。


form_validが実行された後にget_success_urlが呼び出されます。

このときにインスタンスのidを取得してダウンロードページへのurlを作成します。


PhotoDetailViewは作成した画像をダウンロードするためのviewです。


urls.py


プロジェクト(mysite)のurls.py

urlpatterns = [
    …
    path('image-editor/', include('image_editor.urls')),
]


/image-editorを検出するとアプリケーションディレクトリ(image_editor)のurls.pyを参照します。


アプリケーションディレクトリのurls.py

from django.urls import path
from .views import CreateView, PhotoDetailView

app_name = 'image_editor'

urlpatterns = [
    path('', CreateView.as_view(), name='home'),
    path('after/<int:pk>', PhotoDetailView.as_view(), name='after'),
]


html作成


今回はform.htmlとphoto_detail.htmlを作成します。

base.htmlは省略します。


form.html

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

{% block content %}
    <h1 class="editor">画像加工ツール</h1>
    <form method="post" enctype="multipart/form-data">
        {{ form.as_p }}
        <button type="submit">作成</button>
        {% csrf_token %}
    </form>
{% endblock %}


photo_detail.html

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

{% block content %}
<img src="{{ photo.after.url }}" alt="after">
<a href="{{ photo.after.url }}" download="{{ after.name }}">ダウンロード</a>
<br>
<a href="{% url 'image_editor:home' %}">戻る</a>
{% endblock %}


マージ


最後にgitのマージを行います。


ターミナル

git checkout main
git merge new_app


最後に


超大変だった!

でも初心者を脱却するにはいろいろ作ってみるしかないと思います。

今回はいろいろ調べながらやって、まる2日くらいかかりました。


このサイトに搭載してみたのでぜひ使ってみてね。


今後の課題


urlを遷移せずにダウンロードできるようにしたいです。

これでは他人がアップロードした画像を見ることができるので、他人に見られたくない画像では使わないでください。


またはランダムで期間限定のurlを生成する方法もあるらしい。

今度試してみるか~


当面の間は毎日手動で画像フォルダを削除します。


SNSで感想をシェアしてね↓