1. 라이브러리 설치

https://django-environ.readthedocs.io/en/latest/

 

Welcome to Django-environ’s documentation! — Django-environ 0.4.4 documentation

Value from environment or default (if set)

django-environ.readthedocs.io

pip install django-environ

2. .env 파일 생성

settings.py 위치에 .env 파일 생성

DEBUG=on
SECRET_KEY=your-secret-key
DATABASE_URL=psql://urser:un-githubbedpassword@127.0.0.1:8458/database
SQLITE_URL=sqlite:///my-local-sqlite.db
CACHE_URL=memcache://127.0.0.1:11211,127.0.0.1:11212,127.0.0.1:11213
REDIS_URL=rediscache://127.0.0.1:6379/1?client_class=django_redis.client.DefaultClient&password=ungithubbed-secret

3. settings.py 에서 불러오기

import os, environ
env = environ.Env(
    # set casting, default value
    DEBUG=(bool, False)
)
# reading .env file
environ.Env.read_env()

4. 환경변수 불러오기

# Raises django's ImproperlyConfigured exception if SECRET_KEY not in os.environ
SECRET_KEY = env('SECRET_KEY')

# Parse database connection url strings like psql://user:pass@127.0.0.1:8458/db
DATABASES = {
    # read os.environ['DATABASE_URL'] and raises ImproperlyConfigured exception if not found
    'default': env.db(),
    # read os.environ['SQLITE_URL']
    'extra': env.db('SQLITE_URL', default='sqlite:////tmp/my-tmp-sqlite.db')
}

CACHES = {
    # read os.environ['CACHE_URL'] and raises ImproperlyConfigured exception if not found
    'default': env.cache(),
    # read os.environ['REDIS_URL']
    'redis': env.cache('REDIS_URL')
}

5. gitignore 에 .env 추가

728x90

'Study > Django' 카테고리의 다른 글

[Django] decorator  (0) 2021.05.25
[Django] code formatting black 설치 및 적용  (0) 2021.05.20
[Django] Views, Generic Views, Viewset  (0) 2021.05.11
[Django] settings.py - cors, static path  (0) 2021.05.11
[Django] Serializers  (0) 2021.05.11

django rest framework

  • Views : Class-based Views(CBV) / Function Based Views(FBV)
  • Generic views : Mixins / generics APIView
  • Viewsets

@api_view, APIView -> Generic views -> viewsets 으로 갈수록 코드를 간략하게 API로 만들 수 있다.

APIView 클래스와 api_view 장식자

APIView 와 api_view 는 각각 CBV(클래스기반뷰)와 FBV(함수기반뷰)에 대응

두 가지 모두 뷰에 여러가지 기본 설정을 부여하게 됩니다. 이는 아래와 같고 상황에 맞춰서 이를 커스튬하여 사용하게 됩니다.

  • 직렬화 클래스 지정
    • renderer_classes
    • default
      • JSON 직렬화 : rest_framework.renderers.JSONRenderer
      • HTML 페이지 직렬화 : rest_framework.renderers.TemplateHTMLRenderer
  • 비직렬화 클래스 지정
    • parser_classes
    • default
      • JSON 포맷 처리 : rest_framework.parsers.JSONParser
      • FormParser : rest_framework.parsers.FormParser
      • MultiPartParser : rest_framework.parsers.MultiPartParser
  • 인증 클래스 지정
    • authentication_classes
    • default
      • 세션기반인증 : rest_framework.authentication.SessionAuthentication
      • HTTP basic 인증 : rest_framework.authentication.BasicAuthentication
  • 사용량 제한 클래스 지정
    • throttle_classes
    • default
      • 빈 튜플
  • 권한 클래스 지정
    • permission_classes
    • default
      • 누구라도 접근 허용 : rest_framework.permissions.AllowAny
  • 요청에 따라 적절한 직렬화/비직렬화 선택
    • content_negotiation_class
    • 같은 URL 요청에 대해서 JSON 응답을 할 지, HTML 응답을 할 지 판단
    • default
      • rest_framework.negotiation.DefaultContentNegotiation
  • 요청 내역에서 API 버전 정보를 탐지할 클래스 지정
    • versioning_class
    • 요청 URL의 HEADER에서 버전 정보를 탐지하여 맞는 버전을 호출
    • default
      • 버전 정보를 탐지하지 않습니다. : None

