사용 목적
- 서비스의 회원 정보 저장
- 서비스를 이용하기 위한 로그인 기능 제공
- 다른 모델과 각각의 관계를 맺어 서비스 연결
ERD 설계
로그인 관련 필드
- email : 사용자를 구분, 및 로그인을 위한 필드이므로 unique 설정
- password : 서비스에 가입한 사용자가 로그인을 하기위한 비밀 번호
- login_type : 복잡한 절차를 생략하고 사용자 편의성을 위한
손 쉬운 가입을 위한 소셜 로그인 구현,
kakao,google,naver 소셜 계정이 있으며, 일반 계정으로 구분합니다.
소셜 로그인 유저는 이메일,비밀번호 필드를 수정할 수 없습니다.
- login_attempts_count : 보완성을 강화하기 위하여 로그인 실패 횟수를 기록하여 일정 횟수 이상의 로그인 실패가 기록될 경우
로그인을 할 수 없습니다.
- last_login : AbstractBaseUser를 상속받아 사용할 수 있게된 필드입니다. 사용자의 마지막 로그인 기록이 언제인지 확인할 수 있습니다.
프로필 관련 필드
- nickname : 서비스내에서 타 유저를 확인하고, 자신을 소개할 별명을 사용하기위한 필드
- profile_image : 서비스에서 타 유저를 확인하고, 자신을 소개할 이미지 필드
- introduction : 커뮤니티 사이트 구축 확장성을 고려하여
사용자의 소개 메시지를 다른 사용자에게 보여주기 위한 필드 입니다.
개인 정보 관련 필드
- customs_code : 통관 번호 필드, 추후 확장성을 고려하여 실 판매 행위가 이루어진다면 해외 직구 사이트의 특성상 필요한 데이터입니다.
권한 필드
- is_admin : True or False, 둘 중 하나의 값을 갖는 Boolean 필드입니다. 위 필드를 통하여 사용자가 관리자 계정인지, 아닌지 판단할 수 있습니다.
- is_active : True or False, 둘 중 하나의 값을 갖는 Boolean 필드입니다. 위 필드를 통하여 사용자가 활성화된 계정인지, 비 활성화된 계정인지 확인할 수 있습니다.
- is_seller : True or False, 둘 중 하나의 값을 갖는 Boolean 필드입니다. 위 필드를 통하여 사용자가 판매자 활동 권한을 갖는 계정인지 판단할 수 있습니다.
관계형 필드
- product_wish_list : 상품 찜 기능 구현을 위한 필드
Product Model과 Many - To - Many
- review_like : 리뷰 좋아요 기능 구현을 위한 필드
Review Model과 Many - To - Many
- Follower : 판매자 팔로우 기능 구현을 위한 필드
User Model과 Many - To - Many
상속
- AbstractBaseUser을 상속 받아
last_login, is_authenticated, set_password 등등의
다양한 편리한 기능을 제공받아 사용하엿다.
공통 상속 Model
- 생성일과 수정일 필드는 모든 모델에서 사용하므로 공통 모델을 지정하였다.
User Model의 경우, 다중 상속을 통해 추가 필드를 제공 받았다.
- created_at : object의 생성 시간 저장
- updated_at : object의 수정 시간 저장
로그인 필드 설정
- USERNAME_FIELD를 email로 지정하여 로그인에 필요한 필드 지정
- REQUIRED_FIELD를 추가로 지정하여 오브젝트 생성시 필수로 받아야할 필드를 지정 했다.
USERNAME_FIELD = "email" REQUIRED_FIELDS = ["nickname", "password"]
모델 설계
class User(AbstractBaseUser, CommonModel):
"""
Base User model 정의
"""
LOGIN_TYPES = [
("normal", "일반"),
("kakao", "카카오"),
("google", "구글"),
("naver", "네이버"),
]
# 로그인 관련 필드
email = models.EmailField("이메일 주소", max_length=100, unique=True)
password = models.CharField("비밀번호", max_length=128)
login_type = models.CharField("로그인유형", max_length=20, choices=LOGIN_TYPES, default="normal")
login_attempts_count = models.PositiveIntegerField("로그인 시도 횟수", default=0)
# 프로필 관련 필드
nickname = models.CharField("사용자 이름", max_length=20)
profile_image = models.ImageField("프로필 이미지", upload_to=img_upload_to, blank=True, null=True)
introduction = models.CharField("소개", max_length=50, blank=True, null=True, default="아직 소개글이 없습니다.")
# 개인 정보 관련 필드
customs_code = models.CharField("통관번호", max_length=100, blank=True, null=True)
# 관계형 관련 필드
product_wish_list = models.ManyToManyField("products.Product", symmetrical=False, related_name="wish_lists", blank=True)
review_like = models.ManyToManyField("products.Review", symmetrical=False, related_name="review_liking_people", blank=True)
follower = models.ManyToManyField('self', symmetrical=False, related_name="followings", blank=True)
# 권한 관련 필드
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
is_seller = models.BooleanField(default=False) # 판매자 신청 후 관리자 승인하 에 판매 권한 획득
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["nickname", "password"]
objects = UserManager()
def __str__(self):
"""
오브젝트의 이름을 email로 지정
"""
return self.email
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
@property
def is_staff(self):
return self.is_admin
BaseUserManager 이해하기
- Django에서 제공하는 사용자 관리 클래스에 상속받는다.
class UserManager(BaseUserManager): """ 커스텀 유저 매니저 """
오버라이딩 메서드
- create_user
일반 사용자를 생성하고 저장하는 메서드
필수 필드를 인자값으로 지정한다.
- create_superuser
관리자 사용자를 생성하고 저장하는 메서드
관리자 모델 생성시, 비밀번호, 닉네임 검증 절차를 커스텀 하였으며
UserModel에서는 is_active를 기본값을 False로 설정하여
관리자 계정 생성시 is_admin을 True로 설정할 필요가 있다.
- 관리자 계정 생성 메서드
def create_superuser(self, email, nickname, password=None): """관리자 계정 생성 커스텀""" user = self.create_user( email, password=password, nickname=nickname, ) user.is_admin = True user.is_active = True user.save(using=self._db) return user
터미널 명령어로 관리자 계정 생성시
is_admin과 is_active를 True로 설정하도록 로직을 구현했다.
- 오브젝트 생성 메서드
def create_user(self, email, nickname, password=None): """관리자 계정 생성""" validated_result = ValidatedData.validated_user_data(email=email, nickname=nickname, password=password) if validated_result is not True: raise ValueError(validated_result[1]) user = self.model( email=self.normalize_email(email), nickname=nickname, ) user.set_password(password) user.save(using=self._db) return user
오브젝트 생성시 필요한 검증 절차를 커스텀 하였다.
여기서 사용된 ValidatedData 메서드는 직접 구현한 검증 모듈이므로
이메일과 비밀번호, 닉네임 데이터의 유효성이 올바른지 검증하는 용도로 사용 된다.
- ValidatedData.validated_user_data
커스텀 벨리데이션
직접 작성한 데이터 유효성 검증 모듈
ValidatedData클래스에서 추상 클래스로 정의된
validated_user_data를 호출하였다.@classmethod def validated_user_data(cls, **kwargs): if not cls.validated_email(kwargs.get('email')): return [False, '이메일 정보가 올바르지 않습니다.'] elif not cls.validated_nickname(kwargs.get('nickname')): return [False, '닉네임 정보가 올바르지 않습니다.'] elif not cls.validated_password(kwargs.get('password')): return [False, '비밀번호가 올바르지 않습니다.'] return True
- 데이터의 유효성을 검증하는 과정은 위에 제시한 링크를 확인하자
- 위 코드는 유효성에 대한 결과 처리를 하는 곳으로 작성하였다.
각각의 검증 데이터에 따라 유효성 검사가 실패할 경우
False와 특정한 메시지를 전송하게 구현하였고.validated_result = ValidatedData.validated_user_data(email=email, nickname=nickname, password=password) if validated_result is not True: raise ValueError(validated_result[1])
- validated_result 라는 변수에 반환값을 저장한다.
- 분기문을 통하여 True값이 아닐경우 ValuError를 반환하고
변수에 저장된 메시지를 출력한다.
전체 코드
class UserManager(BaseUserManager):
"""
커스텀 유저 매니저
"""
def create_user(self, email, nickname, password=None):
"""관리자 계정 생성"""
validated_result = ValidatedData.validated_user_data(email=email, nickname=nickname, password=password)
if validated_result is not True:
raise ValueError(validated_result[1])
user = self.model(
email=self.normalize_email(email),
nickname=nickname,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, nickname, password=None):
"""관리자 계정 생성 커스텀"""
user = self.create_user(
email,
password=password,
nickname=nickname,
)
user.is_admin = True
user.is_active = True
user.save(using=self._db)
return user