git branch명령은 branch 생성및 제거, 확인등의 기능을 하는 명령어로 주요 명령어만 요약하였습니다.
자세한 내용은 git-scm Git-브랜치-브랜치-관리에서 확인하세요.


1. git branch [-l]

로컬 branch 정보를 보여줍니다. (-l 옵션은 생략가능)

$ git branch
* master
  work1

2. git branch -v

로컬 branch의 정보를 마지막 커밋 내역과 함께 보여줍니다.

$ git branch -v
* master   4bbc62f commit message 'm1'
  work1    fe7f049 commit message 'w1'
  work_new 4bbc62f commit message 'work_new1'

3. git branch -r

리모트 저장소의 branch 정보를 보여줍니다.

$ git branch -r
  origin/master
  origin/work1

4. git branch -a

로컬/리모트 저장소의 모든 branch 정보를 보여줍니다.

$ git branch -a
* master
  work1
  remotes/origin/master
  remotes/origin/work1

5. git branch (이름)

로컬에 새로운 branch를 생성합니다.

$ git branch work_new
$ git branch -a
* master
  work1
  work_new
  remotes/origin/master
  remotes/origin/work1

※ 생성과 동시에 해당 branch로 이동하려면 아래 명령어를 사용합니다.

$ git checkout -b work2

※ 원격에 있는 branch를 가져오려면 아래 명령어를 사용합니다.

[특정이름 지정시]
$ git checkout -b work2 origin/work2      
[원격 branch 이름을 그대로 사용할 경우]
$ git checkout -t origin/work2

6. git branch (–merged | –no-merged)

--merged는 이미 merge된 branch를 표시해주고 --no-merged는 아직 merge가 되지 않은 branch만 표시합니다.
--merged에 branch 목록 이미 merge되었기 때문에 *가 표시되지 않은 branch는 삭제 가능합니다.

$ git branch --merged
* master
  work_new
  work_old

$ git branch --no-merged
  work1
  work2

7. git branch -d (branch 이름)

branch를 삭제합니다.

아직 merge하지 않은 커밋을 담고 있는 경우 삭제되지 않습니다.

(강제종료 옵션 -D으로만 삭제 가능)

$ git branch -d work3
error: The branch 'work3' is not fully merged.
If you are sure you want to delete it, run 'git branch -D work3'.

$ git branch -d work_new
Deleted branch work_new (was 4bbc62f).

8. git branch -m (변경할 branch이름) (변경될 branch이름)

A 브랜치를 B 브랜치로 변경합니다.

$ git branch -v
* master   4bbc62f m1
  work2    c728ddc w2
  work_old 4bbc62f m1

$ git branch -m work2 work3

$ git branch -v
* master   4bbc62f m1
  work3    c728ddc w2
  work_old 4bbc62f m1

※ -M 옵션을 사용할 경우 기존에 동일한 이름의 branch가 있더라도 덮어씁니다.


출처

https://jistol.github.io/vcs/2017/01/27/git-branch/

 

Git branch 주요 명령어 정리

 

jistol.github.io

 

1. 들어가며

# (수정) 추후 설명하는 그림을 첨부할 예정입니다.

# 공부하며 쓰는 글이라 제일 좋은 방법이 아닐 것이며, 때로 부정확한 정보가 있을 수 있습니다.

 

지난 시간에는 DRF에서 가장 중요한 개념인 시리얼라이저를 이해하고 어떻게 와 작성하는지를 보았습니다.

이번 글에서는 DRF로 토큰 기반 회원 인증 / 가입 및 로그인을 구현해보도록 하겠습니다.

 

진행에 앞서 토큰 기반 인증에 대해 알 필요가 있습니다.

해당 개념에 대해서는 다음 블로그 링크를 첨부하는 것으로 넘어가겠습니다.

쉽게 알아보는 서버 인증 1편 - 이호연 블로그

 

또한, 요즘 서비스에서 소셜 인증을 안 쓸 수 없기 때문에

추후에 소셜 인증으로 해당 포스트를 그대로 다시 작성해보겠습니다.


2. django-rest-knox?

우리가 앞서 설치한 패키지인 django-rest-knox는

사용자 당 여러 개의 토큰을 지원하고 여러 보안 기술을 제공합니다.

 

앞서 설정했던 것처럼 settings.py의 INSTALLED_APPS에 knox를 넣어주셨다면,

아래 내용을 settings.py에 추가해야 합니다.

# mysite/settings.py
...
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ("knox.auth.TokenAuthentication",),
}
...

3. 시리얼라이저 작성

다음 단계는 시리얼라이저 작성입니다.

# api/serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User
from django.contrib.auth import authenticate

# 회원가입
class CreateUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ("id", "username", "password")
        extra_kwargs = {"password": {"write_only": True}}

    def create(self, validated_data):
        user = User.objects.create_user(
            validated_data["username"], None, validated_data["password"]
        )
        return user


# 접속 유지중인지 확인
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ("id", "username")


# 로그인
class LoginUserSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

    def validate(self, data):
        user = authenticate(**data)
        if user and user.is_active:
            return user
        raise serializers.ValidationError("Unable to log in with provided credentials.")

다른 시리얼라이저는 다 ModelSerializer로 작성하여 간단하게 처리하였고,

로그인의 경우 연결되는 모델이 없기 때문에 그냥 Serializer로 작성하였습니다.


4. 뷰 작성

다음은 views.py를 작성하겠습니다.

# api/views.py
from rest_framework import viewsets, permissions, generics, status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import api_view
from knox.models import AuthToken
from .serializers import CreateUserSerializer, UserSerializer, LoginUserSerializer

# Create your views here.
@api_view(["GET"])
def HelloAPI(request):
    return Response("hello world!")


class RegistrationAPI(generics.GenericAPIView):
    serializer_class = CreateUserSerializer

    def post(self, request, *args, **kwargs):
        if len(request.data["username"]) < 6 or len(request.data["password"]) < 4:
            body = {"message": "short field"}
            return Response(body, status=status.HTTP_400_BAD_REQUEST)
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.save()
        return Response(
            {
                "user": UserSerializer(
                    user, context=self.get_serializer_context()
                ).data,
                "token": AuthToken.objects.create(user),
            }
        )


class LoginAPI(generics.GenericAPIView):
    serializer_class = LoginUserSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data
        return Response(
            {
                "user": UserSerializer(
                    user, context=self.get_serializer_context()
                ).data,
                "token": AuthToken.objects.create(user)[1],
            }
        )


class UserAPI(generics.RetrieveAPIView):
    permission_classes = [permissions.IsAuthenticated]
    serializer_class = UserSerializer

    def get_object(self):
        return self.request.user

이번 뷰는 generic 기반 클래스 뷰로 작성되었습니다.

우선 함수 기반 뷰에서 클래스 기반 뷰로 바뀌면서 수정된 점은

앞서 데코레이터로 미리 http 메소드를 정의해주었던 것을 클래스 안으로 넣었다는 것이 있습니다.

 

또한 제네릭 뷰의 경우에는

기본적인 기능을 모두 포함하는 뷰로, 자세한 설명은 추후에 첨부하겠습니다.


5. url 설정

마지막으로 url을 설정하면 완료됩니다.

# api/urls.py
from django.urls import path, include
from .views import HelloAPI, RegistrationAPI, LoginAPI, UserAPI

urlpatterns = [
    path("hello/", HelloAPI),
    path("auth/register/", RegistrationAPI.as_view()),
    path("auth/login/", LoginAPI.as_view()),
    path("auth/user/", UserAPI.as_view()),
]
```

```python
# mysite/urls.py
from django.urls import path, include
from django.contrib import admin

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/", include("api.urls")),
    path("api/auth", include("knox.urls")),
]

6. 여태까지 내용 확인해보기

백엔드 API 테스트를 위해 Insomnia 라는 툴을 다운 받아 사용할 수 있습니다.

여러가지 편한 기능이 있으니 사용하시면 좋고, 아니면 그냥 기본 제공 웹 페이지에서 진행해도 됩니다.

회원 가입이나 로그인의 경우 POST 요청으로 보내면 되며 다음과 같이 요청을 보내면 됩니다.

 

- 회원가입

- 로그인


7. Django 기본 유저 모델 확장하기

여기까지 진행한 회원 모델은 Django에서 기본적으로 제공하는 유저 모델입니다.