APIView

우선 APIView 부터 자세히 알아보도록 합시다.

  • 위에서 말했듯이 이는 CBV 중 하나이기 때문에 하나의 URL 에 대해서만 처리를 할 수 있습니다.
    • /post/ 에 대한 CBV
      • get : 포스팅 목록
      • post : 새 포스팅 생성
    • /post/int:pk/ 에 대한 CBV
      • get : pk 번 포스팅 내용
      • put : pk번 포스팅 수정
    • delete : pk번 포스팅 삭제
  • 요청 method 에 맞게 맴버함수를 정의하면 해당 method 로 request가 들어올 때 호출되게 됩니다.
  • def get(self, request): pass def post(self, request): pass def put(self, request): pass def delete(self, request): pass
  • 각 method 가 호출되면 위에서 봤던 설정에 맞춰 처리가 이루어집니다.
    • 직렬화/비직렬화
    • 인증 체크
    • 사용량 제한 체크
    • 권한 체크
    • 요청한 버전 체크
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions
from django.contrib.auth.models import User

class ListUsers(APIView):
    """
    View to list all users in the system.

    * Requires token authentication.
    * Only admin users are able to access this view.
    """
    authentication_classes = [authentication.TokenAuthentication]
    permission_classes = [permissions.IsAdminUser]

    def get(self, request, format=None):
        """
        Return a list of all users.
        """
        usernames = [user.username for user in User.objects.all()]
        return Response(usernames)
  • 실습
# models.py

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    create_at = models.DateTimeField(auto_now_add=True)
    update_at = models.DateTimeField(auto_now=True)
# serializers.py

from rest_framework.serializers import ModelSerializer
from .models import Post

class PostSerializer(ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'
# views.py

from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Post
from .serializers import PostSerializer

# 포스팅 목록 및 새 포스팅 작성
class PostListAPIView(APIView):
    def get(self, request):
        serializer = PostSerializer(Post.objects.all(), many=True)
        return Response(serializer.data)
    def post(self, request):
        serializer = PostSerializer(data=request.data)
        if serializer.is_valid():
              serializer.save()
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)

from django.shortcuts import get_object_or_404

# 포스팅 내용, 수정, 삭제
class PostDetailAPIView(APIView):
    def get_object(self, pk):
        return get_object_or_404(Post, pk=pk)

    def get(self, request, pk, format=None):
        post = self.get_object(pk)
        serializer = PostSerializer(post)
        return Response(serializer.data)

    def put(self, request, pk):
          post = self.get_object(pk)
        serializer = PostSerializer(post, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk):
        post = self.get_object(pk)
        post.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
# urls.py

from django.urls import path, include
from . import views

urlpatterns = [
    # FBV
    path('post/', views.PostListAPIView.as_view()),
    path('post/<int:pk>/',views.PostDetailAPIView.as_view()),
]

@api_view 장식자

api_view는 FBV 에 대해서 사용하는 장식자

  • 실습
# views.py

from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Post
from .serializers import PostSerializer
from rest_framework.decorators import api_view

@api_view(['GET','POST'])
def post_list(request):
    if request.method == 'GET':
        qs = Post.objects.all()
        serializer = PostSerializer(qs, many=True)
        return Response(serializer.data)
    else:
        serializer = PostSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)

