흔히 백엔드에서 모델의 관계는 크게 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