Django에서 Social Account 연동 시 IntegrityError 해결 방법
문제
Django 프로젝트에서 여러 소셜 계정을 연동할 때, 동일한 social_id를 가진 계정이 다른 provider로 등록하려고 하면 IntegrityError가 발생
문제 발생 배경
CustomSocialAccount 모델의 기존 설정에서 uid 필드가 unique=True로 설정되어 있었음
이로 인해 social_id가 동일한 계정을 다른 소셜 제공자(provider)로 등록하려고 할 때도 중복으로 간주하여 IntegrityError가 발생함
예를 들어 kakao와 discord에서 동일한 social_id로 가입하려는 경우 문제가 발생함
이러한 설정은 여러 소셜 계정에 동일한 ID를 사용하는 일반적인 사용자 패턴을 처리하지 못하므로 수정이 필요함
기존 모델 및 뷰 코드
CustomSocialAccount 모델
class CustomSocialAccount(models.Model):
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="custom_social_accounts"
)
provider = models.CharField(max_length=50)
uid = models.CharField(max_length=255, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.provider} - {self.user.username}"
uid 필드가 unique=True로 설정되어 있어 social_id 값의 중복 여부만 검사함
provider와의 조합에 따라 중복을 허용하도록 설정하지 못함
LinkSocialAccountView 뷰
class LinkSocialAccountView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request):
provider = request.data.get('provider')
social_id = request.data.get('social_id')
user = request.user
if CustomSocialAccount.objects.filter(user=user, provider=provider).exists():
return Response({"message": "이미 연결된 소셜 계정입니다."}, status=400)
CustomSocialAccount.objects.create(user=user, provider=provider, uid=social_id)
if provider not in user.connected_social_providers:
user.connected_social_providers.append(provider)
user.is_social_connected = True
user.save()
return Response({"message": f"{provider} 계정이 성공적으로 연결되었습니다."})
중복 검사가 user와 provider만을 기준으로 이루어짐
동일한 social_id를 다른 provider로 등록하려고 하면 IntegrityError가 발생함
수정된 모델 및 뷰 코드
CustomSocialAccount 모델 수정
class CustomSocialAccount(models.Model):
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="custom_social_accounts"
)
provider = models.CharField(max_length=50)
uid = models.CharField(max_length=255, unique=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ('provider', 'uid')
def __str__(self):
return f"{self.provider} - {self.user.username}"
uid 필드에서 unique=True를 제거하고 대신 provider와 uid의 조합을 unique_together로 설정함
이를 통해 동일한 social_id를 가진 계정을 다른 소셜 제공자에서 사용할 수 있도록 함
LinkSocialAccountView 뷰 수정
class LinkSocialAccountView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request):
provider = request.data.get('provider')
social_id = request.data.get('social_id')
user = request.user
if CustomSocialAccount.objects.filter(provider=provider, uid=social_id).exists():
return Response({
"status": "error",
"message": "이미 연결된 소셜 계정입니다."
}, status=400)
CustomSocialAccount.objects.create(user=user, provider=provider, uid=social_id)
if provider not in user.connected_social_providers:
user.connected_social_providers.append(provider)
user.is_social_connected = True
user.save()
return Response({"message": f"{provider} 계정이 성공적으로 연결되었습니다."})
중복 검사를 provider와 uid 조합으로 변경하여 같은 social_id라도 다른 provider에서는 새로운 계정으로 등록 가능하게 수정
수정 후 결과
동일한 social_id를 사용하는 계정이라도 provider가 다르면 중복 없이 정상적으로 계정을 연결 가능
추가 설명
unique_together를 사용하면 데이터베이스 수준에서 provider와 uid의 조합이 고유하도록 보장
이러한 방식은 소셜 계정 연동뿐 아니라 여러 필드 조합으로 고유성을 보장해야 하는 다양한 경우에 활용 가능