@api_view(['GET','PUT','DELETE'])
def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.method == 'GET':
        serializer = PostSerializer(post)
        return Response(serializer.data)
    elif request.method == 'PUT':
        serializer = PostSerializer(post, data=reqeust.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    else:
        post.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

from news.models import Article
from news.api.serializers import ArticleSerializer

@api_view(['GET', 'POST'])
def article_list_create_api_view(request):
  '''
  코드 중략
  '''

@api_view(['GET', 'PUT', 'DELETE'])
def article_detail_api_view(request, pk):
  # try, except 대신 get_object_or_404를 import 해서 쓸 수도 있다.
  try:
    # pk(인스턴스의 id)값을 받아 어떤 인스턴스인지 특정
    # url slug로 pk값을 받도록 urls.py에서 설정해준다.
    article = Article.objects.get(pk = pk)
  # 받은 pk값으로 조회했을 때 해당하는 인스턴스가 없다면 출력할 에러 코드와 메시지를 설정한다.
  except Article.DoesNotExist:
    return Response({'error' : {
      'code' : 404,
      'message' : "Article not found!"
    }}, status = status.HTTP_404_NOT_FOUND)
  # 만약 article이 존재한다면,
  if request.method == 'GET':
    serializer = ArticleSerializer(article)
    return Response(serializer.data)
  elif request.method == 'PUT':
    serializer = ArticleSerializer(article, data = request.data)
    # request에서 data를 받았으니 .is_valid() 필수
    if serializer.is_valid():
      serializer.save()
      return Response(serializer.data)
    return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)
  elif request.method == 'DELETE':
    article.delete()
    # 인스턴스를 삭제한 뒤에는 204 NO CONTENT를 리턴
    return Response(status = status.HTTP_204_NO_CONTENT)
# urls.py
from django.urls import path, include
from . import views
urlpatterns = [ # FBV
    path('cbv/post/', views.post_list),
    path('cbv/post/<int:pk>/',views.post_detail),
]

generics APIView

  1. Basic settings
  • 다음 속성들을 통해 View를 컨트롤함
    • queryset : View에서 객체를 반환하는 데 사용해야 하는 쿼리셋. 반드시 1) queryset 속성을 설정하거나, 2) get_queryset() 메서드로 override해서 사용해야 함.
    • serializer_class : 입력된 값을 validate하거나 deserialize하거나, 출력값을 serialize할 때 사용하는 serializer 클래스. 일반적으로 이 속성을 설정하거나 get_serializer_class()메소드로 override해서 사용해야 함
    • lookup_field : 개별 모델 인스턴스의 object 조회를 수행 할 때 사용해야하는 모델 필드. 기본값은 'pk'임. 하이퍼링크 된 API에 custom 값을 사용해야 하는 경우 API views와 serializer 클래스가 lookup필드를 설정해야 함.
  1. Pagination
  • 다음 속성을 통해 리스트뷰에서 페이지네이션을 컨트롤하게 된다.
    • pagination_class : 리스트 결과들을 페이지네이션할 때 사용되는 클래스임. 디폴트는 DEFAULT_PAGINATION_CLASS (rest_framework.pagination.PageNumberPagination 모듈안에) 세팅으로 결정됨.
  1. Filtering
  • filter_backends : A list of filter backend classes that should be used for filtering the queryset. Defaults to the same value as the DEFAULT_FILTER_BACKENDS setting.

Mixins 상속

APIView 는 위에서 봤듯이 각 request method 마다 직접 serializer 처리를 해주었습니다.
하지만 이러한 부분들은 많이 사용되므로 여러 serializer 에 대해서 중복이 발생합니다.
따라서 rest_framework.mixins 에서는 이러한 기능들이 미리 구현이 되어 있습니다.

