Code mirror를 적용해야해서 Wagtail Admin 에서 Code Block Field를 만들어 보기로 했다. 많은 시행착오가 있었지만 결국 해결이 되어 적용방법을 적어보려고 한다.
https://docs.wagtail.io/en/latest/advanced_topics/customisation/streamfield_blocks.html
Wagtail 공식 문서 custom StreamField Block 이다. Wagtail 문서에 찾는 모든 것이 있는데 전부 영어로 되어 있어서 간과하고 지나간 것이 많다. 꼼꼼하게 읽어보니 필요한 내용을 찾을 수 있었다.
문서에서 자바스크립트를 적용한 forms를 만드는 방법에 대해서 나와 있었다.
문서의 코드보단 직접 적용한 코드를 적어보려고 한다.
1. Custom Block 정의
Custom block을 하나 만들어서 CodingBlock으로 적용했다.
card/widgets.html
from wagtail.core.blocks import FieldBlock
from django import forms
class CodingBlock(FieldBlock):
def __init__(self, required=True, help_text=None, **kwargs):
self.field = forms.CharField(required=False, widget=forms.Textarea()) # attrs={'class':'codingblock'}
super().__init__(**kwargs)
Wagtail에서는 FieldBlock을 상속받아 나만의 custom block을 만들 수 있는데 Code mirror에서는 Textarea가 필요해서 Textarea로 만들었다. 바로 forms.Textarea()로 적용하려 했더니 되지 않아서 검색해 본 결과 Textarea를 적용하려면 위에 같이 적용해야한다고 한다.
참고로 Custom block을 설정하면 추가 attrs를 적용할 수 있다.
self.field = forms.CharField(required=False, widget=forms.Textarea(attrs={'class':'codingblock'}))
이런식으로 적용하면 class를 codingblock이라는 textarea를 만들 수가 있다. 하지만 id값은 재정의 되지 않는다.
2. Block 정의
card/models.py
from django import forms
from django.utils.functional import cached_property
from card.widgets import CodingBlock
from django.forms import Media
class CodeBlock(StructBlock):
code = CodingBlock()
class CodeBlockAdapter(StructBlockAdapter):
js_constructor = 'card.blocks.CodeBlock'
@cached_property
def media(self):
structblock_media = super().media
return forms.Media(
js=structblock_media._js + ['js/mycode.js'],
)
register(CodeBlockAdapter(), CodeBlock)
StructBlock을 상속받아 CodeBlock을 만들어 준다.
wagtail에는 StructBlockAdapter라는 것이 있다. 이걸 사용해서 자신이 원하는 블럭의 js파일을 적용 할 수 있다.
return forms.Media(
js=structblock_media._js + ['js/mycode.js'],
)
여기에서 자신이 원하는 js 파일을 넣어주면 된다.
3. JavaScript 정의
static/js/mycode.js
class CodeBlockDefinition extends window.wagtailStreamField.blocks.StructBlockDefinition {
render(placeholder, prefix, initialState, initialError) {
const block = super.render(placeholder, prefix, initialState, initialError);
const textareaid = prefix + '-code';
var editor = CodeMirror.fromTextArea(document.getElementById(textareaid), {
mode: "python",
theme: "dracula",
lineNumbers: true,
});
editor.setSize("100%","100%"); //사이즈 설정으로 codemirror 크기를 조절할 수 있다.
return block;
}
}
window.telepath.register('card.blocks.CodeBlock', CodeBlockDefinition);
다만, 기존처럼 js코드만 쓰면 되는 것이 아니고 telepath를 이용하여 textarea의 정보를 가져와야 한다. 여기서 prefix라는 것은 block의 행순서를 말한다. block이 여러개가 추가 될 수록 prefix 값이 달라지고 이를 통해서 구별하게 된다. 그리고 이를 id 값으로 사용한다. 정리하자면
body-0-value-body-0-value
body-0-value-body-1-value
body-0-value-body-2-value
이런식으로 저장되어진다.
그리고 뒤에 필드명인 code가 붙어서
body-0-value-body-0-value-code 이런형태로 id값이 설정되게 된다. 나는 Code Mirror를 적용하였다.
Code Mirror는 id 값을 입력 해줘야하므로 prefix와 field명인 code를 합한 각 textarea의 아이디명을 textareaid 변수에 저장하고 입력해주었다.
4. 추가 JS, CSS 적용
Code mirror의 기본 코드는 admin 페이지 자체에 wagtail hook을 통해 설정하였다. wagtail_hooks.py를 통해 wagtail admin에 추가 정보를 입력할 수 있다.
card/wagtail_hooks.py
@hooks.register('insert_editor_css')
def editor_css():
css_files = [
'codemirror/lib/codemirror.css',
'codemirror/theme/dracula.css',
]
css_includes = format_html_join('\n', '<link rel="stylesheet" href="{0}"></link>',
((static(filename),) for filename in css_files)
)
return css_includes
@hooks.register('insert_editor_js')
def editor_js():
js_files = [
'codemirror/lib/codemirror.js',
'codemirror/mode/xml/xml.js',
'codemirror/mode/python/python.js',
]
js_includes = format_html_join('\n', '<script src="{0}"></script>',
((static(filename),) for filename in js_files)
)
return js_includes
wagtail hook에 대해서는 다음에 문서로 전체 정리해보려고 한다. 당장은 필요한 부분만 기술하였다.
(wagtail_hooks.py파일은 wagtail이 실행될 때 파일을 찾아서 자동으로 실행시키게 된다.)
insert_global_admin_js와 insert_editor_js가 있는데 둘의 차이는 script 위치를 어디에다가 적용할 것이냐의 차이가 있다. 여러개의 css파일이나 js파일을 적용하고 싶다면 위와 같이 정의해 놓으면 된다.
https://docs.wagtail.io/en/stable/reference/hooks.html#admin-hooks
추가적인 정보가 필요하면 윗 문서를 참고하자.
추가)Custom editing interfaces for StructBlock
Page를 생성할 때 StruckBlock의 layout 설정이나 js,css를 적용하려고 할 때 참고하자.
myapp/blocks.py
class PersonBlock(blocks.StructBlock):
first_name = blocks.CharBlock()
surname = blocks.CharBlock()
photo = ImageChooserBlock(required=False)
biography = blocks.RichTextBlock()
def get_form_context(self, value, prefix='', errors=None):
context = super().get_form_context(value, prefix=prefix, errors=errors)
context['suggested_first_names'] = ['John', 'Paul', 'George', 'Ringo']
return context
class Meta:
icon = 'user'
form_template = 'myapp/block_forms/person.html'
공식문서 그대로 코드를 가져왔다. class Meta에 form_template의 수정을 통해 원하는 레이아웃이나 css를 적용할 수 있다.
templates/block_forms/person.html
{% load wagtailadmin_tags %}
<div class="{{ classname }}">
{% if help_text %}
<span>
<div class="help">
{% icon name="help" class_name="default" %}
{{ help_text }}
</div>
</span>
{% endif %}
{% for child in children.values %}
<div class="field {% if child.block.required %}required{% endif %}" data-contentpath="{{ child.block.name }}">
{% if child.block.label %}
<label class="field__label" {% if child.id_for_label %}for="{{ child.id_for_label }}"{% endif %}>{{ child.block.label }}</label>
{% endif %}
{{ child.render_form }}
</div>
{% endfor %}
</div>
위가 wagtail의 admin field의 기본 구조이다.
{% for child in children.values %}
<div class="field {% if child.block.required %}required{% endif %}" data-contentpath="{{ child.block.name }}">
{% if child.block.label %}
<label class="field__label" {% if child.id_for_label %}for="{{ child.id_for_label }}"{% endif %}>{{ child.block.label }}</label>
{% endif %}
{{ child.render_form }}
</div>
{% endfor %}
child가 우리가 작성한 field들의 정보가 들어 있는 곳이다.
child.block.name이 필드의 변수 명이므로 if 문을 사용하여 필드를 구별해서 원하는대로 custom을 하면 된다.
ex) {% if child.block.name == "변수이름" %} . . . {% endif %}
'Back-End > Wagtail, Django' 카테고리의 다른 글
Wagtail Custom Field, Panel (0) | 2021.09.29 |
---|---|
Wagtail hooks로 RichTextField에 구글 폰트 적용 (0) | 2021.09.19 |
Wagtail 중첩 StreamField (0) | 2021.09.01 |
Wagtail Custom User Field (0) | 2021.09.01 |
Wagtail All Auth(Google Login) (0) | 2021.08.25 |