하지만 실제로 사용하고 싶은 유저 모델은 포인트나 주소 등 여러 부가적인 정보가 들어갈 수 있는 유저이어야 합니다.

따라서 기본 유저 모델을 확장해야 하는데, 확장하는 방법에는 여러가지가 있습니다.

Django의 사용자 모델을 수정해보자!

 

그중에서 제일 간단한 방법인

"프로필 모델 생성 후 one-to-one 연결" 방법을 사용하겠습니다.

(1) Profile 모델 생성

모델은 다음과 같이 정의하겠습니다.

내용 외 필요한 컬럼이 있다면 추가하시면 됩니다.

# api/models.py
from django.db import models

# Create your models here.
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver


class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    user_pk = models.IntegerField(blank=True)
    email = models.EmailField(max_length=500, blank=True)
    nickname = models.CharField(max_length=200, blank=True)
    point = models.IntegerField(default=0)
    like = models.CharField(max_length=200, blank=True)
    phone = models.CharField(max_length=200, blank=True)


@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance, user_pk=instance.id)


@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

여기서 @receiver로 작성된 함수들은 User 모델로부터 post_save라는 신호,

 

즉 User 모델 인스턴스 생성에 맞춰 Profile 모델 인스턴스 또한 함께 생성하라는 것입니다.

이로 인해 여러분이 처음 유저를 생성하고 username과 password만 입력해도 해당 사용자에 대한 Profile 인스턴스가 함께 생성 됩니다.

 

(2) Profile Serializer 생성

다음은 Profile 모델에 대한 시리얼라이저를 만들어보겠습니다.

기본적으로 프로필 정보 조회에 필요한 프로필 ModelSerializer가 필요합니다.

# api/models.py
from django.db import models

# Create your models here.
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver


class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    user_pk = models.IntegerField(blank=True)
    email = models.EmailField(max_length=500, blank=True)
    nickname = models.CharField(max_length=200, blank=True)
    point = models.IntegerField(default=0)
    like = models.CharField(max_length=200, blank=True)
    phone = models.CharField(max_length=200, blank=True)


@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance, user_pk=instance.id)


@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

 

(3) Profile View 생성

시리얼라이저를 만들었으니 뷰를 생성할 때입니다.

이번 뷰는 앞선 회원 관련 뷰와 동일하게 제네릭 뷰의 UpdateAPIView를 사용하며,

이로 인해 별도의 로직 작성 없이 간편하게 Update 기능을 구현할 수 있습니다.

# api/views.py
class ProfileUpdateAPI(generics.UpdateAPIView):
    lookup_field = "user_pk"
    queryset = Profile.objects.all()
    serializer_class = ProfileSerializer

 

(4) Profile url 설정

언제나 마지막 단계는 urls.py입니다.

프로필 업데이트에 대한 url을 제공하면 되며, 다음과 같이 작성할 수 있습니다.

# api/urls.py
from django.urls import path, include
from .views import HelloAPI, RegistrationAPI, LoginAPI, UserAPI, ProfileUpdateAPI

urlpatterns = [
    path("hello/", HelloAPI),
    path("auth/register/", RegistrationAPI.as_view()),
    path("auth/login/", LoginAPI.as_view()),
    path("auth/user/", UserAPI.as_view()),
    path("auth/profile/<int:user_pk>/update/", ProfileUpdateAPI.as_view()),
]

 

(5) 번외 admin에 Profile 등록

Django의 어드민 페이지에서 Profile 모델의 데이터를 보고 싶다면 admin.py에 등록해야 합니다.

또한 Profile 모델은 User 모델에 종속되어 있는 상황으로 User 인스턴스에 Profile 인스턴스가 포함되어 있는 형태로 보기 위해 다음과 같이 코드를 작성하여 등록합니다.

# api/admin.py
from django.contrib import admin

# Register your models here.
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from .models import Profile


class ProfileInline(admin.StackedInline):
    model = Profile
    can_delete = False
    verbose_name_plural = "profile"


class UserAdmin(BaseUserAdmin):
    inlines = (ProfileInline,)


admin.site.unregister(User)
admin.site.register(User, UserAdmin)

 

(6) 결과 확인하기

이제 모든 개발을 마쳤으니 Profile 기능을 확인할 차례입니다.

Insomnia를 활용하여 테스트 해보겠습니다.

Django에서 update 기능을 사용할 때는 PUT 메소드로 요청을 보내야 합니다.

- User 생성 요청

- Profile 수정 요청

이제 어드민 페이지에서 확인해보겠습니다.

위처럼 superuser 외에 생성한 testuser1이 보이며,

이렇게 나오며 프로필 수정 또한 잘 된 것을 확인할 수 있습니다.


8. 마치며

여태까지 회원 가입 / 로그인 관련 API를 작성하였습니다.

 

사실 이렇게 하면 백엔드에서 할 역할은 얼추 끝이지만,

실제 웹 사이트를 만들어보지 않으면 이게 어떻게 되는지 이해할 수 없기 때문에

다음 시간에는 번외편으로 아주아주 간단한 프론트를 만들어서

실제로 회원가입하고 로그인하는 것을 구현해보겠습니다.


출처

https://devkor.tistory.com/entry/03-Django-Rest-Framework-%ED%9A%8C%EC%9B%90-%EC%9D%B8%EC%A6%9D-%EC%9C%A0%EC%A0%80-%EB%AA%A8%EB%8D%B8-%ED%99%95%EC%9E%A5%ED%95%98%EA%B8%B0

 

03. Django Rest Framework 회원 인증, 유저 모델 확장하기

들어가며 # (수정) 추후 설명하는 그림을 첨부할 예정입니다. # 공부하며 쓰는 글이라 제일 좋은 방법이 아닐 것이며, 때로 부정확한 정보가 있을 수 있습니다. 지난 시간에는 DRF에서 가장 중요한

devkor.tistory.com

 

1. Hello World 코드 설명

# (수정) 추후 설명하는 그림을 첨부할 예정입니다.

GET 127.0.0.1:8000/api/hello/

 

지난 시간에 해당 주소로 요청을 보내면 hello world를 print하는 api를 만들었습니다.

각 단계를 다시 따라가보며 세부적인 내용을 살펴보겠습니다.

 

첫번째 단계는 views.py를 작성하는 것입니다.

현재 사용하고 있는 view의 구조는 함수 기반 뷰, 이른바 FBV 입니다.

# api/views.py

from rest_framework.response import Response
from rest_framework.decorators import api_view

# Create your views here.
@api_view(['GET']) # 해당 함수 view에서 처리할 http 메소드
def HelloAPI(request):
    return Response("hello world!") # http response 형태로 return

 

views.py에서 다룬 내용은 그냥 Django에서 다룬 것과 크게 다를 것 없이,

요청을 받아 해당 요청에 대한 응답을 제공하는 기능을 합니다.

오늘 배우게 될 serializer와 함께 하면 어떻게 할 수 있는지 이따 알아보겠습니다.

 

그 다음은 api/urls.py 입니다.

# api/urls.py

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

urlpatterns = [
    path("hello/", HelloAPI),
]

크게 특별할 것 없이 view에 있는 HelloAPI를 가져와 url로 할당시켰습니다.

path 사용을 권장한다는 것을 지난 시간에 말씀드렸습니다.


2. Serializer?

Serializer는 Django Rest Framework에서 나온 새로운 요소입니다.

사전적 의미는 직렬화 하는 무언가 정도로 볼 수 있습니다.

 

직렬화라는 말이 와닿지 않는데,

이는 간단하게 파이썬 데이터를 JSON 타입의 데이터로 변환해준다 정도로 생각하시면 됩니다.

 

기본적으로 웹에서 통신을 할 때, 즉 데이터를 주고 받을 때는 어느 정도 정해진 포맷이 있습니다.

대표적인 타입이 JSON이나 XML인데, 대부분의 REST API에서는 JSON으로 주고 받기 때문에

우리는 그냥 JSON만 잘 알고 있으면 됩니다:)

 

정확한 의미의 직렬화는 Django 프로젝트에서 내가 만든 모델로부터 뽑은 queryset,

즉 모델 인스턴스를 JSON 타입으로 바꾸는 것입니다.

 

기존 일반적인 Django에서의 폼과 같은 개념이라고 비유하는 경우가 있는데,