단, Mixin 클래스에 존재하는 메소드나 속성을 상속받는 클래스에서 사용할 경우 믹스인 클래스의 메소드가 오버라이딩되어 의도하지 않게 작동할 수 있으니 주의

  • CreateModelMixin
    • 모델 인스턴스를 생성하고 저장하는 역할을 하는 믹스인
    • .create(request, *args, **kwargs) 메소드로 호출하여 사용
    • 성공 시, 201 Created 리턴
    • 실패 시, 400 Bad Request 리턴
  • ListModelMixin
    • Queryset을 리스팅하는 믹스인
    • .list(request, *args, **kwargs) 메소드로 호출하여 사용
    • GenericAPIView의 self.filter_queryset, self.get_queryset, self.get_serializer 등의 메소드를 활용해 데이터베이스에 저장되어 있는 데이터들을 목록 형태로 response body로 리턴
    • 성공 시, 200 OK response 리턴
  • RetrieveModelMixin
    • 존재하는 모델 인스턴스를 리턴해 주는 믹스인
    • .retrieve(request, *args, **kwargs) 메소드로 호출하여 사용
    • 성공 시, 200 OK response 리턴
    • 실패 시, 404 Not Found 리턴
  • UpdateModelMixin
    • 모델 인스턴스를 수정하여 저장해 주는 믹스인
    • .update(request, *args, **kwargs) 메소드로 호출하여 사용
    • 부분만 변경하고자 할 경우, .partial_update(request, *args, **kwargs)메소드를 호출하여야 하며, 이 때 요청은 HTTP PATCH requests여야 함
    • 성공 시, 200 OK response 리턴
    • 실패 시, 404 Not Found 리턴
  • DestroyModelMixin
    • 모델 인스턴스를 삭제하는 믹스인
    • .destroy(request, *args, **kwargs) 메소드로 호출하여 사용
    • 성공 시, 204 No Content 리턴
    • 실패 시, 404 Not Found 리턴

queryset 과 serializer_class 를 지정해주기만 하면 나머지는 상속받은 Mixin 과 연결해주기만 하면 됩니다.

  • 실습
# views.py

from rest_framework.response import Response
from rest_framework import generics
from rest_framework import mixins
from .models import Post
from .serializers import PostSerializer

class PostListMixins(mixins.ListModelMixin, mixins.CreateModelMixin,generics.GenericAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request)

    def post(self, request, *args, **kwargs):
        return self.create(request)

class PostDetailMixins(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.delete(request, *args, **kwargs)
# urls.py

from django.urls import path, include
from . import views

urlpatterns = [
    # Mixin
    path('mixin/post/', views.PostListMixins.as_view()),
    path('mixin/post/<int:pk>/', views.PostDetailMixins.as_view()),
]

Concrete View Classes

Mixin 을 상속함으로서 반복되는 내용을 많이 줄일 수 있었습니다. 하지만 여러 개를 상속해야 하다보니 가독성이 떨어집니다. 다행히도 rest_framework 에서는 저들을 상속한 새로운 클래스를 정의해놨습니다.

총 9개의 클래스로 다음과 같습니다.

  • generics.CreateAPIView : 생성
  • generics.ListAPIView : 목록
  • generics.RetrieveAPIView : 조회
  • generics.DestroyAPIView : 삭제
  • generics.UpdateAPIView : 수정
  • generics.RetrieveUpdateAPIView : 조회/수정
  • generics.RetrieveDestroyAPIView : 조회/삭제
  • generics.ListCreateAPIView : 목록/생성
  • generics.RetrieveUpdateDestroyAPIView : 조회/수정/삭제
# rest_framework/generics.py

class ListCreateAPIView(mixins.ListModelMixin,
                        mixins.CreateModelMixin,
                        GenericAPIView):

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)
  • 실습
# views.py

from rest_framework import generics
from .models import Post
from .serializers import PostSerializer

