AMP 지원 사이트를 구축하는 방법¶
이 문서는 AMP 버전의 Wagtail 사이트를 만들고 URL 접두사를 사용하여 사이트의 나머지 부분과 별도로 호스팅하는 방법을 설명합니다. 또한 사용자가 사이트의 AMP 버전에서 페이지를 방문할 때 Wagtail이 <amp-img> 태그로 이미지를 렌더링하도록 하는 방법도 설명합니다.
개요¶
다음 섹션에서는 Wagtail의 내부 serve() 뷰를 가리키는 새로운 URL 항목을 추가하여 /amp 접두사 아래에서 전체 사이트를 다시 렌더링하는 효과를 얻을 것입니다.
그런 다음, 요청 객체 없이도 현재 요청이 사이트의 /amp 접두사 버전에 있는지 추적할 수 있는 유틸리티를 추가할 것입니다.
그 후, 템플릿에서 사이트의 어떤 버전이 렌더링되고 있는지 확인할 수 있는 템플릿 컨텍스트 프로세서를 추가할 것입니다.
마지막으로, 사이트의 AMP 버전을 렌더링할 때 {% image %} 태그의 동작을 수정하여 <amp-img> 태그를 렌더링하도록 할 것입니다.
두 번째 페이지 트리 생성하기¶
프로젝트 urls.py 파일에서 Wagtail URL을 복제하고 접두사를 부여하여 다른 접두사에서 전체 사이트를 렌더링할 수 있습니다. 이는 Wagtail의 기본 URL 앞에 있어야 합니다. 그렇지 않으면 페이지로 /amp 를 찾으려고 시도할 것입니다:
# <project>/urls.py
urlpatterns += [
# 기본 ``include(wagtail_urls)`` 라인 바로 앞에 이 줄을 추가하세요
path('amp/', include(wagtail_urls)),
path('', include(wagtail_urls)),
]
이제 브라우저에서 http://localhost:8000/amp/ 를 열면 홈페이지가 표시될 것입니다.
페이지가 “AMP 모드”를 인식하도록 만들기¶
이제 모든 페이지가 /amp 접두사 아래에서 렌더링되지만, 현재 AMP 버전과 일반 버전 사이에는 차이가 없습니다.
변경하려면 페이지를 렌더링하는 데 사용된 URL을 감지하는 방법을 추가해야 합니다. 이를 위해 Wagtail의 serve() 뷰를 래핑하고 스레드-로컬을 설정하여 모든 다운스트림 코드에 AMP 모드가 활성화되어 있음을 알려야 합니다.
참고
왜 스레드-로컬인가?
(이 부분에 관심이 없다면 건너뛰어도 됩니다)
request 객체를 수정하는 것이 가장 일반적인 방법일 것입니다.
그러나 이미지 태그 렌더링은 요청에 접근할 수 없는 Wagtail의 일부에서 수행됩니다.
스레드-로컬은 각 실행 스레드마다 다른 값을 가질 수 있는 전역 변수입니다. 각 스레드는 한 번에 하나의 요청만 처리하므로, 요청 객체를 모든 곳에 전달하지 않고도 해당 요청에 특정한 데이터를 전달하는 방법으로 사용할 수 있습니다.
Django는 내부적으로 스레드-로컬을 사용하여 요청에 대해 현재 활성화된 언어를 추적합니다.
Python은 threading.local 클래스를 통해 스레드-로컬 데이터를 구현하지만, Django 3.x부터는 여러 요청이 단일 스레드에서 처리될 수 있으므로 스레드-로컬은 더 이상 단일 요청에 고유하지 않습니다. 따라서 Django는 대체품으로 asgiref.Local 을 제공합니다.
이제 스레드-로컬과 이와 상호 작용하는 몇 가지 유틸리티 함수를 만들어 보겠습니다.
이 모듈을 프로젝트의 앱에 amp_utils.py 로 저장하세요:
# <app>/amp_utils.py
from contextlib import contextmanager
from asgiref.local import Local
_amp_mode_active = Local()
@contextmanager
def activate_amp_mode():
"""
AMP 모드를 활성화하는 컨텍스트 매니저
"""
_amp_mode_active.value = True
try:
yield
finally:
del _amp_mode_active.value
def amp_mode_active():
"""
AMP 모드가 현재 활성화되어 있으면 True를 반환합니다
"""
return hasattr(_amp_mode_active, 'value')
이 모듈은 두 가지 함수를 정의합니다:
activate_amp_mode는 Python의with구문을 사용하여 호출할 수 있는 컨텍스트 매니저입니다.with문의 본문에서는 AMP 모드가 활성화됩니다.amp_mode_active는 AMP 모드가 활성화되었을 때True를 반환하는 함수입니다.
다음으로, Wagtail의 내장 serve 뷰를 래핑하고 activate_amp_mode 컨텍스트 매니저를 호출하는 뷰를 정의해야 합니다:
# <app>/amp_views.py
from django.template.response import SimpleTemplateResponse
from wagtail.views import serve as wagtail_serve
from .amp_utils import activate_amp_mode
def serve(request, path):
with activate_amp_mode():
response = wagtail_serve(request, path)
# AMP 모드가 여전히 활성화된 상태에서 템플릿 응답을 렌더링합니다
if isinstance(response, SimpleTemplateResponse):
response.render()
return response
그런 다음 같은 앱에 amp_urls.py 파일을 만들어야 합니다:
# <app>/amp_urls.py
from django.urls import re_path
from wagtail.urls import serve_pattern
from . import amp_views
urlpatterns = [
re_path(serve_pattern, amp_views.serve, name='wagtail_amp_serve')
]
마지막으로, 프로젝트의 메인 urls.py 를 업데이트하여 /amp 접두사에 이 새로운 URLs 파일을 사용해야 합니다:
# <project>/urls.py
from myapp import amp_urls as wagtail_amp_urls
urlpatterns += [
# 이 줄을 Wagtail의 urls 대신 amp_urls를 가리키도록 변경하세요
path('amp/', include(wagtail_amp_urls)),
re_path(r'', include(wagtail_urls)),
]
이 후에는 사이트의 AMP 버전에 눈에 띄는 차이가 없어야 합니다.
템플릿에서 AMP 상태를 확인할 수 있는 템플릿 컨텍스트 프로세서 작성하기¶
이는 선택 사항이지만, 지금까지 모든 것이 제대로 작동하는지 확인하기 위해 수행할 가치가 있습니다.
다음 내용이 포함된 amp_context_processors.py 파일을 앱에 추가하세요:
# <app>/amp_context_processors.py
from .amp_utils import amp_mode_active
def amp(request):
return {
'amp_mode_active': amp_mode_active(),
}
이제 이 컨텍스트 프로세서의 경로를 TEMPLATES 설정의 ['OPTIONS']['context_processors'] 키에 추가하세요:
# <project>/settings.py 또는 <project>/settings/base.py
TEMPLATES = [
{
...
'OPTIONS': {
'context_processors': [
...
# 다른 컨텍스트 프로세서 뒤에 이것을 추가하세요
'myapp.amp_context_processors.amp',
],
},
},
]
이제 템플릿에서 amp_mode_active 변수를 사용할 수 있어야 합니다.
예를 들면:
{% if amp_mode_active %}
AMP 모드가 활성화되었습니다!
{% endif %}
AMP 모드가 활성화되었을 때 다른 페이지 템플릿 사용하기¶
AMP 사이트에서 일반 웹 사이트와 동일한 템플릿을 사용하고 싶지 않을 것입니다. AMP가 활성화된 상태에서 페이지가 제공될 때마다 Wagtail이 별도의 템플릿을 사용하도록 하는 로직을 추가해 보겠습니다.
다양한 페이지 유형에서 로직을 재사용할 수 있는 믹스인을 사용할 수 있습니다. 다음 내용을 앞서 만든 amp_utils.py 파일의 맨 아래에 추가하세요:
# <app>/amp_utils.py
import os.path
...
class PageAMPTemplateMixin:
@property
def amp_template(self):
# 기본 템플릿 이름을 가져와서 확장자 앞에 `_amp` 를 삽입합니다
name, ext = os.path.splitext(self.template)
return name + '_amp' + ext
def get_template(self, request):
if amp_mode_active():
return self.amp_template
return super().get_template(request)
이제 이 믹스인을 모든 페이지 모델에 추가하세요, 예를 들면:
# <app>/models.py
from .amp_utils import PageAMPTemplateMixin
class MyPageModel(PageAMPTemplateMixin, Page):
...
AMP 모드가 활성화되면, 기본 템플릿 대신 app_label/mypagemodel_amp.html 템플릿이 사용됩니다.
다른 명명 규칙이 있다면, 모델에서 amp_template 속성을 재정의할 수 있습니다. 예를 들면:
# <app>/models.py
from .amp_utils import PageAMPTemplateMixin
class MyPageModel(PageAMPTemplateMixin, Page):
amp_template = 'my_custom_amp_template.html'
<amp-img> 태그를 출력하도록 {% image %} 태그 재정의하기¶
마지막으로, Wagtail의 {% image %} 태그를 변경하여 AMP가 활성화된 상태에서 페이지를 렌더링할 때 <amp-img> 태그를 렌더링하도록 해보겠습니다. 이 변경을 Rendition 모델 자체에 적용하면 {% image %} 태그로 렌더링된 이미지와 리치 텍스트 필드에 렌더링된 이미지 모두에 적용됩니다.
사용자 정의 이미지 모델을 사용하면 더 쉽게 할 수 있습니다. 사용자 정의 Rendition 모델의 img_tag 메서드를 재정의하여 다른 태그를 반환할 수 있습니다.
예를 들면:
from django.forms.utils import flatatt
from django.utils.safestring import mark_safe
from wagtail.images.models import AbstractRendition
...
class CustomRendition(AbstractRendition):
def img_tag(self, extra_attributes):
attrs = self.attrs_dict.copy()
attrs.update(extra_attributes)
if amp_mode_active():
return mark_safe('<amp-img{}>'.format(flatatt(attrs)))
else:
return mark_safe('<img{}>'.format(flatatt(attrs)))
...
사용자 정의 이미지 모델 없이는 내장 Rendition 모델을 몽키패치해야 합니다.
이 코드를 시작 시 가져올 프로젝트의 아무 곳에나 추가하세요:
from django.forms.utils import flatatt
from django.utils.safestring import mark_safe
from wagtail.images.models import Rendition
def img_tag(rendition, extra_attributes={}):
"""
Rendition.img_tag의 대체 구현
AMP 모드가 켜져 있을 때, <img> 태그 대신 <amp-img> 태그를 반환합니다
"""
attrs = rendition.attrs_dict.copy()
attrs.update(extra_attributes)
if amp_mode_active():
return mark_safe('<amp-img{}>'.format(flatatt(attrs)))
else:
return mark_safe('<img{}>'.format(flatatt(attrs)))
Rendition.img_tag = img_tag