Chapter 03無料公開

画像アップロード機能

はる@Python、Djangoプログラミング講師
はる@Python、Djangoプログラミング講師
2021.03.20に更新

ブログにヘッダー画像をアップロードする機能を追加します。

requirements

画像をアップロードするには、Pillowパッケージを使用します。

requirements.txt

Django~=3.1.4
django-widget-tweaks~=1.4.8
django-allauth~=0.41.0
Pillow~=8.1.0

インストール

仮想環境にパッケージをインストールします。

(myvenv) ~$ pip3 install -r requirements.txt

Pillow がインストールできない場合(Mac)

(myvenv) ~$ pip3 install openssl
(myvenv) ~$ export LDFLAGS="-L/usr/local/opt/openssl/lib"
(myvenv) ~$ export CPPFLAGS="-I/usr/local/opt/openssl/include"
(myvenv) ~$ pip3 install psycopg2
(myvenv) ~$ pip3 install -r requirements.txt

settings

画像をアップロードする URL を指定します。

mysite/settings.py

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

プロジェクト URL

urlpatternsに画像の場所を追加します。

mysite/urls.py

from django.contrib import admin
from django.urls import path, include

from django.conf.urls.static import static # 追加
from django.conf import settings # 追加

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('app.urls')),
    path('accounts/', include('accounts.urls')),
    path('accounts/', include('allauth.urls')),
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # 追加

モデル

画像のモデルを追加します。

app/models.py

from django.conf import settings
from django.db import models
from django.utils import timezone


class Post(models.Model):
	author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
	title = models.CharField("タイトル", max_length=200)
	image = models.ImageField(upload_to='images', verbose_name='イメージ画像', null=True, blank=True) # 追加
	content = models.TextField("本文")
	created = models.DateTimeField("作成日", default=timezone.now)

	def __str__(self):
		return self.title

コード解説

upload_toで画像のアップロード先を指定します。

image = models.ImageField(upload_to='images', verbose_name='イメージ画像', null=True, blank=True)

マイグレーション実行

モデルを変更したら、下記コマンドで必ずデータベースの再構築が必要になります。

マイグレーションをするのを忘れて起動させると、エラーが発生しますので、必ずマイグレーションをしましょう。

(myvenv) ~$ python3 manage.py makemigrations
(myvenv) ~$ python3 manage.py migrate

フォーム

画像をアップロードするフォームを追加します。

app/forms.py

from django import forms


class PostForm(forms.Form):
    title = forms.CharField(max_length=30, label='タイトル')
    content = forms.CharField(label='内容', widget=forms.Textarea())
    image = forms.ImageField(label='イメージ画像', required=False) # 追加

ビュー

画像をアップロード用の処理を追加します。

app/views.py

class CreatePostView(LoginRequiredMixin, View):
    def get(self, request, *args, **kwargs):
        form = PostForm(request.POST or None)

        return render(request, 'app/post_form.html', {
            'form': form
        })

    def post(self, request, *args, **kwargs):
        form = PostForm(request.POST or None)

        if form.is_valid():
            post_data = Post()
            post_data.author = request.user
            post_data.title = form.cleaned_data['title']
            post_data.content = form.cleaned_data['content']
            if request.FILES:
                post_data.image = request.FILES.get('image') # 追加
            post_data.save()
            return redirect('post_detail', post_data.id)

        return render(request, 'app/post_form.html', {
            'form': form
        })


class PostEditView(LoginRequiredMixin, View):
    def get(self, request, *args, **kwargs):
        post_data = Post.objects.get(id=self.kwargs['pk'])
        form = PostForm(
            request.POST or None,
            initial={
                'title': post_data.title,
                'content': post_data.content,
                'image': post_data.image, # 追加
            }
        )

        return render(request, 'app/post_form.html', {
            'form': form
        })

    def post(self, request, *args, **kwargs):
        form = PostForm(request.POST or None)

        if form.is_valid():
            post_data = Post.objects.get(id=self.kwargs['pk'])
            post_data.title = form.cleaned_data['title']
            post_data.content = form.cleaned_data['content']
            if request.FILES:
                post_data.image = request.FILES.get('image') # 追加
            post_data.save()
            return redirect('post_detail', self.kwargs['pk'])

        return render(request, 'app/post_form.html', {
            'form': form
        })

コード解説

フォームから画像を取得する方法は、request.FILESを使用します。

if request.FILES:
    post_data.image = request.FILES.get('image') # 追加

画像の初期データは、他のデータと同じように指定します。

initial={
    'title': post_data.title,
    'content': post_data.content,
    'image': post_data.image,
}

テンプレート

index

トップページに画像を表示させます。

app/templates/app/index.html

<div class="row my-4">
  <div class="col-md-8">
    {% for post in post_data %}
    <div class="card mb-4 index">
      {% if post.image %}
      <img class="card-img-top index-img" src="{{ post.image.url }}" alt="" />
      {% endif %}
      <div class="card-body">
        <h2 class="card-title">{{ post.title }}</h2>
        <p class="card-text">{{ post.content|truncatechars:100 }}</p>
        <div class="btn btn-warning">詳細</div>
      </div>
      <div class="card-footer text-muted">
        {{ post.created|date }} by {{ post.author }}
      </div>
      <a class="stretched-link" href="{% url 'post_detail' post.id %}"></a>
    </div>
    {% endfor %}
  </div>
</div>

コード解説

画像を指定する場合は、post.image.urlと url まで記載します。

{% if post.image %}
<img class="card-img-top index-img" src="{{ post.image.url }}" alt="" />
{% endif %}

post_form

投稿画面に画像アップロードを追加します。

app/templates/app/post_form.html

<form method="post" enctype="multipart/form-data">
  {% csrf_token %}
  <div class="formpost">
    {% render_field form.title class="form-control" placeholder="タイトルを入力"
    %}
  </div>
  <div class="formpost">{{ form.image }}</div>
  <div class="formpost">
    {% render_field form.content class="form-control" placeholder="本文を入力"
    %}
  </div>
  <button class="btn btn-warning" type="submit">投稿する</button>
</form>

コード解説

画像をフォームからアップロードする場合は、必ずフォームにenctype="multipart/form-data"を指定する必要があります。

これがないと、画像をフォームからアップロードできません。

エラーも出ないので、よく覚えておいて下さい。

<form method="post" enctype="multipart/form-data"></form>

post_detail

詳細画面に画像を表示させます。

app/templates/app/post_detail.html

<h2 class="my-4">{{ post_data.title }}</h2>
<hr />
<p>{{ post_data.created }} by {{ post_data.author }}</p>
<hr />
{% if post_data.image %}
<img class="rounded detail-img" src="{{ post_data.image.url }}" alt="" />
{% endif %}
<hr />
<p>{{ post_data.content|linebreaksbr }}</p>
{% if user.is_authenticated %}
<hr />
<div class="d-flex mb-5">
  <a class="btn btn-warning mr-2" href="{% url 'post_edit' post_data.id %}"
    >編集する</a
  >
  <a class="btn btn-danger" href="{% url 'post_delete' post_data.id %}"
    >削除する</a
  >
</div>

CSS

CSS で画像を装飾します。

app/static/css/style.css

/* index */
.index-img {
  object-fit: cover;
  height: 240px;
}

/* detail */
.detail-img {
  object-fit: cover;
  width: 100%;
  height: 400px;
}

確認

新規投稿や編集で画像がアップロードできることを確認します。

完成