Published on

[django] 장고에서 여러 유니코드 데이터를 다루는 방법

Authors
  • avatar
    Name
    Almer Minified
    Twitter

Django에서 유니코드 데이터를 사용하는 방법

Django는 유니코드 데이터를 지원한다.

이는 데이터나 템플릿이 ASCII가 아닌 다른 인코딩으로 작성된 경우에도 적용된다.

데이터베이스 생성 시, 임의의 문자열 데이터를 저장할 수 있도록 UTF-8이나 UTF-16과 같은 인코딩으로 설정하는 것이 중요하다. 제한적인 인코딩을 사용하면 특정 문자를 저장할 수 없어 정보 손실이 발생할 수 있다. 예를 들어, MySQL 사용자는 MySQL 매뉴얼을 참조하여 데이터베이스 문자 세트 인코딩을 설정하거나 변경하는 방법을 확인해야 한다. PostgreSQL이나 Oracle 사용자도 각각의 매뉴얼에 따라 적절한 인코딩으로 데이터베이스를 생성하거나 변경해야 한다. SQLite 사용자는 별도의 조치가 필요 없다. SQLite는 항상 UTF-8을 내부 인코딩으로 사용한다.

Django의 데이터베이스 백엔드는 자동으로 문자열을 데이터베이스와 통신하기 적합한 인코딩으로 변환한다. 데이터베이스에서 검색한 문자열도 자동으로 변환한다. 사용자는 데이터베이스가 어떤 인코딩을 사용하는지 Django에 알릴 필요가 없다.

일반 문자열 처리와 관련해서 Django에서 문자열을 사용할 때 일반 문자열 또는 바이트 문자열(‘b’로 시작) 중에서 선택할 수 있다. 바이트 문자열은 인코딩 정보를 포함하지 않기 때문에 Django는 모든 바이트 문자열이 UTF-8이라고 가정한다. 다른 형식으로 인코딩된 문자열을 Django에 전달하면 UnicodeDecodeError가 발생할 수 있다다. ASCII 데이터만 사용하는 경우에는 일반 문자열을 안전하게 사용할 수 있다.

Django는 대부분의 경우 문자열을 처리하기 전에 문자열로 변환한다. 따라서 바이트 문자열을 전달하면 결과로 문자열을 받을 준비를 해야 한다.

lazy translation에 대해

국제화 기능을 사용하는 경우 lazy translation 문자열이라는 유형을 고려해야 한다. 이는 번역이 됐어도 실제 결과는 문자열이 사용될 때까지 보류되는 객체다. django.utils.functional.proxy 객체로 표시되는 객체가 바로 이런 경우이다. 해당 객체에 str()을 호출하면 현재 로케일의 문자열을 생성할 수 있게 된다.

기타 Utils

Django엔 유용한 몇 가지 유틸리티가 내재되어 있다.

변환 함수

django.utils.encoding 모듈에는 문자열 간의 변환을 쉽게 해주는 함수가 있다.

smart_str()

입력된 데이터를 문자열로 변환한다. Django는 폼 입력 데이터 처리 시 이 함수를 내부적으로 사용한다.

force_str()

smart_str()과 거의 동일하다. 근데 첫 번째 인자가 레이지 트랜슬레이션 인스턴스일 때가 다르다. smart_str()는 레이지 트랜슬레이션을 지키지만 force_str()는 강제로 문자열로 변환한다. 일반적으로 smart_str()을 사용하고 문자열로 작동해야만 하는 템플릿 태그나 필터에서 force_str()를 쓰면 된다.

smart_bytes()

smart_str()의 반대다. 첫 번째 인자를 바이트 문자열로 강제 변환한다. 일반적으로 입력 데이터가 문자열이나 바이트 문자열인지 확실하지 않은 경우에 쓴다. 이 경우엔 가능한 한 빨리 force_str()을 호출하고 그 결과를 항상 문자열로 취급하는 방식을 사용한다.

URI 및 IRI 처리

웹 프레임워크는 URL 처리를 해야만 한다. URL은 ASCII 문자만을 사용해 인코딩되어야 한다. 하지만 다국어 환경에서는 유니코드 문자를 포함할 수 있는 IRI로부터 URL을 구성해야 할 수도 있습니다. IRI를 URI로 변환하고 인용하는 데 이 함수들도 제공된다.

django.utils.encoding.iri_to_uri()

함수는 RFC 3987에 따라 IRI에서 URI로 변환을 구현한다.

urllib.parse.quote(), urllib.parse.quote_plus()

이 두 함수는 파이썬의 표준함수 중 하나이다. 두 함수는 약간 사용 목적이 다르다.

둘다 일반적으로 IRI 또는 URI 경로의 개별 부분에 quote()를 사용하여 ‘&’나 ‘%’와 같은 문자의 인코딩을 돕는다. 그 다음 전체 IRI에 iri_to_uri()를 적용해서 나머지 문자를 인코딩한다.

iri_to_uri() 및 uri_to_iri() 함수는 동일한 URI/IRI에 여러 번 호출하여도 이중 인용 문제가 발생하지 않는다.

Django에서는 데이터베이스로부터 검색된 모든 문자열이 스트링 타입으로 반환된다. 그리고 스트링 기반 모델 필드(CharField, TextField, URLField 등)는 유니코드 값을 포함한다. 이는 데이터가 ASCII 바이트 문자열에 들어갈 수 있더라도 항상 적용된다.

get_absolute_url()