class PostListGenericAPIView(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

class PostDetailGenericAPIView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
# urls.py

from django.urls import path, include
from . import views

urlpatterns = [
    # Mixin
    path('mixin/post/', views.PostListGenericAPIView.as_view()),
    path('mixin/post/<int:pk>/', views.PostDetailGenericAPIView.as_view()),
]

ViewSet

ViewSet 은 CBV 가 아닌 헬퍼클래스로 두 가지 종류가 있습니다.

  • viewsets.ReadOnlyModelViewSet : 목록 조회, 특정 레코드 조회
  • viewsets.ModelViewSet : 목록 조회, 특정 레코드 생성/조회/수정/삭제
  • 실습
# views.py
# post 모델

from .models import Post
from .serializers import PostSerializer
from rest_framework import viewsets

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

Router 를 통해서 하나의 url 로 처리가 가능

#. urls.py

from django.urls import path, include
from . import views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('viewset',views.PostViewSet)

urlpatterns = [
    path('',include(router.urls)),
]
728x90

'Study > Django' 카테고리의 다른 글

[Django] code formatting black 설치 및 적용  (0) 2021.05.20
[Django] 환경 변수 분리하기 django-environ  (0) 2021.05.18
[Django] settings.py - cors, static path  (0) 2021.05.11
[Django] Serializers  (0) 2021.05.11
[Django] models.py  (0) 2021.05.10

settings.py

초기설정

  1. Allowed_hosts

ALLOWED_HOSTS = ['*']

  1. Time_zone

TIME_ZONE = 'Asia/Seoul'

  1. INSTALLED_APPS

cors 설정

CORS란? (Crosss-Origin Resource Sharing)

웹 페이지 상의 제한된 리소스를 최초 자원이 서비스된 도메인 밖의 다른 도메인으로부터 요청할 수 있게 허용하는 구조

  1. django-cors-headers 설치

pip install django-cors-headers

  1. settings.py 에 설정 추가
INSTALLED_APPS =[
    'corsheaders', # CORS 관련 추가
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', # CORS 관련 추가
]

# CORS 관련 추가
CORS_ORIGIN_WHITELIST = ['http://127.0.0.1:3000' ,'http://localhost:3000'] 
CORS_ALLOW_CREDENTIALS = True

MIDDLEWARE에 CorsMiddleware를 최상단에 기입해준다.

CORS_ORIGIN_WHITELIST에 연동할 ip와 포트를 적어준다.

mysql 연결

https://mugon-devlog.tistory.com/28

[

[Django] app, mysql 추가 및 연결

1. user, board app 추가 # user app 추가 django-admin startapp user # board app 추가 django-admin startapp board 2. rest framework 추가 pip install djangorestframework 3. settings.py 설정 app, rest f..

mugon-devlog.tistory.com

](https://mugon-devlog.tistory.com/28)

static path 추가

STATIC_URL = "/static/"

# collectstatic 명령어로 모이는 static 파일들을 모을 위치
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")

STATICFILES_DIRS = [
    BASE_DIR / "static",
]

# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

LOGIN_REDIRECT_URL = reverse_lazy("home")
LOGOUT_REDIRECT_URL = reverse_lazy("accountapp:login")

MEDIA_URL = "/media/"

MEDIA_ROOT = os.path.join(BASE_DIR, "media")

root 폴더위치에 static 폴더 생성

728x90

'Study > Django' 카테고리의 다른 글

[Django] 환경 변수 분리하기 django-environ  (0) 2021.05.18
[Django] Views, Generic Views, Viewset  (0) 2021.05.11
[Django] Serializers  (0) 2021.05.11
[Django] models.py  (0) 2021.05.10
[Django] app, mysql 추가 및 연결  (0) 2021.05.10

Serializers

Serialize(직렬화)

쿼리셋,모델 인스턴스 등의 complex type(복잡한 데이터)를 JSON, XML등의 컨텐트 타입으로 쉽게 변환 가능한 python datatype으로 변환시켜줌

Deserialize

받은 데이터(크롤링시 parse사용>python datatype)를 validating 한 후에 parsed data를 complex type으로 다시 변환
이때는 반드시 is_valid()를 호출하여 검사하자


인스턴스 생성 (== model)

Serializer의 생성자는 아래 코드와 같이 첫번째 인자로 instance를 받으며, 두번째 인자로 data를 받는다.

# rest_framework/serializers.py

class BaseSerializer(Field):
    def __init__(self, instance=None, data=empty, **kwargs):
        # 생략

class Serializer(BaseSeializer):
  # 생략

############

#DB
from datetime import datetime

class Comment:
    def __init__(self, email, content, created=None):
        self.email = email
        self.content = content
        self.created = created or datetime.now()

#원하는곳에서의 인스턴스생성
comment = Comment(email='leila@example.com', content='foo bar')

Serialize 생성

#serializers.py
from rest_framework import serializers

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

serialize 하기


serializer = CommentSerializer(comment)
serializer.data

# {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}

data 인자만 지정할 때에는 아래 코드와 같이 필수적으로 keyword를 지정해주어야 한다.

serializer = PostSerializer(post)
serializer = PostSerializer(data=request.data)
serializer = PostSerializer(post, data=reqeust.data)
serializer = PostSerializer(post, reqeust.data)
serializer = PostSerializer(reqeust.data) # 오류

data= 인자가 주어지면, 다음 순서로 처리된다.

.is_valid()가 호출이 되면

.initial_data 필드에 접근할 수 있고,

.validated_data 를 통해 유효성 검증에 통과한 값들에 대한 사전에 접근. .save()시에 사용됨.

.errors : 유효성 검사에 대한 오류 내역

.data : 유효성 검사 후에, 갱신된 인스턴스에 대한 필드값 사전


deserialize

  • Parsing된 데이터(Python datatype)을 is_valid()해주고 추후 save()시에 qs로 가능
import io
from rest_framework.parsers import JSONParser

stream = io.BytesIO(json) #JSON 문자열을 바이트 타입으로 바꾸고, ByteIO 객체로 바꾼다.
data = JSONParser().parse(stream) #JSONParser 의 parser() 메서드를 이용하여 딕셔너리 형태로 변환한다.
serializer = CommentSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}

Saving instances

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

    def create(self, validated_data):
        return Comment(**validated_data)

    def update(self, instance, validated_data):
        instance.email = validated_data.get('email', instance.email)
        instance.content = validated_data.get('content', instance.content)
        instance.created = validated_data.get('created', instance.created)
        return instance

# .save() will create a new instance.
serializer = CommentSerializer(data=data)

# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)

serializer.save()

# save()에 추가적인 attribute 사용가능
serializer.save(owner=request.user)

save()가 호출되면, 유효성 검사를 통과한 .validated_data와 kwargs dict를 합쳐서 DB로의 저장을 시도한다. 이 때, self.instance의 유무에 따라 저장하는 방식이 다르다.

  • self.instance 값이 있을 때 : update() 를 통해서 저장
  • self.instance 값이 없을 때 : create() 를 통해서 저장

Validators

DRF에서는 유일성 여부 체크를 도와주는 Validator를 제공하며, queryset 범위를 제한하여 지정 범위 내에서의 유일성 여부를 체크 가능



유효성 검사 예외

rest_framework.exceptions.ValidationError를 기본으로 사용하며, 이는 응답 상태코드 400으로 처리한다.

Serializer에서 유효성 검사 함수 지정

** ModelSerializer를 사용한다면, 유효성 검사 함수는 모델 측에 지정하는 것이 관리측면에서 좋다.

아래에서는 모델 측이 아닌 Serializer에서 유효성 검사를 하는 예시를 알아본다.

  1. Field 에 대한 validator
    validate_{field name} 이름의 함수를 사용하며 특정 필드에 대해 검사한다.
# serializers.py

from rest_framework import serializers
from rest_framework.exceptions import ValidationError

class PostSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=100)

    def validate_title(self, value):
        if '제목' not in value:
            raise ValidationError('제목이라는 말이 들어가야 합니다.')
        return value
  1. object 에 대한 validator

