[DRF, Python, Django] user 모델의 프로필 이미지 변경 시 다른 모델의 프로필 이미지가 변경 안되는 문제

[문제 상황]

 

인스타그램, 페이스북 처럼 자주 사용하는 sns에서 프로필 이미지나 이름을 변경하면 내가 쓴 댓글이나 글에서의

나의 프로필 이미지 혹은 이름도 변경 되어야 한다.

내가 직면한 문제는 이미지 변경 시 다른 객체의 이름은 변경되었으나 프로필 이미지가 변경되지 않는 것이였다.

이름은  model 끼리 외래키(Foreign Key)로 연결 되어있어서 update 시 변경 되었지만 프로필 이미지인

image Field에는 아무런 조치가 되어있지 않았기에 방법이 필요했다.

 

[해결 방법]

 

고민한 결과 번거롭지만 가장 좋은 방법은  user가 작성하는 것들인  like model (좋아요) , comment model (댓글) 등에 userImage Filed를 만들고 user model 에서 이미지 변경 시 해당 필드를 리셋 해주는 것이다.

 

우선 각 모델을 살펴보면 

 

아래 모델은 프로필 모델이다.

사용자의 토큰과 패스워드를 가지고 있는 User 필드와 1: 1 관계를 가지고 있다.

사용자 생성 시 User 모델이 생성되고, 자동으로 Profile 모델이 생성 되도록 코딩하였다.

즉 Profile 모델은 User 모델의 확장판이라고 생각하면 된다.

 

class Profile(models.Model): #user의 username을 참조하므로 username_id필드를 가지게됨
    username = models.OneToOneField(User,on_delete=models.CASCADE,db_column='username',to_field='username')
    customName = models.CharField(max_length=50,db_column='customName',default="")
    userImage = models.ImageField(default='default.png',upload_to="%Y/%m/%d")
    userComment = models.TextField(null=True,default="")
    def __str__(self):
        return self.username.username

 

아래의 모델들은 Profile 모델을 Foreign Key로 가지고 있는 모델들이다.

 

#좋아요
class Like(models.Model):
    likeId = models.BigAutoField(primary_key=True,help_text="Like ID")
    posterId = models.ForeignKey(poster,on_delete=models.CASCADE,related_name='likePost',db_column='posterId',to_field='posterId')
    liker = models.ForeignKey(Profile,on_delete=models.CASCADE,related_name='likePost',db_column='liker',to_field='username_id')
    likerImage = models.ImageField(default='default.png',upload_to="%Y/%m/%d")
    uploadTime = models.DateTimeField(auto_now_add=True)
    
#스토리 시청자
class StoryViewer(models.Model):
    storyId = models.ForeignKey(Story,on_delete=models.CASCADE,db_column='story_id',
                                related_name='storyViewerPost',to_field='storyId')
    viewer = models.ForeignKey(Profile,on_delete=models.CASCADE,related_name='storyViewerPost',to_field='username_id')
    viewerImage = models.ImageField(default='default.png',upload_to="%Y/%m/%d")
    
#댓글
class Comment(models.Model):
    commentId = models.BigAutoField(primary_key=True,help_text="Comment ID")
    posterId = models.ForeignKey(poster,on_delete=models.CASCADE,related_name='commentPost',db_column='posterId',to_field='posterId')
    writer = models.ForeignKey(Profile,on_delete=models.CASCADE,related_name='commentPost',db_column='writer',to_field='username_id')
    writerImage = models.ImageField(default='default.png',upload_to="%Y/%m/%d")
    uploadTime = models.DateTimeField(auto_now_add=True)
    body = models.TextField()

 

세 모델 모두 주체인 User(Profile)와 Foreign Key로 연결되어 있으며, 

User의 userImage가 변경 시  likerImage, viewerImage, writerImage 들이 userImage와 같은 이미지로 변경 되어야 한다.

 

<step 1>

업데이트를 진행하는 view를 만들어 준다.

 

#프로필 수정 
# *******프로필 수정시 like , comment, storysviewer의 이미지 초기화 
class UpdateProfileView(APIView):

 

프로필 이미지 수정 시 3가지 모델의 이미지를 초기화해줄 view이다.

 

<step 2>

앱으로 부터 id 값이 넘어오면 해당 id 값에 맞는 프로필을 return 해주는 함수를 작성한다.

 

    def get_object(self,id):
        try:
            return Profile.objects.get(id=id)
        except Profile.DoesNotExist:
            return Response(id,status=status.HTTP_400_BAD_REQUEST)

 

위의 함수는 맞는 프로필이 존재하면 Profile을 return하고, 해당 프로필이 존재하지 않으면 BAD REQUEST를 전송해준다.

 

<step 3>

앞서 제작한 함수를 이용해서 프로필을 받아오고, 자신의 이미지를 받아온 이미지 데이터로 변경 해준다.

 

    def post(self,request):
        data = request.data
        profile = self.get_object(data['id']) #1.

        serializer = ProfileSeralizer(profile,data=data)
        if serializer.is_valid(): #2.
            serializer.save()
            profile = self.get_object(data['id'])
            setImage=serializer.data['userImage'] #3.

 

앱으로부터 id와 userImage를 받는다.

id 값은 해당 Profile을 찾는데 사용하고,  userImage는 새로 변경할 이미지의 Url이 저장되어있다.

#.1 앞서 제작한 함수에 id 값을 넣어서 프로필을 받아오고,

#.2 받아온 profile의 serializer에 접근해서 해당 데이터가 유효한지 확인,

#.3 데이터가 유효하면 기존 image를 새로운 image로 변환

 

<step 4> 

다른 모델의 image도 변경 해준다.

 

            #중첩 uri 제거 후 다른 필드 업데이트 
            setImage=setImage.replace('/media','')
            Like.objects.filter(liker=data['username']).update(likerImage=setImage)
            Comment.objects.filter(writer=data['username']).update(writerImage=setImage)
            StoryViewer.objects.filter(viewer=data['username']).update(viewerImage=setImage)

 

여기서 중요한 점은 중첩 url을 제거 해야한다.

서버에서 데이터를 /media에 저장하는데, 새로 받아온 이미지를 저장한 후에 다른 모델에 저장하는 방법을 사용하고

있으므로, 중첩 저장이 된다. 따라서 /media/media에 저장 되어지면서 url이 꼬이게 된다.

replace 함수를 이용해서 /mdeia를 제거 해주고 다른 모델을 update 해주어야 정상적인 url이 저장된다.

 

            return Response(serializer.data,status=status.HTTP_200_OK) #정상
            
        return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST) #유효 x

 

이제 성공적으로 데이터가 업데이트 되었다는 응답을 보내주어야 하는데

데이터가 유효하지 않은 경우에는 BAD REQUEST를 보내주면 된다.

 

 

[리뷰]

 

인스타그램의 클론 코딩으로 앱 개발을 진행하고 있었다.

실제 인스타그램 앱을 사용하다가 사용자 이미지를 연속으로 변경하면 제대로된 이미지가 적용되기까지 시간이 한참

걸리는 경우를 발견했고 댓글의 이미지 같은 경우는 제대로 변경이 되지 않은 경우도 있었다.

위와 같은 방법을 사용하면서 대량의 데이터를 업데이트 하면서 데이터 변경이 너무 오래걸리는

것인가하는 생각이 들었다.

문제를 해결하면서 많은 데이터들을 더 빠르게 변경하는 방법은 없는지 고민해보았다.