Django 3.2 릴리스와 주요 변경 사항

이 글에서는 Django 3.2에 추가된 기능과 바뀐 점을 알아보려 합니다. Django 3.2 release notes를 참고하였습니다.

Django는 3년 마다 LTS를 위해 메이저 버전을 하나씩 올리고, 8개월마다 마이너 버전을 올립니다. 이번에 출시한 3.2는 3년간 지원되는 LTS 업데이트입니다.

Django 릴리스 사이클, 출처: https://www.djangoproject.com/

파이썬 호환성

Django 3.2는 파이썬 3.6, 3.7, 3.8, 3.9를 지원합니다.

새 기능

함수형 인덱스(functional index)

함수형 인덱스를 생성할 수 있는 표현식을 지원합니다. 다음과 같이 인덱스를 생성했다고 가정합시다.

class Person(models.Model):
    ...
    class Meta:
        indexes = [
            Index(Lower('name').desc(), 'birthday', name='name_and_birthday_idx')
        ]

앞의 코드는 이름의 역순과 생일순 조합으로 정렬된 인덱스를 생성합니다.

pymemcache 지원

캐시 백엔드에 pymemcache를 지원합니다.

관리자 도구에서 사용할 수 있는 display 데코레이터

사용자 정의 필드에 속성을 부여하다보면 조금 지저분해보이던 코드를 display 데코레이터로 해결할 수 있습니다.

def was_born(self, obj):
    return obj.birthday is not None
was_born.boolean = True
was_born.admin_order_field = '-birthday'
was_born.short_description = 'Was Born?'

앞의 코드를 다음처럼 수정하면 됩니다.

@admin.display(
    boolean=True,
    ordering='-birthday',
    description='Was Born?',
)
def was_born(self, obj):
    return obj.birthday is not None

자잘한 변경

  • 관리자 화면(django.contrib.admin)
    • ModelAdmin.search_fields에 대해, 따옴표로 감싼 공백 섞인 문자열을 검색할 수 있습니다.
    • 읽기 전용 연관 필드가 Admin에 등록된 모델이라면, 링크 형태로 렌더링됩니다.
    • 테마 기능을 지원합니다. (브라우저 설정이 다크 테마라면 이를 따르는 등)
    • 자동 완성 필드(ModelAdmin.autocomplete_fields)도 ForeignKey.to_fieldForeignKey.limit_choices_to의 영향을 받습니다.
    • model enumeration privacy 침해 사고를 방지하는 차원에서 허가되지 않은 사용자는 URL이 존재하든 하지 않든, 로그인 페이지로 리디렉션합니다. 이 기능을 끄려면 AdminSite.final_catch_all_view 값을 False로 바꾸면 되지만, 권장하지는 않습니다.
  • 인증(django.contrib.auth)
    • PBKDF2 비밀번호 해시의 반복 횟수가 216,000회에서 260,000회로 늘었습니다.
    • Argon2, MD5, PBKDF2, SHA-1 비밀번호 해시의 솔트 엔트로피가 71비트에서 128비트로 증가했습니다.
  • 사이트맵(django.contrib.sitemaps)
  • 피드 프레임워크(django.contrib.syndication)
    • 피드 항목마다 댓글용 URL을 특정할 수 있는 item_comments 훅을 추가했습니다.
  • 데코레이터
    • no_append_slash() 데코레이터를 사용하면 특정한 뷰만 마지막 슬래시(/)를 붙이지 않을 수 있습니다.
  • 파일 업로드
  • 제네릭 뷰
  • 관리 명령어
    • loaddata 명령어가 XZ 압축파일(.xz)과 LZMA 압축 파일(.lzma)을 지원합니다.
    • dumpdata 명령어가 bz2, gz, lzma, xz 포맷을 지원합니다.
    • makemigrations 명령어를 데이터베이스 연결 없이도 실행할 수 있습니다. 이 경우 데이터베이스 일관성(consistent migration history) 검사는 실행되지 않습니다.
    • BaseCommand.requires_system_checks 속성이 목록/튜플형으로 바뀌었습니다.
  • 마이그레이션
  • 모델
    • QuerySet.select_for_update() 에 추가된 no_key 파라미터를 사용하면, PostgresSQL의 약한 잠금(weaker lock)을 적용할 수 있습니다. (특정 행이 잠기더라도, 이 행들을 외래키로 참조하는 데이터를 생성할 수는 있습니다.)
    • When() 표현식에 condition 전달인자를 사용할 수 있습니다.
    • Index.includeUniqueConstraint.include 속성을 사용하여 PostgresSQL의 커버링 인덱스(covering index)를 생성할 수 있습니다.
    • MySQL과 MariaDB에 대해 QuerySet.update() 메서드가 order_by()의 영향을 받습니다.
    • FilteredRelation() 이 중첩된 연관 모델(nested relation)을 지원합니다.
    • Value() 표현식이 output_field를 자동으로 판단할 수 있습니다.
    • QuerySet.alias() 메서드를 사용하여 재사용할 수 있는 축약 표현식을 만들 수 있습니다. 이 표현식은 annotate, exclude, filter, order_by, update 메서드에서 사용할 수 있습니다. aggregate에 사용하려면 annotate을 거쳐야 합니다.
