[장고심화]4주차

2022. 10. 28. 07:53스파르타코딩클럽[AI트랙 3기]/장고

Checklist

  • [ ] restful한 api 설계를 할 수 있다.
  • [ ] 미디어 파일과 스태틱 파일에 대해 이해한다.
  • [ ] 게시글 모델과 조회/업로드를 위한 serializer를 만들 수 있다.
  • [ ] 이미지를 포함한 게시글 기능을 개발할 수 있다.
  • [ ] 포스트맨으로 백엔드 개발을 하면서 테스팅을 할 수 있다.
  • [ ] drf에서 댓글 기능을 개발할 수 있다.
  • [ ] drf에서 좋아요 기능을 개발할 수 있다.
  • [ ] drf에서 follow 기능을 개발할 수 있다.
  • [ ] many-to-many 관계를 설정하는 경우와 방법, 그리고 related_name의 사용방법을 이해한다.

 

참고자료

  • 창호 튜터님의 DRF 특강 1
    • RDBMS(RDB) : Relational DataBase Management system의 약자로 MySql, OracleDB 등 관계형 데이터베이스를 지칭한다.
    • Sql : Structured Query Language의 약자로 데이터베이스의 CRUD를 위해 사용되는 언어이다.
      • CRUD : Create(생성), Read(읽기), Update(갱신), Delete(삭제)
    • NoSql : Not Only Sql의 약자로 관계형 데이터베이스가 아닌 다른 형태로 데이터를 저장하며, mongoDB 등이 여기에 해당한다.
    • Table : DB는 기본적으로 테이블로 이루어져 있으며, 필드와 레코드가 존재한다.
      • django에서는 아래와 같이 사용되며, 레코드는 django에서 object라는 이름으로 사용된다.
      • # 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라는 필드가 존재함 # 사용자가 회원가입을 할 때마다 레코드가 하나씩 추가됨. # 즉, 레코드란 데이터베이스에 저장 되는 값들을 지칭하는 것
    • 키 종류
      • FK : Foreign Key의 약자이며, 다른 테이블을 참조 할 때 사용된다.
      • UK : Unique Key의 약자이며, 중복 값을 허용하지 않는다.
      • PK : Primary Key의 약자이며, 테이블에서 반드시 존재해야 한다.
        • PK는 두개 이상 존재 할 수 없고, UK와 마찬가지로 중복 값을 허용하지 않는다.
        • Foreign Key를 사용할 경우 참조 할 테이블의 PK를 바라본다.
    2. django 프로젝트 구조에 대한 이해
    • settings.py
      • django 프로젝트를 실행 할 때 해당 파일을 참조한다.
      • 데이터베이스 설정, 앱 설정, 기본 정책 설정 등을 할 수 있다.
    • models.py
      • DB에 테이블을 추가하고 관리 할 때 사용된다.
      • 테이블에 들어갈 필드, 필드의 속성값 등을 설정 할 수 있다.
      • python manage.py migrations / miarate 명령어를 통해 설정을 DB에 반영시킬 수 있다.
    • views.py
      • django 에서 request 데이터를 받은 후 처리 할 전반적인 로직이 들어간다.
      • urls.py에서 views에 있는 class나 함수를 호출해서 사용하게 된다.
    • urls.py
      • 웹에서 django 프로젝트로 request를 전달 할 때 받아줄 경로를 설정할 수 있다.
    3. DB 모델링 실습
    • django에서 제공해주는 다양한 필드의 속성을 사용해 DB 모델링 하기
      • code(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 : 아무런 동작을 하지 않는다. 참조 관계의 무결성이 손상될 수 있기 때문에 권장하지 않는다.
      • DateField와 DateTimeField는 default 값을 여러 형태로 지정할 수 있다.
        • default = $date : 지정한 값을 기본 값으로 설정한다.
        • auto_now_add = True : 레코드가 생성될 때의 date를 기준으로 값을 지정한다.
        • auto_now = True : 레코드가 save()될 때마다 갱신된다.
    4. admin 페이지 활용
    • 모델링 한 테이블들을 admin에서 추가, 확인, 수정하기
      • code(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)
  • 1. 데이터베이스 용어 정리
  • 창호 튜터님의 DRF 특강 2
    • http method 종류
      • get : 조회
      • post : 생성
      • put : 수정
      • delete : 삭제
    2. views.py에서 리퀘스트 처리하기
    • 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!!'})
    3. postman을 활용한 리퀘스트 실습
    • 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 추가
        • Key : X-CSRFToken
        • Value : {{csrftoken}}
    4. db orm과 구조에 대한 이해
    • queryset, object의 차이에 대한 이해
      • object : 테이블에 입력 된 특정 레코드
      • queryset : object의 집합 ex) [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
    • 데이터 추가, 조회, 삭제, 수정하기
      • code
      • # 추가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()
    • 자주 사용하는 패턴 모음
      • code
      • # 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
    5. custom user 생성 및 사용자 로그인 구현
    • 일반 user model은 필드가 고정되어 있어 커스텀이 어려움
    • custom user model 생성 시 필드들을 자유롭게 커스텀 가능
      • code(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
      • code(settings.py)
      • AUTH_USER_MODEL = 'user.User' # app.table 형태
    • 로그인 기능 구현하기
      • code(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)
    • user admin 설정하기
      • admin을 다른 필드와 동일하게 설정할 경우 비밀번호 설정 시 평문 비밀번호로 입력되어 로그인이 불가능하다. 때문에 아래 예시와 같이 UserAdmin을 상속받아야 한다.
      • code(admin.py)
      • 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', )
    6. settings.py 자주 사용하는 설정
    • debug = True / False debug 모드 설정. static file 처리, allow host, 에러 페이지 등의 설정이 달라진다.
    • LANGUAGE_CODE = 'ko-kr' : 언어 설정
    • TIME_ZONE = 'Asia/Seoul' : Timezone 설정
    • DATABASES : DB 설정
    • INSTALLED_APPS : 사용할 앱 설정
    • 실행되는 SQL 쿼리 보기
    • # <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', }, } }
  • 1. rest api에 대한 이해
  • 창호 튜터님의 DRF 특강 3
    • 외래 키 종류
      • ForeignKey : many-to-one 형태로 특정 테이블에서 다른 테이블을 참조 할 수 있다,
        • 영화관과 시청자의 관계를 나타 낼 때, 시청자 테이블에서 영화관 테이블을 Foreign Key를 사용해 관계를 맺을 수 있다.
      • OneToOneField : one-to-one 형태로 ForeignKey와 동일하지만, 1:1 관계만 가능하다.
        • 사용자 계정 테이블과 사용자 프로필 테이블이 별도로 존재 할 때, 계정 테이블을 프로필에서 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를 역으로 찾을 수 있다.
        • 왜래 키 지정 시 related_name 옵션을 사용해 역참조 시 사용될 이름을 지정할 수 있다.
        • releated_name을 지정하지 않는다면 기본적으로 tablename_set 형태로 지정된다.
        • ex1) user_profile.hobby → 정참조
        • ex2) hobby.userprofile_set → hobby를 참조하고 있는 UserProfile 테이블의 object를 져옴
          • models.py에서 releated_name을 user_hobby 로 지정했다면 hobby.user_hobby와 같이 사용
      • 외래 키 별 역참조 사용 방법
        • foreignkey, many-to-many
          • 한 object를 여러 object에서 참조 가능
            • ex) 한 개의 hobby object를 여러 개의 userprofile에서 참조 가능함
          • 특정 hobby의 object에서 userpofile_set을 사용해 역참조를 할 경우, 데이터는 object가 아닌 queryset으로 보여지게 됨
          • sample code
          • # 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
          • 한 object를 한 ojbect에서만 참조 가능
          • 2개 이상 참조 가능한 fk나 mtm과는 다르게, 무조건 1:1 관계로만 참조 가능
          • 특정 user의 object에서 userprofile을 사용해 역참조를 할 겨웅 데이터는 queryset이 아닌 object로 보여지게 됨
          • sample code
          • # 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이 붙게 됨
    2. drf의 꽃, serializer 활용
    • serializer란?
      • django의 object, queryset 인스턴스 등 복잡한 테이터들을 json같은 다른 콘텐츠 유형으로 쉽게 변환 할 수 있다.
      • create, update 시 validation 기능을 제공한다.
    • serializer Meta class
      • serializer에서 사용되는 설정 파일이다.
      • model에 사용 될 테이블을 적어주고, field에 사용될 필드를 적어준다.
      • extra_kwargs, read_only_fields와 같은 옵션을 통해 다양한 설정이 가능하다.
        • 자세한 내용은 serializer 심화에서 다룰 예정
    • 기본적인 serializer 사용법
      • 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를 생성해 함께 사용할 수 있다.
      • code(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들을 함께 사용할 수 있다.
      • code(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" ] } ] } } """
    3. permission_classes를 활용한 접근 권한 설정
    • view에 접근 할 수 있는 요청을 drf의 permission_classes를 활용해 관리 할 수 있다.
      • permissions.AllowAny : 모든 사용자를 대상으로 접근 허용
      • permissions.IsAuthenticated : 로그인 된 사용자를 대상으로 접근 허용
      • permissions.AllowAny : 모든 사용자를 대상으로 접근 허용
      • 이외에도 다양한 permission class들이 존재
    • permission class 커스텀하기
      • 가입일이 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 if user.is_authenticated and request.method in self.SAFE_METHODS: return True return False
    4. django admin 심화
    • 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)
    • admin 페이지에 Thumbnail 이미지 띄우기
      • 추가 예정
  • 1. 외래 키에 대한 이해
  • 창호 튜터님의 DRF 특강 4
    • 다양한 데이터 검색 문법을 활용해 원하는 값을 찾아낼 수 있다.
      • 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=["운동", "독서"])
        • 더 많은 Field lookups는 여기
    • django orm과 kwargs의 활용
      • 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("?") # 랜덤 셔플
    • 쿼리에서 and와 or 활용
      • 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)
    2. serializer 심화
    • serializer는 데이터 직렬화 외에도 data validation, create, update 기능을 사용할 수 있다.
      • validator
        • serializer에서는 기본적으로 Meta class 내부 field에 포함되어 있는 항목에 맞게 validate를 진행한다.
        • 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" } """
        • serializer에서 사용 가능한 옵션들
        • 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 }, }
        • view에서 사용 가능한 옵션들
        • # 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)
        • custom validator
          • custom validator는 validator 이후에 동작한다.
          • custom validator는 validator와 별개로 동작한다.
            • validator는 데이터의 requierd, invalid 등을 판단하고 custom validator에서는 사용자가 원하는 validation을 추가로 검증 할 수 있다.
          • custom validator 예시(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
        • serializer에서는 validation을 통과할 경우 .save() 메소드를 통해 검증 된 오브젝트를 생성 할 수 있다.
        • 사용 방법은 validator 예시에 작성한 코드와 동일하다.
        • custom creator 코드는 기존 create 코드를 덮어쓰며, custom creator를 생성할 경우 기존 create 코드는 동작하지 않는다.
        • custom 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
        • update 예시(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"]
      • serializer를 사용해 기존 데이터들 쉽게 업데이트 할 수 있다.
    3. mysql 설치 및 연동
    • dumpdata / loaddata를 활용한 데이터 마이그레이션
    • settings.py에서 database 변경
  • 1. django orm 심화
  • Managing files | Django documentation | Django (djangoproject.com)
  • How to manage static files (e.g. images, JavaScript, CSS) | Django documentation | Django (djangoproject.com)
  • How to manage static files (e.g. images, JavaScript, CSS) | Django documentation | Django (djangoproject.com)
  • Model field reference | Django documentation | Django (djangoproject.com)
  • How to deploy static files | Django documentation | Django (djangoproject.com)
  • python - Add a count field to a django rest framework serializer - Stack Overflow
  • Serializers - Django REST framework (django-rest-framework.org)
  • Serializer fields - Django REST framework (django-rest-framework.org)
  • Serializer relations - Django REST framework (django-rest-framework.org)
  • Making queries | Django documentation | Django (djangoproject.com)

'스파르타코딩클럽[AI트랙 3기] > 장고' 카테고리의 다른 글

[장고DRF]1,2주차(CRUD)  (1) 2022.11.14
[장고심화]5주차  (0) 2022.10.30
[장고심화]3주차  (0) 2022.10.27
[장고심화]2주차  (0) 2022.10.26
[장고심화]1주차  (0) 2022.10.25