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.pyclass 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.pyclass 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.pyclass 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.pyclass 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.pyclass 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.pyclass 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를 사용