저는 그냥 Django 모델을 JSON으로 변환하기 위한 모양 틀 정도로 이해하고 있고 그게 제일 깔끔한 것 같습니다!

간단한 코드로 예를 들어보겠습니다.(게시판 프로젝트 코드가 아닙니다)

# test/models.py
from django.db import models
# 설명만을 위한 모델로, 상당히 대충 작성 되었습니다:)
class Person(models.Model):
    id = models.IntegerField()
    name = models.CharField()
    phone = models.CharField()
    addr = models.CharField()
    email = models.CharField()
# test/serializers.py
from rest_framework import serializers
from .models import Person

# ModelSerializer 뒤에서 설명합니다.
class BasePersonSerializer(serializers.ModelSerializer):
    class Meta:
        model = Person
        fields = ('id', 'name', 'phone', 'addr')

위에서 보시면 Person이라는 모델을 models.py에 만들고,

BasePersonSerializer라는 시리얼라이저를 serializers.py에 만들었습니다.(앱 내에 파일을 생성하셔야 합니다)

 

이게 어떤 뜻이냐면,

"내가 만든 Person이라는 모델에서 데이터를 뽑아서 응답으로 보낼텐데,

응답의 형태 중 하나인 Base 형태를 BasePersonSerializer라고 정의할게!" 라는 뜻입니다.

 

자세히 보시면 모델에는 email도 정의되어있는데 Base의 fields에는 email이 없습니다.

이는 개발자가 정의한 Base 형태에는 email이 없다는 뜻이죠.

그러면 사람의 이메일 정보를 요청할 때 쓸 수 있는 시리얼라이저는 어떻게 만들 수 있을까요?

# test/serializers.py
from rest_framework import serializers
from .models import Person

# ModelSerializer 뒤에서 설명합니다.
class EmailPersonSerializer(serializers.ModelSerializer):
    class Meta:
        model = Person
        fields = ('id', 'email')

이렇게 만들어 볼 수 있습니다.

 

이제 우리는 한 가지 깨달음을 얻을 수 있습니다.

시리얼라이저는 응답으로 보낼 데이터의 형태를 정해주는 하나의 틀과 같구나!


3. Serializer - View 연결하기

여기까지 잘 이해되셨다면 이후 단계에서는 더 잘 이해될 것입니다.

이제는 그럼 views.py에 실제로 시리얼라이저가 어떻게 사용되는지 알려드리겠습니다.

# test/views.py
from rest_framework.response import Response
from rest_framework.decorators import api_view
from .models import Person
from .serializers import BasePersonSerializer, EmailPersonSerializer

@api_view(['GET'])
def PersonAPI(request, id):
    now_person = Person.object.get(id=id)
    serializer = BasePersonSerializer(now_person)
    return Response(serializer.data)
# => id=1에 대해 리턴된 Response: {'id': 1, 'name': '태뽕', 'phone': '01012345678', 'addr': '주소주소'}

@api_view(['GET'])
def EmailAPI(request, id):
    now_person = Person.object.get(id=id)
    serializer = EmailPersonSerializer(now_person)
    return Response(serializer.data)
# => id=1에 대해 리턴된 Response: {'id': 1, 'email': 'email@email.com'}

어떠신가요 바로 이해가 되셨나요?

제가 시리얼라이저를 변환기, 모양 틀로 설명드린게 이런 맥락이었습니다.

 

각 view에서 무언가 데이터를 요청할 때,

지금 예시에서는 PersonAPI는 사람에 대한 데이터, EmailAPI에서는 이메일에 대한 데이터를 요청할 때 각각 원하는 형태로 응답해줘야 합니다. 하지만 모델은 하나니 필요한 데이터만 골라서 보내줘야겠죠?

이 역할을 해주는게 시리얼라이저라고 이해하시면 됩니다.

 

내가 보낼 데이터(now_person(즉, 모델 인스턴스))를 시리얼라이저에 넣어 변환시키고

그 데이터를 응답으로 보내주는 것이 시리얼라이저 - 뷰 연동 개념입니다.


4. ModelSerializer

앞서 진행한 코드에서 한가지 설명 안하고 그냥 넘어간 코드가 있습니다.

바로 시리얼라이저를 선언할 때 사용한 serializers.ModelSerializer입니다.

# test/serializers.py
# ModelSerializer 뒤에서 설명합니다.
class BasePersonSerializer(serializers.ModelSerializer):
    class Meta:
        model = Person
        fields = ('id', 'name', 'phone', 'addr')

원래 가장 기본적인 형태의 시리얼라이저 선언 방법은 serializers.Serializer를 상속 받는 것입니다.

이 형태로 선언하면 다음과 같이 코드를 작성할 수 있습니다.

# test/serializers.py
class BasePersonSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField()
    phone = serializers.CharField()
    addr = serializers.CharField()

    def create(self, validated_data):
        """
        검증한 데이터로 새 `Person` 인스턴스를 생성하여 리턴합니다.
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        검증한 데이터로 기존 `Person` 인스턴스를 업데이트한 후 리턴합니다.
        """
        ...
        ...
        return instance

위에서 작성한 시리얼라이저보다 훨씬 길고 복잡해졌죠?

원래 시리얼라이저를 선언할 때는

1. 데이터 형태에 대한 필드(앞선 방법에서는 fields, 지금은 일일이 선언),

2. 그리고 그 데이터를 어떻게 처리할건지에 대한 메소드가 필요합니다.

 

이 메소드는 create나 update와 같이 이미 선언되어있는 메소드를 재작성하는 것이며,

views.py에서 직접 create나 update를 해야 할 것을 시리얼라이저에서 대신 해주는 것이라고 보면 됩니다.

대략 그런 역할을 선언해주고 필드에 대한 상세적인 내용을 직접 정의해야 했다면

 

serializers.ModelSerializer는 이 내용들을 알아서 해준 형태의 시리얼라이저라고 보시면 됩니다.

따라서 웬만하면 저희는 간편한 ModelSerializer의 형태로 선언하겠습니다. ★★


5. 마치며

여태까지 Django Rest Framework에서 가장 중요한 개념인 시리얼라이저에 대해 배웠습니다.

코드 작성 없이 보면서 따라가느라 조금 지루하셨을텐데,

다음 포스트에서는 이렇게 열심히 공부한 개념을 실제 게시판 프로젝트에 적용하겠습니다.

가장 첫 단계인 회원 인증부터 시작합니다:)


출처

https://devkor.tistory.com/entry/03-Django-Rest-Framework-Serializer-View-%EA%B0%9C%EB%85%90-%EC%9D%B5%ED%9E%88%EA%B8%B0?category=734691 

 

02. Django Rest Framework, Serializer, View 개념 익히기

Hello World 코드 설명 # (수정) 추후 설명하는 그림을 첨부할 예정입니다. GET 127.0.0.1:8000/api/hello/ 지난 시간에 해당 주소로 요청을 보내면 hello world를 print하는 api를 만들었습니다. 각 단계를 다시..

devkor.tistory.com

 

1. 시작하며

Python: 3.6.3
IDE: VS Code

본 포스트는 Django 튜토리얼은 해보았다는 가정 하에 작성되었습니다.
만약 Django가 아예 처음이라면 장고걸스튜토리얼을 먼저 해보시길 바랍니다.

 


2. Django 개발 환경 세팅

원활한 Django 개발을 위해서는 파이썬 가상환경 세팅이 필요합니다.

다음과 같은 명령어로 venv 생성 및 패키지 설치, 초기 세팅을 진행합니다.

$ mkdir Rest-CRUD
$ cd Rest-CRUD
$ python3 -m venv venv
$ source venv/bin/activate
$ (venv) pip install django
$ (venv) django-admin startproject mysite .
$ (venv) python manage.py startapp api

 

여기까지 진행했을 때 디렉토리 구조는 다음과 같습니다.

- Rest-CRUD
    - api
    - manage.py
    - mysite
    - venv

 

여기에 추가로 djangorestframework 패키지django-rest-knox를 설치하겠습니다.

djangorestframework는 당연하게도 Django Rest Framework를 위함이며,

django-rest-knox는 추후에 진행할 회원가입/인증에 사용될 패키지입니다.

$ (venv) pip install djangorestframework
$ (venv) pip install django-rest-knox

 

당장 쓸 패키지가 아닌데도 미리 설치한 이유는

