본문 바로가기
부트캠프TIL, WIL/AI웹개발(스파르타코딩클럽)

[2023.09.23토TIL] 쇼핑몰 웹개발 최종플젝 2일차

by 우지uz 2023. 9. 24.
김성우 (9월 23일 토)
    - 1차공부, 14:18-17:22(3h04m)
    - 운동, 17:30-21:00
    - 2차공부, 22:30-01:35 (3h05m)
        -총 공부 시간 : 6h 09m**

오늘은 User API, Product API, Profile API, Product Inquery API 관련해서 
Postman 을 통해서 테스트 마쳤고 

하면서 Serializers.py 에 잘못된 필드나 로직, 에러처리가 있어서 수정했습니다. 

사실 오늘 한 기능들에 대해서 

프론트엔드도 연결해서, 결과물을 보고 만족하고 싶었는데 

오늘 너무 띵가띵가 놀았더니 다 하지 못하고 자야하네요 ... 너무 아쉽습니다

 

API 명세서에도 아직, url 이나 Response 를 적지못해서 

내일 할게 더 많을 것 같습니다 ㅎㅎ 괜찮아요~ 천천히라도 꾸준히 하면 되죠 

 

다음은 오늘 중점적으로 작업했던 친구들입니다

 

유저모델
# 최초 작성일 :23년6월7일
# 업데이트 일자 : 23년 9월 23일
class User(AbstractUser):
    # 메타 클래스는, DB 정보들에 대한 정보를 입력하는 곳
    class Meta:
        db_table = "user" # DB 테이블 이름을 user 로 설정해줌

    email = models.EmailField(verbose_name='이메일', max_length=255, unique=True)
    # Email , account 는 unique 해야 한다.
    nickname = models.CharField("별명", null=False, blank=False, max_length=50)
    username = models.CharField("이름", null=True, blank=True, max_length=50)
    phoneNumberRegex = RegexValidator(regex = r'^01([0|1|6|7|8|9]?)-?([0-9]{3,4})-?([0-9]{4})$')
    phone = models.CharField("휴대폰번호", validators = [phoneNumberRegex], max_length = 11, unique = True, blank=True, null=True)
    # 핸드폰 번호 전용 필드가 있지만, CharField를 통해 RegexValidator를 사용하면 휴대폰번호 형식을 입력받을 수 있습니다.
    LOGIN_TYPE = [
        ("normal", "일반"),
        ("google", "구글"),
        ("kakao", "카카오"),
        ("naver", "네이버"),
    ]
    login_type = models.CharField(
        "로그인 타입", max_length=10, choices=LOGIN_TYPE, default="normal"
    )
    joined_at = models.DateField("계정 생성일", auto_now_add=True)
    is_active = models.BooleanField("활성화 여부", default=True)
    is_staff = models.BooleanField("스태프 여부", default=False)
    is_admin = models.BooleanField("관리자 여부", default=False)
    is_phonecertify = models.BooleanField("폰번호 인증 여부", default=False)

    objects = UserManager()

    USERNAME_FIELD = 'email' # 회원가입시, 계정이름으로 가입하기 때문에, Unique=True 로 해주어야 하는 필드
    REQUIRED_FIELDS = ['nickname',]


    def __str__(self):
        return str(self.email)

    def has_perm(self, perm, obj=None):
        "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always
        return True

    def has_module_perms(self, app_label):
        "Does the user have permissions to view the app `app_label`?"
        # Simplest possible answer: Yes, always
        return True
상품 모델
# 수정 날짜 : 23년 9월 22일 금요일
# ---------------- 상품 모델 시작 ----------------
class Product(models.Model):
    class Meta:
        db_table = "product"
    PRODUCT_TYPE = [
        ("상의", "상의"),
        ("하의", "하의"),
        ("신발", "신발"),
        ("etc", "기타등등"),
    ]
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="products")
    product_type = models.CharField(verbose_name="상품 종류", choices=PRODUCT_TYPE, null=False, max_length=30,blank=False)
    product_name = models.CharField(verbose_name="상품 이름", null=False, max_length=100, unique=True)
    price = models.PositiveIntegerField(verbose_name="상품 가격", null=False, blank=False)
    info = models.TextField(verbose_name="상품 정보")
    created_at = models.DateTimeField(auto_now_add=True, verbose_name="생성 시간")
    updated_at = models.DateTimeField(auto_now=True, verbose_name="수정 시간")
    likes = models.ManyToManyField(User, verbose_name="좋아하는 상품", blank=True, related_name="liked_products")
    manufacturer = models.CharField(verbose_name="제조사", max_length=30)
    product_img = models.ImageField(upload_to=rename_imagefile_to_uuid, verbose_name="상품 이미지", blank=True, null=True)

