DRF 보다 빠르고, 추상화가 적어서 사용자가 자유롭게 파라미터를 정의할 수 있는 도구 입니다. 이번 페이지 에서는 Django Ninja Tutorial 내용을 정리하면서 핵심적인 내용들을 요약해 보도록 하겠습니다.

Contents

Request Body

  1. Ninja API
  2. Schema Data Model
  3. Request body with path & Query parameters

이렇게 구성되어 있습니다. API 연결하는 Serializer 를 사용할 때에는, Schema Class 에서 함수 (value()) 를 정의해서 활용할 수 있습니다.

# Example 1 (inline Schema)
@api.get("/items/{int:item_id}")
def read_item(request, item_id:int):
    return {"item_id": item_id}

# Example 2 (Schema Class)
import datetime
from ninja import Schema, Path

## Schema Data Model
class PathDate(Schema):
    year: int
    month: int

    def value(self):
        return datetime.date(self.year, self.month)

## Request body
@api.get("/events/{year}/{month}")               # Path parameters
def events(request, date: PathDate = Path(...)): # Query parameters
    return {"date": date.value()}

Path & Query Parameters

PathQuery에서 정의한 파라미터 변수의 이름을 일치 시키면, 자동으로 상호 동작을 합니다.

이때 파라미터 변수가 필수인지 여부는, 초깃값을 설정 여부 에 따라서 결정 됩니다. 초깃값을 입력하면 그에 따라서 Request Body 함수결과를 출력 합니다. 초깃값이 없으면 사용자가 필수로 입력을 해야만 함수가 동작하는 구조로 되어 있습니다.

Response Schema

@api.get("product/{project_id}", response=ProjectOut)
def get_project(request, project_id:int):
    return get_object_or_404(Project, id=project_id)

@api.api_operation(['PUT','PATCH'], "product/{project_id}")
def update_project(request, project_id:int, payload: ProjectUpdate):
    project = get_object_or_404(Project, id=project_id)
    for key, value in payload.dict().items():
        setattr(project, key, value)
    project.save()

조작할 변수가 많고 복잡해 지는 경우에는 보다 효과적인 방법을 필요로 하게 되는데, from ninja import PathQuery parametor 에 사용 합니다.

Form Data

Post Form 형식의 API 를 사용할 때에는 request.POST 데이터를 application x-www-form-urlencoded or multipart/form-data 포맷으로 전송해야 합니다.

from ninja import Form

@api.post("/login")
def login(request, user:str=Form(...), psword:str=Form(...)):
    return {'user': user, 'psword': '*****'}

앞에서 살펴본 Path 함수와 동일하게 Form 함수를 사용하면 Schema 클래스를 바로 Queryset 의 파라미터로 재활용 할 수 있습니다. Schema 재활용과 함께 inline 방식을 혼합하여 사용할 수 있습니다.

class Item(Schema):
    name: str
    description: str = None
    price: float
    quantity: int

@api.post("/items/{item_id}")
def update(request, item_id: int, q: str, item: Item=Form(...)):
    return {"item_id": item_id, "item": item.dict(), "q": q}

File Upload

Django 의 FileField 를 API 로 작업할 때에 활용하는 예시 입니다.

from ninja import NinjaAPI, File
from ninja.files import UploadedFile
from typing import List

@api.post("/upload")
def upload(request, file: UploadedFile = File(...)):
    data = file.read()
    return {'name': file.name, 'len': len(data)}

@api.post("/upload-list")
def upload_many(request, files: List[UploadedFile] = File(...)):
    return [f.name for f in files]

Response Schema

Request Body 함수에서 서버동작에 따른 메세지 결과를 각각 정의할 수 있습니다.

from ninja.responses import codes_4xx, codes_2xx

@api.post('/login', response={200: Token, codes_4xx: Message})
def login(request, payload: Auth):
    if `auth_not_valid`:
        return 401, {'message': 'Unauthorized'}
    if `negative_balance`:
        return 402, {'message': 'Insufficient balance amount. Please proceed to a payment page.'}
    return 200, {'token': xxx, ...}

Schema 정의하는 클래스는 다음과 같은 방식으로도 사용할 수 있습니다.

class UserSchema(ModelSchema):
    class Config:
        model = User
        model_fields = "__all__"

class UserSchema(ModelSchema):
    class Config:
        model = User
        model_exclude = ['password', 'last_login', 'user_permissions']

Schema 클래스를 상속 할 수 있습니다. 상속을 받는 자손 클래스에서는 필드와 주석의 내용을 추가할 수 있습니다

class GroupSchema(ModelSchema):
    class Config:
        model = Group
        model_fields = ['id', 'name']


class UserSchema(ModelSchema):
    groups: List[GroupSchema] = []

    class Config:
        model = User
        model_fields = ['id', 'username', 'first_name', 'last_name']

Schema 클래스의 API End point 중, 사용자 함수를 사용하는 End Point 를 추가할 수 있습니다. 아래의 예시는

def to_camel(string: str) -> str:
    return ''.join(word.capitalize() for word in string.split('_'))

class CamelModelSchema(Schema):
    str_field_name: str
    float_field_name: float

    class Config(Schema.Config):
        alias_generator = to_camel

클래스 형식과 별개로, 함수 형태로도 활용할 수 있습니다. .update_forward_refs() 메서드는 객체 내부에 ForeignKey 를 사용하는 객체가 있는 경우에 적용 합니다. Create Schema 에 대한 보다 자세한 내용은 링크를 참고 합니다.

UserSchema = create_schema(
    User,
    name='UserSchema',  # !!! this is important for update_forward_refs()  
    fields=['id', 'username']
    custom_fields=[
        ('manager', 'UserSchema', None),
    ]
)
UserSchema.update_forward_refs()


Ninja Auth

Django 에서 React 와 보안을 위해 사용하는 방법중에 PyJWT 를 활용한 Token 을 검증하는 방법부터, SNS 소셜 미디어를 활용한 로그인 방법까지 알아 보도록 하겠습니다.

로그인 관련 개념 내용들은 쉽게 알아보는 서버 인증 2편(Access Token + Refresh Token)DRF JWT 인증방식 로그인, 회원가입 내용을 참고합니다

PyJWT

아래 코드는 {"some":"payload"} 객체를 HS256 로 암화화된 방법을 사용해서 전달하는 내용 입니다.

암호를 생성하고, 해석하는데 필요한 Key 객체로써, 아래의 예시에서는 key 데이터를 사용하여 확인 합니다.

import jwt
key = "secret"
encoded = jwt.encode({"name": "payload", "password":"secret1234"}, key, algorithm="HS256")
print(encoded)
#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg

jwt.decode(encoded, key, algorithms="HS256")
#{'some': 'payload'}

Django Ninja

Reddit 에 설명한 예시 내용을 이해 해보겠습니다.