DISTRICT 37

なにか

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.POSTrequest.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),
    })

これでほぼ終了。というのもStdImageFieldImageFieldとほぼ同じ動きをするからだ。違いは画像アップロード時に登録したバリエーション分だけリサイズして登録してくれるというところ。この場合オリジナルに加えて<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として書き込むようにいずれしたい。