settings.py를 한번에 건드리기 위함입니다(귀찮을테니ㅎㅎ).

# mysite/settings.py

ALLOWED_HOSTS = ['127.0.0.1']

INSTALLED_APPS = [
    ...
    'api',
    'rest_framework',
    'knox',
]

TIME_ZONE = 'Asia/Seoul'

 

위와 같이 앱을 등록하고 각종 설정을 마치면 migration 단계가 남았습니다.

$ (venv) python manage.py makemigrations
$ (venv) python manage.py migrate

 

여기까지 세팅이 완료되었다면 실행해봅시다!

$ (venv) python manage.py runserver

 

실행하여 로컬호스트 주소로 들어가면 잘 아는 Django 기본 화면이 나오게 됩니다.

여기까지는 그냥 Django 프로젝트랑 다를 바 없기 때문에 그래도 아주 간단한 api 하나 만들고 마치도록 하겠습니다.

GET 127.0.0.1:8000/api/hello/

 

해당 주소로 요청을 보내면

hello world를 print하는 api를 만들겠습니다.

 

첫번째 단계는 views.py를 작성하는 것입니다.

view를 작성하는 방법에는 여러가지가 있고 추후에 정리하겠으며, 지금은 그냥 하셔도 됩니다.

# api/views.py

from rest_framework import viewsets, permissions, generics, status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import api_view

# Create your views here.
@api_view(['GET'])
def HelloAPI(request):
    return Response("hello world!")

 

그 다음 api/urls.py 파일을 만들겠습니다.

# api/urls.py

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

urlpatterns = [
    path("hello/", HelloAPI),
]

마지막으로는 mysite/urls.py에 url을 등록하겠습니다.

(참고)

urls.py에서 urlpatterns를 작성하는 방법

최신버전부터 기존의 정규표현식으로 작성되던 url 기반에서 path 기반으로 변경되었으며

되도록 이 방식을 따르길 권장합니다.

# mysite/urls.py

from django.urls import path, include
from django.contrib import admin

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/", include("api.urls"))
    ]

이제 실행시키고, 127.0.0.1:8000/api/hello/ 로 접속하시면 다음과 같은 화면을 볼 수 있습니다.

와 처음 보는 형태의 화면이죠?

Django Rest Framework에서 기본적으로 제공하는 이 화면은

api 형태의 요청에 대한 응답을 다음과 같이 표시해줍니다.

api 테스트 툴 대신 충분히 사용할 만한 기능입니다.

 

여기까지 아주 간단하게 Django Rest Framework에 대한 감 잡기 및 설정까지 마쳤습니다.

다음 포스트에서는 위의 코드에 대한 조금 더 자세한 설명과 view를 작성하는 여러 방법에 대해 다뤄보겠습니다.


출처

https://devkor.tistory.com/entry/02-Django-Rest-Framework-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85?category=734691

1. 프론트엔드와 백엔드

요즘 웹 개발의 흐름은 react.js, vue.js의 JS 기반 프론트엔드 프레임워크가 지배하고 있다고 느껴집니다.

웹 개발을 할 때 react.js나 vue.js 없이 개발하는 사례가 거의 없는거 같아요. 

(프론트를 거의 안해봐서 잘 모르겠지만)

워낙 쉽게 모든 걸 할 수 있고, 기존 jquery, js로 구현하기 어려웠거나 불편했던 것의 해결, 유지 보수 등이 좋아 인기가 많은 것 같습니다.

이러다 보니 웹을 만드는 프로젝트를 할 때(비즈니스 레벨 혹은 학교 팀 프로젝트 레벨이더라도)

프론트엔드 프레임워크를 담당하는 사람(들)이 꼭 있습니다.

짧은 식견으로는 라우팅이나 기존 새로고침을 통해 제공해야 했던 데이터의 변경을 프론트엔드에게 넘겨주고,

엔드에서는 그저 DB에 접근해 요청에 맞는 데이터를 제공하는 API 서버를 개발하도록 된 것으로 생각됩니다.

이렇게 개발하게 되면 얻을 수 있는 장점은 프론트엔드 개발자와 백엔드 개발자 간 협업이 상당히 쉬워진다는 것입니다. 프론트엔드 개발자는 UI나 로직을 구현하면 되고, 필요한 데이터가 발생한다면 백엔드 개발자가 만들어놓은 API를 가져다 쓰면 됩니다.

 

혼자 개발을 한다면 이 둘을 굳이 분리할 필요가 없겠지만,

개발자 둘이 한 몸이 되지 않는 이상 코드 공유부터 수정, 테스트까지 정말 불편한 일들이 많을 것입니다.

 

특히 백엔드의 API 서버는 단지 웹에만 적용되지 않고,

앱이나 여러 http 프로토콜로 통신하는 프로젝트에 그대로 적용될 수 있어 활용성이 정말 좋습니다. 

REST API가 API 개발에서 제일 대표적인 방법이며, 이에 대한 설명은 다른 분들이 더 잘해주셔서 이를 첨부하겠습니다.


2. 왜 Django Rest Framework??

REST API를 개발하기 위한 프레임워크는 정말 다양합니다.

최근 정말 인기 있는 node.js의 express, python의 flask, java의 spring boot까지.

정말 다양한 언어로 개발된 프레임워크들이 있고

모든 분야가 그렇듯 다 경험 해보고 본인이 쓰기 편한 것으로 개발하시면 됩니다.

저 또한 express, flask, 그리고 Django Rest Framework 이렇게 세 종류를 경험해보았고

이 중에서 Django Rest Framework를 주 활용 스택으로 선택한 이유는 다음과 같습니다.

 

(1) Django!

: Django의 가장 큰 장점은 "Django스럽다" 라고 말할 수 있습니다. 이게 무슨 뜻이냐 하면, 개발해야하는 여러 대표적인 기능들이 거의 모두 이미 개발되었으니 그냥 가져다 쓰면 된다는 것입니다. 처음엔 오히려 이것저것 복잡해서 낯설고 친해지는데 오래 걸릴 수 있지만, 나중에는 이미 구현된 이 기능 하나하나에 정말 큰 고마움을 느낄 것입니다.

 

(2) Django의 강력한 ORM

: Django를 사용하다보면 내가 데이터베이스를 쓰고 있던가 싶을 정도로 개발자가 쓰기 좋게 잘 추상화 되어있습니다. 상당히 python 스럽고 직관적인 방법으로 데이터에 접근, 필터링할 수 있으며, 다른 프레임워크로는 얻을 수 없는 경험이라고 생각됩니다.


3. 프로젝트 소개

본 프로젝트에서는 Django Rest Framework를 활용하여 게시판을 만들어보겠습니다.

게시판을 만들면서 당연히 CRUD를 구현하게 될 것이고, 이 과정을 시작하면서 최대한 다양한 방법을 제시하고 그 중에서 한 가지 방법으로 통일하여 쭉 작업할 예정입니다.

 

또한 항상 프로젝트의 시작이 되면서 은근 귀찮고 어려운 회원 가입 / 로그인 인증도 개발할 예정입니다.

당연히 코드도 깃헙을 통해 공유드립니다.

 

기왕이면 해당 프로젝트 하나만 쭉 따라하면서 공부해도 

Django Rest Framework를 여러분들이 마스터 할 수 있기를 바랍니다:)


출처

https://devkor.tistory.com/entry/01-%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0-Django-Rest-Framework%EB%A1%9C-%EA%B2%8C%EC%8B%9C%ED%8C%90-%EB%A7%8C%EB%93%A4%EA%B8%B0?category=734691

메타 데이터는 다른 데이터에 대한 정보를 제공하는 특정 데이터 집합을 나타냅니다.

Django에서는 Django 모델을 사용하여 데이터베이스의 테이블과 해당 필드를 설계합니다.

 

모델 자체에 대한 데이터를 추가해야하는 경우Meta클래스를 사용합니다.

이 기사에서 Django 모델의Meta클래스에 대해 자세히 알아보십시오.


Django의 Meta클래스

Meta클래스는 inner 클래스입니다.

즉, 다음과 같이 모델 내부에 정의됩니다.

from django.db import models

class MyModel(models.Model):
    ...
    class Meta:
        ...

Meta클래스는 권한, 데이터베이스 이름, 단 복수 이름, 추상화, 순서 지정 등과 같은

모델에 대한 다양한 사항을 정의하는 데 사용할 수 있습니다.

 

