User관련 구현
user/models.py
# user/models.py
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
# custom user model 사용 시 UserManager 클래스와 create_user, create_superuser 함수가 정의되어 있어야 함
class UserManager(BaseUserManager):
def create_user(self, username, password=None):
if not username:
raise ValueError('Users must have an username')
user = self.model(
username=username,
)
user.set_password(password)
user.save(using=self._db)
return user
# python manage.py createsuperuser 사용 시 해당 함수가 사용됨
def create_superuser(self, username, password=None):
user = self.create_user(
username=username,
password=password
)
user.is_admin = True
user.save(using=self._db)
return user
# 유저
class User(AbstractBaseUser):
username = models.CharField("사용자계정", max_length=50, unique=True)
password = models.CharField("비밀번호", max_length=128)
email = models.EmailField("이메일", max_length=100)
fullname = models.CharField("이름", max_length=20)
join_date = models.DateTimeField("가입일", auto_now_add=True)
is_active = models.BooleanField(default=True) # 계정활성화 여부
is_admin = models.BooleanField(default=False) # 관리자 계정 여부
USERNAME_FIELD = 'username' # 로그인 시 사용할 필드 지정
REQUIRED_FIELDS = [] # createsuperuser 할 때 추가로 요구할 필드 지정
objects = UserManager() # custom user 생성 시 필요
def __str__(self):
return self.username
# 로그인 사용자의 특정 테이블의 crud 권한을 설정, perm table의 crud 권한이 들어간다.
# admin일 경우 항상 True, 비활성 사용자(is_active=False)의 경우 항상 False
def has_perm(self, perm, obj=None):
return True
# 로그인 사용자의 특정 app에 접근 가능 여부를 설정, app_label에는 app 이름이 들어간다.
# admin일 경우 항상 True, 비활성 사용자(is_active=False)의 경우 항상 False
def has_module_perms(self, app_label):
return True
# admin 권한 설정
@property
def is_staff(self):
return self.is_admin
# 유저 프로필 - 취미
class Hobbies(models.Model):
name = models.CharField("취미", max_length=20)
def __str__(self):
return self.name
# 유저 프로필
class UserProfile(models.Model):
user = models.OneToOneField(User, verbose_name="유저",on_delete=models.CASCADE)
discription = models.TextField("자기소개", null=True, blank=True)
hobby = models.ManyToManyField(Hobbies, verbose_name="취미")
birthday = models.DateField("생일")
def __str__(self):
return self.user.username
url connection
# projectOOO/url.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('user/', include('user.urls')),
]
# user/urls.py
from django.urls import path
from user import views
urlpatterns = [
# user/
path('', views.UserAPIView.as_view()),
path('sign', views.UserSignAPIView.as_view()),
]
user/view.py (로그인/로그아웃/회원가입/수정/탈퇴)
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import permissions
from django.contrib.auth import login, authenticate, logout
from rest_framework import status
from .serializers import UserSerializer
from .models import User as UserModel
# Create your views here.
class UserAPIView(APIView):
permission_classes = [permissions.AllowAny]
# 요청을 보낼 method의 이름으로 함수명을 지어 오버라이딩 해서 사용해야함
# 회원 정보 출력
def get(self, request):
user = request.user
#serializer에 queryset을 인자로 줄 경우(ManyToMany관계일 때) many=True 옵션을 줘야한다.
serialized_user_data = UserSerializer(user).data
return Response(serialized_user_data, status=status.HTTP_200_OK)
# 로그인
def post(self, request):
#username = request.data.get('username', '')
#password = request.data.get('password', '')
#user = authenticate(request, username=username, password=password)
user = authenticate(request, **request.data)
if not user:
return Response({'error': '아이디와 패스워드를 확인해주세요!'})
login(request, user)
return Response({'success': '로그인 성공!'}, status=status.HTTP_200_OK)
# 로그아웃
def delete(self, request):
logout(request)
return Response({'success': '로그아웃 성공!'}, status=status.HTTP_200_OK)
class UserSignAPIView(APIView):
# 회원 가입
def post(self, request):
userSerializer = UserSerializer(data=request.data)
userSerializer.is_valid(raise_exception=True)
userSerializer.save()
return Response(userSerializer.data, status=status.HTTP_200_OK)
# 회원 수정
def put(self, request):
userSerializer = UserSerializer(request.user, data=request.data, partial=True)
userSerializer.is_valid(raise_exception=True)
userSerializer.save()
return Response(userSerializer.data, status=status.HTTP_200_OK)
# 회원 탈퇴
def delete(self, request):
UserModel.objects.get(id=request.user.id).delete()
return Response({"success": "회원탈퇴 성공!"}, status=status.HTTP_200_OK)
user/serializers.py
from rest_framework import serializers
from .models import User as UserModel
from .models import UserProfile as UserProfileModel
from .models import Hobbies as HobbiesModel
class HobbySerializer(serializers.ModelSerializer):
# 같은 취미를 가진 유저를 함께 출력
same_hobby_users = serializers.SerializerMethodField()
def get_same_hobby_users(self, obj):
user_list = []
# obj => HobbiesModel 이를 이용해서 userprofile을 역참조
for user_profile in obj.userprofile_set.all():
user_list.append(user_profile.user.username)
return user_list
class Meta:
model = HobbiesModel
fields = ["name", "same_hobby_users"]
class UserProfileSerializer(serializers.ModelSerializer):
hobby = HobbySerializer(many=True)
class Meta:
model = UserProfileModel
fields = ["discription", "birthday", "hobby"]
class UserSerializer(serializers.ModelSerializer):
def create(self, validated_data):
# 패스워드 암호화를 위해서 패스워드만 분리
password = validated_data.pop("password", "")
# 유저 모델에 나머지 데이터 세팅
user = UserModel(**validated_data)
# 패스워드 암호화하여 유저모델에 세팅
user.set_password(password)
# 유저 정보 DB에 저장
user.save()
return user
userprofile = UserProfileSerializer()
class Meta:
# serializer에 사용될 model, field지정
model = UserModel
# 모든 필드를 사용하고 싶을 경우 fields = "__all__"로 사용
fields = ["username", "fullname", "email", "userprofile", ]
Article관련 구현
article/models.py
from datetime import timedelta
from datetime import datetime
from django.db import models
from user.models import User
# 카테고리
class Category(models.Model):
name = models.CharField("카테고리명",max_length=20)
discription = models.TextField("설명")
def __str__(self):
return self.name
class Article(models.Model):
#글 작성자, 글 제목, 카테고리, 글 내용
user = models.ForeignKey(User, verbose_name="작성자", on_delete=models.CASCADE)
title = models.CharField("제목", max_length=50)
category = models.ManyToManyField(Category, verbose_name="카테고리")
content = models.TextField("글내용")
start_date = models.DateTimeField("시작날짜", auto_now_add=True)
end_date = models.DateTimeField("끝날짜", default=datetime.now() + timedelta(days=7) )
def __str__(self):
return self.title
# 댓글
class Comment(models.Model):
user = models.ForeignKey(User, verbose_name="작성자", on_delete=models.CASCADE)
article = models.ForeignKey(Article, verbose_name="게시글", on_delete=models.CASCADE)
comment = models.TextField("댓글내용")
def __str__(self):
return (f"{self.user} / {self.article} / {self.comment}")
article/views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from DRF_study.permissions import RegistedMoreThan3MinsUser
from rest_framework import status
from .models import Article as ArticleModel
from DRF_study.serializers import ArticleSerializer
class ArticleView(APIView):
# 가입한지 3분지난 유저들만 글을 쓸 수 있도록 하는 permission클래스
permission_classes = [RegistedMoreThan3MinsUser]
# 전체 글 조회
def get(self, request):
return Response(ArticleSerializer(ArticleModel.objects.all(), many=True).data, status=status.HTTP_200_OK)
# 글 쓰기
def post(self, request):
user = request.user
request.data["user"] = user.id
article_serializer = ArticleSerializer(data=request.data)
article_serializer.is_valid(raise_exception=True)
article_serializer.save()
return Response(article_serializer.data, status=status.HTTP_200_OK)
article/serializers.py
from rest_framework import serializers
from blog.models import Comment as CommentModel
from blog.models import Category as CategoryModel
from blog.models import Aricle as ArticleModel
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = CommentModel
fields = ["user", "article", "comment"]
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = CategoryModel
fields = ["name", "discription"]
class ArticleSerializer(serializers.ModelSerializer):
# 하나의 게시글의 여러개의 댓글이므로 역참조를 이용하기
comment = CommentSerializer(many=True, read_only=True, source="comment_set")
# 카테고리는 id 값이아닌 카테고리명으로 출력되도록
def get_category(self, obj):
return obj.category.name
class Meta:
model = ArticleModel
fields = ["user", "title", "category", "content", "comment"]
read_only_fields = ["category"]
특강 숙제 Product 관련 구현
product/models.py
from django.db import models
from user.models import User as UserModel
# 상품 <작성자, 썸네일, 상품 설명, 등록일자, 노출 종료 일자, 가격, 수정 일자, 활성화 여부>
class Product(models.Model):
author = models.ForeignKey(UserModel, verbose_name="작성자", on_delete=models.CASCADE)
title = models.CharField("제목", max_length=50, blank=False, null=False)
thumnail = models.FileField("이미지", upload_to="product/")
discription = models.TextField("설명")
add_date = models.DateTimeField("등록일자", auto_now_add=True)
exposure_end_date = models.DateTimeField("끝일자")
price = models.IntegerField("가격")
update_data = models.DateTimeField("수정일자", auto_now=True)
is_active = models.BooleanField("활성화여부", default=True)
# 리뷰 <작성자, 상품, 내용, 평점, 작성일>
class Review(models.Model):
author = models.ForeignKey(UserModel,verbose_name="작성자", on_delete=models.SET_NULL, null=True)
product = models.ForeignKey(Product, verbose_name="상품", on_delete=models.SET_NULL, null=True)
content = models.TextField("내용", default="")
created = models.DateTimeField("작성일", auto_now_add=True)
rating = models.IntegerField("평점")
product/serializers.py
from django.utils import timezone
from rest_framework import serializers
from django.db.models import Avg
from product.models import Product as ProductModel
from product.models import Review as ReviewModel
class ReviewSerializer(serializers.ModelSerializer):
author = serializers.SerializerMethodField()
def get_author(self, obj):
return obj.author.fullname
class Meta:
model = ReviewModel
fields = ["author", "product", "content", "created", "rating", ]
class ProductInfoSerializer(serializers.ModelSerializer):
# 상품 정보를 리턴 할 때 상품에 달린 review와 평균 점수를 함께 리턴해주세요
review = serializers.SerializerMethodField()
def get_review(self, obj):
# 해당 게시물에 달린 리뷰들을 전부 다 가져옴
reviews = obj.review_set
# 작성 된 리뷰는 모두 return하는 것이 아닌, 가장 최근 리뷰 1개만 리턴해주세요
return {
"last_review": ReviewSerializer(reviews.last()).data,
"average_rating": reviews.aggregate(avg=Avg("rating"))["avg"]
}
author = serializers.SerializerMethodField()
def get_author(self, obj):
return obj.author.username
class Meta:
model = ProductModel
fields = ["author", "title", "thumnail",
"description", "created",
"exposure_end_date", "review" ]
class ProductSerializer(serializers.ModelSerializer):
# 노출 종료 일자가 현재보다 더 이전 시점이라면 상품을 등록할 수 없도록
def validate(self, data):
end_date = data.get("exposure_end_date", "")
# end_date 가 DatetiemField 이기 때문에! django.utils 의 timezone을 써야함!
# end_date 잇고! 현재보다 end_date가 과거 일 경우 error 출력
if end_date and timezone.now() > end_date:
raise serializers.ValidationError(
detail= {"error": "노출 일자가 종료되었습니다."}
)
return data
def create(self, validated_data):
#상품 설명의 마지막에 "<등록 일자>에 등록된 상품입니다." 라는 문구를 추가해주세요
# 우선 검증이 끝난 데이터들을 Model에 다 맞게 담고
product = ProductModel(**validated_data)
# 세이브를 먼저 한번해야 product.created가 생기게 하기 위함
product.save()
# product.created를 원하는 형식으로 조정한뒤 다시 save()
product.description += f"\n\n{product.created.replace(microsecond=0, tzinfo=None)}에 등록된 상품입니다."
product.save()
return product
def update(self, instance, validated_data):
# 수정하였을 때 상품 설명 마지막에 <등록일자>에 등록된 상품이 수정되도록
# dict 형태인 validated_data를 풀어서 쓰기 위해 items()함수로 for문 돌리기
for key, value in validated_data.items():
# 상품설명만 바꿔야되기 때문에 key 가 description 인 경우에만한에서
if key == "description":
# value에 created 그대로 집어넣기
value += f"\n\n{instance.created.replace(microsecond=0, tzinfo=None)}에 등록된 상품입니다."
setattr(instance, key, value)
instance.save() # save한번해야지 modified 값을 가져올 수 있다
instance.description = f"{instance.modified.replace(microsecond=0, tzinfo=None)}에 수정 되었습니다. \n" + instance.description
instance.save()
return instance
class Meta:
model = ProductModel
fields = ["author", "title", "thumnail",
"description", "created", "price",
"exposure_end_date", "modified", ]
product/views.py
from datetime import datetime
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from rest_framework import permissions
from django.db.models import Q
from product.serializers import ProductSerializer
from product.serializers import ProductInfoSerializer
from product.models import Product as ProductModel
class ProductAPIView(APIView):
permission_classes = [RegistedMoreThan3MinsUser]
# 상품 목록 출력
def get(self, request):
today = datetime.now()
# 오늘기준으로 노출시작날짜가 이전, 노출종료날짜가 이후 or 로그인된 유저거나
products = ProductModel.objects.filter(
Q(exposure_start_date__lte=today, exposure_end_date__gte=today)
| Q(author=request.user)
)
# serializer에 queryset을 주기 때문에 many=True를 줘야함
serialized_data = ProductInfoSerializer(products, many=True).data
return Response(serialized_data, status.HTTP_200_OK)
# 상품 등록
def post(self, request):
# 현재 접속중인 유저의 데이터의 id request.data의 author에 저장
request.data["author"] = request.user.id
product_serializer = ProductSerializer(data=request.data)
if product_serializer.is_valid():
product_serializer.save()
return Response(product_serializer.data, status=status.HTTP_200_OK)
return Response(product_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# 상품 수정
def put(self, request, product_id):
# product/<product_id> 헤더에서 id값을 받아오고 그 상품의 object를 get으로 얻어옴
try:
product = ProductModel.objects.get(id=product_id)
# 해당 상품이 없을 수 도 있기 때문에 try except 문 사용
except ProductModel.DoesNotExist:
return Response({"error" : "없는 상품 입니다."},
status=status.HTTP_400_BAD_REQUEST)
# request.data['user'].pop() #바꾸고 싶지 않은 데이터는 제외하기
product_serlizer = ProductSerializer(product, data=request.data, partial=True)
product_serlizer.is_valid(raise_exception=True)
product_serlizer.save()
return Response(product_serlizer.data, status=status.HTTP_200_OK)
permissions.py
# DRF_study/permissions.py
from rest_framework.permissions import BasePermission
from datetime import timedelta
from django.utils import timezone
from rest_framework.exceptions import APIException
from rest_framework import status
# BasePermission 상속 필수!
class RegistedMoreThan3MinsUser(BasePermission):
# False 리턴 시 보여주는 메세지
message = '가입 후 3분 이상 지난 사용자만 사용하실 수 있습니다.'
SAFE_METHODS = ('GET', )
# has_permission 오버라이딩
def has_permission(self, request, view):
user = request.user
# 로그인한 이용자만 사용 가능
if not user.is_authenticated:
response ={
"detail": "서비스를 이용하기 위해 로그인 해주세요.",
}
raise GenericAPIException(status_code=status.HTTP_401_UNAUTHORIZED, detail=response)
# 로그인한 사용자가 get 요청 시(self.SAFE_METHODS)
# if request.method == "GET":
if user.is_authenticeted and request.method in self.SAFE_METHODS:
return True
# 그 외의 방식으로 요청 시
# 가입 후 3분이 지난 사용자만 허가 ex) 글쓰기 권한
return bool(user.is_authenticated and
request.user.join_date < (timezone.now() - timedelta(minutes=3)))
# is_authenticated 값이 없을 경우 발생 시킬 예외 정의
class GenericAPIException(APIException):
def __init__(self, status_code, detail=None, code=None):
self.status_code=status_code
super().__init__(detail=detail, code=code)
class IsAdminOrIsAuthenticatedReadOnly(BasePermission):
# 메서드 지정을 통해서도 세부적인 권한 조절 가능
SAFE_METHODS = ('GET', )
# return 값 False 일 때 전달할 메세지
message = '접근 권한이 없습니다.'
def has_permission(self, request, view):
user = request.user
# 먼저 로그인 되어있지 않은 것을 판별해서 예외 처리해야만한다 하지않을 경우
# 로그인되어있지 않다는 오류가 메세지로 나오고 접근 권한에 대해서는 판별도 되지 않게 됨
if not user.is_authenticated:
response ={
"detail": "서비스를 이용하기 위해 로그인 해주세요.",
}
raise GenericAPIException(status_code=status.HTTP_401_UNAUTHORIZED, detail=response)
# 로그인 ok, admin ok
if user.is_authenticated and user.is_admin:
return True
# 로그인 ok, method==GET ok,
elif user.is_authenticated and request.method in self.SAFE_METHODS:
return True
return False
'회고록(TIL&WIL)' 카테고리의 다른 글
WIL 2022.07.01 CORS, rest_framework_simplejwt, E2E 회원가입/로그인 (0) | 2022.07.02 |
---|---|
TIL 2022.06.25 DRF/ 아침 숙제 풀이 (0) | 2022.06.25 |
TIL 2022.06.23 머신러닝 특강 (0) | 2022.06.23 |
TIL 2022.06.21 DRF/ Permissions, django admin 심화, ORM심화 (0) | 2022.06.21 |
WIL 2022.06.20 DRF/ Custom User, login/out, 역참조, Serializer (0) | 2022.06.21 |