들어가기 전에,
안녕하세요, 똑똑한개발자에서 백엔드개발을 하는 차재훈입니다.
똑똑한개발자에서 사용하는 코드 컨벤션을 소개하려고 합니다.
코드 컨벤션의 경우 PEP8을 참고하여 작성했으나 위반하는 내용이 포함되어 있을 수 있습니다. 똑똑한개발자에서 이렇게 코드를 작성하고 있구나’ 정도로 봐주시면 좋을 것 같습니다.
목차
Django
코드 컨벤션이란
코드의 가독성을 증진시키고 여러 명이 협업하는 과정에서 일관된 코드 스타일을 유지하는 데 있어서 중요하다. 따라서 코드 컨벤션을 잘 지키며 코드를 작성하는 것은 읽기 좋은 코드를 작성하는 첫걸음이 될 것이다.
파이썬의 경우 PEP8을 기반으로 작성하며, 팀 또는 프로젝트마다 정해진 코드 컨벤션에 따라 작성합니다.
Project Architecture
똑똑한개발자에서는 위와 같은 구조로 작업을 진행합니다.
- api는 장고의 app을 관리하는 디렉토리입니다.
- config는 프로젝트의 구성 파일들을 관리하는 디렉토리입니다.
- .ebextension은 Elastic Beanstalk 관련 셋팅을 관리하는 디렉토리입니다.
- .github는 Github action 관련 셋팅을 관리하는 디렉토리입니다.
- .platform은 cron과 nginx 관련 셋팅을 관리하는 디렉토리입니다.
- Procfile은 서비스 관련 셋팅을 관리하는 파일입니다.
코드 컨벤션
- import는 알파벳 순 으로 정렬한다.
# python의 Library
import base64
# Django, DjangoRestFramework 등의 Library 순서
from django.db import models
from django.db.models import (
Q,
OuterRef,
Subquery,
)
from rest_framework.authtoken.models import Token
# 자신이 만든 Third party app
from apps.account.models import Profile
from apps.snippet.models import Snippet
- Class = Pascal Case, Extra = Snake Case
# Pascal Case
class CultureTag(models.Model):
nickname = models.Charfield('닉네임', max_length=127, blank=True, null=True)
# Snake Case
def get_nick_name(self):
return self.nickname
- Class 사이는 2번, Class 내의 함수에서는 1번 개행한다.
from django.db import models
# 2번 개행
class A(models.Model):
# 1번 개행
def a(self):
pass
# 2번 개행
class B(models.Model):
pass
# 코드의 마지막줄 1번 개행
- 변수명 네이밍
# 변수명은 SnakeCase사용
# Boolean 타입의 경우 변수명에 is_를 붙혀준다.
is_best = models.BooleanField(verbose_name='베스트리뷰', default=False)
# 생성일시, 수정일시 등의 경우 과거형+date로 생성
created_date = models.DateTimeField(verbose_name='생성일시', auto_now_add=True)
updated_date = models.DateTimeField(verbose_name='수정일시', auto_now=True)
paid_date = models.DateTimeField(verbose_name='결제일시')
canceled_date = models.DateField(verbose_name='취소일자')
# ForeignKey는 참조 모델명, ManyToManyField는 참조 모델명_set
# 모델 임포트가 아닌 '앱이름.모델명'으로 참조
user = models.ForeignKey('user.User', verbose_name='매장명', on_delete=models.CASCADE)
company_set = models.ManyToManyField('shop.Company', verbose_name='업체')
- Choices 사용시 사용하는 모델 위에 작성
from django.db import models
class StatusChoices(models.TextChoices):
CANCELED = 'C', '주문취소'
PROCESSED = 'P', '주문완료'
COMPLETED = 'O', '접수완료'
RETURN = 'R', '반품요청'
APPROVED = 'A', '반품승인'
NOTAPPROVED = 'N', '반품거절'
class QuarterChoices(models.IntegerChoices):
ONE = 1, '1차'
TWO = 2, '2차'
class Snippet(models.Model):
status = models.CharField(verbose_name='주문상태', max_length=1, choices=StatusChoices.choices, default='P')
quarter = models.PositiveSmallIntegerField(verbose_name='차수', choices=QuarterChoices.choices, default=QuarterChoices.ONE)
- 주석
# 빈줄에 주석을 작성 할 시 '#' 띄어쓰기 한번 후 작성
snippet = Snippet.objects.get(id=10) # 코드 옆에 주석 작성시 '#' 띄어쓰기 두번 후 작성
- filter와 get의 결과에 대한 파라미터는 아래와 같이 작성하며, 3개 이상의 파라미터가 필요한 경우 줄바꿈
- 줄바꿈을 했을경우 마지막에 , 를 작성한다.
Snippet.objects.filter(
like=True,
post__id=1,
user=user, # , 붙음
)
- kwargs.get 에 default 설정하고
''
대신None
을 사용한다.
user = kwargs.get('user', None)
- Static, Media 파일은 .gitignore에 작성하여 저장소에 올리지 않는다. ckeditor plugins 사용 등으로 인해 같이 배포해야 하는경우 저장소에 같이 업로드한다.
- log는 한글로 명시하며, print를 이용하여 각 def마다 디테일하게 설정하고, uwsgi에서 찍힌 로그를 확인한다.
- log를 찍는 목적은 이후 서비스 중 에러가 일어났을 때 해당 과정을 트래킹하기 위함이며, 어떤 함수가 실행되었는지 작게는 어떤 기능이 일어나고 있는지를 명시해야한다.
- Data가 변경되는 Create, Update, Delete 앞, 뒤로는 log가 필요하며, 어떤 데이터 값이 어떻게 변경되어지는지 상세한 로그를 기록해야한다.
사용법
Queryset
# ORM get을 사용할 때는 DoesNotExist를 주의
# (X)
snippet = Snippet.objects.get(id=10)
# (O)
try:
snippet = Snippet.objects.get(id=10)
except DoesNotExist:
pass
# ORM filter 사용할 때는 쿼리 최적화를 고려하여 작성
class Parent(models.Model):
name = models.CharField()
age = models.IntegerField()
class Child(models.Model):
parent = models.ForeignKey() # 자동생성 related_name="child_set"
name = models.CharField()
age = models.IntegerField()
# prefetch_related, select_related, annotate 등
# ex1) 기본
Parent.objects.prefetch_related('child_set').all()
# ex1) 심화
# 특정 자식만 가져올 경우
from django.db.models import Prefetch
Parent.objects.prefetch_related(
Prefetch('child_set', queryset=Child.objects.filter(age=20)),
).all()
# ex2)
Child.objects.select_related('parent').all()
# ex3) 기본
from django.db.models import Count
Parent.objects.annotate(child_set__count=Count('child_set')).prefetch_related('child_set').all()
# ex3) 심화
# Parent의 자식중 age가 20이상 갯수 구하기
from django.db.models import Count, Q
Parent.objects.annotate(child_set__count=Count('child_set', filter=Q(child_set__age__gte=20))).prefetch_related('child_set').all()
View
-
get_queryset()
- queryset은 request 발생시 한번만 쿼리셋이 동작한다.
- get_queryset()은 모든 request마다 쿼리를 발생시킨다.
- 쿼리를 동적으로 조정하려는 경우 get_queryset()이 유용합니다. 예를 들어 현재 사용자에게 속한 객체를 반환 할 수 있습니다.
# 필터의 값이 동적일 경우 잘못된 쿼리가 나올 수 있음 # (X) class ListView(ListAPIView): queryset = order.objects.filter(created_date=date.today()) # 앱이 시작될 때 date.today() 실행 됨 # (O) class ListView(ListAPIView): def get_queryset(self): return order.objects.filter(created_date=date.today()) # request 마다 date.today() 실행 됨
-
get_object()
- get_object()는 queryset과 pk값을 인자로 받아서, queryset.filter(pk=pk)로 queryset을 뽑고, obj = queryset.get()으로 객체만 뽑아서 리턴해 주는 메소드
- queryset.filter(pk=pk) 이전에 queryset에 여러 조건이 필요한 경우 get_queryset()에서 처리 후 get_object() 작성
SerializerMethodField
- 시리얼라이저로 객체를 직렬화한 JSON에 모델에 없는 필드를 추가하거나, 모델에 있는 값을 변형해서 새로운 필드의 값으로 넣을 경우 SerializerMethodField를 사용
class Serializer(serializers.Serializer):
field_name = serializers.SerializerMethodField()
def get_field_name(self, obj):
return 'test'
- 함수명을 get*
으로 지정하면 기본값으로 설정 돼 있기 때문에 method_name에 함수명을 지정하지 않아도 되지만 함수명을 get* 형태가 아닐경우, SerializerMethodField(method_name)과 같이 인자로 함수명을 넘겨 사용할 수 있다.
Signal
- 어떤 특정한 일을 수행할 때마다 알려줄 것을 설정하고, 그 때에 지정한 동작을 수행할 수 있게 하는 신호를 발생하는 기능
# apps.py
from django.apps import AppConfig
class OrderConfig(AppConfig):
name = 'api.order'
# 해당앱이 실행될 때 시그널 등록
def ready(self):
import api.order.signals
# signals.py
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
# receiver에 등록된 sender의 액션이 발생했을 때 함수 실행
@receiver(post_save, sender=Order)
def order_saved(sender, instance, created, *args, **kwargs):
# Order save되고 난 후 해당 객체가 instance로 전달,
# 객체의 생성여부(업데이트, 크리에이트)가 True or False로 전달된다.
if created:
print(instance)
else:
print(instance)
Model Meta
class Model(models.Model):
class Meta:
verbose_name = '모델'
verbose_name_plural = verbose_name
def __str__(self):
return self.field # null 값 주의, not str type 주의
-
verbose_name
옵션사용자가 읽기 쉬운 모델 객체의 이름으로 관리자 화면 등에서 표시된다. 영어를 기준으로 단수형이다.
verbose_name
옵션을 지정하지 않으면CamelCase
클래스 이름을 기준으로camel case
이와 같이 모두 소문자로 변경한다. -
verbose_name_plural
옵션사용자가 읽기 쉬운 모델 객체의 이름으로 관리자 화면 등에서 표시되는 것은 동일하나 영어를 기준으로 복수형이다.
한국어에서는 굳이 단수와 복수를 구별해 사용하지 않으므로
verbose_name
과 동일하게 쓸 수 있다.verbose_name_plural
옵션을 지정하지 않으면verbose_name
에s
를 붙인다.