Django 모델에Meta클래스를 추가하는 것은 전적으로 선택 사항입니다.

이 클래스에는 구성 할 수있는 많은 옵션도 제공됩니다.

다음은 일반적으로 사용되는 몇 가지 메타 옵션입니다. 여기에서 모든 메타 옵션을 탐색 할 수 있습니다.


1. Django 메타 옵션 - Abstract

이 옵션은 모델이 추상적인지 여부를 정의하는 데 사용됩니다. 추상 클래스와 동일하게 작동합니다.

 

추상 클래스는 인스턴스화 할 수없고 확장 또는 상속 만 가능한 클래스입니다.

추상으로 설정된 모델은 상속만 가능합니다.

공통 필드가있는 여러 모델이있는 경우이 옵션을 사용할 수 있습니다.

from django.db import models

class Human(models.Model):
    genders = (
        ("M", "Male"),
        ("F", "Female"),
        ("NB", "Non-binary"),
        ("T", "Transgender"),
        ("I", "Intersex"),
        ("O", "Other"),
        ("PNTS", "Prefer not to say")
    )

    name = models.CharField(max_length = 200)
    age = models.IntegerField(default = 0)
    gender = models.CharField(max_length = 50, choices = genders)

    class Meta:
        abstract = True # Important

class Teacher(Human):
    subject = models.CharField(max_length = 200)
    

class Student(Human):
    grade = models.IntegerField(default = 0)
    

여기에서 선생님 및 학생 모델은 Human 모델 내부에 모든 필드가 있습니다.

데이터베이스 내에는Teacher 및 Student 모델 만 생성됩니다.


2. Django 메타 옵션 - db_table

이 옵션은 데이터베이스 내에서

테이블을 식별하는 데 사용해야하는 이름을 설정하는 데 사용됩니다.

예를 들어 다음과 같은 작업을 수행하면 데이터베이스에서 모델 이름이 job이됩니다.

from django.db import models

class JobPosting(models.Model):
    
    
    class Meta:
        db_table = "job"

 


3. Django 메타 옵션 - Ordering

이 옵션은 모델 필드 인 문자열 값 목록을 사용합니다. 모델 객체의 순서를 정의하는 데 사용됩니다.

이 모델의 개체가 검색되면이 순서대로 표시됩니다.

from django.db import models

class JobPosting(models.Model):
    dateTimeOfPosting = models.DateTimeField(auto_now_add = True)
    
    
    class Meta:
        ordering = ["-dateTimeOfPosting"]

위의 예에서 검색된 개체는dateTimeOfPosting필드를 기준으로 내림차순으로 정렬됩니다.

(-접두사는 내림차순을 정의하는 데 사용됩니다.)


4. Django 메타 옵션 - verbose_name

이 옵션은 사람이 읽을 수있는 모델의 단일 이름을 정의하는 데 사용되며

Django의 기본 명명 규칙을 덮어 씁니다.

이 이름은 관리자 패널 (/admin/)에도 반영됩니다.

from django.db import models

class JobPosting(models.Model):
    
    
    class Meta:
        verbose_name = "Job Posting"

 

5. Django 메타 옵션 - Verbose name plural

이 옵션은 모델에 대해 사람이 읽을 수있는 복수형 이름을 정의하는 데 사용되며

다시 Django의 기본 명명 규칙을 덮어 씁니다. 이 이름은 관리자 패널 (/admin/)에도 반영됩니다.

from django.db import models

class JobPosting(models.Model):
    
    
    class Meta:
        verbose_name_plural = "Job Postings"

 


출처

https://www.delftstack.com/howto/django/class-meta-in-django/

 

ForeignKeyManyToManyField 그리고 OneToOneField 들을 제외한,

각 필드 타입은 선택적으로 첫번째 위치인자로, verbose name 을 받습니다.

 

verbose name 이 주어지지 않았을때는,

Django 는 자동으로 field 의 속성명의 언더스코어를 공백으로 전환하여 verbose name 을 생성합니다.

 

verbose name 은

사용자가 사용하는 자세한 이름을 의미합니다

 

verbose name 을 지정하지 않으면,

어드민에 표시되는 이름이 이상하다는것을 느낄수 있을겁니다.


아래 예시에서, verbose name 은 “person’s first name” 입니다

first_name = models.CharField("person's first name", max_length=30)​
 
 
아래 예시에서는, verbose name 이 “first_name” 입니다.

필드 타입의 첫번째 인자로, verbose name 이 명시되지 않았기 때문에,

필드명 first_name 에서, 언더스코어를 공백으로 전환하여 verbose name 을 표시합니다.

first_name = models.CharField(max_length=30)​
 
verbose_name 을 정의한 모델의 예시입니다.
class Members(models.Model):
    name = models.CharField(max_length=50, verbose_name='이름')
    nick_name = models.CharField(max_length=50, verbose_name="닉네임")
    birth_date = models.DateField(verbose_name="생년월일", null=True)

 

위 Members 모델에서는, name, nick_name, birth_date 이

각각 한글로 표시된 verbose_name 들을 어드민에 표시할것입니다.


1. 외래키 모델들

아직 살펴보지는 않았지만, ForeignKeyManyToManyField 그리고 OneToOneField 들은

첫번째 인자로 모델 클래스가 옵니다.

 

따라서, verbose_name을 키워드 인자로 사용해줍니다.

poll = models.ForeignKey(Poll, on_delete=models.CASCADE, verbose_name="the related poll")
# poll 은 ForeignKey (외래키) 로 Poll 이라는 클래스를 첫번째 인자로 받음 
# verbose_name 은 키워드 인자로 넣어줌 

sites = models.ManyToManyField(Site, verbose_name="list of sites")
# sites 는 ManyToManyField 로 Site 라는 클래스를 첫번째 인자로 받음 
# verbose_name 은 키워드 인자로 넣어줌 

place = models.OneToOneField(Place, on_delete=models.CASCADE, verbose_name='related place')
# place 는 OneToOneField 로 Place 라는 클래스를 첫번째 인자로 받음 
# verbose_name 은 키워드 인자로 넣어줌​

 

위의 예시에서는, 단지 ForeignKeyManyToManyField 그리고 OneToOneField 를 사용할때에,

verbose_name 이 어떻게 키워드 인자로 들어가는지에 대한 코딩 스타일만 보여주고 있습니다.

 

관례적으로,

verbose_name 의 첫번째 글자는 대문자로 써주지 않습니다.

Django 가 필요할때 자동으로 첫번째 글자를 대문자화 해줍니다.


2. 마치며..

verbose_name 에 대해서 알아보았습니다.
verbose_name 을 사용하면, 어떤것이 달라지는지 완벽한 예시가 있으면 좋았을텐데,

현재까지는, verbose_name 을 사용할경우,

해당 필드가 어드민에 표시되는 바가 달라지게 된다는점을 알고 넘어가면 될것 같습니다.

 

지난 포스팅에서 Runner 모델을 작성했었는데,

아래와 같이, name 필드에 verbose_name 을 추가해주었습니다. “러너 이름”

 

class Runner(models.Model):
    MedalType = models.TextChoices('MedalType', 'GOLD SILVER BRONZE')
    name = models.CharField("러너이름", max_length=60)
    medal = models.CharField(blank=True, choices=MedalType.choices, max_length=10)
    
    def __str__(self):
        return self.name + " " + self.medal​

 

위와같이 verbose_name 을 모델 필드에 추가하면,

래처럼 어드민에 “러너이름” 이라고 표시가 됩니다.

 


1. 정참조와 역참조 객체 서로 호출하기

데이터베이스에서 두 테이블이 참조 관계에 있는 경우를 생각해보자.

예를 들어, User 테이블과 사용자의 직업인 Occupation 테이블이 있다.

 

두 테이블은 N:1 관계에 있으며, User 객체가 Occupation 객체를 참조하고 있다. 

User 가 Occupation 을 선택하여 입사 원서를 작성한다고 가정해보자.

class User(models.Model):
    name	= models.CharField(max_length = 50)
    job		= models.ForeignKey('Occupation', on_delete = models.CASCADE)
    created_at	= models.DateTimeField(auto_now_add = True)
    	
class Occupation(models.Model):
    name = models.CharField(max_length = 50)

 

User 객체는 Occupation 객체를 정참조 하고 있으므로,

