들어가기 전에,


안녕하세요, 똑똑한개발자에서 백엔드개발을 하는 차재훈입니다.

똑똑한개발자에서 사용하는 코드 컨벤션을 소개하려고 합니다.

코드 컨벤션의 경우 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를 붙인다.

jaehun.cha's profile image

jaehun.cha

2021-04-01 15:30

Read more posts by this author