2021-10-24

2021-02-05

技術ノート

eyecatch
コンソール画面を動画にしてブログに貼りたくなったので、対応しました。

はじめに

言いたいことは例によって見出しの通りです。例えば以下の記事みたいな感じで video タグでの動画再生を導入してみました。

動画フィールドを実装するに当たって、すでに実装済みの画像フィールドと今回実装する動画フィールドでディレクトリ構成や URL 構成が違うのがイヤだったので、メディアファイルの持ち方を全体的に見直しました。もともとdjango-cloudinary-storageを便利に使っていたのですが、リソースが動画となるといろいろ考えるべき点が出てきたため、すべてのImageFieldCloudinaryFieldに変更することにしました。今回この作業を行うに当たって、Django 自体のアップデートも行いました。


- Django==2.2.8
+ Django==3.1.6
  django-bootstrap4==2.2.0
  django-cleanup==5.0.0
- django-cloudinary-storage==0.2.3
+ django-cloudinary-storage[video]==0.3.0
  django-debug-toolbar==2.2
  django-environ==0.4.5
  django-summernote==0.8.11.6
  djangorestframework==3.11.1
  google-api-python-client==1.10.0
  Janome==0.3.10
  oauth2client==4.1.3
  Pillow==7.2.0
  psycopg2==2.8.5
  psycopg2-binary==2.8.5
  pyOpenSSL==19.1.0
  uWSGI==2.0.19.1

やったこと

モデルの変更点を書いていきます。変更前のモデル構成をまとめた別記事は以下となります。

まずはインポートから。


from cloudinary.models import CloudinaryField

記事モデルにはアイキャッチ画像があるのでこれを変更します。ディレクトリに/%Y/%m/%d/のような日付情報は入れられないようです。さほど困ることではないので、これまでと違うディレクトリ構成にしました。


  """ 記事 | メイン """
  class Post(Base):
  ・・・
      title = models.CharField(verbose_name='タイトル', max_length=128)
      subtitle = models.CharField(verbose_name='サブタイトル', max_length=128, null=True, blank=True)
      category = models.ForeignKey(Category, verbose_name='カテゴリー', null=True, blank=True, on_delete=models.SET_NULL)
      tag = models.ManyToManyField(Tag, verbose_name='タグ', blank=True)
      related_posts = models.ManyToManyField('self', verbose_name='関連記事', blank=True)
-     eyecatch = models.ImageField(verbose_name='アイキャッチ画像', upload_to='image/post/eyecatch/%Y/%m/%d/', default='image/post/eyecatch/default/default.png',null=True, blank=True)
+     eyecatch = CloudinaryField(verbose_name='アイキャッチ画像', null=True, blank=True, overwrite=True, resource_type="auto", folder="media/image/eyecatch", tags="EyeCatch")
      description = models.TextField(verbose_name='説明', blank=True, null=True)
      text = models.TextField(verbose_name='本文', blank=True, null=True)

  ・・・

記事の本文で表示する画像は個別のモデルImageとして実装しており、管理サイトのPost詳細画面でインライン表示しています。これにも変更を加えます。


  """ 画像 | 記事の本文で利用 """
  class Image(Base):
      class Meta:
          db_table = 'image'
          verbose_name = '画像'
          verbose_name_plural = '画像'
          ordering = ['index']

      index = models.IntegerField(verbose_name='並び順', null=True, blank=True)
      post = models.ForeignKey(Post, verbose_name='記事', on_delete=models.PROTECT)
      title = models.CharField('タイトル', max_length=255, blank=True, help_text='画像の alt 属性として利用されます')
-     image = models.ImageField(verbose_name='画像', upload_to='image/post/text/%Y/%m/%d/', null=True, blank=True, help_text='保存後、本文挿入用 HTML を生成します')
+     image = CloudinaryField(verbose_name='画像', null=True, blank=True, help_text='保存後、本文挿入用 HTML を生成します', overwrite=True, resource_type="auto", folder="media/image/post/", tags="Post")

      def __str__(self):
          return < 省略 >

次に管理サイトのPost詳細画面でインライン表示するためのVideoモデルを新規で作成します。


+ """ 動画 | 記事の本文で利用 """
+ class Video(Base):
+    class Meta:
+        db_table = 'video'
+        verbose_name = '動画'
+        verbose_name_plural = '動画'
+        ordering = ['index']
+
+    index = models.IntegerField(verbose_name='並び順', null=True, blank=True)
+    post = models.ForeignKey(Post, verbose_name='記事', on_delete=models.PROTECT)
+    title = models.CharField('タイトル', max_length=255, blank=True, help_text='動画の alt 属性として利用されます')
+    video = CloudinaryField(verbose_name='動画', null=True, blank=True, help_text='保存後、本文挿入用 HTML を生成します', overwrite=True, resource_type="auto", folder="media/video/", tags="Video")
+
+    def __str__(self):
+        return < 省略 >

ImageFieldからCloudinaryFieldに差し替えた個所はほかにもありますが割愛します。続いてadmin.pyです。


+ class VideoInline(admin.StackedInline):
+     model = Video
+     ordering = ('index',)
+     extra = 1
+     readonly_fields = ('id', 'created_at', 'updated_at')

  ・・・

  class PostAdmin(SummernoteModelAdmin):
      model = Post
-     inlines = [ImageInline, LinkInline]
+     inlines = [ImageInline, VideoInline, LinkInline]

  ・・・

投稿画面にこんな感じのフォームが挿入されます。

管理画面

あとは、Xbox Gaming Bar で動画を撮影して、Windows フォトでトリミングすればいいでしょう。Windows 標準で十分でした。

おわりに

書いてみると短いのですが、ImageFieldのまま実装しようとして失敗したり、FileFieldで実装しようとして失敗したり、django-cloudinary-storageの仕様を確認したりでけっこう試行錯誤しました。その過程で不要なマイグレーションファイルが溜まってしまったので、一度環境をクリーンアップして DB データを再ロードしました。こういう作業するたびにコンテナで運用していてよかったなと思います。画像の数がまだ少ないので、再アップロードと本文内の URL の差し替えは手でやりました・・