Django2で画像をアップロードする処理を作る
忘備録。基本的な処理と、ちょっと応用した処理。
設定
画像を扱うライブラリとしてpillow
が必要
pip install pillow
続いて<projekct>/settings.py
を編集
STATIC_URL = '/static/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/'
<project>/url.py
も編集
urlpatterns = [ path('admin/', admin.site.urls), path('accounts/', include('django.contrib.auth.urls')), path('accounts/', include('accounts.urls')), ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
と、これで準備完了
Model
<myapp>/models.py
の編集。画像をアップロードをする際のフィールドはImageFieldとなり。この例だとアップロードする際のファイルは/media/upload/<uploadfilename>
に保存されることになる。
class MyModel(models.Model): user = models.ForeignKey('auth.User', on_delete=models.CASCADE) title = model.CharField(max_length=200) image = models.ImageField(upload_to="upload", blank=True, null=Ture)
html
投稿するほうのHTML。formタグにecttype="mutipart/form-data"
とするのがポイント
<form method="POST" class="post-form", enctype="multipart/form-data">{% csrf_token %} {{ form.as_p }} <button type="submit" class="save btn btn-default">Save</button> </form>
表示するほうのHTML。こちらは簡単でimgタグをつけるだけでいい。ソースの取り出し方はclass.property.url
とする
<h1>{{ myModel.title }}</h1> <img src="{{myModel.image.url}}" />
form
特に変わった処理は無い
class MyModelForm(forms.ModelForm): class Meta: model = MyModel fields = ('title', image',)
View
form投稿時のviewの処理。POST時の処理でrequest.POST
とrequest.FILES
を引数にformをインスタンス化する。
def post(request, pk): if request.moethod == "POST": form = MyModelForm(request.POST, request.FILES) if form.is_valid(): model = form.save(commit.False) model.user = request.user model.save() return redirect('mymodel_detail', pk=model.pk) else: form = MyModelForm() return render(request, 'myapp/post.html', {'form': form})
アップロード先の変更
設定では同じディレクトリにアップロードするファイルが保存されるようになる。これをユーザ毎のディレクトリの年月毎に保存先を変えて、元のファイル名ではなくランダムな文字列のファイル名で保存するようにModelを編集。
def get_upload_path(instance, filename): n = datetime.now() prefix = "upload/" ymd='/'.join([n.strftime('%Y'), n.strftime('%m'), n.strftime('%d'), ""]) + "/" directory=str(instance.user.id) + "/" name=str(uuid.uuid4()).replace("-", "") extension=os.path.splitext(filename)[-1] return ''.join([prefix, directory, ymd, name, extension]) class MyModel(models.Model): user = models.ForeignKey('auth.User', on_delete=models.CASCADE) title = model.CharField(max_length=200) image = models.ImageField(upload_to=get_upload_path, blank=True, null=Ture)
やり方はmodelのupload_to
に任意の関数を渡すだけ。その関数で保存先を適当に組んだらいい。この例の場合は現在時刻から年月をとって<yyyy>/<mm>/
という文字列を作成。MyModelのインスタンスに登録されている<user.id>
を取得。あとはUUIDを使って適当な文字列を作って、元ファイルの拡張子を付けるという処理となり、<upload_root>/<upload>/<user.id>/<yyyy>/<mm>/uuid.ext
という文字列ができあがる。
削除処理
モデルが削除されてもアップロードしたファイルは消えない。という事で自分で処理をする必要がある。といってもdeleteしたらいいだけ
def delete(request, pk): myModel = get_object_or_404(MyModel, pk=pk) myModel.image.delete() myModel.delete() return redirect('mymodel_list')
StdImage
アップロードする画像によってはサイズがまちまちなので、統一するといろいろと見栄えが良い。という事で試したのがdjango-stdimage
というライブラリ。
インストール
pip install django-stdimage
続いて<projekct>/settings.py
にアプリを追加
# Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'stdimage', ]
これで準備完了。
StdImageを使うにはmodelを修正する
from stdimage.models import StdImageField class MyModel(models.Model): user = models.ForeignKey('auth.User', on_delete=models.CASCADE) title = model.CharField(max_length=200) image = StdImageField(upload_to=get_upload_path, blank=True, null=True, variations={ 'large': (600, 400), 'thumbnail': (100, 100, True), 'medium': (300, 200), })
これでほぼ終了。というのもStdImageField
はImageField
とほぼ同じ動きをするからだ。違いは画像アップロード時に登録したバリエーション分だけリサイズして登録してくれるというところ。この場合オリジナルに加えて<large>
、<thumbnail>
、<medium>
の4サイズが登録されることになる。ファイル名も<ファイル名>.<variation>.jpg
といったように保存されることになる。
という事でHTMLもリサイズした画像をそれぞれ表示することができるようになる。
<h1>{{ myModel.title }}</h1> <img src="{{myModel.url}}" /> <img src="{{myModel.large.url}}" /> <img src="{{myModel.thumbnail.url}}" /> <img src="{{myModel.medium.url}}" />
基本的な処理として画像ファイルを直接ディレクトリに置くような処理をまずは作ったけど、DBにBLOBとして書き込むようにいずれしたい。