ForeignKey
: many-to-one 형태로 특정 테이블에서 다른 테이블을 참조 할 수 있다,OneToOneField
: one-to-one 형태로 ForeignKey와 동일하지만, 1:1 관계만 가능하다.ManyToManyField
: many-to-many 형태로 한 개의 필드에서 여러개의 테이블을 참조 할 수 있다.[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
user_profile.hobby
→ 정참조hobby.userprofile_set
→ hobby를 참조하고 있는 UserProfile 테이블의 object를 져옴user_hobby
로 지정했다면 hobby.user_hobby
와 같이 사용# 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이 붙게 됨
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']
"""
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
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"
}
"""
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들을 함께 사용할 수 있다.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
: 모든 사용자를 대상으로 접근 허용permission.py
가입일이 1주일 이상 된 사용자만 접근 가능하도록 설정하기
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)))
admin은 모든 권한이 있고, 인증 된 사용자는 조회만 가능하도록 설정하기
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
elif 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_fields = ('username', '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.TabulaInline):
class UserProfileInline(admin.StackedInline):
model = UserProfile
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == 'hobby':
kwargs['queryset'] = Hobby.objects.filter(id__lte=7)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
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)