SSブログ

1.15 Django REST Framework(DRF) 画像アップロード [Django]

Django Rest Frameworkで画像アップロードの実現サンプルとなります。

画像アップロードは、カスタムのUserModelに、imageという画像用列を追加します。

動作環境 pip list コマンド結果は下記
asgiref                 3.5.2
Django                  3.2.15
django-cors-headers     3.10.1
django-filter           22.1
djangorestframework     3.13.1
djangorestframework-jwt 1.11.0
importlib-metadata      4.12.0
Markdown                3.4.1
mysqlclient             2.1.1
Pillow                  9.2.0
pip                     22.2.2
PyJWT                   1.7.1
pytz                    2022.2.1
setuptools              57.5.0
sqlparse                0.4.2
uWSGI                   2.0.20
wheel                   0.37.1
zipp                    3.8.1


DRFサイトに記載があるfileuploadparser、MultiPartParserを使用します。
クライアント側から送信時はmedia_typeを「multipart/form-data」にしてくれとのこと
https://www.django-rest-framework.org/api-guide/parsers/#fileuploadparser

setting.py
REST_FRAMEWORK = {
    #
    # Default Parser Class Setting
    # https://www.django-rest-framework.org/api-guide/parsers/#fileuploadparser
    'DEFAULT_PARSER_CLASSES': [
       'rest_framework.parsers.JSONParser',
       'rest_framework.parsers.MultiPartParser',
    ],
}

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



カスタムのUser Model(画像保存の項目はimage)
model.py
class User(AbstractBaseUser, PermissionsMixin):
    uuid = models.UUIDField(default=uuid_lib.uuid4,
                            primary_key=True, editable=False)
    username_validator = UnicodeUsernameValidator()

    username = models.CharField(_("username"), max_length=50, validators=[username_validator], unique=True)
    first_name = models.CharField(_("first_name"), max_length=50, )
    last_name = models.CharField(_("last_name"), max_length=50, )
    nick_name = models.CharField(_("nick_name"), max_length=50, )
    email = models.EmailField(_("email_address"), unique=True)
    is_staff = models.BooleanField(_("staff status"), default=False)
    is_active = models.BooleanField(_("active"), default=True)
    authorization = models.IntegerField(_("authorization"), default=0 )
    date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
    image = models.ImageField(upload_to='images/', blank=True)

    objects = UserManager()
    USERNAME_FIELD = "username"
    EMAIL_FIELD = "email"
    REQUIRED_FIELDS = ['email',]

    class Meta:
        verbose_name = _("user")
        verbose_name_plural = _("users")

    def clean(self):
        super().clean()
        self.email = self.__class__.objects.normalize_email(self.email)

    def email_user(self, subject, message, from_email=None, **kwargs):
        send_mail(subject, message, from_email, [self.email], **kwargs)


とくだん説明はないです。
urls.py
from users.views import (
    UserViewSet, GroupViewSet,
)
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)

urlpatterns = format_suffix_patterns(urlpatterns)
urlpatterns += router.urls


クライアントからは、Formdataで、"image"というキーワードにイメージファイルを設定してる場合request.data.imageでアクセス出来る。
InMemoryFileUploadHandlerというオブジェクトになるらしいので、
どんなメソッドがあるかの確認はソースをみると理解出来ます。
Djaogo Document

保存するファイル名は、request.data['image'].nameに設定することで
親クラスのupdateを呼出すことで、Django側が良しなに処理してくれる。
(画像保存先のフォルダまで作成してくれるかはしらん)
ソース内のコメントアウトしているコードは、Djangoが保存してくれると知らずに、
自力で保存した処理を記念?に残している

view.py
from rest_framework.parsers import FileUploadParser

class UserViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    parser_class = (FileUploadParser, MultiPartParser)

    queryset = User.objects.all().order_by('username')
    serializer_class = UserSerializer
    # permission_classes = [permissions.IsAuthenticated]

    #
    # Filte機能設定
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['uuid', 'username','email', 'first_name', 'last_name','nick_name']

    def update(self, request, *args, **kwargs):
        logger.debug("Request Data")
        print(request.data)
        image_file = ""
        if 'image' in request.data:
            image_file = "images/" + str(uuid_lib.uuid4()) + ".png"
            request.data['image'].name = image_file
        super(UserViewSet, self).update(request, *args, **kwargs)
        '''
        if 'image' in request.data:
            logger.debug("Image File Write")
            # 保存してみる
            logger.debug(f'MEDIA_ROOT={settings.MEDIA_ROOT}')
            image_file = "images/" + str(uuid_lib.uuid4()) + ".png"
            image_fullpath = os.path.join(settings.MEDIA_ROOT, image_file)
            # reqeust.data['image']は、InMemoryFileUploadHandlerというオブジェクトらしい
            with open(os.path.join(settings.MEDIA_ROOT, image_file), 'wb') as f:
                for chunk_data in request.data['image'].chunks():
                    f.write(chunk_data)
            logger.debug(f'Image File Write Success file[{image_file}]')
        else:
            logger.debug("No Image File")
        '''
        return Response({'success': 'Imported successfully'})



これでクライアントから、PATCH or PUT メソッドで送信すればUserの更新が出来ました。
もちろん、他のDB項目であるusernameやfirst_nameも更新されました。


クライアントは、Next.jsでやったのですが、axiosを利用せず、
fetch関数で実行しましたが、沢山はまったけど、なかでもハマリポイントは下記だった。
Content-Typeを指定しないようにしよう

Next.jsの理解が進んだら、アップロードのクラアント側も掲載しようかな・・?









nice!(0)  コメント(0) 
共通テーマ:パソコン・インターネット

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

※ブログオーナーが承認したコメントのみ表示されます。
独自Layerを参照している Lambd..|- ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。