속성 이름으로 바로 접근 할 수 있다. User1을 선택하여, 그 사람의 job을 찾아보자.

user1 = User.objects.get(id = 1)
user1.job.name
>>> 'Developer'

 

그러나 Occupation 객체는 User 객체를 역참조 하고 있으므로 바로 접근이 불가능하다. 

developer 이라는 Occupation을 가지고 있는 유저를 모두 찾아보자.

job1   = Occupation.objects.get(name = 'developer')
people = job1.user.all() # 이게 될까?

 

❌ 안 됨 ❌

Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Occupation' object has no attribute 'user'

 

그렇다고 절대로 사용하지 못하는 것은 절대 아니니 걱정하지말자.

역참조 관계에 있을 때는 [classname]_set 이라는 속성을 사용하여 접근해야한다.

job1   = Occupation.objects.get(id = 1)
people = job1.user_set.all()
>>> <QuerySet[<Object User Object(1)>, <Object User Object(2)>]>

이 때, user_set 대신 사용할 수 있는 것이 related_name이다. 역참조 대상인 user 객체를 부를 이름.

 

즉, User 클래스를 정의할 때,

정참조 하고 있는 Occupation 클래스의 인스턴스에서 어떤 명칭으로 거꾸로 호출당할 지 정해주는 이름인 것이다.


2. What related names do

앞의 예시를 다시 보자.

아무래도 Occupation의 입장에서는 입사 지원자들을 appliers라고 부르는 것이 더 직관적이고 편할 것 같다.

class User(models.Model):
    name = models.CharField(max_length = 50)
    job	 = models.ForeignKey( 
            'Occupation',
            on_delete    = models.CASCADE,
            related_name = 'appliers' ------------------------- [Key Point !]
        )
    created_at	= models.DateTimeField(auto_now_add = True)
    	
class Occupation(models.Model):
    name = models.CharField(max_length = 50)

 

[Key Point] 를 눈여겨 보자.
User객체를 정의할 때, job이라는 속성에 Occupation객체가 연결되어 정참조하고 있다.

 

Occupation객체의 인스턴스와 연결되어 있는 User 객체를 거꾸로 불러올 때, 

appliers 라는 이름으로 부르기 위해 job 속성에 related_names = 'appliers'를 함께 지정해주었다.

job1   = Occupation.objects.get(id = 1)
people = job1.appliers.all()
>>> <QuerySet[<Object User Object(1)>, <Object User Object(2)>]>

 

잘 동작하는 것을 알 수 있다.

모든 Foreign Key에 related_name을 붙여줄 필요는 없다.

때에 따라, 참조하고 있는 객체 이름에 _set을 붙이는 것이 더 직관적인 경우가 굉장히 많기 때문이다.


3. Related name이 필수인 경우가 있다.

바로 한 클래스에서 서로 다른 두 컬럼(속성)이 같은 테이블(클래스)를 참조하는 경우이다.
앞서 설명한 상황에서, 지원자가 필수로 신청한 occupation외에, 2지망인 occupation도 받는다고 가정해보자.

class User(models.Model):
    name       = models.CharField(max_length = 50)
    job	       = models.ForeignKey('Occupation', on_delete = models.CASCADE)
[*] choice_2nd = models.ForeignKey('Occupation', on_delete = models.CASCADE, null = True)
    created_at = models.DateTimeField(auto_now_add = True)
    	
class Occupation(models.Model):
    name = models.CharField(max_length = 50)

 

참고로 위와 같은 선언은 애초에 마이그레이션이 되지 않는다.

related_name 지정하라는 문구만 뜸.

 

User객체에서 Occupation객체를 정참조 하는 속성이 두 개이다.

다시 말해 developer이라는 Occupations객체의 인스턴스를 1지망으로 선택한 지원자와 2지망으로 선택한 지원자가

따로 구별되어있다는 뜻이 된다. 아래 두 인스턴스를 보자.

user1 = User.objects.create(name = 'Nick', job_id = 1) #developer
user2 = User.objects.create(name = 'Sue', job_id = 2, choice_2nd_id = 1)

user1은 1지망은 job으로 id가 1번인 developer이다.
user2의 1지망은 2번 job이고, 2지망이 developer이다.

job1 = Occupation.objects.get(id = 1)
job1.user_set.all()

의 결과가 생성될 수 있을까?
❌ 안 됨 ❌

 

Occupation객체를 정참조 하고 있는 컬럼이 job과choice_2nd두 개이므로,

그저 user_set이라는 속성만으로는 자신을 바라보고 있는 두 User 객체 가운데

어떤 속성에 접근해야할 지 알 수가 없기 때문이다.

 

즉, developer을

1지망으로 고른 사람들의 목록(Nick)을 가져와야할 지,

2지망으로 고른 사람들의 목록(Sue)을 가져와야할 지 알 수가 없기 때문이다.

class User(models.Model):
    name = models.CharField(max_length = 50)
    job	 = models.ForeignKey( 
            'Occupation',
            on_delete    = models.CASCADE,
            related_name = 'appliers' ------------------------- [Key Point !]
        )
    choice_2nd  = models.ForeignKey(
            'Occupation',
            on_delete    = models.CASCADE,
            null         = True
            related_name = 'second_appliers'
        )
    created_at	= models.DateTimeField(auto_now_add = True)

 

이제는 developer을 1지망으로 지원한 Nick과 2지망으로 지원한 Sue를 구분하여 호출할 수 있다.

job1 = Occupation.objects.get(id = 1)

job1.appliers.all()
>>> <QuerySet[<Object User Object(1)>]> # ---> Nick

job1.second_appliers.all()
>>> <QuerySet[<Object User Object(2)>]> # ---> Sue

ManyToMany 관계에 있을 때에도 related_name은 같은 원리로 동작한다. 헷갈리지 않게 주의하자.

 


4. 마치며

웹의 구조나 서비스가 복잡해질 수록, 클래스 사이의 참조가 많아진다.

일대다는 물론이고, 다대다 관계도 계속 늘어난다.

그럴 때일 수록 related name이 중요하다고 생각한다.

 

어떻게든 migration만 되면 되지. 라는 생각으로 related_name을 마음대로 설정하다 보면,

나중엔 변수 이름을 아무렇게나 정했을 때만큼이나 의미를 알수 없는 코드를 양산하게 되기 때문이다.

오늘도 많이 배웠다.

 


출처

https://djangojeng-e.github.io/2020/08/02/Django-Models-6%ED%8E%B8-Fields-verbose-field-names/

 

Django Models 6편 - Fields (verbose field names)

Models - Fields (verbose field names) ForeignKey, ManyToManyField 그리고 OneToOneField 들을 제외한, 각 필드 타입은 선택적으로 첫번째 위치인자로, verbose name 을 받습니다. verbose name 이 주어지지 않았을때는, Django

djangojeng-e.github.io

https://velog.io/@brighten_the_way/Django%EC%99%80-Reverse-relations%EA%B3%BC-Relatedname

 

Django와 Reverse relations과 Related_name

그래서 결국, 너의 이름은

velog.io

 

Django 프로젝트에서 Model의 테스트 코드를 작성하는 방법에 대해서 알아봅시다.


1. 개요

이제 소프트웨어 개발에서 테스트 코드 작성은 빼놓을 수 없는 부분으로 자리잡았습니다.

테스트 코드는 서비스의 품질 상승, 서비스 검증의 시간 감소 및 개발 생산성 향상 등

서비스를 제공하는 회사에 여러면에서 큰 이익을 가져다 줍니다.

 

이번 블로그 포스트에서는 Django 프로젝트에서 Model을 테스트하는 방법에 대해서 소개합니다.


2. 프로젝트 준비

다음 블로그 포스트를 참고하여 새로운 장고 프로젝트를 생성합니다.


3. 사용자 모델

우선 테스트의 대상이 되는 사용자 모델(User model)에 대해서 살펴봅시다.

이번 블로그에서 소개할 유저 모델은 다음과 같습니다.

from django.db import models
from django.contrib.auth.models import (BaseUserManager, AbstractBaseUser)
...
class User(AbstractBaseUser):
    email = models.EmailField(
        verbose_name='email',
        unique=True,
    )

    wrong_pw = models.DecimalField(max_digits=1, decimal_places=0, default= 0)
    password_lock = models.DateTimeField(blank=True, null=True)
    certificated_at = models.DateTimeField(blank=True, null=True)
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    objects = UserManager()
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    def __str__(self):
        return self.email

    def has_perm(self, perm, obj=None):
        return True

    def has_module_perms(self, app_label):
        return True

    @property
    def is_staff(self):
        return self.is_admin

