Custom Widget을 작성하는 방법에 대해서 기술하려고 한다.

아무리 찾아봐도 나오지 않아 그냥 생각대로 해봤더니 성공했다. 

Wagtail에서 Codemirror Widget을 만들고 적용하는 방법으로 해결 했다. 

 

1. Widget 만들기

myapp/widgets.py

from django.forms import widgets
from wagtail.utils.widgets import WidgetWithScript


#custom widget 적용
class MyCustomWidget(WidgetWithScript, widgets.Textarea):
    template_name = 'widgets/codingwidget.html'

    def __init__(self, attrs=None):
        default_attrs = {}
        if attrs:
            default_attrs.update(attrs)

        super().__init__(default_attrs)
        
    class Media:
        js = ('test.js',)

Codemirror를 적용하기 위해서는 Textarea가 필요하므로 django widget에서 textarea.html을 가져와야 한다. 

(widget만드는 방법은 wagtail 자체에서 widget 만드는 부분을 참고하여 작성해보았다.)

 

default_attrs는 정의 할 attrs값을 적어주면 되는데 id값은 정의가 불가능 하다. (wagtail에서 widget을 구별할 때 id값으로 구별하기 때문에 그렇다.)

ex) default_attrs = {'class':'test'} 이런식으로 태그의 속성 값을 정의 해줄 수가 있다.

 

template_name에서는 사용할 widget template을 적어주면된다. 

 

2. 사용할 Widget 가져오기

django/forms/widgets/textarea.html

<textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
{% if widget.value %}{{ widget.value }}{% endif %}</textarea>

code mirror에서는 textarea가 필요하기 때문에 textarea widget을 가져왔다.

(다른 widget이 필요하다면 django widget공식문서를 확인하거나 wagtail이나 django widget에서 찾아보자!)

 

textarea.html을 그대로 복사해서 우리가 새로 만들 widget html에 붙여넣는다. 

 

templates/widgets/codingwidget.html

<textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
{% if widget.value %}{{ widget.value }}{% endif %}</textarea>

<script>
    var editor = CodeMirror.fromTextArea(document.getElementById('{{ widget.attrs.id }}'), {
        mode: "python",
        theme: "dracula",
        lineNumbers: true,
    });
    editor.setSize("100%","100%");
</script>

그리고 필요한 css나 js를 작성해 주면 된다. 

Codemirror javascript를 작성하였다. textarea의 id값이 필요한데 widget의 정보는 {{ widget }}을 통해 확인할 수 있다. 

{'name': 'mtf-0-testfield', 'is_hidden': False, 'required': False, 'value': 'ddd', 'attrs': {'cols': '40', 'rows': '10', 'id': 'id_mtf-0-testfield'}, 'template_name': 'widgets/codingwidget.html'}

내가 작성한 widget에서는 위와 같은 정보가 있었다. 접근 방법은 {{ widget.name }} 이런방법으로 접근하면 되는데

attrs는 {{ widget.attrs.id }}와 같이 한번 더 접근해주면 된다. 이걸로 우리는 codemirror 적용에 필요한 id값을 얻을 수가 있다. 

 

JS, CSS 추가방법

Widget에 JS파일이나 CSS 추가하고 싶다면 Custom Widget에 Midea를 설정해주면 된다. 

    class Media:
        js = ('test.js',)

주의)

'test.js'하고 뒤에 , 를 꼭 붙여야한다. 그래야지 'test.js'를 하나의 문자열로 인식하고 가져올 수 있다. 

 

3. Widget 적용 하기

myapp/models.py

class MytestRelated(Orderable):
    page = ParentalKey('CardPage', on_delete=models.CASCADE, related_name='mtf')

    testfield = models.TextField(null=True, blank=True)

    panels = [
        FieldPanel('testfield', widget=MyCustomWidget())
    ]

나는 Inlinepanel을 통해 여러개의 codemirror 필드를 사용하려고 ParentalKey를 사용하였다.

(필드 하나만 필요하다면 그냥 Page 모델에 필드를 정의하고 FieldPanel에 widget을 우리가 만든 widget을 적어주면된다.) 

CardPage라는 Page모델에 추가시켜보겠다. 

class CardPage(Page):

    content_panels = Page.content_panels + [
        InlinePanel('mtf', label="test"),
    ]

4. 결과

정상적으로 적용되어 있는 codemirror widget을 확인할 수 있다.

 

추가) Inline으로 작성된 필드들을 html에서 표현해보자. 

 

ParentalKey로 정의되어 있으므로 따로 return 값을 정의해주어야 한다. CardPage 맨 아래에 추가시켜보자.

@property
def codes(self):
  codes = [n.testfield for n in self.mtf.all()]
  return codes

이렇게 되면 html에서 page.codes로 가져올 수 있다. 

 

card_page.html

{% for code in page.codes %}
    {{ code }}
{% endfor  %}

 

번외) custom model 작성방법

모델을 custom 해서 새로 작성할 경우가 생긴다. 이럴경우 다음과 같이 model을 custom 하면 된다.

class MytestField(models.Model):
    testfield = models.TextField(null=True, blank=True)

    panels = [
        FieldPanel('testfield', widget=MyCustomWidget())
    ]

    class Meta:
        abstract = True

 

+ Recent posts