# models.py
class User(models.Model):
username = models.CharField("사용자 계정", max_length=50, primary_key=True)
password = models.CharField("비밀번호", max_length=200, unique=True)
# User라는 테이블에 username, password라는 필드가 존재함
# 사용자가 회원가입을 할 때마다 레코드가 하나씩 추가됨.
# 즉, 레코드란 데이터베이스에 저장 되는 값들을 지칭하는 것
models.py
) # models.py
from django.db import models
class User(models.Model):
username = models.CharField("사용자 계정", max_length=20, unique=True)
email = models.EmailField("이메일 주소", max_length=100, unique=True)
password = models.CharField("비밀번호", max_length=20)
fullname = models.CharField("이름", max_length=20)
join_date = models.DateTimeField("가입일", auto_now_add=True)
class UserProfile(models.Model):
user = models.OneToOneField(to=User, verbose_name="사용자", on_delete=models.CASCADE)
hobby = models.ManyToManyField(to="Hobby", verbose_name="취미")
introduction = models.TextField("소개")
birthday = models.DateField("생일")
age = models.IntegerField("나이")
class Hobby(models.Model):
name = models.CharField("취미", max_length=50)
fk
에서 사용되는 on_delete
에는 여러 속성들이 있으며, 상황에 맞게 사용해야 한다.CASCADE
: FK로 참조하는 레코드가 삭제 될 경우 해당 레코드를 삭제한다.SET_NULL
: FK 필드의 값을 Null로 변경해준다. null=True가 정의되어 있어야 사용 가능하다.PROTECT
: 해당 레코드가 삭제되지 않도록 보호해준다.SET_DEFAULT
: FK 필드의 값을 default로 변경해준다. default=””가 정의되어 있어야 사용 가능하다.SET()
: FK 필드의 값을 SET에 설정된 함수를 통해 원하는 값으로 변경할 수 있다.DO_NOTHING
: 아무런 동작을 하지 않는다. 참조 관계의 무결성이 손상될 수 있기 때문에 권장하지 않는다. default
= $date : 지정한 값을 기본 값으로 설정한다.auto_now_add
= True : 레코드가 생성될 때의 date를 기준으로 값을 지정한다.auto_now
= True : 레코드가 save()될 때마다 갱신된다.admin.py
) # admin.py
from django.contrib import admin
from user.models import User, UserProfile, Hobby
admin.site.register(User)
admin.site.register(UserProfile)
admin.site.register(Hobby)
code(views.py
)
# views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import permissions
class UserView(APIView): # CBV 방식
permission_classes = [permissions.AllowAny] # 누구나 view 조회 가능
# permission_classes = [permissions.IsAdminUser] # admin만 view 조회 가능
# permission_classes = [permissions.IsAuthenticated] # 로그인 된 사용자만 view 조회 가능
def get(self, request):
return Response({'message': 'get method!!'})
def post(self, request):
return Response({'message': 'post method!!'})
def put(self, request):
return Response({'message': 'put method!!'})
def delete(self, request):
return Response({'message': 'delete method!!'})
postman 설정 방법
http method(get, post, put, delete)의 차이를 이해하고 구현하기
POST / PUT / DELETE 통신 시 csrf error가 발생 할 때
Tests에 코드 추가
*var* xsrfCookie = postman.getResponseCookie("csrftoken");
postman.setGlobalVariable('csrftoken', xsrfCookie.value);
Headers에 Key / Value 추가
X-CSRFToken
{{csrftoken}}
queryset, object의 차이에 대한 이해
[object(1), object(2), object(3)]
objects.get, objects.filter의 차이에 대한 이해
code
Model.objects.get(id=obj_id) # => return object
Model.objects.filter(date=datetime.today()) # => return queryset
데이터 추가, 조회, 삭제, 수정하기
# 추가1
model = Model(
field1="value1",
field2="value2"
)
model.save()
# 추가2
Model.objects.create(
field1="value1",
field2="value2"
)
# 조회
Model.objects.all()
Model.objects.filter()
Model.objects.get()
# 수정1
model = Model.object.get(id=obj_id)
model.field = value
model.save()
# 수정2
Model.objects.filter(field__contains=value).update(
field1="value1",
field2="value2"
)
# 삭제
Model.objects.filter(field="value").delete()
Model.objects.get(id=obj_id).delete()
자주 사용하는 패턴 모음
# objects.get에서 객체가 존재하지 않을 경우 DoesNotExist Exception 발생
try:
Model.objects.get(id=obj_id)
except Model.DoesNotExist:
# some event
return Response("존재하지 않는 오브젝트입니다.")
# -join_date처럼 "-"를 붙이면 역순으로 정렬
# .order_by("?")사용시 무작위 셔플
Model.objects.all().order_by("join_date")
# queryset에서 첫번째 object를 가져옴. all()[0]과 동일
Model.objects.all().first()
# 입력한 object가 존재 할 경우 해당 object를 가져오고, 존재하지 않을 경우 새로 생성
object, created = Model.objects.get_or_create(
field1="value1",
field2="value2",
)
if created:
# created event
else:
# already exist event
models.py
) from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
# 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=20, unique=True)
email = models.EmailField("이메일 주소", max_length=100)
password = models.CharField("비밀번호", max_length=128)
fullname = models.CharField("이름", max_length=20)
join_date = models.DateTimeField("가입일", auto_now_add=True)
# is_active가 False일 경우 계정이 비활성화됨
is_active = models.BooleanField(default=True)
# is_staff에서 해당 값 사용
is_admin = models.BooleanField(default=False)
# id로 사용 할 필드 지정.
# 로그인 시 USERNAME_FIELD에 설정 된 필드와 password가 사용된다.
USERNAME_FIELD = 'username'
# user를 생성할 때 입력받은 필드 지정
REQUIRED_FIELDS = []
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
settings.py
)AUTH_USER_MODEL = 'user.User' # app.table 형태
views.py
)from django.contrib.auth import login, authenticate
class UserApiView(APIView):
# 로그인
def post(self, request):
username = request.data.get('username', '')
password = request.data.get('password', '')
user = authenticate(request, username=username, password=password)
if not user:
return Response({"error": "존재하지 않는 계정이거나 패스워드가 일치하지 않습니다."}, status=status.HTTP_401_UNAUTHORIZED)
login(request, user)
return Response({"message": "로그인 성공!!"}, status=status.HTTP_200_OK)
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
class UserAdmin(BaseUserAdmin):
list_display = ('id', 'username', 'fullname', 'email')
list_display_links = ('username', )
list_filter = ('username', )
search_fields = ('username', 'email', )
fieldsets = (
("info", {'fields': ('username', 'password', 'email', 'fullname', 'join_date',)}),
('Permissions', {'fields': ('is_admin', 'is_active', )}),)
filter_horizontal = []
def get_readonly_fields(self, request, obj=None):
if obj:
return ('username', 'join_date', )
else:
return ('join_date', )
debug = True / False
debug 모드 설정. static file 처리, allow host, 에러 페이지 등의 설정이 달라진다.LANGUAGE_CODE = 'ko-kr'
: 언어 설정TIME_ZONE = 'Asia/Seoul'
: Timezone 설정DATABASES
: DB 설정INSTALLED_APPS
: 사용할 앱 설정# https://docs.djangoproject.com/en/1.11/topics/logging/
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
}
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'level': 'DEBUG',
},
}
}
ForeignKey
: many-to-one 형태로 특정 테이블에서 다른 테이블을 참조 할 수 있다,
OneToOneField
: one-to-one 형태로 ForeignKey와 동일하지만, 1:1 관계만 가능하다.
ManyToManyField
: many-to-many 형태로 한 개의 필드에서 여러개의 테이블을 참조 할 수 있다.
영화라는 테이블에서 카테고리 테이블의 object를 참조하고 싶을 때, many to many 관계를 사용해 2개 이상의 object를 참조할 수 있다.
Many-to-many 관계로 생성 된 필드는 db에 값이 2개 이상 저장되는 것이 아닌, 중간 테이블이 생성된다.
예시)
[models.py](http://models.py)
구조
class UserProfile(models.Model):
user = models.OneToOneField(to=User, verbose_name="사용자", on_delete=models.CASCADE, primary_key=True)
hobby = models.ManyToManyField("Hobby", verbose_name="취미")
introduction = models.TextField("소개")
birthday = models.DateField("생일")
age = models.IntegerField("나이")
def __str__(self):
return f"{self.user.username} 님의 프로필"
class Hobby(models.Model):
name = models.CharField("취미", max_length=50)
def __str__(self):
return self.name
실제 DB 테이블 구조
외래 키를 사용해 참조하는 object를 역으로 찾을 수 있다.
user_profile.hobby
→ 정참조hobby.userprofile_set
→ hobby를 참조하고 있는 UserProfile 테이블의 object를 져옴user_hobby
로 지정했다면 hobby.user_hobby
와 같이 사용외래 키 별 역참조 사용 방법
foreignkey, many-to-many
# Hobby model에서 무작위 object를 지정
hobby = HobbyModel.objects.all().order_by("?").first()
# userprofile_set은 many to many기 때문에 queryset 형태
# 아래와 같이 사용 할 경우 hobby object를 참조하는 모든 userprofile을 return
# .all()을 붙여주지 않으면 user.UserProfile.None와 같은 형태로 return됨
hobby_users = hobby.userprofile_set.all()
# queryset 형태기 때문에 필요에 따라 특정 filter를 걸어 줄 수도 있다.
hobby_users = hobby.userprofile_set.filter(field=value)
one-to-one
# User model에서 무작위 object를 지정
user = UserModel.objects.all().order_by("?").first()
# userprofile은 역참조지만 one-to-one 관계이기 때문에 _set이 붙지 않음
# 아래와 같이 사용 할 경우 user를 바라보는 userprofile object를 return
user_profile = user.userprofile
# object를 return 받았기 때문에 user_profile의 field들을 확인 할 수 있다.
print(user_profile.hobby)
print(user_profile.introduction)
print(user_profile.birthday)
print(user_profile.age)
역참조를 활용해 나와 같은 취미를 가진 사람을 찾는 코드
def get(self, request):
user = request.user
hobbys = user.userprofile.hobby.all()
for hobby in hobbys:
# exclde : 매칭 된 쿼리만 제외, filter와 반대
# annotate : 필드 이름을 변경해주기 위해 사용, 이외에도 원하는 필드를 추가하는 등 다양하게 활용 가능
# values / values_list : 지정한 필드만 리턴 할 수 있음. values는 dict로 return, values_list는 tuple로 ruturn
# F() : 객체에 해당되는 쿼리를 생성함
hobby_members = hobby.userprofile_set.exclude(user=user).annotate(username=F('user__username')).values_list('username', flat=True)
hobby_members = list(hobby_members)
print(f"hobby : {hobby.name} / hobby members : {hobby_members}")
# result print
"""
hobby : 산책 / hobby members : ['user1']
hobby : 음악감상 / hobby members : ['user1', 'user2']
hobby : 스쿠버다이빙 / hobby members : ['user2']
hobby : 여행 / hobby members : ['user2']
"""
꿀팁
# python에는 객체에 존재하는 변수, 메소드 등을 출력해주는 dir()이라는 함수가 존재
# 특정 object를 dir(object) 형태로 사용 시 역참조 확인 가능한 항목들을 불러올 수 있음
# models.py
class User(AbstractBaseUser):
...
class UserProfile(models.Model):
user = models.OneToOneField(to=User)
hobby = models.ManyToManyField("Hobby", verbose_name="취미")
...
class Hobby(models.Model):
...
# views.py
user = User.objects.get(id=obj_id)
hobby = Hobby.objects.get(id=obj_id)
print(dir(user))
print(dir(hobby))
# result dir(user) print
"""
[..., userprofile, ...]
"""
# result dir(hobby) print
"""
[..., userprofile_set, ...]
"""
# user를 바라보고 있는 UserProfile은 One-to-one 관계기 때문에 _set이 붙지 않음
# hobby를 바라보고 있는 UserProfile은 Many-to-many 관계기 때문에 _set이 붙음
# ForeignKey를 사용했을 떄 또한 _set이 붙게 됨
model
에 사용 될 테이블을 적어주고, field
에 사용될 필드를 적어준다.extra_kwargs
, read_only_fields
와 같은 옵션을 통해 다양한 설정이 가능하다.serializers.py
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
# serializer에 사용될 model, field지정
model = User
# 모든 필드를 사용하고 싶을 경우 fields = "__all__"로 사용
fields = ["username", "password", "fullname", "email"]
views.py
from rest_framework.response import Response
from rest_framework import status
from user.serializers import UserSerializer
def get(self, request):
user = request.user
# serializer에 queryset을 인자로 줄 경우 many=True 옵션을 사용해야 한다.
serialized_user_data = UserSerializer(user).data
# context= 를 통해 원하는 데이터를 serializer에 넘겨주고, self.context를 사용해 호출 가능하다.
# serialized_user_data = UserSerializer(user, context={"some_key": "some_value"}).data
return Response(serialized_user_data, status=status.HTTP_200_OK)
# return data
"""
{
"username": "user",
"password": "pbkdf2_sha256$320000$u5YnmKo9luab9csqWpzRsa$pKfqHnBiF5Rgdo1Mj9nxNOdhpAl9AhPVXFPXkbPz7Mg=",
"fullname": "user's name",
"email": "user@email.com"
}
"""
serializer.py
)class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = "__all__"
class UserSerializer(serializers.ModelSerializer):
"""
외래 키는 UserProfile에서 User 테이블로 설정되어 있지만
one to one 필드기 때문에 userprofile이라는 명칭으로 역참조가 가능하다.
"""
userprofile = UserProfileSerializer()
class Meta:
model = User
fields = ["username", "password", "fullname", "email", "userprofile"]
SerializerMethodField
를 활용해 원하는 필드를 추가하고, 더 나아가서 여러 serializer들을 함께 사용할 수 있다.serializers.py
) class HobbySerializer(serializers.ModelSerializer):
# serializers.SerializerMethodField()를 사용해 원하는 필드를 생성한다.
same_hobby_users = serializers.SerializerMethodField()
def get_same_hobby_users(self, obj):
user_list = []
for user_profile in obj.userprofile_set.all():
user_list.append(user_profile.user.username)
return user_list
class Meta:
model = Hobby
fields = ["name", "same_hobby_users"]
class UserProfileSerializer(serializers.ModelSerializer):
# 외래 키 관계로 이어져 있는 필드는 Serializer를 바로 호출할 수 있다.
hobby = HobbySerializer(many=True)
class Meta:
model = UserProfile
fields = "__all__"
class UserSerializer(serializers.ModelSerializer):
# One-to-one 관계에서는 fk처럼 사용 가능하다.
userprofile = UserProfileSerializer()
**** class Meta:
model = User
fields = ["username", "password", "fullname", "email", "userprofile"]
# views.py
...
class UserView(APIView)
def get(self, request):
user = request.user
return Response(UserSerializer(user).data, status=status.HTTP_200_OK)
# response data
"""
{
"username": "admin",
"password": "pbkdf2_sha256$320000$u5YnmKo9luab9csqWpzRsa$pKfqHnBiF5Rgdo1Mj9nxNOdhpAl9AhPVXFPXkbPz7Mg=",
"fullname": "zxcv",
"email": "zxv@asd.com",
"userprofile": {
"birthday": "2022-06-08",
"age": 1,
"introduction": "asdac",
"hobby": [
{
"name": "독서",
"same_hobby_users": [
"user1",
"user2",
"user3"
]
}
]
}
}
"""
permissions.AllowAny
: 모든 사용자를 대상으로 접근 허용permissions.IsAuthenticated
: 로그인 된 사용자를 대상으로 접근 허용permissions.AllowAny
: 모든 사용자를 대상으로 접근 허용 from rest_framework.permissions import BasePermission
from datetime import timedelta
from django.utils import timezone
class RegistedMoreThanAWeekUser(BasePermission):
"""
가입일 기준 1주일 이상 지난 사용자만 접근 가능
"""
message = '가입 후 1주일 이상 지난 사용자만 사용하실 수 있습니다.'
def has_permission(self, request, view):
return bool(request.user and request.user.join_date < (timezone.now() - timedelta(days=7)))
from rest_framework.permissions import BasePermission
from rest_framework.exceptions import APIException
from rest_framework import status
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):
"""
admin 사용자는 모두 가능, 로그인 사용자는 조회만 가능
"""
SAFE_METHODS = ('GET', )
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)
if user.is_authenticated and user.is_admin:
return True
if user.is_authenticated and request.method in self.SAFE_METHODS:
return True
return False
list_display
/ object 목록에 띄워줄 필드를 지정한다.list_display = ('id', 'username', 'fullname')
list_display_links
/ object 목록에서 클릭 시 상세 페이지로 들어갈 수 있는 필드를 지정한다.list_display_links = ('username', )
list_filter
/ filter를 걸 수 있는 필드를 생성한다.list_filter = ('name', )
search_fields
/ 검색에 사용될 필드를 지정한다.search_fields = ('username', )
readonly_fields
/ 읽기 전용 필드를 설정할 때 사용된다.# 생성 / 수정 모두 readonly로 설정
readonly_fields = ('join_date', )
# 생성 시 write 가능, 수정 시 readonly field로 설정
def get_readonly_fields(self, request, obj=None):
if obj:
return ('username', 'join_date', )
else:
return ('join_date', )
fieldsets
/ 상세페이지에서 필드를 분류하는데 사용된다.fieldsets = (
("info", {'fields': ('username', 'fullname', 'join_date')}),
('permissions', {'fields': ('is_admin', 'is_active', )}),
)
Tabulainline
/ Stackinline
설정from django.contrib import admin
from user.models import User, UserProfile, Hobby
# 사용 방법은 TabulaInline과 StackedInline 모두 동일
# 둘 다 사용해보고 뭐가 좋은지 비교해보기
# class UserProfileInline(admin.TabularInline):
class UserProfileInline(admin.StackedInline):
model = UserProfile
class UserAdmin(admin.ModelAdmin):
inlines = (
UserProfileInline,
)
admin.site.register(User, UserAdmin)
from django.contrib import admin
class UserAdmin(admin.ModelAdmin):
def has_add_permission(self, request, obj=None): # 추가 권한
return False
def has_delete_permission(self, request, obj=None): # 삭제 권한
return False
def has_change_permission(self, request, obj=None): # 수정 권한
return False
admin.site.register(User, UserAdmin)
get
, filter
, exclude
를 사용해 검색 시 다양한 Field lookups 문법을 사용할 수 있다.contains
: 특정 string이 포함된 object 찾기# fullname에 "이름"이라는 string이 포함된 사용자들을 찾는다.
UserModel.objects.filter(fullname__contains="이름")
startswith
/ endswith
**:** 특정 string으로 시작하는/끝나는 object 찾기# email이 "@naver.com"으로 끝나는 사용자들을 찾는다.
UserModel.objects.filter(email__endswith="@naver.com")
gt
/ lt
/ gte
/ lte
: 특정 값보다 크거나/작거나/크거나같거나/작거나같은 object 찾기# 사용자 프로필의 나이가 19 이상인 사용자들을 찾는다.
UserProfileModel.objects.filter(age__gte=19)
in
: 특정 list에 포함된 object 찾기# 사용자 프로필의 운동 혹은 독서를 취미로 가진 사용자들을 찾는다.
UserProfileModel.objects.filter(hobby__name__in=["운동", "독서"])
kwargs
를 활용해 여러 줄의 코드를 간소화 시킬 수 있다.# sample request.data
"""
{
"username": "someuser",
"password": "P@ssw0rd"
}
"""
class UserView(APIView):
def post(self, request):
# request.data의 dict 형태가 **를 붙임으로써 kwargs로 들어감
UserModel.objects.create(**request.data)
order_by
를 사용해 queryset을 정렬시킬 수 있다.User.objects.all().order_by("join_date") # 가입일 순 정렬
User.objects.all().order_by("-join_date") # 가입일 역순 정렬
User.objects.all().order_by("?") # 랜덤 셔플
Q
를 사용해 쿼리에 and, or을 적용시킬 수 있다.from django.db.models.query_utils import Q
class UserView(APIView)
def get(self, request):
# 취미 중 산책이 있거나 나이가 19살보다 많고 김씨인 사람만 필터 사람만 필터
query = Q(hobby__name="산책") | Q(age__gt=19, user__name__startswith="김")
# 취미 중 산책이 있으면서 나이가 19살보다 많은 사람만 필터
query = Q(hobby__name="산책") & Q(age__gt=19)
user_profile_list = UserProfileModel.objects.filter(query)
serializer는 데이터 직렬화 외에도 data validation, create, update 기능을 사용할 수 있다.
validator
views.py
)from user.serializers import UserSerializer
...
class UserView(APIView):
def post(self, request):
# serializer의 data 인자에는 model로 지정 된 테이블의 field:value를 dictionary로 넘겨준다.
user_serializer = UserSerializer(data=request.data)
# serializer validator를 통과하지 않을 경우 .is_valid()가 False로 return된다.
if user_serializer.is_valid():
# validator를 통과했을 경우 데이터 저장
user_serializer.save()
return Response({"message": "정상"}, status=status.HTTP_200_OK)
# .errors에는 validator에 실패한 필드와 실패 사유가 담겨져 있다.
return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# sample request.data
"""
{
"username": "new_user",
"password": "MyL0ve1yP@ssw0rd",
"fullname": "myname",
"userprofile": {
"introduction": "자기소개입니다.",
"birthday": "2000-1-01",
"age": 30
},
"trash": "zczxcvx"
}
"""
class UserSerializer(serializers.ModelSerializer):
# 외래 키 관계에 있는 필드의 required를 설정하고 싶을 경우 인자로 넘겨줘야 한다.
userprofile = UserProfileSerializer(required=False) # default : True
...
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
},
}
# serializer의 인자에 object를 넣어 직렬화 된 데이터를 가져올 수 있다.
user = request.user
return Response(UserSerializer(user).data, status=status.HTTP_200_OK)
# object와 마찬가지로 queryset을 인자로 넣어 여러개의 직렬화 된 데이터를 가져올 수 있다.
hobbys = Hobby.objects.all()
# queryset을 인자로 넣을 경우 many=True 설정 필요하다.
return Response(HobbySerializer(hobbys, many=True).data, status=status.HTTP_200_OK)
# partial을 True로 설정할 경우 required field에 대한 validation을 수행하지 않는다.
# 주로 일부 필드를 update 할 때 사용된다.
user_serializer = UserSerializer(data=request.data, partial=True)
# raise_exception을 True로 설정할 경우 validation을 통과하지 못했을 때 exception을 발생시킨다.
user_serializer = UserSerializer(data=request.data, raise_exception=True)
serializers.py
)...
class UserSerializer(serializers.ModelSerializer):
...
# validate 함수 선언 시 serializer에서 자동으로 해당 함수의 validation을 해줌
def validate(self, data):
# custom validation pattern
if data.get("userprofile", {}).get("age", 0) < 12:
# validation에 통과하지 못할 경우 ValidationError class 호출
raise serializers.ValidationError(
# custom validation error message
detail={"error": "12세 이상만 가입할 수 있습니다."},
)
# validation에 문제가 없을 경우 data return
return data
...
creator
serializers.py
)...
class UserProfileSerializer(serializers.ModelSerializer):
# hobby는 데이터를 직렬화 할 때, get_hobbys는 profile을 등록할 떄 사용된다.
hobby = HobbySerializer(many=True, required=False, read_only=True)
get_hobbys = serializers.ListField(required=False)
class Meta:
model = UserProfile
fields = ["birthday", "age", "introduction", "hobby", "get_hobbys"]
class UserSerializer(serializers.ModelSerializer):
userprofile = UserProfileSerializer()
def create(self, validated_data):
# object를 생성할때 다른 데이터가 입력되는 것을 방지하기 위해 미리 pop 해준다.
user_profile = validated_data.pop('userprofile')
get_hobbys = user_profile.pop("get_hobbys", [])
# User object 생성
user = User(**validated_data)
user.save()
# UserProfile object 생성
user_profile = UserProfile.objects.create(user=user, **user_profile)
# hobby 등록
user_profile.hobby.add(*get_hobbys)
user_profile.save()
...
class Meta:
model = User
fields = ["username", "password", "fullname", "email", "userprofile"]
...
# sample request data
"""
{
"username": "user_name",
"password": "H0t$ix",
"fullname": "이름",
"email": "sample@email.com",
"userprofile": {
"introduction": "자기소개입니다.",
"birthday": "2000-1-01",
"age": 13,
"get_hobbys": [3,4,5,6]
}
}
"""
updater
serializer를 사용해 기존 데이터들 쉽게 업데이트 할 수 있다.
views.py
)from user.serializers import UserSerializer
...
class UserView(APIView):
def post(self, request):
user = request.user
if user.is_anonymous:
return Response({"error": "로그인 후 이용해주세요", status=status.HTTP_400_BAD_REQUEST}
# 기본적인 사용 방법은 validator, creater와 다르지 않다.
# update를 해줄 경우 obj, data(수정할 dict)를 입력한다.
# partial=True로 설정해 주면 일부 필드만 입력해도 에러가 발생하지 않는다.
user_serializer = UserSerializer(user, data=request.data, partial=True)
if user_serializer.is_valid():
# validator를 통과했을 경우 데이터 저장
user_serializer.save()
return Response({"message": "정상"}, status=status.HTTP_200_OK)
return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
custom update 코드는 기존 update 코드를 덮어쓰며, custom updater를 생성할 경우기존 create 코드는 동작하지 않는다.
custom update 예시(views.py
)
class UserSerializer(serializers.ModelSerializer):
userprofile = UserProfileSerializer()
...
def update(self, instance, validated_data):
# instance에는 입력된 object가 담긴다.
for key, value in validated_data.items():
if key == "password":
instance.set_password(value)
continue
setattr(instance, key, value)
instance.save()
return instance
...
class Meta:
model = User
fields = ["username", "password", "fullname", "email", "userprofile"]