validate 이름의 함수를 사용하며 다수 필드에 대해 검사한다.

class PostSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=100)

    def validate(self, data):
        if '제목' not in data['title']:
            raise ValidationError('제목이라는 말이 들어가야 합니다.')
        return data

DB 반영을 돕는 perform 함수

사용자의 입력과 함께 추가적인 정보(예를 들면, 사용자의 ip)를 함께 DB에 저장해야 하는 경우, perform 함수를 재정의해 커스튬해야한다.

먼저, 아래와 같이 model을 정의하고 title의 값만 사용자로부터 입력받는다.

# models.py

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    ip = models.GenericIPAddressField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)


# serializers.py

from rest_framework.serializers import ModelSerializer
from .models import Post

class PostSerializer(ModelSerializer):
    class Meta:
        model = Post
        fields = ['title']

그리고 ip를 자동으로 추가하기 위해 perform_create를 커스튬한다.

# views.py

from rest_framework.viewsets import ModelViewSet
from .models import Post
from .serializers import PostSerializer

class PostViewSet(ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    def perform_create(self, serializer):
        serializer.save(ip=self.request.META['REMOTE_ADDR'])
728x90

'Study > Django' 카테고리의 다른 글

[Django] Views, Generic Views, Viewset  (0) 2021.05.11
[Django] settings.py - cors, static path  (0) 2021.05.11
[Django] models.py  (0) 2021.05.10
[Django] app, mysql 추가 및 연결  (0) 2021.05.10
[Django + pycharm] 개발환경 세팅  (0) 2021.05.02

models.py

https://docs.djangoproject.com/ko/3.2/topics/db/models/
https://brunch.co.kr/magazine/django-doc


간단한 구조

  • models.py
from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
  • sql문
CREATE TABLE myapp_person (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
);
  • 일반적인 모델
from django.db import models

class MyModelName(models.Model):
    """A typical class defining a model, derived from the Model class."""

    # Fields
    my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')
    ...

    # Metadata
    class Meta:
        ordering = ['-my_field_name']

    # Methods
    def get_absolute_url(self):
        """Returns the url to access a particular instance of MyModelName."""
        return reverse('model-detail-view', args=[str(self.id)])

    def __str__(self):
        """String for representing the MyModelName object (in Admin site etc.)."""
        return self.field_name

필드

https://brunch.co.kr/@ddangdol/1

모델에서 가장 중요하고, 유일하게 필수적인 부분은 데이터베이스 필드 목록을 정의하는 것입니다. 필드는 클래스 속성으로 정의됩니다. clean, save, delete 같은 :doc:모델 API와 충돌할 수 있는 단어를 필드 이름으로 사용하지 않도록 주의하세요.

from django.db import models

class Musician(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    instrument = models.CharField(max_length=100)

class Album(models.Model):
    artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    release_date = models.DateField()
    num_stars = models.IntegerField()

Field Option

https://brunch.co.kr/@ddangdol/3

  • null
  • blank
  • choice
  • db_column
  • db_index
  • db_tablespace
  • default
  • editable
  • error_messages
  • help_text
  • primary_key
  • unique
  • verbose_name
  • validators

Field Type

https://docs.djangoproject.com/ko/3.2/ref/models/fields/#field-types
https://brunch.co.kr/@ddangdol/4

  • AutoField(**options)
  • BigAutoField(**options)
  • BigIntegerField(**options)
  • BinaryField(max_length=None, **options)
  • BooleanField(**options)
  • CharField(max_length=None, **options)
    • For large amounts of text, use TextField.
  • DateField(auto_now=False, auto_now_add=False, **options)
  • DateTimeField(auto_now=False, auto_now_add=False, **options)
  • DecimalField(max_digits=None, decimal_places=None, **options)
  • DurationField(**options)
  • EmailField(max_length=254, **options)
  • FileField(upload_to=None, max_length=100, **options)
  • FilePathField(path='', match=None, recursive=False, allow_files=True, allow_folders=False, max_length=100, **options)
  • FloatField(**options)
  • ImageField(upload_to=None, height_field=None, width_field=None, max_length=100, **options)
  • IntegerField(**options)
  • GenericIPAddressField(protocol='both', unpack_ipv4=False, **options)
  • JSONField(encoder=None, decoder=None, **options)
  • NullBooleanField(**options)
  • PositiveBigIntegerField(**options)
  • PositiveIntegerField(**options)
  • PositiveSmallIntegerField(**options)
  • SlugField(max_length=50, **options)
  • SmallAutoField(**options)
  • SmallIntegerField(**options)
  • TextField(**options)
  • TimeField(auto_now=False, auto_now_add=False, **options)
  • URLField(max_length=200, **options)
  • UUIDField(**options)

Relationship fields

https://docs.djangoproject.com/ko/3.2/ref/models/fields/#module-django.db.models.fields.related
https://brunch.co.kr/@ddangdol/5


meta 데이터

https://docs.djangoproject.com/en/2.0/ref/models/options/#model-meta-options

아래와 같이 class Meta를 선언하여 모델에 대한 모델-레벨의 메타데이타를 선언

class Meta:
    ordering = ['-my_field_name']

모델 타입을 쿼리(query)할 때 반환되는 기본 레코드 순서를 제어
예를 들어, 우리가 기본적으로 아래와 같이 책들을 정렬하려고 한다면:

ordering = ['title', '-pubdate']

책들은 A-Z까지 알파벳 순으로 정렬되고, 그 후에는 제목(title) 안에 있는 발행일 별로 가장 최근 것부터 가장 오래된 것 순으로 정렬


model methods

비지니스 로직을 담는 곳

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):
        return self.name

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()

    def baby_boomer_status(self):
        "Returns the person's baby-boomer status."
        import datetime
        if self.birth_date < datetime.date(1945, 8, 1):
            return "Pre-boomer"
        elif self.birth_date < datetime.date(1965, 1, 1):
            return "Baby boomer"
        else:
            return "Post-boomer"

    @property
    def full_name(self):
        "Returns the person's full name."
        return '%s %s' % (self.first_name, self.last_name)

Model inheritance

https://docs.djangoproject.com/en/3.2/topics/db/models/#model-inheritance

from django.db import models

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True
        ordering = ['name']

class Unmanaged(models.Model):
    class Meta:
        abstract = True
        managed = False

class Student(CommonInfo, Unmanaged):
    home_group = models.CharField(max_length=5)

    class Meta(CommonInfo.Meta, Unmanaged.Meta):
        pass
728x90

'Study > Django' 카테고리의 다른 글

[Django] settings.py - cors, static path  (0) 2021.05.11
[Django] Serializers  (0) 2021.05.11
[Django] app, mysql 추가 및 연결  (0) 2021.05.10
[Django + pycharm] 개발환경 세팅  (0) 2021.05.02
Python 가상환경 설치 및 비교  (0) 2021.05.02

1. user, board app 추가

# user app 추가
django-admin startapp user

# board app 추가
django-admin startapp board

2. rest framework 추가

pip install djangorestframework

3. settings.py 설정

app, rest frame work 추가

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 아래부분 추가
    'user',
    'board',
    'rest_framework',
]

