[DRF] W6 개념 정리

justyoon·2023년 4월 24일
0
post-thumbnail

DRF 1

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

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)

DRF 2

1. rest api에 대한 이해

  • http method 종류
    • get : 조회
    • post : 생성
    • put : 수정
    • delete : 삭제

2. views.py에서 request 처리하기

  • 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',
            },
        }
    }

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 테이블 구조

      Untitled

역참조에 대한 이해

  • 외래 키를 사용해 참조하는 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 이미지 띄우기
    • 추가 예정

DRF 4

1. django orm 심화

  • 다양한 데이터 검색 문법을 활용해 원하는 값을 찾아낼 수 있다.
    • 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

      serializer를 사용해 기존 데이터들 쉽게 업데이트 할 수 있다.

      • 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"]

3. mysql 설치 및 연동

  • dumpdata / loaddata를 활용한 데이터 마이그레이션
  • settings.py에서 database 변경
profile
with gratitude, optimism is sustainable

0개의 댓글