커스텀 템플릿 태그 및 필터를 만드는 방법¶
Django의 템플릿 언어는 애플리케이션의 프레젠테이션 논리 요구를 해결하도록 설계된 다양한 :doc:’내장 태그 및 필터’와 함께 제공됩니다. 그럼에도 불구하고 템플릿의 핵심 요소 집합에서 다루지 않는 기능이 필요할 수 있습니다. Python을 사용하여 사용자 지정 태그와 필터를 정의하여 템플릿 엔진을 확장한 다음 :t 태그:’{% load %}’ 태그를 사용하여 템플릿에서 사용할 수 있도록 설정할 수 있습니다.
코드 레이아웃¶
사용자 정의 템플릿 태그 및 필터를 지정하는 가장 일반적인 장소는 Django 앱 내부입니다. 기존 앱과 관련된 경우 해당 앱에 번들을 추가하는 것이 좋습니다. 그렇지 않으면 새 앱에 추가할 수 있습니다. :seting:’에 Django 앱이 추가되면설치됨_아래에 설명된 기존 위치에서 정의하는 모든 태그는 템플릿 내에서 자동으로 로드됩니다.
이 앱은 ``템플릿 태그” 디렉토리를 ``모델”과 동일한 수준으로 포함해야 한다.파이로, “말하자면”피클 등 만약 이것이 존재하지 않는다면, 그것을 만들어라 - 디렉토리가 파이썬 패키지로 취급되도록 하기 위해 “_init__py” 파일을 잊지 마세요
개발 서버는 자동으로 다시 시작되지 않습니다
“템플릿 태그” 모듈을 추가한 후 서버를 재시작해야 템플릿에 태그 또는 필터를 사용할 수 있습니다.
사용자 지정 태그와 필터는 ``템플릿 태그” 디렉토리 내의 모듈에 상주하게 된다. 모듈 파일의 이름은 나중에 태그를 로드하는 데 사용할 이름이므로 다른 앱의 사용자 지정 태그 및 필터와 충돌하지 않는 이름을 선택하십시오.
For example, if your custom tags/filters are in a file called
poll_extras.py
, your app layout might look like this:
polls/
__init__.py
models.py
templatetags/
__init__.py
poll_extras.py
views.py
템플릿에서 다음을 사용합니다.
{% load poll_extras %}
사용자 지정 태그가 포함된 앱은 :seting:’에 있어야 합니다.설치됨_:t 태그의 경우 APPS’는 ‘{% load %}’ 태그가 작동합니다. 보안 기능은 다음과 같습니다. 이를 통해 모든 Django 설치에 대해 모든 템플릿 라이브러리에 대한 액세스를 설정하지 않고 단일 호스트 시스템의 여러 템플릿 라이브러리에 대해 Python 코드를 호스팅할 수 있습니다.
“템플릿 태그” 패키지에 얼마나 많은 모듈을 넣었는지는 제한이 없다. :t태그:’{%load %}’ 문은 앱 이름이 아닌 지정된 Python 모듈 이름에 대한 태그/필터를 로드한다는 점을 유념하십시오.
유효한 태그 라이브러리가 되려면 모듈 레벨 변수인 ``등록”을 포함해야 한다.모든 태그와 필터가 등록된 라이브러리 인스턴스. 따라서 모듈 상단 근처에 다음을 배치합니다.
from django import template
register = template.Library()
또는 “라이브러리” 인수를 통해 템플릿 태그 모듈을 :class에 등록할 수 있다.~django.backends.django장고사찰. 이 기능은 템플릿 태그를 로드할 때 템플릿 태그 모듈 이름과 다른 레이블을 사용하려는 경우에 유용합니다. 또한 응용 프로그램을 설치하지 않고도 태그를 등록할 수 있습니다.
사용자 정의 템플릿 필터 쓰기¶
사용자 정의 필터는 하나 또는 두 개의 인수를 사용하는 Python 함수입니다.
- 변수 값(입력) - 반드시 문자열은 아닙니다.
- 인수 값 - 기본값을 사용하거나 완전히 제외할 수 있습니다.
예를 들어 필터 ``{var|foo:”bar}”에서 필터 ``foo”는 변수 “var”와 변수 “bar”라는 주장 “bar”가 전달된다.
템플리트 언어는 예외 처리를 제공하지 않으므로, 템플리트 필터에서 발생하는 예외는 서버 오류로 노출됩니다. 따라서 필터 기능은 반환할 적절한 예비 값이 있는 경우 예외를 발생시키지 않아야 한다. 템플릿에서 명확한 버그를 나타내는 입력의 경우 예외를 발생시키는 것이 버그를 숨기는 자동 장애보다 나을 수 있습니다.
다음은 필터 정의 예제입니다.
def cut(value, arg):
"""Removes all values of arg from the given string"""
return value.replace(arg, "")
다음은 해당 필터를 사용하는 방법의 예입니다.
{{ somevariable|cut:"0" }}
대부분의 필터는 인수를 사용하지 않습니다. 이 경우 인수는 함수에서 제외합니다.
def lower(value): # Only one argument.
"""Converts a string into all lowercase"""
return value.lower()
사용자 정의 필터 등록¶
-
django.template.Library.
filter
()¶
일단 필터 정의를 작성했으면 “라이브러리” 인스턴스에 등록해야 django의 템플릿 언어로 사용할 수 있습니다.
register.filter("cut", cut)
register.filter("lower", lower)
The Library.filter()
method takes two arguments:
- 필터의 이름 - 문자열.
- 컴파일 함수 –파이썬 함수( 문자열로 함수 이름이 아님)입니다.
``register.filter()``를 장식자 대신으로 사용할 수 있습니다.
@register.filter(name="cut")
def cut(value, arg):
return value.replace(arg, "")
@register.filter
def lower(value):
return value.lower()
위의 두 번째 예에서와 같이 “name” 인수를 생략하면 Django는 함수 이름을 필터 이름으로 사용합니다.
마지막으로 ``등록하십시오.필터’도 ``is_safe”, ``autoescape”, “local time”의 세 가지 키워드 주장을 수용한다. 이러한 인수는 아래 ref:’필터 및 자동 이스케이프’ 및:ref:’필터 및 시간대’에 설명되어 있습니다.
문자열이 필요한 템플릿 필터¶
-
django.template.defaultfilters.
stringfilter
()¶
첫 번째 인수로 문자열만 예상하는 템플릿 필터를 작성하려면 장식기 ``스트링 필터”를 사용해야 한다. 이렇게 하면 기능이 전달되기 전에 개체가 문자열 값으로 변환됩니다.
from django import template
from django.template.defaultfilters import stringfilter
register = template.Library()
@register.filter
@stringfilter
def lower(value):
return value.lower()
이렇게 하면 정수 같은 것을 필터에 전달할 수 있고 정수에는 “속성 오류”가 발생하지 않습니다(정수에는 “낮은()” 방법이 없기 때문입니다.
필터 및 자동 이스케이프¶
사용자 정의 필터를 작성할 때 필터가 Django의 자동 이스케이핑 동작과 어떻게 상호 작용하는지 생각해 보십시오. 템플릿 코드 내에서 두 가지 유형의 문자열을 전달할 수 있습니다.
**원시 문자열*은(는) 기본 Python 문자열입니다. 출력 시 자동 이스케이프가 적용되고 변경되지 않은 상태로 표시되는 경우, 그렇지 않은 경우 이 기능은 이스케이프됩니다.
**safe strings**는 출력 시 추가 이스케이프로부터 안전한 것으로 표시된 문자열입니다. 필요한 모든 탈출은 이미 이루어졌다. 일반적으로 클라이언트 측에서 있는 그대로 해석할 원시 HTML을 포함하는 출력에 사용됩니다.
내부적으로 이러한 문자열은 다음과 같은 유형입니다.’~django.utils.tils.dring.SafeString’. 다음과 같은 코드를 사용하여 테스트할 수 있습니다.
from django.utils.safestring import SafeString if isinstance(value, SafeString): # Do something with the "safe" string. ...
템플릿 필터 코드 두 가지 상황 중 하나에 속합니다:
당신의 필터는 HTML이 안전하지 않은 문자(
<
,>
,'
,"
or&
)를 아직 존재하지 않는 결과에 도입하지 않는다. 이 경우, Django가 당신을 위해 모든 자동 대피 처리를 하도록 할 수 있습니다. 다음과 같이 필터 기능을 등록할 때 “is_safe” 플래그를 “true”로 설정하면 된다.@register.filter(is_safe=True) def myfilter(value): return value
이 플래그는 Django에게 “안전한” 문자열을 필터에 전달해도 결과는 “안전”하며, 비안전 문자열이 전달되면 Django는 필요할 경우 자동으로 이 문자열을 이스케이프할 것이라고 합니다.
여러분은 이것을 “이 필터가 안전하다 - 이것은 안전하지 않은 HTML의 가능성을 전혀 유발하지 않는다”라는 의미로 생각할 수 있습니다.
``is_safe”가 필요한 이유는 ``SafeData” 물체를 정상적인 “str” 물체로 되돌리고 그것들을 모두 잡으려고 하기보다는 필터 완료 후 손상을 Django가 수리하는 정상적인 문자열 작업이 많기 때문이다.
예를 들어, 입력 끝에 문자열 “xx”를 추가하는 필터가 있다고 가정합시다. 이것은 (이미 존재했던 어떤 것과도 별도로) 결과에 위험한 HTML 문자를 도입하지 않기 때문에, 필터를 “is_safe”로 표시해야 한다.
@register.filter(is_safe=True) def add_xx(value): return "%sxx" % value
자동 이스케이프가 활성화된 템플릿에서 이 필터를 사용할 경우 Django는 입력이 “안전”으로 표시되지 않을 때마다 출력을 이스케이프합니다.
기본적으로 “is_safe”는 “False”이며 필요하지 않은 모든 필터에서 제외할 수 있습니다.
필터가 실제로 안전한 문자열을 남기는지 결정할 때 주의하십시오. 문자를 *제거*하는 경우 결과에 불균형한 HTML 태그 또는 엔티티를 남길 수 있습니다. 예를 들어 입력에서 “”를 제거하면 “”가 “”로 바뀔 수 있는데, 문제가 발생하지 않도록 출력에서 “a”를 빼야 할 것이다. 마찬가지로 세미콜론(‘;’)을 제거하면 ``&”이 ``&”로 바뀔 수 있는데, 이것은 더 이상 유효한 실체가 아니기 때문에 더 이상의 탈출이 필요하다. 대부분의 경우 이렇게 까다롭지는 않겠지만 코드를 검토할 때 이와 같은 문제를 주의 깊게 살펴보십시오.
필터를 “is_safe”로 표시하면 필터의 값을 문자열로 반환해야 합니다. 필터가 부울 값이나 기타 문자열이 아닌 값을 반환해야 하는 경우 “is_safe”로 표시하면 의도하지 않은 결과가 발생할 수 있습니다(예: 부울 False를 문자열 ‘False’로 변환).
또는 필터 코드가 필요한 이스케이프를 수동으로 처리할 수 있습니다. 이것은 결과에 새 HTML 마크업을 도입할 때 필요합니다. HTML 마크업이 더 이상 유출되지 않도록 출력을 안전한 것으로 표시하여 입력을 직접 처리해야 합니다.
출력을 안전한 문자열로 표시하려면 func:’django.utils.safeestring.mark_safe’를 사용합니다.
하지만 조심하세요. 출력을 안전한 것으로 표시하는 것 이상을 수행해야 합니다. 실제로 *안전성을 보장해야 하며, 자동 이스케이핑이 적용되는지 여부에 따라 작업이 달라집니다. 자동 이스케이핑이 설정되거나 해제된 템플릿에서 작동할 수 있는 필터를 작성하여 템플릿 작성자에게 보다 쉽게 제공할 수 있습니다.
필터가 현재의 자동 탈출 상태를 알기 위해서는 필터 기능을 등록할 때 “needs_autoscape” 플래그를 “true”로 설정하십시오. (이 깃발을 지정하지 않으면 기본값은 “False”입니다.) 이 깃발은 장고에게 당신의 필터 기능이 “자동 탈출”이라는 추가적인 키워드 인수가 통과되기를 원한다는 것을 알려주고 있는데, 그것은 자동 탈출이 유효하다면 “참”이고 그렇지 않으면 “거짓”이다. “자동탈출” 파라미터의 디폴트를 “트루”로 설정하여 파이썬 코드에서 함수를 호출할 경우 기본적으로 이스케이프가 활성화되도록 할 것을 권고한다.
예를 들어 문자열의 첫 번째 문자를 강조하는 필터를 작성하겠습니다.
from django import template from django.utils.html import conditional_escape from django.utils.safestring import mark_safe register = template.Library() @register.filter(needs_autoescape=True) def initial_letter_filter(text, autoescape=True): first, other = text[0], text[1:] if autoescape: esc = conditional_escape else: esc = lambda x: x result = "<strong>%s</strong>%s" % (esc(first), esc(other)) return mark_safe(result)
“needs_autoscape” 깃발과 “autoscape” 키워드 주장은 필터가 호출될 때 자동 탈출이 유효한지 여부를 우리의 기능이 알게 된다는 것을 의미한다. 우리는 입력 데이터가 ``django.utils.html.conditional_escape”를 통과해야 하는지 여부를 결정하기 위해 “autoscape”를 사용한다. (후자의 경우, 우리는 ID 기능을 “scape” 기능으로 사용한다.) 조건부_탈출() 기능은 ***이 아닌 ``안전한 데이터” 인스턴스인 입력만 빠져나간다는 점을 제외하면 ``탈출()”과 같다. “안전한 데이터” 인스턴스가 ``조건적”으로 넘어가면 데이터는 변경되지 않고 반환된다.
마지막으로, 위의 예에서, 우리는 HTML이 더 이상 탈출하지 않고 템플릿에 직접 삽입되도록 결과를 안전한 것으로 표시하는 것을 기억한다.
(이를 포함하더라도) 이 경우 “안전하다”는 깃발에 대해선 걱정할 필요가 없다. 당신이 수동으로 자동 탈출 문제를 처리하고 안전한 줄을 돌려줄 때마다 “is_safe” 깃발은 어느 쪽도 변하지 않을 것이다.
경고
내장된 필터를 재사용할 때 XSS 취약성 방지
django의 내장 필터에는 ``autoescape=True”이 있다.올바른 자동 이스케이프 동작을 취하고 사이트 간 스크립트의 취약성을 피하기 위해 기본적으로 True’를 선택합니다.
이전 버전의 장고에서는 장고의 내장 필터를 ``자동 탈출” 디폴트로 재사용할 때 주의해야 한다. ``자동 탈출”을 통과해야 할 것이다.맞다’는 말은 자동 탈출을 하는 것이다.
예를 들어 :t필터를 조합한 “urlize_and_linebreaks”라는 사용자 정의 필터를 작성하려는 경우:urlize와 :tfilter:”line breaksbr” 필터의 모양은 다음과 같습니다.
from django.template.defaultfilters import linebreaksbr, urlize
@register.filter(needs_autoescape=True)
def urlize_and_linebreaks(text, autoescape=True):
return linebreaksbr(urlize(text, autoescape=autoescape), autoescape=autoescape)
그러면
{{ comment|urlize_and_linebreaks }}
다음과 같습니다:
{{ comment|urlize|linebreaksbr }}
필터 및 시간대¶
만일 Class: datetime.datetime 의 Object 에 적용되는 커스텀 필터를 사용할려면 보통은 그것을 “expects_localtime” 이라는 플래그에 True 라고 세팅 해야합니다.
@register.filter(expects_localtime=True)
def businesshours(value):
try:
return 9 <= value.hour < 17
except AttributeError:
return ""
플래그값이 세팅 되었을때 만일 필터의 첫번째 인자가 datetime 이 허용하는 time zone 이라면 장고는 적절한 싯점에 time zone 에 대한 참조 규칙에 의거한 정형화된 변경을 하도록 그 필터를 전달함으로서 현재의 time zone 으로 바꾸 도록 합니다.
커스텀 탬플릿 태그 만들기¶
태그는 필터보다 더 복잡합니다, 그 이유는 태그가 그 어떤것도 할 수 있기 때문입니다. 장고는 빠르고 쉬운 다양한 태그 작성법을 만들도록 합니다. 먼저 우리는 이들 빠른 방법을 탐색 할 것 입니다. 그 다음에 그 빠른 방법이 충분하다고 생각되지 않으면 태그 작성법을 스크래치(끄적거림) 부터 설명 할 겁니다.
간단한 태그¶
-
django.template.Library.
simple_tag
()¶
대부분의 템플릿 태그는 문자열 또는 템플릿 변수 등 여러 인수를 사용하고 입력 인수와 일부 외부 정보만을 기반으로 일부 처리를 수행한 후 결과를 반환합니다. 예를 들어 “current_time” 태그는 형식 문자열을 허용하고 그에 따라 형식을 지정한 문자열로 시간을 반환할 수 있다.
이러한 유형의 태그를 쉽게 만들 수 있도록 Django는 “simple_tag”라는 도우미 기능을 제공합니다. 이 기능은 ``django. template”의 한 방법이다.라이브러리’는 임의의 수의 인수를 수용하여 이를 “렌더” 기능과 위에서 언급한 다른 필요한 비트로 감싸서 템플릿 시스템에 등록한다
따라서 우리의 ``current_time” 기능은 다음과 같이 쓰여질 수 있다.
import datetime
from django import template
register = template.Library()
@register.simple_tag
def current_time(format_string):
return datetime.datetime.now().strftime(format_string)
“단순_태그” 도우미 기능에 대해 몇 가지 주목할 점은 다음과 같다.
- 필요한 수의 인수 등을 확인하는 작업은 기능이 호출될 때까지 이미 수행되었으므로 그렇게 할 필요가 없습니다.
- 인수에 대한 따옴표가 이미 제거되었으므로 일반 문자열을 수신합니다.
- 인수가 템플릿 변수였다면, 우리의 함수는 변수 자체가 아니라 변수의 현재 값을 전달합니다.
“simple_tag”는 다른 태그 유틸리티와 달리 템플릿 컨텍스트가 자동 이스케이프 모드인 경우 출력을 func:’~django.utils.html.conditional_escape’를 통해 전달하여 올바른 HTML을 보장하고 XSS 취약점으로부터 사용자를 보호합니다.
추가 이스케이프가 필요하지 않은 경우 코드에서 XSS 취약성이 포함되지 않은 것이 확실한 경우 func:’~django.utils.safeestring.mark_safe’를 사용해야 합니다. 작은 HTML 조각을 만들 때는 ``mark_safe()” 대신 :func:’~django.utils.html.format_html’을 사용하는 것이 좋다.
템플릿 태그가 현재 컨텍스트에 액세스해야 하는 경우 태그 등록 시 “takes_context” 인수를 사용할 수 있습니다.
@register.simple_tag(takes_context=True)
def current_time(context, format_string):
timezone = context["timezone"]
return your_get_current_time_method(timezone, format_string)
첫 번째 논거를 *context*라고 해야 한다는 점에 유의하십시오.
“takes_context” 옵션의 작동 방식에 대한 자세한 내용은 ref:”포함 태그” 섹션을 참조하십시오.
태그 이름을 변경해야 하는 경우 태그의 사용자 지정 이름을 제공할 수 있습니다.
register.simple_tag(lambda x: x - 1, name="minusone")
@register.simple_tag(name="minustwo")
def some_function(value):
return value - 2
‘’단순_태그 함수들은 위치 또는 키워드 인수를 얼마든지 수용할 수 있다. 예:
@register.simple_tag
def my_tag(a, b, *args, **kwargs):
warning = kwargs["warning"]
profile = kwargs["profile"]
...
return ...
그런 다음 템플릿에서 공백으로 구분된 개수의 인수를 템플릿 태그에 전달할 수 있습니다. Python에서처럼 키워드 인수의 값은 동일한 부호(”’ =를 사용하여 설정되며, 위치 인수 뒤에 제공되어야 한다. 예:
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
태그 결과를 직접 출력하지 않고 템플릿 변수에 저장할 수 있습니다. 이것은 변수 이름 뒤에 나오는 “as”라는 논거를 사용하여 이루어진다. 이렇게 하면 다음과 같은 상황에 맞는 컨텐츠를 직접 출력할 수 있습니다.
{% current_time "%Y-%m-%d %I:%M %p" as the_time %}
<p>The time is {{ the_time }}.</p>
포함 태그¶
-
django.template.Library.
inclusion_tag
()¶
템플릿 태그의 또 다른 일반적인 유형은 다른 템플릿을 렌더링하여 일부 데이터를 표시하는 유형입니다. 예를 들어, Django의 관리 인터페이스는 사용자 정의 템플릿 태그를 사용하여 “추가/변경” 양식 페이지의 아래쪽에 단추를 표시합니다. 이러한 버튼은 항상 동일하게 표시되지만 링크 대상은 편집 중인 개체에 따라 달라지므로 현재 개체의 세부 정보가 채워진 작은 템플릿을 사용할 수 있습니다. (관리자의 경우, 이것은 ``제출_행” 태그이다.)
이러한 종류의 태그를 “포함 태그”라고 합니다.
포함 태그를 작성하는 것이 가장 좋은 예일 수 있습니다. 자습서에서 만든 것과 같이 주어진 “Poll” 객체에 대한 선택 목록을 출력하는 태그를 작성합시다. 태그는 다음과 같습니다.
{% show_results poll %}
…그리고 결과는 다음과 같습니다.
<ul>
<li>First choice</li>
<li>Second choice</li>
<li>Third choice</li>
</ul>
먼저 인수를 사용하고 결과에 대한 데이터 사전을 만드는 함수를 정의합니다. 여기서 중요한 점은 더 복잡한 것이 아니라 사전만 돌려주면 된다는 것입니다. 템플릿 조각의 템플릿 컨텍스트로 사용됩니다. 예:
def show_results(poll):
choices = poll.choice_set.all()
return {"choices": choices}
그런 다음 태그의 출력을 렌더링하는 데 사용되는 템플릿을 만듭니다. 이 템플릿은 태그의 고정 기능입니다. 태그 작성기는 템플릿 디자이너가 아니라 태그를 지정합니다. 이 예에서는 템플릿이 매우 짧습니다.
<ul>
{% for choice in choices %}
<li> {{ choice }} </li>
{% endfor %}
</ul>
이제 “도서관 목적”에 “포함_태그()” 방식을 “도서관 목적”이라고 부르며 포함 태그를 만들고 등록해야 한다. 위의 템플릿이 템플릿 로더가 검색하는 디렉토리의 “results.html” 파일에 있는 경우 다음과 같이 태그를 등록합니다.
# Here, register is a django.template.Library instance, as before
@register.inclusion_tag("results.html")
def show_results(poll): ...
또는 :class:’django.template를 사용하여 포함 태그를 등록할 수 있습니다.템플릿 인스턴스:
from django.template.loader import get_template
t = get_template("results.html")
register.inclusion_tag(t)(show_results)
…처음 함수를 생성할 때 사용합니다.
때때로 포함 태그에 많은 인수가 필요할 수 있으므로 템플릿 작성자가 모든 인수를 전달하고 순서를 기억하기가 어렵습니다. 이를 해결하기 위해 Django는 태그 포함을 위한 “takes_context” 옵션을 제공한다. 템플릿 태그를 만들 때 “takes_context”를 지정하면 태그에 필요한 인수가 없으며 기본 Python 함수에 태그가 호출되었을 때 템플릿 컨텍스트라는 하나의 인수가 있게 됩니다.
예를 들어 메인 페이지로 돌아가는 ``home_link”와 “home_title” 변수를 포함하는 컨텍스트에서 항상 사용되는 포함 태그를 작성한다고 하자. Python 기능은 다음과 같습니다.
@register.inclusion_tag("link.html", takes_context=True)
def jump_link(context):
return {
"link": context["home_link"],
"title": context["home_title"],
}
이 함수에 대한 첫 번째 매개 변수는 *”context”라고 해야 합니다.
그 ``register.laught_tagles” 라인으로 우리는 “laf_laught =”이라고 명기했다.True’와 템플릿의 이름입니다. “link.html”이라는 템플릿은 다음과 같다.
Jump directly to <a href="{{ link }}">{{ title }}</a>.
그런 다음 언제든지 해당 사용자 지정 태그를 사용하려면 라이브러리를 로드하고 다음과 같은 인수 없이 호출하십시오.
{% jump_link %}
참고: “disclos_disclosen =”을 사용할 때true’는 템플릿 태그에 인수를 전달할 필요가 없습니다. 자동으로 컨텍스트에 액세스할 수 있습니다.
“takes_context”라는 매개변수는 기본적으로 “False”로 설정되어 있다. “참”으로 설정하면 이 예에서와 같이 태그가 컨텍스트 객체를 통과합니다. 그것이 이번 사건과 이전의 ``포함_태그” 사례와의 유일한 차이점이다.
“filt_tagles 함수는 위치 또는 키워드 인수를 얼마든지 수용할 수 있다. 예:
@register.inclusion_tag("my_template.html")
def my_tag(a, b, *args, **kwargs):
warning = kwargs["warning"]
profile = kwargs["profile"]
...
return ...
그런 다음 템플릿에서 공백으로 구분된 개수의 인수를 템플릿 태그에 전달할 수 있습니다. Python에서처럼 키워드 인수의 값은 동일한 부호(”’ =를 사용하여 설정되며, 위치 인수 뒤에 제공되어야 한다. 예:
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
고급 사용자 지정 템플릿 태그¶
사용자 지정 템플릿 태그를 만드는 기본 기능이 충분하지 않은 경우도 있습니다. 걱정하지 마십시오. Django는 처음부터 템플릿 태그를 만드는 데 필요한 내부 액세스 권한을 제공합니다.
간략한 개요¶
템플릿 시스템은 컴파일 및 렌더링의 2단계 프로세스로 작동합니다. 사용자 정의 템플릿 태그를 정의하려면 컴파일의 작동 방식과 렌더링 방식을 지정합니다.
Django는 템플릿을 컴파일할 때 원시 템플릿 텍스트를 “노드”로 분할합니다. 각 노드는 ``django.template”의 인스턴스이다.노드’는 “렌더()” 방식을 가지고 있다. 컴파일된 템플릿은 “노드” 개체의 목록입니다. 컴파일된 템플릿 객체의 “렌더()”를 호출할 때 템플릿은 주어진 컨텍스트를 사용하여 노드 목록에 있는 각 “노드”에 “렌더()”를 호출한다. 결과는 모두 함께 연결되어 템플릿 출력을 형성합니다.
따라서 사용자 정의 템플릿 태그를 정의하려면 원시 템플릿 태그를 “노드”(컴파일 기능)로 변환하는 방법과 노드의 “렌더()” 메서드가 수행하는 방법을 지정합니다.
컴파일 기능 쓰기¶
각 템플릿 태그에 대해 템플릿 파서가 발견하면 태그 내용 및 파서 개체 자체를 사용하여 Python 함수를 호출합니다. 이 기능은 태그의 내용에 따라 “노드” 인스턴스를 반환하는 역할을 한다.
예를 들어, 태그에 지정된 매개 변수에 따라 포맷된 현재 날짜/시간을 표시하는 템플릿 태그 “{% current_time %}”의 전체 구현을 :func:’~time.strftime’ 구문으로 작성해보자. 태그 구문은 다른 것보다 먼저 결정하는 것이 좋습니다. 이 경우 태그는 다음과 같이 사용해야 합니다.
<p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p>
이 기능의 파서는 매개 변수를 잡고 다음과 같은 ``노드” 개체를 만들어야 한다.
from django import template
def do_current_time(parser, token):
try:
# split_contents() knows not to split quoted strings.
tag_name, format_string = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError(
"%r tag requires a single argument" % token.contents.split()[0]
)
if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
raise template.TemplateSyntaxError(
"%r tag's argument should be in quotes" % tag_name
)
return CurrentTimeNode(format_string[1:-1])
각주:
- “파서라는 것은 템플릿 파서 개체이다. 이 예에서는 필요하지 않습니다.
- “알아두다.알아두다”는 태그의 생색내기 내용물의 줄이다. 우리의 예에서 그것은 “‘current_time %Y-%m- %I:%M”이다.
- “token.split_contents()” 방식은 인용된 문자열을 함께 유지하면서 공간에 대한 인수를 구분한다. 좀더 간단한 ``token.contents.split()”는 인용된 문자열 내의 공간을 포함하여 모든 공간에서 순진하게 분리되기 때문에 그렇게 강력하지는 않을 것이다. 항상 ``token.split_contents()”를 사용하는 것이 좋다.
- 이 기능은 ``장고 템플릿”을 올리는 데 책임이 있다.모든 구문 오류에 대해 유용한 메시지가 포함된 TemplateSyntaxError’입니다.
- “템플릿 구문 오류” 예외는 “tag_name” 변수를 사용한다. 오류 메시지에서 태그 이름을 하드 코딩하지 마십시오. 태그 이름이 기능과 결합됩니다. “항상”은 당신의 태그 이름이 될 것이다. 심지어 그 태그가 아무런 언쟁도 없을 때에도 말이다.
- 이 기능은 노드가 이 태그에 대해 알아야 할 모든 것을 포함한 “Current TimeNode”를 반환합니다. 이 경우 이는 “%Y-%m-%I:%M”이라는 논리를 통과한다. 템플릿 태그의 선행 및 후행 따옴표는 ``format_string[1:-1]”에서 제거됩니다.
- 구문 분석은 매우 낮은 수준입니다. Django 개발자들은 EBNF 문법과 같은 기술을 사용하여 이 구문 분석 시스템 위에 작은 프레임워크를 쓰는 것을 실험했지만, 그러한 실험들은 템플릿 엔진을 너무 느리게 만들었다. 가장 빠르기 때문에 낮은 수준입니다.
렌더러 작성¶
The second step in writing custom tags is to define a Node
subclass that
has a render()
method.
위의 예를 계속하면서 우리는 ``Current TimeNode”를 정의해야 한다.
import datetime
from django import template
class CurrentTimeNode(template.Node):
def __init__(self, format_string):
self.format_string = format_string
def render(self, context):
return datetime.datetime.now().strftime(self.format_string)
각주:
- “_init__()”는 “do_current_time”로부터 “format_string”을 얻는다. 옵션/매개변수/논란을 항상 “_init__()”를 통해 “노드”에 전달한다.
- “렌더() 방식”은 그 일이 실제로 일어나는 곳이다.
- ``고장”은 일반적으로, 특히 생산 환경에서 묵묵히 실패해야 한다. 그러나 경우에 따라서는 특히 ``context.template.engine.debug”가 “true”인 경우 이 방법은 디버깅을 쉽게 하기 위해 예외를 발생시킬 수 있다. 예를 들어 몇 개의 핵심 태그가 ``django.template”을 올린다.잘못된 인수 번호나 유형의 인수를 수신하는 경우 ‘TemplateSyntaxError’를 선택합니다.
결국, 컴파일과 렌더링의 이러한 디커플링은 효율적인 템플릿 시스템이 됩니다. 왜냐하면 템플릿은 여러 번 구문 분석할 필요 없이 여러 컨텍스트를 렌더링할 수 있기 때문입니다.
자동 회피 고려 사항¶
템플릿 태그의 출력은 ****가 아니며 자동 이스케이프 필터를 통해 자동으로 실행됩니다( :meth:’~django.template 제외).위에서 설명한 Library.simple_tag’입니다. 그러나 템플릿 태그를 작성할 때 기억해야 할 몇 가지 사항이 있습니다.
템플릿 태그의 ‘’렌더()’’ 방식이 결과를 문자열로 반환하는 것이 아니라 컨텍스트 변수에 저장한다면 적절하다면 ``mark_safe()”라고 부르는 것이 좋다. 변수가 궁극적으로 렌더링될 때, 그것은 그 당시에 유효한 자동 탈출 설정의 영향을 받게 되므로, 추가 탈출로부터 안전해야 하는 콘텐츠는 다음과 같이 표시해야 한다.
또한 템플릿 태그가 일부 하위 렌더링을 수행하기 위한 새 컨텍스트를 만드는 경우 자동 이스케이프 속성을 현재 컨텍스트 값으로 설정하십시오. ``_init__”의 문맥 계급 방법은 당신이 이 목적을 위해 사용할 수 있는 ``자동 탈출”이라는 매개 변수를 취한다. 예:
from django.template import Context
def render(self, context):
# ...
new_context = Context({"var": obj}, autoescape=context.autoescape)
# ... Do something with new_context ...
이는 매우 흔한 상황은 아니지만 만약 당신이 템플릿을 스스로 렌더링한다면 유용하다. 예를 들어:
def render(self, context):
t = context.template.engine.get_template("small_fragment.html")
return t.render(Context({"var": obj}, autoescape=context.autoescape))
만약 우리가 현재의 ``상황”에서 통과를 소홀히 했다면 좋았을 것이다.이 예에서 우리의 새로운 “컨텍스트”에 대한 자동 이스케이프’ 값은 항상 자동으로 이스케이프되었을 것이며, 템플릿 태그를 :ttag:’{% autoscape off %}’ 블록 내에서 사용할 경우 바람직하지 않은 동작일 수 있다.
나사산 안전 고려 사항¶
노드가 한번 구문 분석되면 “렌더” 방법은 얼마든지 호출할 수 있다. Django는 때때로 다중 스레드 환경에서 실행되므로, 단일 노드는 두 개의 개별 요청에 대응하여 서로 다른 컨텍스트로 동시에 렌더링될 수 있습니다. 따라서 템플릿 태그가 스레드에 안전한지 확인하는 것이 중요합니다.
템플릿 태그가 스레드 안전하도록 하려면 노드 자체에 상태 정보를 저장해서는 안 됩니다. 예를 들어, Django는 다음을 렌더링할 때마다 주어진 문자열 목록을 순환하는 내장 :ttag:’cycle’ 템플릿 태그를 제공합니다.
{% for o in some_list %}
<tr class="{% cycle 'row1' 'row2' %}">
...
</tr>
{% endfor %}
“CycleNode”의 안일한 구현은 이와 같이 보일 것이다:
import itertools
from django import template
class CycleNode(template.Node):
def __init__(self, cyclevars):
self.cycle_iter = itertools.cycle(cyclevars)
def render(self, context):
return next(self.cycle_iter)
그러나 위에서 템플릿 조각을 렌더링하는 두 개의 템플릿이 동시에 있다고 가정합니다.
- 스레드 1은 첫 번째 루프 반복 ‘’CycleNode.render()’’ returns ‘row1’을 수행합니다.
- 스레드 2는 첫 번째 루프 반복 “”CycleNode.render()”를 반환하는 ‘row2’를 수행합니다.
- 스레드 1은 두 번째 루프 반복 “”CycleNode.render()””는 ‘행1’을 반환합니다.
- 스레드 2는 두 번째 반복 “CycleNode.render()“‘returns ‘row2’를 수행합니다.
CycleNode는 반복되고 있지만 전 세계적으로 반복되고 있습니다. 스레드 1과 스레드 2에 관한 한 항상 동일한 값을 반환합니다. 이건 우리가 원하는 게 아니야!
이 문제를 해결하기 위해 장고는 현재 렌더링되고 있는 템플릿의 “컨텍스트”와 관련된 “렌더_컨텍스트”를 제공한다. “render_context”는 파이썬 사전처럼 동작하며 “node” 상태를 “render” 방식의 호출 사이에 저장하는 데 사용해야 한다.
우리의 ``CycleNode” 구현을 “render_context”를 사용하기 위해 재구성해 보자.
class CycleNode(template.Node):
def __init__(self, cyclevars):
self.cyclevars = cyclevars
def render(self, context):
if self not in context.render_context:
context.render_context[self] = itertools.cycle(self.cyclevars)
cycle_iter = context.render_context[self]
return next(cycle_iter)
주의할 점은 “노드”의 수명 내내 변하지 않는 글로벌 정보를 속성으로 저장하는 것이 매우 안전하다는 것이다. “CycleNode”의 경우 “Node”가 인스턴스화된 후에도 “Cyclevars”의 주장은 변하지 않으므로 “render_context”에 넣을 필요가 없다. 그러나 현재 ``CycleNode”의 반복처럼 현재 렌더링되고 있는 템플릿에 특정한 국가 정보는 “render_context”에 저장되어야 한다.
태그를 등록한다.¶
마지막으로 위의 ref:”사용자 정의 템플릿 태그 작성”에서 설명한 대로 모듈의 “라이브러리” 인스턴스에 태그를 등록합니다. 예:
register.tag("current_time", do_current_time)
“태그()” 방식은 두 가지 주장을 한다.
- 템플릿 태그의 이름 - 문자열. 이 옵션을 제외하면 컴파일 기능의 이름이 사용됩니다.
- 컴파일 함수 –파이썬 함수( 문자열로 함수 이름이 아님)입니다.
필터 등록과 마찬가지로, 다음 구성 요소로 사용할 수도 있습니다.
@register.tag(name="current_time")
def do_current_time(parser, token): ...
@register.tag
def shout(parser, token): ...
위의 두 번째 예와 같이 “name” 인수를 생략하면 Django는 함수의 이름을 태그 이름으로 사용합니다.
태그에 템플릿 변수 전달¶
``token.split_contents()”를 사용하여 템플릿 태그에 원하는 수의 인수를 전달할 수 있지만 인수는 모두 문자열 리터럴로 압축 해제됩니다. 동적 내용(템플릿 변수)을 템플릿 태그에 인수로 전달하려면 조금 더 많은 작업이 필요합니다.
이전 예에서는 현재 시간을 문자열로 포맷하고 문자열을 반환했지만 :class에서 전달하고자 한다고 가정합니다.’~django.db.db.know.개체에서 ‘DateTimeField’로, 날짜-시간의 템플릿 태그 형식을 갖습니다.
<p>This post was last updated at {% format_time blog_entry.date_updated "%Y-%m-%d %I:%M %p" %}.</p>
당초 ``token.split_contents()”는 세 가지 값을 반환한다.
- 태그 이름은 “format_time”
- ‘’blog_entry.date_updated’’ 문자열(주변 인용문 없음)
- 형식 문자열 “”%Y-%m-%I:%M””” “split_contents()”에서 반환되는 값은 이와 같은 문자열 리터럴의 선행 및 후행 인용을 포함한다.
이제 너의 태그는 이렇게 보이기 시작해야한다.
from django import template
def do_format_time(parser, token):
try:
# split_contents() knows not to split quoted strings.
tag_name, date_to_be_formatted, format_string = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError(
"%r tag requires exactly two arguments" % token.contents.split()[0]
)
if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
raise template.TemplateSyntaxError(
"%r tag's argument should be in quotes" % tag_name
)
return FormatTimeNode(date_to_be_formatted, format_string[1:-1])
``blog_entry” 개체의 ``날짜_업데이트” 속성의 실제 내용을 검색하려면 렌더러를 변경해야 한다. 이는 ``django. template”에서 ``Variable()” 클래스를 사용하면 이룰 수 있다.
“Variable” 클래스를 사용하려면 해결할 변수의 이름으로 인스턴스화한 다음 “variable.resolve(컨텍스트)”라고 부른다. 예를 들어 다음과 같습니다.
class FormatTimeNode(template.Node):
def __init__(self, date_to_be_formatted, format_string):
self.date_to_be_formatted = template.Variable(date_to_be_formatted)
self.format_string = format_string
def render(self, context):
try:
actual_date = self.date_to_be_formatted.resolve(context)
return actual_date.strftime(self.format_string)
except template.VariableDoesNotExist:
return ""
가변 해상도는 페이지의 현재 컨텍스트에서 전달된 문자열을 해결할 수 없는 경우 “Variable DonsNot Exception.
컨텍스트에 변수를 설정¶
위의 예제는 값을 출력합니다. 일반적으로 템플릿 태그가 값을 출력하는 대신 템플릿 변수를 설정하는 것이 더 유연합니다. 이렇게 하면 템플릿 작성자는 템플릿 태그가 생성한 값을 재사용할 수 있습니다.
컨텍스트에서 변수를 설정하려면 컨텍스트 객체에 대한 사전 할당을 “렌더()” 방법으로 사용하십시오. 여기 템플릿 변수를 출력하는 대신 “current_time”을 설정하는 “CurrentTimeNode”의 업데이트된 버전이 있다.
import datetime
from django import template
class CurrentTimeNode2(template.Node):
def __init__(self, format_string):
self.format_string = format_string
def render(self, context):
context["current_time"] = datetime.datetime.now().strftime(self.format_string)
return ""
“render()”는 빈 문자열을 반환합니다. “만들다”는 항상 문자열 출력을 반환해야 한다. 템플릿 태그가 변수를 설정하기만 하면 “render()”는 빈 문자열을 반환해야 한다.
새 버전의 태그를 사용하는 방법은 다음과 같습니다.
{% current_time "%Y-%m-%d %I:%M %p" %}<p>The time is {{ current_time }}.</p>
컨텍스트의 가변 범위
컨텍스트에 설정된 변수는 할당된 템플릿의 동일한 “블록”에서만 사용할 수 있습니다. 이 동작은 의도적인 것입니다. 다른 블록의 컨텍스트와 충돌하지 않도록 변수에 대한 범위를 제공합니다.
그러나 ``Current TimeNode2”에는 문제가 있다. 변수 이름 “current_time”은 하드 코딩되어 있다. 이는 “{% current_time }”이 변수의 값을 무턱대고 덮어쓸 것이기 때문에 템플릿이 “{{current_time}”을 다른 곳에 사용하지 않도록 해야 한다는 것을 의미한다. 더 깨끗한 해결책은 템플릿 태그가 출력 변수의 이름을 다음과 같이 지정하도록 하는 것입니다.
{% current_time "%Y-%m-%d %I:%M %p" as my_current_time %}
<p>The current time is {{ my_current_time }}.</p>
그러기 위해서는 컴파일 기능과 ``노드” 클래스를 모두 다음과 같이 리팩터링해야 한다.
import re
class CurrentTimeNode3(template.Node):
def __init__(self, format_string, var_name):
self.format_string = format_string
self.var_name = var_name
def render(self, context):
context[self.var_name] = datetime.datetime.now().strftime(self.format_string)
return ""
def do_current_time(parser, token):
# This version uses a regular expression to parse tag contents.
try:
# Splitting by None == splitting by spaces.
tag_name, arg = token.contents.split(None, 1)
except ValueError:
raise template.TemplateSyntaxError(
"%r tag requires arguments" % token.contents.split()[0]
)
m = re.search(r"(.*?) as (\w+)", arg)
if not m:
raise template.TemplateSyntaxError("%r tag had invalid arguments" % tag_name)
format_string, var_name = m.groups()
if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
raise template.TemplateSyntaxError(
"%r tag's argument should be in quotes" % tag_name
)
return CurrentTimeNode3(format_string[1:-1], var_name)
여기서 다른 점은 “do_current_time()”이 형식 문자열과 변수 이름을 모두 “CurrentTimeNode3”로 넘겨준다는 점이다.
마지막으로 사용자 정의 컨텍스트 업데이트 템플릿 태그에 대해 간단한 구문만 있으면 되는 경우 :meth:’~django.template을 사용하는 것이 좋습니다.Library.simple_tag’ 바로 가기이며, 템플릿 변수에 태그 결과를 할당할 수 있습니다.
다른 블록 태그까지 구문 분석¶
템플릿 태그는 함께 사용할 수 있습니다. 예를 들어 표준:ttag:’{% comment %}’ 태그는 “{% end comment %}”까지 모든 것을 숨깁니다. 이와 같은 템플릿 태그를 만들려면 컴파일 기능에 “parser.parse()”를 사용하십시오.
단순화된 ``{% comment %” 태그를 구현하는 방법은 다음과 같다.
def do_comment(parser, token):
nodelist = parser.parse(("endcomment",))
parser.delete_first_token()
return CommentNode()
class CommentNode(template.Node):
def render(self, context):
return ""
참고
:ttag:’{% comment %}’의 실제 구현은 깨진 템플릿 태그를 ``{% comment %”와 “{% end comment %}” 사이에 표시할 수 있다는 점에서 약간 다르다. 그것은 ``parser.delete_first_token(‘end comment’)’이 아닌 ``parser.skip_past(‘end comment’)‘“라고 불러서 노드 리스트의 생성을 피한다.
‘파서.파서’는 ‘까지’ 구문 분석하기 위해’ 블록 태그의 이름 두 개를 사용한다. 그것은 ``django.template”의 예를 반환한다.노드리스트’는 파서가 이전에 만났던 모든 “노드” 오브젝트 목록이며 튜플에서 명명된 태그 중 하나를 발견했다.
위의 예에서 “‘끝글리스트 =’에서 “끝글리스트”는 ``{% comment %”와 “{% comment %”를 세지 않는 ``{% comment %”와 “{% comment %}” 사이의 모든 노드를 나열한 것이다.
``parser.parse()”가 호출된 후 파서는 아직 “{% end comment %” 태그를 “소비”하지 않았기 때문에 코드에서 “parser.delete_first_token()”이라고 명시적으로 불러야 한다.
“commentNode.render()”는 빈 문자열을 반환합니다. “{% comment %”와 “{% end comment %}” 사이의 어떤 것도 무시된다.
다른 블록 태그까지 구문 분석하고 내용 저장¶
앞의 예에서 “do_comment()”는 “{% comment %”와 “{% end comment %” 사이의 모든 것을 폐기했다. 대신 블록 태그 간에 코드를 사용하여 작업을 수행할 수 있습니다.
예를 들어,여기 “{% oper %}”라는 사용자 정의 템플릿 태그가 있는데, 이 태그는 자신들 사이의 모든 것을 대문자로 한다.
용례
{% upper %}This will appear in uppercase, {{ your_name }}.{% endupper %}
앞의 예와 같이 “파서.파스()”를 사용할 것이다. 그러나 이번에는 ``노드 리스트”를 ``노드”에게 넘겼다.
def do_upper(parser, token):
nodelist = parser.parse(("endupper",))
parser.delete_first_token()
return UpperNode(nodelist)
class UpperNode(template.Node):
def __init__(self, nodelist):
self.nodelist = nodelist
def render(self, context):
output = self.nodelist.render(context)
return output.upper()
여기서 유일한 새로운 개념은 ``UpperNode.render()”의 ``self.nodelist.render(컨텍스트)”이다.
For more examples of complex rendering, see the source code of
{% for %}
in django/template/defaulttags.py and
{% if %}
in django/template/smartif.py.