mysql 추가

pip install mysqlclient

 

mysqlclient 설치오류
아래와 같은 오류가 발생할때

Could not find a version that satisfies the requirement mysqlclient


mysql이 환경변수에 등록되지 않아서 발생할 수 있음

  1. 확인
which mysql_config

 

  1. zshrc 경우 (vscode 설치)
code ~/.zshrc

# 환경변수 등록
export PATH=$PATH:/usr/local/mysql/bin

 

settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # mysql 엔진 설정
        'NAME': 'blog', # 데이터베이스 이름
        'USER': 'root', # 데이터베이스 연결시 사용할 유저 이름
        'PASSWORD': 'password', # 유저 패스워드
        'HOST': 'localhost',
        'PORT': '3306'
    }
}

 

migrate 할때 NameError: name '_mysql' is not defined 에러

  1. 원인
brew 로 mysql-client 를 설치한 경우 /usr/local/mysql/bin/mysql/lib의 경로에 cpython-38-darwin.so 파일이 위치해 있는데, 파이썬에서 찾지 못해서 에러가 난다. 간단하게는 심볼릭 링크를 만들어주면 해결이 된다.


2. 해결 환경변수 등록

code ~/.zshrc

export DYLD_LIBRARY_PATH=/usr/local/mysql/lib/:$DYLD_LIBRARY_PATH

 

db 연결 확인

python manage.py migrate
728x90

'Study > Django' 카테고리의 다른 글

[Django] settings.py - cors, static path  (0) 2021.05.11
[Django] Serializers  (0) 2021.05.11
[Django] models.py  (0) 2021.05.10
[Django + pycharm] 개발환경 세팅  (0) 2021.05.02
Python 가상환경 설치 및 비교  (0) 2021.05.02

+ Recent posts