django heroku の環境で画像アップロードにcloudinaryを使う方法 

djangoでherokuデプロイをする際に、画像をアップロードできる機能を実装するため、
cloudinaryを使ってみました。

ですが、ググった記事の通りにしてもうまく動作せず、詰まったためメモを残したいと思います。

## cloudinaryをherokuで使う設定

herokuにログインし、resourceからcloudinaryを追加する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380952/6eb6b7a9-e989-347a-9196-f155ca69d6c7.png)

cloudinaryのダッシュボードにいって、
API keyと API Secretをherokuのsettings>config varに入れる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380952/ad57e021-570b-50f5-0493-a947ad364a96.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380952/9d48488e-87f0-7f8f-84f7-1ede91401199.png)

これでherokuの設定は完了です。

## cloudinaryをdjangoで使う設定
pipfileにcloudinaryを追加します。

```
pipenv install cloudinary
pipenv lock -r > requirements.txt
```

settings.pyでcloudinaryを使えるようにします。
本番環境では、先程heokuに設定した値を読み込んでくれるように設定します。

``` settings.py
import cloudinary

INSTALLED_APPS = [
    'django.contrib.admin',
    ...
    'django.contrib.staticfiles',
    'cloudinary',
...
try:
    from .local_settings import *
except ImportError:
    pass

if not DEBUG:
    SECRET_KEY = os.environ['SECRET_KEY']
    KEY = os.environ['KEY']
    ENDPOINT = os.environ['ENDPOINT']
    import django_heroku
    django_heroku.settings(locals())
    cloudinary.config(
        cloud_name='he93wwe4y',
        api_key=os.environ['CLOUDINARY_API_KEY'],
        api_secret=os.environ['CLOUDINARY_API_SECRET']
    )


```

ローカル環境では、直接値を読み込むようにします。
このlocal_settingsはgitにあげないように注意してください。

```config/local_settings.py
cloudinary.config(
    cloud_name='he93wwe4y',
    api_key='xxxxxxxxxxx',
    api_secret='xxxxxxxxxxx'
)
```

### djangoで画像をアップロードする

次にmodels.pyを修正します。
今まで`ImageField`などを使っていたのを、`CloudinaryField`にします。

正直ImageFieldのままで行きたいところですが、変えても問題は特に生じないのと、
Django Cloudinary Storageを使用するほうが結構大変だったため、モデルの内容を変えることにしました。
https://pypi.org/project/django-cloudinary-storage/

```
from cloudinary.models import CloudinaryField
from django.contrib.auth.models import User
from django.db import models


class UploadedImage(models.Model):
    user_im = CloudinaryField(
        'image', blank=True, null=True, folder="media/face_images")
    product_im = CloudinaryField(
        'image', blank=True, null=True, folder="media/face_images")
    image = CloudinaryField('image', blank=True, null=True,
                            folder="media/face_images")
    author = models.ForeignKey(
        User, on_delete=models.CASCADE, null=True, blank=True
    )
    created_at = models.DateTimeField(auto_now_add=True)
    token = models.CharField(max_length=256, null=True, blank=True)
```

`folder="media/face_images"`とはオプションになるので、必須ではありません。
保存する際にこのフォルダにアップロードされるという意味です。

他にも様々なオプションがあるので、使用してみてください。
https://cloudinary.com/documentation/image_upload_api_reference#upload

モデルをcloudinaryfieldにすれば、あとは他にやることは殆どありません。
参考までにforms.pyとviews.pyを載せておきますが、普通にdjnagoで書くのと変わりないです。

```forms.py
from django import forms


class ImageForm(forms.Form):
    product_im = forms.ImageField()
    user_im = forms.ImageField()
```

```views.py
def upload_image(request):
    if request.method == "POST":
        form = ImageForm(request.POST, request.FILES)
        if form.is_valid():
            user_im = request.FILES.get("user_im")
            product_im = request.FILES.get("product_im")
            ....
            uploaded_image = UploadedImage()
            uploaded_image.user_im = user_im
            uploaded_image.product_im = product_im
            uploaded_image.image = SimpleUploadedFile(name='temp.png', content=open(
                output, 'rb').read(), content_type='image/png')

            uploaded_image.save()
```

### 画像を削除したい時
画像を定期実行などで削除したい場合もあると思います。
そんなときに使うのが`cloudinary.uploader.destroy()`です。
引数に、パスを渡すと思って、削除されず悩んでいましたが、パスではなくpublic_idを渡します。

```job.py
import datetime
import os
import sys
from datetime import timedelta

import cloudinary
import django
from django.utils.timezone import make_aware

def delete_anonymous_uploaded_image():
    django.setup()
    from face.models import UploadedImage
    one_hour_ago = make_aware(datetime.datetime.now()) - timedelta(hours=1)
    images = UploadedImage.objects.filter(
        author=None, created_at__lte=one_hour_ago)
    for im in images:
        cloudinary.uploader.destroy(im.user_im.public_id)
        cloudinary.uploader.destroy(im.product_im.public_id)
        cloudinary.uploader.destroy(im.image.public_id)
        im.delete()


if __name__ == '__main__':
    delete_anonymous_uploaded_image()

```

お読みいただきありがとうございました。

### 参考サイト
とりあえずこの記事を読めば突破できます!
https://alphacoder.xyz/image-upload-with-django-and-cloudinary/
https://cloudinary.com/documentation/django_image_and_video_upload#django_forms_and_models
https://cloudinary.com/documentation/image_upload_api_reference#upload
https://dev.classmethod.jp/articles/getting-started-with-cloudinary-python-sdk/
https://github.com/cloudinary/cloudinary-django-sample
https://stackoverflow.com/questions/70514627/how-to-save-cloudinaryfield-in-a-folder-named-by-the-user/70519767