위의 모델은 Django의 기본 사용자 모델을 커스터마이징한 사용자 모델 일부입니다.

Django에서 커스텀 사용자 모델을 사용하는 방법에 대해서는 아래 링크를 참고하시기 바랍니다.

이 사용자 모델은 다음과 같은 특징을 가지고 있습니다.

 

- email

: 사용자 이름 대신 이메일을 사용합니다.

 

- wrong_pw

: 암호를 틀린 횟수를 저장하기 위한 필드

 

- password_lock

: 암호를 연속해서 3번 틀렸을 경우, 1시간 동안 암호를 입력하지 못하게 하기 위한 필드

 

- certificated_at

: 이메일의 본인 인증을 한 시점을 저장하기 위한 필드

 

- created_at

: 사용자 생성 시점

 

- updated_at

: 사용자 정보의 업데이트 시점

 

그럼 이제 이런 특징을 가진 사용자 모델을 테스트 하는 방법에 대해서 알아봅시다.


4. 사용자 모델 테스트

그럼 위에서 만든 사용자 모델에 관한 테스트 코드를 작성해 봅시다.

모델에 관한 테스트 코드는 ./AppName/test_models.py을 생성하고 다음과 같이 수정합니다.

from django.test import TestCase
from unittest import mock
from datetime import datetime

from .models import User

class UserModelTest(TestCase):
    def test_default_values(self):
        mock_date = datetime(2021, 3, 4, 14, 57, 11, 703055)
        with mock.patch('django.utils.timezone.now') as mock_now:
            mock_now.return_value = mock_date
            user = User.objects.create(email='test@test.test', password='12345')

        self.assertEquals(user.email, 'test@test.test')
        self.assertEquals(user.wrong_pw, 0)
        self.assertEquals(user.password_lock, None)
        self.assertEquals(user.certificated_at, None)
        self.assertEquals(user.is_active, True)
        self.assertEquals(user.is_admin, False)
        self.assertEquals(user.created_at, mock_date)
        self.assertEquals(user.updated_at, mock_date)

    def test_updated_at(self):
        mock_date = datetime(2021, 3, 4, 14, 57, 11, 703055)
        with mock.patch('django.utils.timezone.now') as mock_now:
            mock_now.return_value = mock_date
            user = User.objects.create(email='test@test.test', password='12345')

        self.assertEquals(user.created_at, mock_date)
        self.assertEquals(user.updated_at, mock_date)
        self.assertEquals(user.updated_at.strftime("%Y-%m-%d"), '2021-03-04')

        mock_update_date = datetime(2021, 3, 5, 14, 57, 11, 703055)
        with mock.patch('django.utils.timezone.now') as mock_now:
            mock_now.return_value = mock_update_date
            user.is_admin = True
            user.save()

        self.assertEquals(user.created_at, mock_date)
        self.assertEquals(user.updated_at, mock_update_date)
        self.assertEquals(user.updated_at.strftime("%Y-%m-%d"), '2021-03-05')

 

그럼 테스트 코드를 좀 더 자세히 살펴봅시다.

class UserModelTest(TestCase):
    def test_default_values(self):
        ...
    def test_updated_at(self):
        ...

 

이 테스트 코드는 test_default_values와 test_updated_at, 두 가지 테스트 케이스를 가지고 있습니다.

 

- test_default_values

: 모델을 통해 데이터를 생성했을 때, 기본값들을 테스트

 

- test_updated_at

: 모델을 통해 데이터를 업데이트 했을 때, updated_at이 잘 동작하는지 확인하는 테스트

 

(1) test_default_values

새로운 데이터를 생성할 때, created_at은 생성 시점의 timestamp를 사용합니다.

이 값이 제대로 동작하는지 확인하기 위해서는 시스템 시간을 Mocking할 필요가 있습니다.

mock_date = datetime(2021, 3, 4, 14, 57, 11, 703055)
with mock.patch('django.utils.timezone.now') as mock_now:
    mock_now.return_value = mock_date
    user = User.objects.create(email='test@test.test', password='12345')

 

이렇게 시간을 Mocking하여 새로운 사용자를 생성한 후,

새롭게 생성된 데이터의 기본값들을 체크합니다.

self.assertEquals(user.email, 'test@test.test')
self.assertEquals(user.wrong_pw, 0)
self.assertEquals(user.password_lock, None)
self.assertEquals(user.certificated_at, None)
self.assertEquals(user.is_active, True)
self.assertEquals(user.is_admin, False)
self.assertEquals(user.created_at, mock_date)
self.assertEquals(user.updated_at, mock_date)

 

이 때, 위에서 Mocking한 시간이 created_at과 updated_at에 잘 저장되었는지도 확인합니다.

self.assertEquals(user.created_at, mock_date)
self.assertEquals(user.updated_at, mock_date)

 

(2) test_updated_at

모델을 사용하여 생성된 데이터를 업데이트하였을 때, 

updated_at이 잘 갱신되는지 확인하기 위한 테스트 케이스입니다.

mock_date = datetime(2021, 3, 4, 14, 57, 11, 703055)
with mock.patch('django.utils.timezone.now') as mock_now:
    mock_now.return_value = mock_date
    user = User.objects.create(email='test@test.test', password='12345')

self.assertEquals(user.created_at, mock_date)
self.assertEquals(user.updated_at, mock_date)
self.assertEquals(user.updated_at.strftime("%Y-%m-%d"), '2021-03-04')

 

우선 시스템 시간을 Mocking하여 새로운 유저 데이터를 생성합니다.

그리고 해당 데이터가 현재 시간을 잘 가지고 있는지 테스트합니다.

mock_update_date = datetime(2021, 3, 5, 14, 57, 11, 703055)
with mock.patch('django.utils.timezone.now') as mock_now:
    mock_now.return_value = mock_update_date
    user.is_admin = True
    user.save()

self.assertEquals(user.created_at, mock_date)
self.assertEquals(user.updated_at, mock_update_date)
self.assertEquals(user.updated_at.strftime("%Y-%m-%d"), '2021-03-05')

 

그런 다음, 시간을 새롭게 Mocking하고, 사용자 데이터를 업데이트합니다.

이렇게 업데이트 한 후, updated_at에 업데이트한 시점의 시간이 잘 기록되었는지 확인합니다.


5. 테스트 실행

이렇게 모델을 가지고 우리가 할 수 있는 동작들을 테스트 케이스로 작성한 후,

다음 명령어를 사용하여 테스트 코드를 실행합니다.

python manage.py test

 

그럼 다음과 같이 우리가 작성한 테스트 코드가 문제없이 통과되는 것을 확인할 수 있습니다.

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
......................
----------------------------------------------------------------------
Ran 22 tests in 2.453s

6. 완료

이것으로 Django에서 모델을 테스트하는 방법에 대해서 알아보았습니다.

모델에 큰 로직이 포함되어있지 않기 때문에, 간단한 테스트 코드로 모델을 테스트할 수 있음을 알 수 있었습니다.

이제 여러분도, 여러분의 Django 프로젝트에 모델 테스트 코드를 작성해 보시기 바랍니다.


출처

https://dev-yakuza.posstree.com/ko/django/test/models/

 

Django 프로젝트에서 pre-commit을 사용하여 Git에 커밋을 할 때, flake8을 실행하도록 설정해 보자.


1. 개요

이전 블로그 포스트에서 파이썬의 코드 정적 분석기인 flake8을 사용하여 코드 스타일을 통일하고, 잠재적인 버그를 줄이는 방법에 대해서 알아보았습니다.

 

이번 블로그 포스트에서는 pre-commit을 사용하여 소스코드를 Git에 커밋할 때,

설정한 flake8을 자동으로 실행하는 방법에 대해서 알아봅니다.


2. pre-commit 설치

pre-commit을 사용하여 flake8을 자동으로 실행하기 위해서는, pre-commit을 설치할 필요가 있습니다.

다음 명령어를 사용하여 pre-commit을 설치합니다.

pip install pre-commit

 

설치를 완료하였다면, 잊지말고 requirements.txt에 저장해 둡니다.