URL은 오직 ASCII 문자만을 포함할 수 있다. ASCII가 아닌 데이터로 URL을 구성할 때는 URL에 맞게 인코딩해야 한다. reverse() 함수는 이를 자동으로 처리해준다.

그러므로 수동으로 URL을 구성할 때(즉, reverse() 함수를 사용하지 않을 때)는 인코딩을 직접 처리해야 한다. 이 경우 위에서 거론한 iri_to_uri() 및 quote() 함수를 사용하자.

from urllib.parse import quote
from django.utils.encoding import iri_to_uri

def get_absolute_url(self):
url = "/new_url/%s/?x=0&y=0" % quote(self.data)
return iri_to_uri(url)

이 함수는 self.data가 한자같은 비 ascii더라도 인코딩된 URL을 반환한다.

템플릿에서 문자열 사용법

from django.template import Template

t2 = Template("This is a string template.")

일반적으로 템플릿은 파일 시스템에서 가져오게 된다. 템플릿 파일이 UTF-8 인코딩으로 저장되어 있지 않다면 TEMPLATES에서 설정을 바꾸면 된다. 'file_charset'옵션을 이용하면 인코딩을 조정할 수 있다.

DEFAULT_CHARSET 은 기본적으로 UTF-8로 설정되어 있다. 근데실제로 쓸 일은 거의 없다.

템플릿 태그와 필터

템플릿 태그의 render() 메서드는 항상 문자열을 반환한다. 이 경우에는 렌더링 도중에 인코딩 작업을 끝내는 것이 좋으므로 레이지 트랜슬레이션을 사용할 이유는 거의 없다.

파일

파일을 업로드하려면 Django를 실행하는 환경이 비-ASCII 파일명도 다룰 수 있게 해야 한다. 환경이 올바르게 구성되어 있지 않으면 비-ASCII 문자가 포함된 파일 또는 콘텐츠를 저장할 때 UnicodeEncodeError 예외가 발생할 수 있기 때문이다.

import sys

sys.getfilesystemencoding()

이걸 이용하면 파일시스템의 현재 인코딩 방법을 알 수 있다. 여기서 “UTF-8”을 출력해야 제대로 사용할 수 있다.

폼 제출

HTML 폼 제출시엔 인코딩 정보가 자동으로 설정되지 않기 때문에 개발할 때 직접 설정해줘야 한다.

Django는 폼 데이터를 디코딩 할 때 기본적으로 lazy 하게 진행한다. 그러므로 HttpRequest 객체의 데이터는 실제로 사용될 때만 디코딩된다. 그렇기때문에 우리가 사용할 대부분의 데이터는 디코딩되지 않는다고 보면 된다. 오직 HttpRequest.GET 및 HttpRequest.POST 의 경우에만 디코딩이 자동으로 적용된다. 이 두 필드는 유니코드데이터로 변환돼서 취급된다. GET이랑 POST를 제외한 나머지는 HttpRequest의 다른 속성이나 메소드는 클라이언트가 제출한 것과 동일한 내용을 보여준다. 원한다면 디코딩을 다르게 해야 할 것이다.

폼 데이터

기본적으로 폼 데이터에 대한 디코딩은 DEFAULT_CHARSET 에서 바꿀 수 있다. 특정 폼에 대해서만 바꾸고 싶다면 HttpRequest 인스턴스에서 encoding 속성을 설정할 수 있다.

def my_encoding_view(request):
    request.encoding = "원하는 인코딩 방식"

Django는 파일 업로드의 경우는 자동으로 디코딩하진 않는다. 바이트를 잘못 건드리면 안되기 때문이다.

폼 입력 차원에서의 유효성 검사

그 전에 폼 입력 차원에서 유효성 검사를 하면 편리하다. 여기서 사용되는 형식의 문자를 결정해 놓으면 디코딩 인코딩 시에 오류도 방지할 수 있다. 개발에서 강제되는 것은 아니지만 들여놓으면 편리한 습관이다.

사용자 등록을 받는 폼을 만들어보자.

forms.py

from django import forms
from django.core.validators import EmailValidator, MaxLengthValidator

class UserRegistrationForm(forms.Form):
    name = forms.CharField(
        label='Your Name',
        max_length=100,
        validators=[MaxLengthValidator(100)],
        required=True
    )
    email = forms.EmailField(
        label='Email Address',
        validators=[EmailValidator()],
        required=True
    )

여기서 name과 email필드는 max_length를 가지고 있어서

validators=[EmailValidator()]

이부분에 의해 유효성 검사가 실행되게 된다.

views.py

from django.shortcuts import render
from .forms import UserRegistrationForm

def register(request):
    if request.method == 'POST':
        form = UserRegistrationForm(request.POST)
        if form.is_valid():
            # 폼 데이터가 유효하면 여기서 처리
            name = form.cleaned_data.get('name')
            email = form.cleaned_data.get('email')
            # 데이터 처리 로직 (예를 들면 데이터베이스 저장)
            return render(request, 'registration/success.html', {'name': name, 'email': email})
    else:
        form = UserRegistrationForm()

    return render(request, 'registration/register.html', {'form': form})

여기서는 form.is_valid 이 부분으로 유효성 검사를 한다.

템플릿파일


<!DOCTYPE html>
<html>
<head>
    <title>User Registration</title>
</head>
<body>
    <h2>Register</h2>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">Register</button>
    </form>
</body>
</html>

템플릿 파일에선

{% csrf_token %} 

을 이용해서 그 외 자잘한 공격들을 막아낼 수 있다.