# ---------------- 좋아요 갯수 ------------------
    def count_likes(self):
        return self.likes.count()

    def __str__(self):
        return str(self.product_name)
# ---------------- 상품 모델 끝 ----------------
상품 옵션 모델
# ---------------- 상품 옵션 시작 ----------------
# 수정 날짜 : 23년 9월 22일 금요일
class ProductSize(models.Model):
    class Meta:
        db_table = "product_size"
    size_value = models.CharField("사이즈 값", max_length=10, unique=True)

    def __str__(self):
        return str(self.size_value)

class ProductColor(models.Model):
    class Meta:
        db_table = "product_color"
    color = models.CharField("색상", max_length=50, unique=True)

    def __str__(self):
        return str(self.color)

class ProductGenderType(models.Model):
    class Meta:
        db_table = "product_gender_type"
    GENDER_TYPE = [
        ("MEN", "MEN"),
        ("WOMEN", "WOMEN"),
        ("UNISEX", "UNISEX"),
    ]
    gender_type = models.CharField("성별구분", choices=GENDER_TYPE, max_length=50,default="UNISEX")

    def __str__(self):
        return str(self.gender_type)
class ProductCategory(models.Model):
    class Meta:
        db_table = "product_category"
    PRODUCT_CATEGORY = [
        ("TOP", "TOP"),
        ("BOTTOM", "BOTTOM"),
        ("SHOES", "SHOES"),
        ("ETC", "ETC"),
    ]
    product_category = models.CharField("상품 종류", choices=PRODUCT_CATEGORY, max_length=50, default="기타등등")

    def __str__(self):
        return str(self.product_category)

# 수정 날짜 : 23년 9월 22일 금요일
class ProductInventoryManagement(models.Model):
    class Meta:
        db_table = "product_inventory"
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    size = models.ForeignKey(ProductSize, on_delete=models.CASCADE)
    color = models.ForeignKey(ProductColor, on_delete=models.CASCADE)
    product_category = models.ForeignKey(ProductCategory, on_delete=models.CASCADE)
    possible_gender_type = models.ForeignKey(ProductGenderType, on_delete=models.CASCADE)
    stockquantity = models.PositiveIntegerField("재고수량", default= 0)

    def __str__(self):
        return str('{self.product}의 재고 현황')

    def decrease_stockquantity(self, stockquantity):
        if self.stockquantity >= stockquantity:
            self.stockquantity -= stockquantity
            self.save()
            return True
        return False
# ---------------- 상품 옵션 끝 ----------------
상품 문의 모델
# ---------------- 상품 문의 글 시작 ----------------
# 수정 날짜 : 23년 9월 23일 토요일
# 구매하든, 안하든 사용자에게 1개이상으로 마음대로 상품에 대한 문의를 할 수 있다.
# 수정 내용 : 문의는 Question 보단 Inquery가 맞다.
class ProductInquery(models.Model):
    class Meta:
        db_table = "product_inquery"

    INQUERY_TYPE = [
        ("상품", "상품"),
        ("배송", "배송"),
        ("환불/취소", "환불/취소"),
        ("교환/반품", "교환/반품"),
        ("기타등등", "기타등등"),
    ]
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="inquiries")
    product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="inquiries")
    inquery_type = models.CharField("상품 문의 유형", choices=INQUERY_TYPE, max_length=50, default="기타등등")
    inquery_title = models.CharField("상품 문의 제목", null=False, max_length=50)
    inquery_content = models.TextField(verbose_name="문의 내용")
    created_at = models.DateTimeField("작성 날짜", auto_now_add=True)
    is_secret = models.BooleanField("비밀글 여부", default=False)
    is_responded = models.BooleanField("관리자 답변 여부", default=False)

    def __str__(self):
        return str(self.inquery_title)