pip freeze > requirements.txt

이것으로 pre-commit을 설치하는 방법에 대해서 알아보았습니다.


3. pre-commit 설정

pre-commit을 사용하여 flake8을 자동으로 실행하기 위해서는, pre-commit의 설정 파일을 작성할 필요가 있습니다.

다음 명령어를 사용하여 pre-commit의 설정 파일을 생성합니다.

pre-commit sample-config > .pre-commit-config.yaml

 

생성된 .pre-commit-config.yaml 파일을 열어보면 다음과 같습니다.

# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v3.2.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files

pre-commit에서 제공하는 샘플 내용을 확인할 수 있습니다.


4. flake8 설정하기

이제 pre-commit의 설정 파일에 flake8을 설정하는 방법에 대해서 알아봅시다.

pre-commit의 설정 파일인 .pre-commit-config.yaml은 기본적으로 다음과 같은 구조를 가지고 있습니다.

repos:
  - repo: repo-url
    rev: version
    hooks:
      - id: hook-id

- repo

: pre-commit이 제공하는 기능의 Repository URL

 

- rev

: 사용하려는 기능의 버전

 

- id

: pre-commit이 제공하는 기능

pre-commit은 기본적으로 제공하는 기능들이 있습니다. 다음 링크를 통해 어떤 기능들이 있는지 확인할 수 있습니다.

 

우리는 이중에서 flake8을 사용할 예정입니다.

그럼 .pre-commit-config.yaml 파일을 열고 다음과 같이 수정합니다.

repos:
  - repo: https://gitlab.com/pycqa/flake8
    rev: 3.8.4
    hooks:
      - id: flake8

pre-commit에서 제공하는 repo의 URL과 버전, flake8 hook을 설정하였습니다.

이 부분은 flake8 공식 문서에서도 확인할 수 있습니다.

 

- flake8 hooks

: https://flake8.pycqa.org/en/latest/user/using-hooks.html

 

이제 이렇게 설정한 파일을

실제 Git의 Commit hook에 등록하기 위해 다음 명령어를 실행합니다.

pre-commit install

이것으로 pre-commit을 사용하여 flake8을 사용할 준비가 되었습니다.


5. pre-commit 실행

그럼 우리가 설정한 pre-commit이 잘 동작하는지 확인해 봅시다.

다음 명령어를 실행하면 우리가 설정한 .pre-commit-config.yaml 파일을 기준으로 pre-commit을 실행할 수 있습니다.

pre-commit run --all-files

 

명령어를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

flake8...................................................................Passed

 

이것으로 우리가 설정한 pre-commit이 잘 동작하는 것을 확인하였습니다.

이제 지금까지 설정한 내용을 Git에 커밋합니다.

git add .
git commit -m 'Add pre-commit for flake8'
git push origin main

6. 완료

이것으로 pre-commit을 사용하여 Git에 커밋을 할 때마다, flake8을 실행하는 방법에 대해서 알아보았습니다.

주의해야할 점은, 새롭게 Repository를 Clone한 경우 잊지말고 pre-commit install을 실행하여 pre-commit의 설정 내용을 Git hooks에 등록해야 합니다.

git clone repository_url
# virtualenv venv
# source venv/bin/activate
pip install -r requirement.txt
pre-commit install

그럼 이제 자동화된 flake8으로 더욱 생산성 높은 개발을 해보시기 바랍니다.

Django 프로젝트에서 파이썬 코드 스타일을 통일하기 위한 코드 정적 분석기인 flake8을 사용하는 방법에 대해서 알아봅시다.


1. 개요

Django로 서버사이드를 개발하면서 다른 개발자분들과 협업을 하게 되었습니다.

여러 개발자가 협업을 하므로, 코드의 스타일을 통일하고, 잠재적인 버그를 줄이기 위해,

코드 정적 분석기인 flake8을 도입하기로 했습니다.

 

이번 블로그 포스트에서는 Django 프로젝트에 flake8을 설정하고 사용하는 방법에 대해서 설명합니다.


2. flake8 설치

Django에서 flake8을 사용하기 위해서는 우선, flake8을 설치할 필요가 있습니다.

다음 명령어를 사용하여 flake8을 설치합니다.

pip install flake8

 

설치를 하였다면, 잊지말고 requirements.txt에 저장해 둡니다.

pip freeze > requirements.txt

 

이것으로 flake8을 설치하는 방법에 대해서 알아보았습니다.


3. flake8 사용법

다음 명령어를 실행하여 flake8을 실행할 수 있습니다.

flake8

 

Django 프로젝트 폴더에서 flake8을 실행하면 다음과 같은 내용을 확인할 수 있습니다.

./venv/lib/python3.8/site-packages/pyflakes/checker.py:153:31: F821 undefined name 'PercentFormat'
./venv/lib/python3.8/site-packages/pyflakes/checker.py:160:9: F821 undefined name 'Generator'
./venv/lib/python3.8/site-packages/pyflakes/checker.py:160:9: F821 undefined name 'PercentFormat'
./venv/lib/python3.8/site-packages/pyflakes/checker.py:180:47: F821 undefined name 'Optional'
./venv/lib/python3.8/site-packages/pyflakes/checker.py:759:35: F821 undefined name 'List'
./venv/lib/python3.8/site-packages/pyflakes/checker.py:760:35: F821 undefined name 'Dict'

flake8의 결과를 보면 checker.py:153:31: F821 undefined name 'PercentFormat'와 같이 파일명:에러위치 에러ID 에러 내용을 확인할 수 있습니다.


4. flake8 설정하기

저는 djanog 프로젝트 폴더에 virtualenv를 생성하여 사용하고 있습니다. 그래서, flake8이 불필요하게 virtualenv 폴더까지 분석하고 있으니, flake8이 virtualenv 폴더를 분석하지 않도록 설정할 필요가 있었습니다.

falke8이 virtualenv 폴더를 무시할 수 있도록 설정하기 위해, .flake8 파일을 Django 프로젝트 폴더에 생성하고 다음과 같이 수정하였습니다.

[flake8]
exclude =
    .git,
    .gitignore,
    *.pot,
    *.py[co],
    __pycache__,
    venv,
    .env

이렇게 설정하고, 설정이 잘 적용되었는지 확인하기 위해 아래에 명령어를 사용하여 flake8을 실행해 봅니다.

flake8

그럼 이전과는 다르게 다음과 같은 결과를 확인할 수 있습니다.

./petmeeting/settings.py:98:80: E501 line too long (91 > 79 characters)
./petmeeting/settings.py:101:80: E501 line too long (81 > 79 characters)
./petmeeting/settings.py:104:80: E501 line too long (82 > 79 characters)
./petmeeting/settings.py:107:80: E501 line too long (83 > 79 characters)
./petmeeting/settings.py:132:33: W292 no newline at end of file

flake8은 많은 내용을 체크하지만, 일반적으로 생각하지 않아도 되는 부분까지 에러로 표시하는 경우가 있습니다. 이런 부분들을 무시하기 위해서 저는 다음과 같이 무시할 내용들을 추가하였습니다.

[flake8]
exclude =
    .git,
    .gitignore,
    *.pot,
    *.py[co],
    __pycache__,
    venv,
    .env

ignore =
    E121,
    E126,
    E127,
    E128,
    E203,
    E225,
    E226,
    E231,
    E241,
    E251,
    E261,
    E265,
    E302,
    E303,
    E305,
    E402,
    E501,
    E741,
    W291,
    W292,
    W293,
    W391,
    W503,
    W504,
    F403,
    B007,
    B950,

max-line-length = 200

이 내용은 Sider라는 회사에서 추천하는 규칙을 적용한 내용입니다.

 

이 규칙은 단순히 정한건 아닌거 같고, 이 회사에서 연구한 결과를 바탕으로 추천하고 있는거 같습니다.

자세한 내용은 아래에 링크를 참고하시기 바랍니다.


5. 완료

이것으로 Django 프로젝트에서 flake8을 사용하는 방법에 대해서 알아보았습니다.

또한 불필요한 규칙들과 폴더를 무시하기 위한 설정 방법에 대해서도 알아보았습니다.

앞으로 다른 개발자들과 협업하면서 이렇게 설정한 flake8이 많은 도움이 되기를 기대해 봅니다.


출처

https://dev-yakuza.posstree.com/ko/django/flake8/

+ Recent posts