리치 텍스트 내부¶
언뜻 보기에 Wagtail의 리치 텍스트 기능은 편집자에게 HTML 콘텐츠 블록에 대한 직접적인 제어권을 제공하는 것처럼 보입니다. 실제로는 여러 가지 이유로 편집자에게 최종 HTML 출력과 몇 단계 떨어진 리치 텍스트 콘텐츠 표현을 제공해야 합니다.
편집기 인터페이스는 특정 종류의 원치 않는 마크업을 필터링해야 합니다. 여기에는 악성 스크립팅, 외부 워드 프로세서에서 붙여넣은 글꼴 스타일, 사이트 디자인의 유효성 또는 일관성을 깨뜨릴 수 있는 요소(예: 페이지는 일반적으로
<h1>요소를 페이지 제목으로 예약하므로 사용자가 리치 텍스트를 통해 자체 추가<h1>요소를 삽입하는 것은 부적절함)가 포함됩니다.리치 텍스트 필드는 필드에서 허용되는 요소를 추가로 제한하기 위해
features인수를 지정할 수 있습니다. - 리치 텍스트 기능을 참조하십시오.HTML의 하위 집합을 강제하면 프레젠테이션 마크업을 데이터베이스에서 제외하여 사이트 유지 관리를 용이하게 하고, 사이트 콘텐츠를 재활용하기 쉽게 만듭니다(잠재적으로 LaTeX와 같은 비HTML 출력을 생성하는 것을 포함).
페이지 링크 및 이미지와 같은 요소는 최종 HTML 표현에 없는 페이지 또는 이미지 ID와 같은 메타데이터를 보존해야 합니다.
이를 위해서는 리치 텍스트 콘텐츠가 여러 유효성 검사 및 변환 단계를 거쳐야 합니다. 편집기 인터페이스와 데이터베이스에 저장된 버전 사이, 그리고 데이터베이스 표현에서 최종 렌더링된 HTML로의 변환이 모두 포함됩니다.
이러한 이유로 Wagtail의 리치 텍스트 처리를 확장하여 새 요소를 지원하는 것은 단순히 (예를 들어) “<blockquote> 요소를 활성화”라고 말하는 것보다 더 복잡합니다. Wagtail의 다양한 구성 요소(클라이언트 및 서버 측 모두)가 해당 기능을 처리하는 방법에 대해 동의해야 하기 때문입니다. 여기에는 편집기 인터페이스에서 어떻게 노출되어야 하는지, 데이터베이스 내에서 어떻게 표현되어야 하는지, 그리고 (적절한 경우) 프런트엔드에서 렌더링될 때 어떻게 번역되어야 하는지가 포함됩니다.
Wagtail의 리치 텍스트 처리에 관련된 구성 요소는 아래에 설명되어 있습니다.
데이터 형식¶
리치 텍스트 데이터(RichTextField 및 StreamField 내의 RichTextBlock 에서 처리됨)는 HTML과 유사하지만 동일하지는 않은 형식으로 데이터베이스에 저장됩니다. 예를 들어, 페이지 링크는 다음과 같이 저장될 수 있습니다.
<p><a linktype="page" id="3">Contact us</a> for more information.</p>
여기서 linktype 속성은 태그를 다시 작성하는 데 사용될 규칙을 식별합니다. |richtext 필터(리치 텍스트 필터 참조)를 통해 템플릿에 렌더링될 때, 이것은 유효한 HTML로 변환됩니다.
<p><a href="/contact-us/">Contact us</a> for more information.</p>
RichTextBlock 의 경우, 블록의 값은 문자열로 렌더링될 때 이 변환을 자동으로 수행하는 RichText 객체이므로 |richtext 필터는 필요하지 않습니다.
마찬가지로, 리치 텍스트 콘텐츠 내의 이미지는 다음과 같이 저장될 수 있습니다.
<embed embedtype="image" id="10" alt="A pied wagtail" format="left" />
이는 렌더링될 때 img 요소로 변환됩니다.
<img
alt="A pied wagtail"
class="richtext-image left"
height="294"
src="/media/images/pied-wagtail.width-500.jpg"
width="500"
/>
다시 말하지만, embedtype 속성은 태그를 다시 작성하는 데 사용될 규칙을 식별합니다. <a linktype="..."> 및 <embed embedtype="..." /> 를 제외한 모든 태그는 변환된 HTML에서 변경되지 않습니다.
<a linktype="..."> 및 <embed embedtype="..." /> 태그에는 변환을 문자열 대체로 효율적으로 수행할 수 있도록 몇 가지 추가 제약 조건이 적용됩니다.
태그 이름과 속성은 소문자여야 합니다.
속성 값은 이중 따옴표로 묶어야 합니다.
embed요소는 XML 자체 닫는 태그 구문(닫는</embed>태그 대신/>로 끝나는 태그)을 사용해야 합니다.속성 값에 허용되는 유일한 HTML 엔티티는
<,>,&및"입니다.
수동 변환¶
RichTextBlock 또는 |richtext 필터가 적절하지 않은 시나리오의 경우, Wagtail의 expand_db_html 유틸리티 함수를 직접 사용하십시오. 이 함수는 CMS 콘텐츠(이미지, 임베드, 문서, 페이지 링크)에 대한 내부 참조를 확장하여 표시할 준비가 된 HTML을 생성합니다.
from wagtail.rich_text import expand_db_html
# 저장된 리치 텍스트 데이터 형식을 렌더링에 적합한 HTML로 변환합니다.
expand_db_html(page.body)
기능 레지스트리¶
프로젝트 내의 모든 앱은 새 linktype 및 embedtype 규칙과 같이 Wagtail의 리치 텍스트 처리에 대한 확장을 정의할 수 있습니다. _기능 레지스트리_라고 하는 객체는 리치 텍스트가 어떻게 동작해야 하는지에 대한 중앙 소스 역할을 합니다. 이 객체는 시작 시 리치 텍스트와 관련된 모든 정의를 수집하기 위해 호출되는 리치 텍스트 기능 등록 후크를 통해 액세스할 수 있습니다.
# my_app/wagtail_hooks.py
from wagtail import hooks
@hooks.register('register_rich_text_features')
def register_my_feature(features):
# 여기에 새 정의를 'features'에 추가합니다.
재작성 핸들러¶
재작성 핸들러는 <a linktype="..."> 및 <embed embedtype="..." /> 와 같은 리치 텍스트 태그의 내용을 프런트엔드 HTML로 변환하는 방법을 아는 클래스입니다. 예를 들어, PageLinkHandler 클래스는 리치 텍스트 태그 <a linktype="page" id="123"> 를 HTML 태그 <a href="/path/to/page/123"> 로 변환하는 방법을 알고 있습니다.
재작성 핸들러는 리치 텍스트 태그에 대한 다른 유용한 정보도 제공할 수 있습니다. 예를 들어, 적절한 태그가 주어지면 PageLinkHandler 를 사용하여 참조되는 페이지를 추출할 수 있습니다. 이는 리치 텍스트에서 참조되는 객체에 대한 정보가 필요한 다운스트림 코드에 유용할 수 있습니다.
새로운 linktype 및 embedtype 태그를 지원하기 위해 사용자 지정 재작성 핸들러를 만들 수 있습니다. 새 핸들러는 wagtail.richtext.LinkHandler 또는 wagtail.richtext.EmbedHandler 중 하나를 상속하는 Python 클래스여야 합니다. 새 클래스는 다음 메서드 중 적어도 일부를 재정의해야 합니다(LinkHandler 에 대해 여기에 나열되어 있지만 EmbedHandler 는 동일한 서명을 가짐).
- class LinkHandler¶
- identifier¶
필수.
identifier속성은 이 핸들러가 처리해야 하는 리치 텍스트 태그를 나타내는 문자열입니다.예를 들어,
PageLinkHandler.identifier는 문자열"page"로 설정되어<a linktype="page">가 있는 모든 리치 텍스트 태그가 이 핸들러에 의해 처리되어야 함을 나타냅니다.
- expand_db_attributes(attrs)¶
선택 사항.
expand_db_attributes메서드는 데이터베이스 리치 텍스트<a>태그(EmbedHandler의 경우<embed>)의 속성 사전을 가져와 유효한 프런트엔드 HTML을 생성하는 데 사용될 것으로 예상됩니다.예를 들어,
PageLinkHandler.expand_db_attributes는{'id': 123}을 수신하여 ID가 123인 Wagtail 페이지를 검색하고<a href="/path/to/page/123">와 같은 URL에 대한 링크를 렌더링할 수 있습니다.이 메서드 또는
expand_db_attributes_many는 사용자 지정 재작성 핸들러에 정의되어야 합니다.
- expand_db_attributes_many(attrs_list)¶
선택 사항.
expand_db_attributes_many메서드는expand_db_attributes와 유사하게 작동하지만 속성 사전 목록을 가져와 HTML 태그 목록을 반환합니다. 이 메서드는 재작성 핸들러가 대량으로 작업하는 데 사용됩니다. 예를 들어, 여러 데이터베이스 쿼리 대신 하나의 데이터베이스 쿼리를 수행하는 기능을 활용합니다.이 메서드 또는
expand_db_attributes는 사용자 지정 재작성 핸들러에 정의되어야 합니다. 정의되지 않은 경우,expand_db_attributes_many의 기본 구현은expand_db_attributes에 대한 일련의 호출을 통해 작동합니다.
- get_model()¶
선택 사항. 정적
get_model메서드는 Django 모델과 관련된 콘텐츠를 렌더링하는 데 사용되는 핸들러에만 적용됩니다. 이 메서드를 사용하면 핸들러가 처리할 수 있는 콘텐츠 유형을 노출할 수 있습니다.예를 들어,
PageLinkHandler.get_model은 Wagtail 클래스Page를 반환합니다.Django 모델과 관련이 없는 핸들러는 이 메서드를 정의하지 않은 상태로 둘 수 있으며, 호출하면
NotImplementedError가 발생합니다.
- get_instance(attrs)¶
선택 사항. 클래스 메서드
get_instance메서드는 Django 모델과 관련된 콘텐츠를 렌더링하는 데 사용되는 핸들러에만 적용됩니다. 이 메서드는 데이터베이스 리치 텍스트<a>태그(EmbedHandler의 경우<embed>)의 속성 사전을 가져와 참조되는 특정 Django 모델 인스턴스를 반환하는 데 사용될 것으로 예상됩니다.예를 들어,
PageLinkHandler.get_instance는{'id': 123}을 수신하여 ID가 123인 WagtailPage클래스의 인스턴스를 반환할 수 있습니다.제공된 속성을 사용하여 Django 모델 인스턴스를 검색할 수 없는 경우(예: 제공된
id속성이 유효하지 않은 경우) 이 메서드는 예외를 발생시켜야 합니다.정의되지 않은 경우, 이 메서드의 기본 구현은 제공된
id속성을 사용하여get_model에서 반환된 클래스의id모델 필드를 쿼리합니다. 이는 다른 모델 필드를 사용하려는 경우 사용자 지정 핸들러에서 재정의할 수 있습니다.
- get_many(attrs_list)¶
선택 사항. 클래스 메서드
get_many메서드는get_instance와 유사하게 작동하지만 속성 사전 목록을 가져와 Django 모델 인스턴스 목록을 반환합니다.검색할 수 없는 모든 인스턴스는 반환된 목록에서
None으로 표시됩니다.
아래는 사용자 이메일 주소에 대한 리치 텍스트 링크를 지원하기 위해 이러한 메서드 중 일부를 구현하는 사용자 지정 재작성 핸들러의 예입니다. <a linktype="user" username="wagtail"> 과 같은 리치 텍스트 태그를 <a href="mailto:hello@wagtail.org"> 와 같은 유효한 HTML로 변환하는 것을 지원합니다. 이 예는 사용자가 이러한 종류의 링크를 리치 텍스트 편집기에 삽입할 수 있도록 동등한 프런트엔드 기능이 추가되었다고 가정합니다.
from django.contrib.auth import get_user_model
from wagtail.rich_text import LinkHandler
class UserLinkHandler(LinkHandler):
identifier = 'user'
@staticmethod
def get_model():
return get_user_model()
@classmethod
def get_instance(cls, attrs):
model = cls.get_model()
return model.objects.get(username=attrs['username'])
@classmethod
def expand_db_attributes(cls, attrs):
user = cls.get_instance(attrs)
return '<a href="mailto:%s">' % user.email
재작성 핸들러 등록¶
재작성 핸들러는 리치 텍스트 기능 등록 후크를 통해 기능 레지스트리에 등록되어야 합니다. 링크 핸들러와 임베드 핸들러를 모두 등록하기 위한 독립적인 메서드가 제공됩니다.
- FeatureRegistry.register_link_type(handler)¶
이 메서드를 사용하면 wagtail.rich_text.LinkHandler 에서 파생된 사용자 지정 핸들러를 등록하고, 리치 텍스트 변환 중에 사용할 수 있는 링크 핸들러 목록에 추가할 수 있습니다.
# my_app/wagtail_hooks.py
from wagtail import hooks
from my_app.handlers import MyCustomLinkHandler
@hooks.register('register_rich_text_features')
def register_link_handler(features):
features.register_link_type(MyCustomLinkHandler)
Wagtail의 내장 external 및 email 링크에 대한 링크 재작성 핸들러를 정의하는 것도 가능합니다. 비록 미리 정의된 linktype 이 없더라도 말입니다. 예를 들어, SEO 목적으로 외부 링크에 rel="nofollow" 속성을 추가하려면 다음과 같이 할 수 있습니다.
from django.utils.html import escape
from wagtail import hooks
from wagtail.rich_text import LinkHandler
class NoFollowExternalLinkHandler(LinkHandler):
identifier = 'external'
@classmethod
def expand_db_attributes(cls, attrs):
href = attrs["href"]
return '<a href="%s" rel="nofollow">' % escape(href)
@hooks.register('register_rich_text_features')
def register_external_link(features):
features.register_link_type(NoFollowExternalLinkHandler)
마찬가지로, email linktype을 사용하여 이메일 링크에 대한 사용자 지정 재작성 핸들러를 추가할 수 있습니다(예: 리치 텍스트에서 이메일 난독화).
- FeatureRegistry.register_embed_type(handler)¶
이 메서드를 사용하면 wagtail.rich_text.EmbedHandler 에서 파생된 사용자 지정 핸들러를 등록하고, 리치 텍스트 변환 중에 사용할 수 있는 임베드 핸들러 목록에 추가할 수 있습니다.
# my_app/wagtail_hooks.py
from wagtail import hooks
from my_app.handlers import MyCustomEmbedHandler
@hooks.register('register_rich_text_features')
def register_embed_handler(features):
features.register_embed_type(MyCustomEmbedHandler)
편집기 위젯¶
리치 텍스트 필드에 사용되는 편집기 인터페이스는 WAGTAILADMIN_RICH_TEXT_EDITORS 설정으로 구성할 수 있습니다. Wagtail은 구현을 제공합니다: wagtail.admin.rich_text.DraftailRichTextArea(Draft.js 기반의 Draftail 편집기).
자신만의 리치 텍스트 편집기 구현을 만드는 것이 가능합니다. 최소한 리치 텍스트 편집기는 options 키워드 인수(WAGTAILADMIN_RICH_TEXT_EDITORS의 OPTIONS 필드에서 가져온 편집기별 구성 옵션 사전)를 허용하고, 위에 설명된 HTML과 유사한 형식으로 문자열 데이터를 사용하고 생성하는 Django 클래스 django.forms.Widget 서브클래스입니다.
일반적으로 리치 텍스트 위젯은 RichTextField / RichTextBlock 또는 WAGTAILADMIN_RICH_TEXT_EDITORS 의 features 옵션에서 전달되는 features 목록을 받습니다. 이 목록은 편집기의 해당 인스턴스에서 사용할 수 있는 기능을 정의합니다(리치 텍스트 기능 참조). 기능을 지원하려면 위젯 클래스에 accepts_features = True 속성을 설정하십시오. 그러면 위젯 생성자는 features 키워드 인수로 기능 목록을 받게 됩니다.
리치 텍스트 기능에 나열된 표준 기능 식별자 세트가 있지만, 이것이 최종 목록은 아닙니다. 기능 식별자는 규칙에 의해서만 정의되며, 각 편집기 위젯이 어떤 기능을 인식하고 그에 따라 동작을 조정할지 결정하는 것은 각 편집기 위젯에 달려 있습니다. 개별 편집기 위젯은 내장 기능으로든 편집기 위젯에 플러그인 메커니즘이 있는 경우 플러그인 메커니즘을 통해서든 기본 세트보다 적거나 많은 기능을 구현할 수 있습니다.
예를 들어, 타사 Wagtail 확장이 table 을 새로운 리치 텍스트 기능으로 도입하고, Draftail 편집기(플러그인 메커니즘을 제공함)에 대한 구현을 제공할 수 있습니다. 이 경우, 타사 확장은 사용자 지정 편집기 위젯을 인식하지 못하므로 위젯은 table 기능 식별자를 처리하는 방법을 알지 못합니다. 편집기 위젯은 인식하지 못하는 기능 식별자를 자동으로 무시해야 합니다.
기능 레지스트리의 default_features 속성은 RichTextField / RichTextBlock 또는 WAGTAILADMIN_RICH_TEXT_EDITORS 에 명시적인 기능 목록이 제공되지 않은 경우 사용될 기능 식별자 목록입니다. 이 목록은 register_rich_text_features 후크 내에서 수정하여 새 기능을 기본적으로 활성화하고, get_default_features() 를 호출하여 검색할 수 있습니다.
@hooks.register('register_rich_text_features')
def make_h1_default(features):
features.default_features.append('h1')
register_rich_text_features 후크 외부(예: 위젯 클래스 내부)에서는 기능 레지스트리를 wagtail.rich_text.features 객체로 가져올 수 있습니다. 기능 지원이 가능한 리치 텍스트 편집기의 가능한 시작점은 다음과 같습니다.
from django.forms import widgets
from wagtail.rich_text import features
class CustomRichTextArea(widgets.TextArea):
accepts_features = True
def __init__(self, *args, **kwargs):
self.options = kwargs.pop('options', None)
self.features = kwargs.pop('features', None)
if self.features is None:
self.features = features.get_default_features()
super().__init__(*args, **kwargs)
편집기 플러그인¶
- FeatureRegistry.register_editor_plugin(editor_name, feature_name, plugin_definition)¶
리치 텍스트 편집기는 새로운 기능을 사용하여 편집기를 확장할 수 있도록 플러그인 메커니즘을 제공하는 경우가 많습니다. register_editor_plugin 메서드는 register_rich_text_features 후크가 주어진 리치 텍스트 기능이 활성화될 때 편집기에 가져올 플러그인을 정의하는 표준화된 방법을 제공합니다.
register_editor_plugin 은 편집기 이름(편집기 위젯을 고유하게 식별하는 문자열 - Wagtail은 내장 편집기에 draftail 식별자를 사용함), 기능 식별자, 플러그인 정의 객체를 전달받습니다. 이 객체는 편집기 위젯에 특화되어 있으며 임의의 값일 수 있지만, 일반적으로 플러그인의 JavaScript 코드를 참조하는 Django 폼 미디어 정의(편집기 위젯 자체의 미디어 정의에 병합됨)와 편집기를 인스턴스화할 때 전달될 관련 구성 옵션을 포함합니다.
- FeatureRegistry.get_editor_plugin(editor_name, feature_name)¶
편집기 위젯 내에서 주어진 기능에 대한 플러그인 정의는 편집기 자체의 식별자 문자열과 기능 식별자를 전달하여 get_editor_plugin 메서드를 통해 검색할 수 있습니다. 일치하는 플러그인이 등록되지 않은 경우 None 을 반환합니다.
Wagtail의 내장 편집기에 대한 플러그인 형식에 대한 자세한 내용은 Draftail 편집기 확장 을 참조하십시오.
형식 변환기¶
편집기 위젯은 Wagtail의 리치 텍스트 형식과 직접 작동할 수 없으며, 자체 기본 형식으로 변환해야 하는 경우가 많습니다. Draftail의 경우, ContentState라고 하는 JSON 기반 형식입니다(Draft.js가 리치 텍스트 데이터를 나타내는 방법 참조). HTML의 contentEditable 메커니즘을 기반으로 하는 편집기는 유효한 HTML을 필요로 하므로, Wagtail은 링크 및 임베드 요소에 필요한 추가 데이터가 data- 속성에 저장되는 “편집기 HTML”이라는 규칙을 사용합니다. 예를 들어: <a href="/contact-us/" data-linktype="page" data-id="3">Contact us</a>.
Wagtail은 리치 텍스트 형식과 기본 편집기 형식 간의 변환을 수행하기 위해 wagtail.admin.rich_text.converters.contentstate.ContentstateConverter 및 wagtail.admin.rich_text.converters.editor_html.EditorHTMLConverter 라는 두 가지 유틸리티 클래스를 제공합니다. 이 클래스는 편집기 위젯과 독립적이며 템플릿에 리치 텍스트를 렌더링할 때 발생하는 재작성 프로세스와는 다릅니다.
두 클래스 모두 생성자에 features 목록을 인수로 허용하며, Wagtail 리치 텍스트 데이터를 편집기 형식으로 변환하는 from_database_format(data) 와 편집기 데이터를 Wagtail 리치 텍스트 형식으로 변환하는 to_database_format(data) 의 두 가지 메서드를 구현합니다.
편집기 플러그인과 마찬가지로 변환기 클래스의 동작은 전달된 기능 목록에 따라 달라질 수 있습니다. 특히, 출력에 현재 활성화된 기능 세트에 해당하는 HTML 요소만 포함되도록 화이트리스트 규칙을 적용할 수 있습니다. 기능 레지스트리는 register_converter_rule 메서드를 제공하여 register_rich_text_features 후크가 주어진 기능이 활성화될 때 활성화될 변환 규칙을 정의할 수 있도록 합니다.
- FeatureRegistry.register_converter_rule(converter_name, feature_name, rule_definition)¶
register_editor_plugin 은 변환기 이름(변환기 클래스를 고유하게 식별하는 문자열 - Wagtail은 contentstate 및 editorhtml 식별자를 사용함), 기능 식별자, 규칙 정의 객체를 전달받습니다. 이 객체는 변환기에 특화되어 있으며 임의의 값일 수 있습니다.
contentstate 변환기에 대한 규칙 정의 형식에 대한 자세한 내용은 Draftail 편집기 확장 을 참조하십시오.
- FeatureRegistry.get_converter_rule(converter_name, feature_name)¶
변환기 클래스 내에서 주어진 기능에 대한 규칙 정의는 변환기 자체의 식별자 문자열과 기능 식별자를 전달하여 get_converter_rule 메서드를 통해 검색할 수 있습니다. 일치하는 규칙이 등록되지 않은 경우 None 을 반환합니다.