다형성 관계?? 파일을 다룰 떄 GenericForeignKey 사용 해 보자.

런던행·2021년 3월 28일
0

Django 업그레이드

목록 보기
13/17

흔히 백엔드에서 모델의 관계는 크게 3가지 알고 있습니다.

  • 일대일
  • 일대다
  • 다대다
    다형성(이 포스트글에서 언급)

이 3가지 관계는 관계형 DBMS의 SQL로 쉽게 표현할 수 있습니다.

다형성 관계는 모델이 하나의 모델과 관련되지 않고 여러 개의 모델과 관련될 수 있는 관계로 관계형 DBMS 의 SQL 로는 표현할 수 없는 관계입니다.

다형성 관계를 프레임워크마다 지원하지 않는 경우가 있습니다. 확인해보니 Django에서는 GenericForeignKey로 다형성 관계를 구현 할 수 있습니다. Laravel에서는 기본으로 제공하고 있습니다.

다형성 관계를 모르는 상황에서 아래와 같이 picture, product 두 개의 모델이 있을 경우 각 모델 필드에 이미지 필드를 두어 각각 모델에서 이미지 파일을 다루고자 할 수 있습니다.

class Picture(models.Model):
    user = models.ForeignKey(User, verbose_name='user', related_name='main_profile_images', on_delete=models.DO_NOTHING)
    photo = models.ImageField(upload_to=date_upload_to)
    created_at = models.DateTimeField(auto_now_add=True)


class Product(models.Model):
    user = models.ForeignKey(User, verbose_name='user', related_name='profile_images', on_delete=models.DO_NOTHING)
    photo = models.ImageField(upload_to=date_upload_to)
    created_at = models.DateTimeField(auto_now_add=True)

다형성 관계를 활용하면 각 모델에 이미지 필드를 두고 관리하지 않고 이미지만 관리하는 별도의 모델을 만들어서 관리할 수 있습니다.
Laravel에서 유명한 MediaLibrary가 다형성 관계로 파일들을 관리하고 있습니다.
(https://spatie.be/docs/laravel-medialibrary/v9/introduction)

class FlexFile(BaseModel):
    # name = models.SlugField()
    name = models.CharField(max_length=64)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveBigIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    photo = models.ImageField(upload_to=date_upload_to)

class Picture(models.Model):
    user = models.ForeignKey(User, verbose_name='user', related_name='main_profile_images', on_delete=models.DO_NOTHING)
    photo = GenericRelation(FlexFile)
    created_at = models.DateTimeField(auto_now_add=True)


class Product(models.Model):
    user = models.ForeignKey(User, verbose_name='user', related_name='profile_images', on_delete=models.DO_NOTHING)
    photo = GenericRelation(FlexFile)
    created_at = models.DateTimeField(auto_now_add=True)

class User(models.Model):
    photo = GenericRelation(FlexFile)
    created_at = models.DateTimeField(auto_now_add=True)

Picutre, Product, User 모델 필드에 GenericRelation 타입이 선언되지만 물리적으로 차지 하지 않습니다. 각각 모델에서 이미지 필드로 관리했던 것을 FlexFile 하나의 모델로 관리하게 됩니다.

아래와 같이 저장할 수 있습니다.

    def update(self, request, *args, **kwargs):

        user_id = self.kwargs['pk']

        try:
            user: User = self.get_queryset().get(id__exact=user_id)
            serializer = UserUpdateSerializer(user, data=request.data)
            serializer.is_valid(raise_exception=True)
            serializer.save()

            picture = request.FILES.get('picture', False)
            # for img in request.FILES.getlist('files'):   # 파일이 여러개일 경우
            #    upload_img = UploadImage.objects.create(image=img, .....)
            if picture:
                FlexFile.objects.create(content_object=user, photo=picture, name="profile")
                

아래와 같이 select 할수 있습니다.

class AccountViewSet(ModelViewSet):
    serializer_class = UserSerializer
    queryset = User.objects.all()
    pagination_class = StandardResultsSetPagination

    def list(self, request, *args, **kwargs):
        queryset = self.get_queryset().prefetch_related(
            Prefetch('photo', queryset=FlexFile.objects.filter(name='profile').all()))
            ....

레퍼런스

https://django-orm-cookbook-ko.readthedocs.io/en/latest/generic_models.html

profile
unit test, tdd, bdd, laravel, django, android native, vuejs, react, embedded linux, typescript

0개의 댓글