【Django】既存のプロジェクトに画像加工アプリを追加してみた!
前回の記事の続きニャ!
実行環境
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 = PhotoCreateViewで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で感想をシェアしてね↓