# alias를 filter에 사용할 수 있습니다.
people = Person.objects.alias(kids=Count('kids')).filter(kids__gt=2)

# aggregate에서 사용하려면 annotate을 거쳐야 합니다.
Person.objects.alias(kids=Count('kids')).annotate(kids=F('kids')).aggregate(Sum('kids'))
  • 모델 2
    • Collate 데이터베이스 함수가 추가되어, 데이터베이스 고유의 정렬과 필터링 기능을 사용할 수 있습니다.
    • TruncDateTruncTime 데이터베이스 함수에 tzinfo 파라미터를 넘기면 특정 타임존 기준으로 datetime 데이터를 가공할 수 있습니다.
    • CharFieldTextFielddb_collation 을 설정하면, 필드별로 데이터베이스 정렬(collation) 방식을 특정할 수 있습니다.
    • Random 데이터베이스 함수가 추가되었습니다.
    • Avg, Count 등의 Aggregate 함수와 F() , OuterRef() 등의 표현식에서 값 변형(transform)을 사용할 수 있습니다.
# 태어난 해에 죽은 사람을 필터링합니다
Person.objects.filter(birtyday__year=F('deathday__year'))

# 가장 최근에 죽은 사람의 년도를 찾습니다
Person.objects.aggregate(last_dead_year=Max('deathday__year'))
  • 모델 3 - JSONObject 데이터베이스 함수가 추가되었습니다.
Person.objects.create(name='Raccoony', num_of_articles=2)
person = Person.objects.annotate(json_object=JSONObject(
    name=Lower('name'),
    num_of_articles=F('num_of_articles') * 10
)).get()
person.json_object  # {'name': 'raccoony', 'num_of_articles': 20}
  • 페이지네이션
    • django.core.paginator.Paginator.get_elided_page_range()를 사용하여 페이지 목록을 축약할 수 있습니다. (축약된 페이지들은 ...로 표시됩니다.) 예를 들어 마지막 페이지가 100인 목록에 대해 get_elided_page_range(50, on_each_side=2, on_ends=2)라고 선언하면, [1, 2, '...', 48, 49, 50, 51, 52, '...', 99, 100]이 리턴됩니다. ( on_ends가 2이므로 1, 2, 99, 100이 포함되었고, on_each_side가 2이므로 48, 49, 51, 52가 포함됩니다.)
  • 요청과 응답
    • 응답 헤더가 HttpResponse.headers에 저장됩니다. 이 값 대신 HttpResponse 객체에 직접 접근할 수도 있습니다.
    • 다음의 예처럼, 응답 객체를 생성할 때 headers를 선언할 수 있습니다.
response = HttpResponse(headers={'Age': 120})

# 다음 두 행은 결과가 같습니다.
response.headers['Age'] = 120
response['Age'] = 120
  • 시리얼라이제이션
    • 행마다 하나의 JSON 객체를 저장하는 JSONL 포맷의 시리얼라이저를 dumpdataloaddata에서 사용할 수 있습니다. 대량 데이터를 생성할 때 행별로 메모리에 적재할 수 있기 때문에 유용합니다.
  • 템플릿
    • floatformat 템플릿 필터에서 g 접미어를 사용하여 천단위 구분자를 강제할 수 있습니다.
float_value = 12345.67000

# {{ value|floatformat:"2g" }}  -> 12,345.67
# {{ value|floatformat:"5g" }}  -> 12,345.67000
# {{ value|floatformat:"g" }}   -> 12,345.7
# {{ value|floatformat:"-3g" }} -> 12,345.67
# {{ value|floatformat:"-5g" }} -> 12,345.67

중단 예정인 기능(화살표 뒤의 기능을 사용하길 권합니다)

  • python-memcached에 문제가 발생했고 관리도 되지 않고 있기 때문에 이를 사용하는 django.core.cache.backends.memcached.MemcachedCache가 중단 예정입니다. django.core.cache.backends.memcached.PyMemcacheCachedjango.core.cache.backends.memcached.PyLibMCCache를 사용하세요.

마치며

요약은 여기까지입니다. 개인적으론 중요하지 않아 보여서 적지 않은 내용이 여러분에겐 더 중요할 수도 있으니, Django 3.2 공식 릴리스 노트를 한 번 살펴보시길 추천합니다.

다양함을 품을 수 있는 소프트웨어 개발자가 되고 싶습니다.