# ---------------- 상품 문의 글 끝 ----------------

 

 

views.py 에서 전체적으로 개선하고자 하는 목표가 생겼는데
APIView 를 ViewSet 을 이용해서 바꾸려는 것입니다

 

ViewSet 을 이용해서 코드를 작성하면, 

하나의 클래스에 5개 6개, 그 이상의 함수들을 마음대로 작성할 수 있기 때문에 

 

지금처럼, ArticleView 따로, ArticleDetailView 따로 작성할 필요가 없고

내 마음대로 함수의 이름을 지정할 수 있기 때문에 

가독성이 좋아진다고 합니다. 

 

추후 변경해보고, 블로그에 기록하겠습니다 .

 

오늘 중점적으로 다뤘던 함수는 

User 관련 함수입니다. 그중에서도 프로필 함수

# 내용 : 프로필 상세보기, 프로필 수정, 회원 탈퇴
# 업데이트 일자 :23년6월7일
class ProfileView(APIView):
    permission_classes = [IsAuthenticatedOrReadOnly]
    # 이 함수를 실행하면, get_object_or_404를 실행한다.
    def get_object(self, user_id):
        return get_object_or_404(User, id=user_id)

    # 회원 정보 프로필은, 쇼핑몰이기 때문에 자기 자신의 프로필만 볼 수 있도록 해줄 것임
    def get(self, request, user_id):
        user = self.get_object(user_id)
        serializer = UserProfileSerializer(user)
        return Response(serializer.data, status=status.HTTP_200_OK)

    # 프로필 수정, 권한이 있어야함.
    def patch(self, request, user_id):
        user = self.get_object(user_id)
        if user == request.user:
            serializer = UserUpdateSerializer(
                user, data=request.data, partial=True)
            serializer.is_valid(raise_exception=True)  # 유효성 검사 및 오류 발생
            serializer.save()
            return Response({"message": "프로필 수정이 완료되었습니다!"}, status=status.HTTP_200_OK)
        else:
            return Response({"message": "권한이 없습니다. 내 프로필만 수정 가능해요."}, status=status.HTTP_403_FORBIDDEN)
        # 이미지 업로드, 교체 가능, 삭제는 없음.

    # 회원 탈퇴 (비밀번호 받아서)
    def delete(self, request, user_id):
        user = self.get_object(user_id)
        datas = request.data.copy()  # request.data → request.data.copy() 변경
        # request.data는 Django의 QueryDict 객체로서 변경이 불가능하여 복사하여 수정한 후 전달하는 방법을 이용!
        datas["is_active"] = False
        # 회원 탈퇴 시, 계정을 비활성화 하는 것으로 설정.
        serializer = UserDeleteSerializer(user, data=datas)
        if user.check_password(request.data.get("password")):
            serializer.is_valid(raise_exception=True)  # 유효성 검사 및 오류 발생
            serializer.save()
            return Response({"message": "계정이 비활성화 되었습니다"}, status=status.HTTP_204_NO_CONTENT)
        else:
            raise ValidationError({"message": "비밀번호가 다릅니다"})

    # 계정 재활성화
class ActivateView(APIView):
    permission_classes = [AllowAny]
    # 이 함수를 실행하면, get_object_or_404를 실행한다.
    def get_object(self, user_id):
        return get_object_or_404(User, id=user_id)

    def post(self, request, user_id):
        user = self.get_object(user_id)
        if user.is_active:
            return Response({"message": "이미 활성화된 계정입니다."}, status=status.HTTP_400_BAD_REQUEST)

        user.is_active = True
        user.save()
        return Response({"message": "계정이 재활성화되었습니다."}, status=status.HTTP_200_OK)

입니다... 

 

다음 번에는 , 유지 보수가 쉽게 전체적인 코드를 개선하고 

이후의 백엔드 코드들도 ViewSet 을 이용해서 작업해보겠습니다. 

오늘 TIL 끝!