Cloudinaryを使ったDjangoアプリで画像をリサイズしてみる - kei_log

kei_log

モダンな自社開発企業を目指すための学習ログ

Cloudinaryを使ったDjangoアプリで画像をリサイズしてみる

はじめに

現在、Djangoを使ったTwitterクローンを作成しています。画像ストレージにCloudinaryを使用しているのですが、アップロード画像のリサイズ方法に詰まったので、やり方をまとめておこうと思います。

前提条件

以下の環境を想定しています。

  • Djangoプロジェクトに Cloudinary が設定済み
  • django-cloudinary-storage を使用して画像アップロードを管理

Cloudinaryの設定やTweetモデルの定義は以下の通りです。

  • settings.py
MEDIA_URL = "/media/"
CLOUDINARY_STORAGE = {
    "CLOUD_NAME": env("CLOUDINARY_NAME"),
    "API_KEY": env("CLOUDINARY_API_KEY"),
    "API_SECRET": env("CLOUDINARY_API_SECRET"),
}
  • models.py
class Tweet(modes.Model):
    """ツイート情報の格納用モデル"""

    class Meta:
        db_table = "tweet"

    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    content = models.TextField("ツイート内容", null=False, blank=False)
    image = models.ImageField("ツイート画像", upload_to="tweets/", blank=True)

上記設定で画像アップロードすると、Cloudinary上の/media/tweetsフォルダに格納されていきます。(下記イメージ) Cloudinaryのフォルダ階層の写真

Cloudinaryの配信URLについて

デフォルトの形式

公式によると、Cloudinaryのデフォルトの配信URLの形式は以下のようになります。

https://res.cloudinary.com/<cloud_name>/<asset_type>/<delivery_type>/<transformations>/<version>/<public_id>.<extension>

それぞれの要素について簡単にまとめます。

  • <cloud_name>:Cloudinaryのアカウント名
  • <asset_type>:配信するアセットタイプ(image,video,rawのいずれか)
  • <delivery_type>:配信タイプ(デフォルトはupload
  • <transformations>:変換パラメータ、カンマ区切りで指定する(※何も指定しないとオリジナルのファイルが配信される)
  • <public_id>:アセットの一意な識別子(フォルダ構造が入る)

ここで注目するべきは<transformations>の部分です。ここにトランスフォーメーション情報(リサイズやクロップ設定など)を追加することで動的に画像を変換できます。

リサイズした画像の例

例えば、<transformations>の部分にc_fill,w_150,h_150を付与することで、150×150pxにリサイズされた画像を配信できます。

https://res.cloudinary.com/<cloud_name>/image/upload/c_fill,w_150,h_150/v1/media/tweets/icecream_kckry7

他にも画像変換で用いるパラメータがあるため、気になる方は下記を参考にしてみてください。 dev.classmethod.jp

実際にリサイズ済みの動的なURLを作成する

Cloudinaryの配信URLの形式がわかったので、デフォルトのURLからリサイズ後のURLになるように変換する処理を作成していきます。

① 画像リサイズ用の関数をつくる

CloudinaryのリサイズURLを生成するget_resized_image_urlという関数を作成します。
引数にはトランスフォーメーション情報となる4つのパラメータを受け取れるようにしました。

ここで配信URLの形式をもう一度確認してみましょう。<transformations><asset_type>/<delicery_type>の後ろに記載する必要があることがわかります。

https://res.cloudinary.com/<cloud_name>/<asset_type>/<delivery_type>/<transformations>/<version>/<public_id>.<extension>

そのため、<asset_type>/<delicery_type>に該当する/image/upload/を起点にしてURLを一度分割してから、URLを再整形するやりかたを取っています。

def get_resized_image_url(
    image_url, width=None, height=None, crop="fill", gravity="auto"
):
    """
    Cloudinaryの画像URLをリサイズ用URLに変換する処理

    Args:
        image_url (str): 元の画像URL
        width (int): リサイズ後の幅(オプショナル)
        height (int): リサイズ後の高さ(オプショナル)
        crop (str): クロップ方法(デフォルトは"fill")
        gravity (str): クロップ位置(デフォルトは"auto")

    Returns:
        str: リサイズ後の画像URL
    """
    if not image_url:
        return None

    # URLを分割してリサイズ用のトランスフォーメーションを挿入
    parts = image_url.split("/image/upload/")
    if len(parts) != 2:
        # URLの形式が期待と異なる場合、元のURLを返す
        return image_url

    # 動的なリサイズ設定(引数のパラメータを含める)
    transformation_list = [f"c_{crop}", f"g_{gravity}"]
    if width is not None:
        transformation_list.append(f"w_{width}")
    if height is not None:
        transformation_list.append(f"h_{height}")

    # リサイズ設定用の文字列作成
    transformation_str = ",".join(transformation_list)

    # リサイズ後のURL整形
    resized_url = f"{parts[0]}/image/upload/{transformation_str}/{parts[1]}"
    return resized_url

② ビューからリサイズ済みのURLをテンプレートに渡す

リサイズ済みのURLを作成する関数ができたので、各ツイートの持つimageに対してURLの変換処理をかけていきます。私の場合は、150×150ピクセルの画像を表示するようにしました。

  • tweets/views.py
class TimelineView(ListView):
    model = Tweet
    template_name = "tweets/index.html"
    context_object_name = "tweet_list"

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)
        # 各ツイートに対してリサイズ済みの画像URLを設定
        for tweet in context["tweet_list"]:
            if tweet.image:
                tweet.resized_image_url = get_resized_image_url(
                    tweet.image.url, 150, 150
                )
        return context

  • templates/tweets/index.html
{% for tweet in tweet_list  %}
  <div>{{ tweet.user.username }}</div>
  <div>{{ tweet.content }}</div>
  <img src="{{ tweet.resized_image_url }}" alt="投稿画像}">
{% endfor %>

実際のアプリでの見た目↓

おわりに

Cloudinaryを使えば、画像を必要な形式、スタイル、寸法に変換できることが分かりました。画像を最適化することで、ファイルサイズを最小限に抑えながら配信できるので、今後使う機会があれば積極的に活用していこうと思います。 ここまで読んでいただきありがとうございます!

参考記事

https://cloudinary.com/documentation/image_transformations cloudinary.com