and
또는 or
를 적용시킬 수 있다.# 검색어 : title, content
ArticleModel.objects.filter(
Q(title__contains=title) | Q(content__contains=content)
)
user/views.py
에서 예시를 들어보도록 하겠다. or
: 나이가 20살보다 많거나 취미가 축구인 user 검색from django.db.models.query_utils import Q
class UserView(APIView):
permission_classes = [IsAdminOrIsAuthenticated]
# 사용자 정보 조회
def get(self, request):
query = Q(hobby__name='축구') | Q(age__gt=19)
user_profile_list = UserProfileModel.objects.filter(query)
print(user_profile_list)
return Response({})
and
: 나이가 20살보다 많고 취미가 축구인 user 검색from django.db.models.query_utils import Q
class UserView(APIView):
permission_classes = [IsAdminOrIsAuthenticated]
# 사용자 정보 조회
def get(self, request):
query = Q(hobby__name='축구') | Q(age__gt=19)
user_profile_list = UserProfileModel.objects.filter(query)
print(user_profile_list)
return Response({})
data validation, create, update
기능을 사용할 수 있다.Meta class 내부 field에 포함되어 있는 항목
에 맞게 validate를 진행해준다. ArticleSerializer
를 먼저 확인해보도록 하겠다. serializers.SerializerMethodField()
인 필드의 경우에는 검사에서 제외된다. read_only=True
를 걸어주어야 검사를 하지 않아 검사에서 에러가 나지 않는다. class ArticleSerializer(serializers.ModelSerializer):
category = serializers.SerializerMethodField()
# related_name을 작성했다면 source는 쓸 필요가 없음
comments = CommentSerializer(many=True, source='comment_set', read_only=True)
# 카테고리를 리스트로 받아오기 위해 다음과 같이 작성
def get_category(self, obj):
return [category.name for category in obj.category.all()]
class Meta:
model = ArticleModel
fields = ["title", "content", "comments", "category"]
blog/views.py
에서 validator를 작성해보고, 일단 포스트맨을 돌려보도록 하자. class ArticleView(APIView):
permission_classes = [IsAdminOrIsAuthenticated]
def get(self, request):
user = request.user
today = datetime.now()
time = ArticleModel.objects.filter(exposure_start__lte=today, exposure_end__gte=today).order_by("-exposure_start")
return Response(ArticleSerializer(time, many=True).data, status=status.HTTP_200_OK)
def post(self, request):
user = request.user
request.data['author'] = user.id
article_serializer = ArticleSerializer(data=request.data)
# request.data의 유효성 검증
if article_serializer.is_valid(): # True or False로 결과값이 나옴
article_serializer.save()
return Response(article_serializer.data, status=status.HTTP_200_OK)
# False인 경우 어디서 에러가 났는지 보여줌
return Response(article_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
author
에 null=True를 주지 않았기 때문에 나는 에러이다. 작성자는 null일 수가 없으니, 이를 해결하기 위해 serializer.py
를 수정해보자.class ArticleSerializer(serializers.ModelSerializer):
category = serializers.SerializerMethodField()
# related_name을 작성했다면 source는 쓸 필요가 없음
comments = CommentSerializer(many=True, source='comment_set', read_only=True)
# 카테고리를 리스트로 받아오기 위해 다음과 같이 작성
def get_category(self, obj):
return [category.name for category in obj.category.all()]
class Meta:
model = ArticleModel
fields = ["author", "title", "content", "comments", "exposure_start", "exposure_end", "category"]
id
이다. 주의하자. id
값이 나온 것!read_only
속성을 부여했다. 이것을 extra_kwargs
를 통해 다른 속성들과 함께 serializer에 부여할 수 있다. class Meta:
...
# 각 필드에 해당하는 다양한 옵션 지정
extra_kwargs = {
# write_only : 해당 필드를 쓰기 전용으로 만들어 준다.
# 쓰기 전용으로 설정 된 필드는 직렬화 된 데이터에서 보여지지 않는다.
'password': {'write_only': True}, # default : False
'email': {
# error_messages : 에러 메세지를 자유롭게 설정 할 수 있다.
'error_messages': {
# required : 값이 입력되지 않았을 때 보여지는 메세지
'required': '이메일을 입력해주세요.',
# invalid : 값의 포맷이 맞지 않을 때 보여지는 메세지
'invalid': '알맞은 형식의 이메일을 입력해주세요.'
},
# required : validator에서 해당 값의 필요 여부를 판단한다.
'required': False # default : True
},
}
class ArticleSerializer(serializers.ModelSerializer):
category = serializers.SerializerMethodField()
# related_name을 작성했다면 source는 쓸 필요가 없음
comments = CommentSerializer(many=True, source='comment_set', read_only=True)
# 카테고리를 리스트로 받아오기 위해 다음과 같이 작성
def get_category(self, obj):
return [category.name for category in obj.category.all()]
class Meta:
model = ArticleModel
fields = ["author", "title", "content", "comments", "exposure_start", "exposure_end", "category"]
extra_kwargs = {
# write_only : 해당 필드를 쓰기 전용으로 만들어 준다.
# 쓰기 전용으로 설정 된 필드는 직렬화 된 데이터에서 보여지지 않는다.
'author': {'write_only': True}, # default : False
}
해당 필드가 필수값인데 왜 안줘?
라고 칭얼거리는 에러 메세지를 볼 수 있다.class ArticleSerializer(serializers.ModelSerializer):
category = serializers.SerializerMethodField()
# related_name을 작성했다면 source는 쓸 필요가 없음
comments = CommentSerializer(many=True, source='comment_set', read_only=True)
# 카테고리를 리스트로 받아오기 위해 다음과 같이 작성
def get_category(self, obj):
return [category.name for category in obj.category.all()]
class Meta:
model = ArticleModel
fields = ["author", "title", "content", "comments", "exposure_start", "exposure_end", "category"]
extra_kwargs = {
# write_only : 해당 필드를 쓰기 전용으로 만들어 준다.
# 쓰기 전용으로 설정 된 필드는 직렬화 된 데이터에서 보여지지 않는다.
'author': {'write_only': True}, # default : False
'title': {
# error_messages : 에러 메세지를 자유롭게 설정 할 수 있다.
'error_messages': {
# required : 값이 입력되지 않았을 때 보여지는 메세지
'required': '제목을 입력해주세요.',
# invalid : 값의 포맷이 맞지 않을 때 보여지는 메세지
'invalid': '제목이 이상해요.'
},
# required : validator에서 해당 값의 필요 여부를 판단한다.
'required': False # default : True
},
}
보이는 것과 같이 title에 대한 에러 메세지가 사라진 것을 알 수 있다.
이제 title의 required 속성값을 True로 변경해서 테스트를 해보자.
변경 후 포스트맨으로 가 실험을 한 결과는 아래와 같다. 우리가 위에서 작성한 내용이 아주 잘 적용된 것을 확인할 수 있다.
구조가 만약 복잡하다면 시리얼라이저를 나눠서 작성하는 것이 나을 수도 있다.
그런데 카테고리 데이터를 넣었는데 왜 값이 들어가지 않았는지에 대한 의문이 생길 수 있다. 이는 custom creator를 사용하지 않아서 그런 것이다. 추후에 작업하도록 하겠다.
회원가입
을 만들어볼 것이다. user/serializers.py
와 user/views.py
를 수정해보도록 하자. user/serializers.py
class UserSerializer(serializers.ModelSerializer):
user_detail = UserProfileSerializer(source="userprofile", read_only=True) # object (OneToOne 관계)
articles = ArticleSerializer(many=True, source="article_set", read_only=True)
comments = CommentSerializer(many=True, source="comment_set", read_only=True)
class Meta:
# 내가 serializer에서 어떤 모델을 쓰겠다는 것을 선언하는 것!(중요)
model = UserModel
# join_data 같은 경우는 시스템에서 자동으로 등록해주는 것으로 기본적으로 read_only임
fields = ["username", "password", "email", "name", "join_data", "user_detail", "articles", "comments"]
extra_kwargs = {
# write_only : 해당 필드를 쓰기 전용으로 만들어 준다.
# 쓰기 전용으로 설정 된 필드는 직렬화 된 데이터에서 보여지지 않는다.
'password': {'write_only': True}, # default : False
'email': {
# error_messages : 에러 메세지를 자유롭게 설정 할 수 있다.
'error_messages': {
# required : 값이 입력되지 않았을 때 보여지는 메세지
'required': '이메일을 입력해주세요.',
# invalid : 값의 포맷이 맞지 않을 때 보여지는 메세지
'invalid': '알맞은 형식의 이메일을 입력해주세요.'
},
# required : validator에서 해당 값의 필요 여부를 판단한다.
'required': False # default : True
},
}
user/views.py
class UserView(APIView):
# permission_classes = [MyAuthenticateOver3]
permission_classes = [IsAdminOrIsAuthenticated]
# 사용자 정보 조회
def get(self, request):
user_serializer = UserSerializer(request.user, context={"request": request}).data
return Response(user_serializer)
# 회원가입
def post(self, request):
user_serializer = UserSerializer(data=request.data)
if user_serializer.is_valid():
user_serializer.save()
return Response(user_serializer.data, status=status.HTTP_200_OK)
return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
수정
에 대한 코드를 작성하며 익혀보도록 하겠다. user/serializers.py
class UserSerializer(serializers.ModelSerializer):
user_detail = UserProfileSerializer(source="userprofile", read_only=True) # object (OneToOne 관계)
articles = ArticleSerializer(many=True, source="article_set", read_only=True)
comments = CommentSerializer(many=True, source="comment_set", read_only=True)
class Meta:
# 내가 serializer에서 어떤 모델을 쓰겠다는 것을 선언하는 것!(중요)
model = UserModel
# join_data 같은 경우는 시스템에서 자동으로 등록해주는 것으로 기본적으로 read_only임
fields = ["username", "password", "email", "name", "join_data", "user_detail", "articles", "comments"]
extra_kwargs = {
# write_only : 해당 필드를 쓰기 전용으로 만들어 준다.
# 쓰기 전용으로 설정 된 필드는 직렬화 된 데이터에서 보여지지 않는다.
'password': {'write_only': True}, # default : False
'email': {
# error_messages : 에러 메세지를 자유롭게 설정 할 수 있다.
'error_messages': {
# required : 값이 입력되지 않았을 때 보여지는 메세지
'required': '이메일을 입력해주세요.',
# invalid : 값의 포맷이 맞지 않을 때 보여지는 메세지
'invalid': '알맞은 형식의 이메일을 입력해주세요.'
},
# required : validator에서 해당 값의 필요 여부를 판단한다.
'required': False # default : True
},
}
user/views.py
class UserView(APIView):
# permission_classes = [MyAuthenticateOver3]
permission_classes = [IsAdminOrIsAuthenticated]
# 사용자 정보 조회
def get(self, request):
user_serializer = UserSerializer(request.user, context={"request": request}).data
return Response(user_serializer)
# 회원가입
def post(self, request):
user_serializer = UserSerializer(data=request.data)
if user_serializer.is_valid():
user_serializer.save()
return Response(user_serializer.data, status=status.HTTP_200_OK)
return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# 회원 정보 수정
def put(self, request, obj_id):
user = request.user
target_user = UserModel.objects.get(id=user.id)
# fields에 있는 데이터를 다 넣을 필요가 없도록 하기 위해 partial=True 사용
user_serializer = UserSerializer(target_user, data=request.data, partial=True)
if user_serializer.is_valid():
user_serializer.save()
return Response(user_serializer.data, status=status.HTTP_200_OK)
return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
에서 변경 사항이 생기게 된다. serializers.py
에 아래 부분을 추가해주고 원래 있던 user 데이터를 쏴보도록 하겠다. class UserSerializer(serializers.ModelSerializer):
user_detail = UserProfileSerializer(source="userprofile", read_only=True) # object (OneToOne 관계)
articles = ArticleSerializer(many=True, source="article_set", read_only=True)
comments = CommentSerializer(many=True, source="comment_set", read_only=True)
def validate(self, data):
print(data)
return data
#### 하단 생략 ####
포스트맨의 결과이다. 원래있던 validator에서 막힌 것을 알 수 있다.
이제 새로운 user8의 회원가입을 똑같은 조건으로 실행해보았더니, 데이터가 잘 들어간 것을 볼 수 있다. 따라서 위에도 작성하였지만, 우리는 custom validator의 동작이 원래있던 validator의 동작 이후에 된다는 것을 알 수 있다.
코드에 있던 print 부분도 잘 찍힌 것을 보니 회원가입이 잘 진행된 것을 볼 수 있다.
이제 custom validator를 사용해 네이버 이메일을 가진 사용자만 가입할 수 있도록 걸러보자.
class UserSerializer(serializers.ModelSerializer):
user_detail = UserProfileSerializer(source="userprofile", read_only=True) # object (OneToOne 관계)
articles = ArticleSerializer(many=True, source="article_set", read_only=True)
comments = CommentSerializer(many=True, source="comment_set", read_only=True)
def validate(self, data):
if not data.get("email", "").endswith("@naver.com"):
raise serializers.ValidationError(
detail={"error": "네이버 이메일만 가입할 수 있습니다."}
)
return data
#### 하단 생략 ####
if user_serializer.is_valid():
user_serializer.save()
return Response(user_serializer.data, context={"request": request}, status=status.HTTP_200_OK)
class UserSerializer(serializers.ModelSerializer):
user_detail = UserProfileSerializer(source="userprofile", read_only=True) # object (OneToOne 관계)
articles = ArticleSerializer(many=True, source="article_set", read_only=True)
comments = CommentSerializer(many=True, source="comment_set", read_only=True)
def validate(self, data):
try:
http_method = self.context['request'].method
except:
http_method=""
if http_method == 'POST':
if not data.get("email", "").endswith("@naver.com"):
raise serializers.ValidationError(
detail={"error": "네이버 이메일만 가입할 수 있습니다."}
)
return data
valid_email_list
는 파일 맨 위로 빼 상수로 선언해 사용하는 것이 좋다.custom creator
와 custom updater
는 기존의 것을 덮어쓰게 되며, 이 둘을 만들게 되면 원래 있던 craetor와 updater는 기능을 하지 않게 된다. ⌘
+ ModelSerializer
)검증된 데이터
가 들어가게 된다. 무언가를 생성하기 전에 검증을 하고 만들면 좋다고 생각하면 될 것 같다. (django의 공홈에서도 똑같은 방식으로 소개하고 있음)save()
를 많이 사용해 왔다. 우리가 POST에서 save()를 하게 되면 create
함수가, PUT에서 save()를 하면 update
함수가 호출되어 왔던 것이다.create에는 obj가 없고, update에는 obj가 들어가야 한다.
수정한다는 것은 수정할 대상이 있다는 뜻이고, 그 수정할 대상인 obj가 들어가는 것이다. 하지만 어떤 것을 생성할 때는 어떤 대상을 찝어서 생성하라고 하지 않는다. )user_profile
을 만들어서 썼었다. 하지만 아까 그냥 readonly=True
를 하고 넘어갔다. 이제 user_profile
을 회원가입 시에도 적용할 수 있도록 수정해보자. user/serializers.py
class UserSerializer(serializers.ModelSerializer):
user_detail = UserProfileSerializer(source="userprofile") # object (OneToOne 관계)
articles = ArticleSerializer(many=True, source="article_set", read_only=True)
comments = CommentSerializer(many=True, source="comment_set", read_only=True)
# custom validator
# 기존의 validator와 같이 쓰임
def validate(self, data):
if not data.get("email", "").endswith("@naver.com"):
raise serializers.ValidationError(
detail={"error": "네이버 이메일만 가입할 수 있습니다."}
)
return data
# custom creator
# 기존의 creator의 기능을 덮어쓰며, 이것을 만들면 기존의 creator는 작동하지 않음
def create(self, validate_data):
return UserModel(**validate_data)
class Meta:
# 내가 serializer에서 어떤 모델을 쓰겠다는 것을 선언하는 것!(중요)
model = UserModel
# join_data 같은 경우는 시스템에서 자동으로 등록해주는 것으로 기본적으로 read_only임
fields = ["username", "password", "email", "name", "join_data", "user_detail", "articles", "comments"]
extra_kwargs = {
# write_only : 해당 필드를 쓰기 전용으로 만들어 준다.
# 쓰기 전용으로 설정 된 필드는 직렬화 된 데이터에서 보여지지 않는다.
'password': {'write_only': True}, # default : False
'email': {
# error_messages : 에러 메세지를 자유롭게 설정 할 수 있다.
'error_messages': {
# required : 값이 입력되지 않았을 때 보여지는 메세지
'required': '이메일을 입력해주세요.',
# invalid : 값의 포맷이 맞지 않을 때 보여지는 메세지
'invalid': '알맞은 형식의 이메일을 입력해주세요.'
},
# required : validator에서 해당 값의 필요 여부를 판단한다.
'required': False # default : True
},
}
hobby를 추가하는 것
이 되어버린다. class UserProfileSerializer(serializers.ModelSerializer):
# input data가 QuerySet일 경우 many=True를 입력해주어야 한다.
hobby = HobbySerializer(many=True, read_only=True) # QuerySet (ManyToMany 관계)
class Meta:
model = UserProfileModel
fields = ["introduction", "birth", "age", "hobby"]
pop()
을 사용해서 userprofile의 내용을 뺀 뒤에 저장을 해주는 방식으로 진행해보자. def create(self, validated_data):
user_profile = validated_data.pop("userprofile")
print(user_profile)
print(validated_data)
return
def create(self, validated_data):
user_profile = validated_data.pop("userprofile")
user = UserModel(**validated_data)
user.save()
user_profile = UserProfileModel.objects.create(user=user, **user_profile)
return user
class UserProfileSerializer(serializers.ModelSerializer):
# input data가 QuerySet일 경우 many=True를 입력해주어야 한다.
hobby = HobbySerializer(many=True, read_only=True) # QuerySet (ManyToMany 관계)
get_hobbys = serializers.ListField(required=False)
class Meta:
model = UserProfileModel
fields = ["introduction", "birth", "age", "hobby", "get_hobbys"]
def create(self, validated_data):
user_profile = validated_data.pop("userprofile")
user = UserModel(**validated_data)
print(user_profile)
return
# user.save()
# user_profile = UserProfileModel.objects.create(user=user, **user_profile)
# return user
user/views.py
class UserView(APIView):
# permission_classes = [MyAuthenticateOver3]
permission_classes = [IsAdminOrIsAuthenticated]
# 사용자 정보 조회
def get(self, request):
user_serializer = UserSerializer(request.user, context={"request": request}).data
return Response(user_serializer)
# 회원가입
def post(self, request):
user_serializer = UserSerializer(data=request.data, context={"request": request})
if user_serializer.is_valid():
user_serializer.save()
return Response(user_serializer.data, status=status.HTTP_200_OK)
return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
user/serializer.py
# custom creator
# 기존의 creator의 기능을 덮어쓰며, 이것을 만들면 기존의 creator는 작동하지 않음
def create(self, validated_data):
user_profile = validated_data.pop("userprofile")
get_hobbys = user_profile.pop("get_hobbys", [])
password = validated_data.pop("password")
user = UserModel(**validated_data)
user.set_password(password)
user.save()
user_profile = UserProfileModel.objects.create(user=user, **user_profile)
user_profile.hobby.add(*get_hobbys)
return user
# 회원 정보 수정
def put(self, request, obj_id):
# user = request.user
target_user = UserModel.objects.get(id=obj_id)
# fields에 있는 데이터를 다 넣을 필요가 없도록 하기 위해 partial=True 사용
user_serializer = UserSerializer(target_user, data=request.data, partial=True, context={"request": request})
if user_serializer.is_valid():
user_serializer.save()
return Response(user_serializer.data, status=status.HTTP_200_OK)
return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def update(self, instance, validated_data):
# instance에는 입력된 object가 담긴다.
print(f'instance : {instance}')
print(f'validated_data : {validated_data}')
# for key, value in validated_data.items():
# if key == "password":
# instance.set_password(value)
# continue
# setattr(instance, key, value)
# instance.save()
return instance
def update(self, instance, validated_data):
# instance에는 입력된 object가 담긴다.
for key, value in validated_data.items():
print(key, value)
if key == "password":
instance.set_password(value)
continue
setattr(instance, key, value)
# instance.save()
return instance
setattr(instance, key, value)
의 의미는 instance.username = "user18"
과 같은 의미이다. 만약 뒤의 구문으로 코드를 작성하게 되면 instance에는 username이라는 속성이 없기 때문에 keyerror가 발생하게 될 텐데, 앞의 구문으로 작성하게 되면 알아서 해당 값을 넣어주게 된다. def update(self, instance, validated_data):
# instance에는 입력된 object가 담긴다.
user_profile = validated_data.pop("userprofile")
get_hobbys = user_profile.pop("get_hobbys", [])
for key, value in validated_data.items():
if key == "password":
instance.set_password(value)
continue
setattr(instance, key, value)
instance.save()
user_profile_obj = instance.userprofile
for key, value in user_profile.items():
setattr(user_profile_obj, key, value)
user_profile_obj.save()
user_profile_obj.hobby.set(get_hobbys)
return instance
raise_exception=True
를 사용