Wagtail Admin Page를 Custom 하고 싶으면 

 

https://docs.wagtail.io/en/stable/contributing/styleguide.html#styleguide

 

UI Styleguide — Wagtail Documentation 2.14.2 documentation

 

docs.wagtail.io

참고하면 된다.

 

다음을 추가해주자

settings/base.py

INSTALLED_APPS = (
    ...
    'wagtail.contrib.styleguide',
    ...
)

다음을 추가해주면 Admin 페이지 setting을 보면 사용할 수 있는 custom style들이 보일 것이다.

 

 

Wagtail에서 유용하게 사용할 수 있는 모듈 오픈소스 사이트이다.

 

https://awesomeopensource.com/project/springload/awesome-wagtail#general-resources

'Back-End > Wagtail, Django' 카테고리의 다른 글

Wagtail StreamField Queryset  (0) 2021.10.27
알아두면 좋을 것들  (0) 2021.10.22
Wagtail Iamport 결제, 결제 페이지  (0) 2021.10.14
Wagtail 중복 로그인 방지 (Django사용)  (0) 2021.10.06
Wagtail Video  (0) 2021.10.01

저번에 Django로 Iamport 결제기능을 구현해보는 포스트를 작성하였다. 

https://vicapor.tistory.com/62

장고에서는 view에서 정의하고 wagtail에서는 model에 정의하기 때문에 wagtail에 맞게 바꾸어 주었다. iamport 결제 기능 구현과 더불어 결제 페이지의 흐름 구현을 같이 기술하려고 한다.

 

1. 결제 페이지 구현

 

기존에 구현해 놓은 course page 이다. 여기서 결제 하기 버튼을 누르면 결제 page로 이동하게 하려고 한다. (기능 구현에 충실하였기 때문에 디자인은 '전혀' 신경쓰지 않았다.. 테스트하기 버튼도 보이는 것 같은데 신경쓰지말고 넘어가자)

 

course/models.py

    @route(r'^pay/$', name='go_payment')
    def go_payment(self, request):
        context = {
            "course": self
        }
        return render(request, 'billing/billing_page.html', context)

templates/course_page.html

<form action="{% pageurl page %}pay/" method="post">
  {% csrf_token %}
  <button class="button is-info" type="submit">결제하기</button>
</form>

결제하기 버튼을 form을 통해 구현했다. 현재 course의 정보를 담아서 결제 페이지로 보낼 것이기 때문에 post를 사용했다. 결제하기 버튼을 누르면 billing_page.html에 현재 course 페이지의 정보를 가지고 render하게 된다. 

 

2. 결제 페이지

billing/billing_page.html

{% extends 'base.html' %}
{% load wagtailcore_tags wagtailimages_tags %}

{% block content %}

<div class="section mt-6">
    <div class="container">
        {{ course.title }}
        {% image course.image fill-540x300 as photo %} <!--강의 이미지-->
            <img src="{{ photo.url }}" alt="">

        {{ course.price }} <!--강의 가격-->

        {{ user.field }}
        <input type="text" value="{{ user.email }}" id="user_email"> <!--구매자 이메일-->
        <input type="text" value="{{ user.username }}" id="user_name"> <!--구매자 이름-->
        <input type="tel" id="user_phonenumber"> <!--구매자 폰번호-->
        <button class="button is-primary mt-6" onclick="start_payment();">결제버튼</button> <!--결제 버튼-->
    </div>
</div>

<script src="http://code.jquery.com/jquery-1.12.4.min.js" ></script>
<script src="http://service.iamport.kr/js/iamport.payment-1.1.5.js"></script>
<script>
    function start_payment(){

        var im_email = document.getElementById('user_email').value;
        var im_name = document.getElementById('user_name').value;
        var im_phonenumber = document.getElementById('user_phonenumber').value;

        IMP.init('imp');
        let csrftoken = '{{ csrf_token }}'
        IMP.request_pay({
            pg: 'html5_inicis',
            pay_method: 'card',
            merchant_uid: 'merchant_' + new Date().getTime(),
            name: '{{ course.title }}',
            amount: '{{ course.price }}',
            buyer_email: im_email,
            buyer_name: im_name,
            buyer_tel: im_phonenumber,
            buyer_addr: '서울특별시 강남구 삼성동',
            buyer_postcode: '123-456'
        }, function (rsp) {
            if (rsp.success) {
                jQuery.ajax({
                    url: "{{ course.url }}check/", 
                    headers:{'X-CSRFToken':csrftoken},
                    type: 'POST',
                    dataType: 'json',
                    data: {
                        imp_uid: rsp.imp_uid
                    },
                }).done(function (data) {
                    if (data.status=='success') {
                        location.href = '{{ course.url }}complete/';
                    } else {
                        var msg = '아직 제대로 결제가 되지 않았습니다.';
                        alert(msg);
                    }
                }).fail(function () {
                    alert("ajax fail");
                }).always(function () {

                });
            } else {
                var msg = '결제에 실패하였습니다.';
                msg += '에러내용 : ' + rsp.error_msg;
                alert(msg);
            }
        });
    }
</script>
{% endblock %}

결제 페이지 이다. (script는 나중에 js파일로 따로 만들어서 정의해 놔야겠다.)

course 페이지의 정보를 가지고 와서 추가로 email과 핸드폰 번호, 결제 사용자 이름을 적을 수 있는 form을 간단하게 구현했다. 

 

(다시한번 디자인은 신경쓰지말도록하자..)

이제 결제 버튼을 누르면 import 가 실행 될 수 있게 함수로 만들어서 button과 연결하였다. 여기서 주의해야할 점은 Django에서 서버로 데이터를 전송하기 위해서는 csrf_token을 반드시 같이 보내야 한다는 점이다. 위에 코드를 보면 알겠지만 JSON에서 csrf_token을 보내는 방식을 확인할 수 있을 것이다. 

 

3.결제

course/models.py

    @route(r'^check/$', name='iamport_data')
    def iamport_data(self, request):
        if request.method == 'POST' and request.is_ajax():
            imp_uid = request.POST.get('imp_uid')
            data = {
                "imp_key": "1234567",
                "imp_secret": "fefefefefe"
            }

            response = requests.post('https://api.iamport.kr/users/getToken', data=data)
            data = response.json()
            my_token = data['response']['access_token']

            headers = {"Authorization": my_token}
            response = requests.get('https://api.iamport.kr/payments/'+imp_uid, data=data, headers = headers)
            data = response.json()

            order_amount = self.price
            amountToBePaid = data['response']['amount']
            status = data['response']['status']

            if order_amount==amountToBePaid:
                if status == 'ready':
                    return HttpResponse(json.dumps({'status': "vbankIssued", 'message': "가상계좌 발급 성공"}),
                                        content_type="application/json")

                elif status=='paid':
                    return HttpResponse(json.dumps({'status': "success", 'message': "일반 결제 성공"}),
                                        content_type="application/json")

                else:
                    pass

            else:
                return HttpResponse(json.dumps({'status': "forgery", 'message': "위조된 결제시도"}), content_type="application/json")
        else:
            pass

billing_page.html에서 json을 통해 결제 정보를 검증하는 코드이다. 기존 Django와 달라진 점은 view에서의 정의한 것이 아니라 models.py에 route를 통해 정의한 것이다. 

 

문제가 없으면 결제 성공을 HttpResponse를 통해 보낸다.

 

4. 결제 완료

 

결제가 정상적이면 complete를 보낸다. 

done(function (data) {
  if (data.status=='success') {
  location.href = '{{ course.url }}complete/';
  } else {
  var msg = '아직 제대로 결제가 되지 않았습니다.';
  alert(msg);
}

 

course/models.py

    @route(r'^complete/$', name='payment_complete')
    def payment_complete(self, request):

        context = {
            "information": self
        }
        return render(request, 'billing/payment_complete.html', context)

결제가 성공적으로 되었으면 결제 페이지 정보와 같이 payment_complete.html에 렌더해준다. 

 

billing/payment_complete.html

{% extends 'base.html' %}
{% load wagtailcore_tags %}

{% block content %}
<div class="section mt-6">
    <div class="container">
        <h1>
            결제가 완료 되었습니다!!!!!!!!!!!
        </h1>
        <a href="{{ information.url }}">돌아가기</a> <!--코스 페이지로 돌아가기-->
    </div>
</div>
{% endblock %}

결제 완료 페이지이다. 그냥 결제 완료를 위해서 사용한 것이다.

여기서 중요한 점은 결제 완료 페이지로 넘어가기 위해서는 js에서 location.ref로 정의 해놔야 한다.

'Back-End > Wagtail, Django' 카테고리의 다른 글

알아두면 좋을 것들  (0) 2021.10.22
Wagtail StyleGuide + 추가 모듈  (0) 2021.10.18
Wagtail 중복 로그인 방지 (Django사용)  (0) 2021.10.06
Wagtail Video  (0) 2021.10.01
Wagtail Custom Widget (+Model)  (0) 2021.09.29

https://yellowdonkey0329.blogspot.com/2018/12/django.html

위의 블로그를 참고하여 만들었다. 

 

공식문서를 확인하고 싶다면

https://docs.iamport.kr/implementation/payment

접속하면 된다.

 

1. iamport 가입

일단 iamport 홈페이지에 접속해서 회원 가입을 한 뒤 로그인을 하고

다음 정보를 기억하자 (메모장에 적어놓으면 된다.)

1. 가맹점 식별코드

2. REST API 키 

3. REST API secret

 

2. billing app 만들기

굳이 app을 만들필요는 없지만 나는 billing app을 만들어 적용을 하였다. 나중에 결제 기능을 수정할 수도 있으니 app을 만들어서 관리하는 것이 좋다고 생각한다. 

 

python manage.py startapp billing을 해주고 settings/base.py에 billing을 등록시켜준다. 그리고 당장은 Django의 view를 사용할 계획이니 urls.py에 billing의 urls을 등록해준다. 

 

base.py
urls.py

3. templates 작성

templates/billing/pay.html

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <script src="http://code.jquery.com/jquery-1.12.4.min.js" ></script>
        <script src="http://service.iamport.kr/js/iamport.payment-1.1.5.js"></script>
        <script>
            IMP.init('imp');
            let csrftoken = '{{ csrf_token }}'
            IMP.request_pay({
                pg: 'html5_inicis',
                pay_method: 'card',
                merchant_uid: 'merchant_' + new Date().getTime(),
                name: '테스트입니다.',
                amount: 100,
                buyer_email: 'iamport@siot.do',
                buyer_name: 'yellowdonkey',
                buyer_tel: '010-1234-5678',
                buyer_addr: '서울특별시 강남구 삼성동',
                buyer_postcode: '123-456'
            }, function (rsp) {
                if (rsp.success) {
                    //[1] 서버단에서 결제정보 조회를 위해 jQuery ajax로 imp_uid 전달하기
                    jQuery.ajax({
                        url: "/payments/complete/", //cross-domain error가 발생하지 않도록 동일한 도메인으로 전송
                        headers:{'X-CSRFToken':csrftoken},
                        type: 'POST',
                        dataType: 'json',
                        data: {
                            imp_uid: rsp.imp_uid
                            //기타 필요한 데이터가 있으면 추가 전달
                        },
                    }).done(function (data) {
                        //[2] 서버에서 REST API로 결제정보확인 및 서비스루틴이 정상적인 경우
                        alert("ajax done");               
                        console.log("ajax done");
                        if (data.status=='success') {
                            var msg = '결제가 완료되었습니다.';
                            msg += '\n고유ID : ' + rsp.imp_uid;
                            msg += '\n상점 거래ID : ' + rsp.merchant_uid;
                            msg += '\결제 금액 : ' + rsp.paid_amount;
                            msg += '카드 승인번호 : ' + rsp.apply_num;
                            alert(msg);
                            console.log(msg);
                        } else {
                            //[3] 아직 제대로 결제가 되지 않았습니다.
                            //[4] 결제된 금액이 요청한 금액과 달라 결제를 자동취소처리하였습니다.
                            var msg = '아직 제대로 결제가 되지 않았습니다.';
                            alert(msg);
                            console.log(msg);
                        }
                    }).fail(function () {
                        alert("ajax fail");
                        console.log("ajax fail");
                    }).always(function () {
                        alert("ajax always");
                        console.log("ajax always");
                    });
                } else {
                    var msg = '결제에 실패하였습니다.';
                    msg += '에러내용 : ' + rsp.error_msg;
                    alert(msg);
                }
            });
        </script>
    </head>

    <body>
    </body>

</html>

IMP.init에는 아까 저장해놓은 가맹점 식별코드를 넣으면 된다.

 

추후에 템플릿 언어로 관련 정보를 바꿔주면 될 것이다. 결제가 완료되면 ajax을 통해 POST로 json 형태의 데이터를 서버에 보내줄 것이다. 

 

4. urls.py 작성

ajax 부분을 통해 결제가 성공했을 때, 관련정보가 /payments/complete/을 통해 전달되는 것을 확인할 수 있다. 이제 billing app의 urls를 작성해보자.

 

billing/urls.py

from . import views
from django.conf.urls import url
from django.views.generic import TemplateView


urlpatterns = [
    url(r'^payment_test/$', TemplateView.as_view(template_name='billing/pay.html'), name='payment_test'),
    url(r'^payments/complete/$', views.payment_complete, name='payment_complete'),
]

 

5. View.py 작성

이제 POST를 통해 데이터가 왔을 때, 처리하기 위해 view를 정의하면된다. 현재는 블로그의 튜토리얼을 따라하는 과정이라 wagtail의 page가 아닌 view로 해보려고 한다.

 

from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
from django.utils.decorators import method_decorator
import json
import requests


# Create your views here.
@method_decorator(csrf_exempt)
def payment_complete(request):

    if request.method == 'POST' and request.is_ajax():
        imp_uid = request.POST.get('imp_uid')

        # // 액세스 토큰(access token) 발급받기
        data = {
            "imp_key": "",
            "imp_secret": ""
        }

        response = requests.post('https://api.iamport.kr/users/getToken', data=data)
        data = response.json()
        my_token = data['response']['access_token']

        #  // imp_uid로 아임포트 서버에서 결제 정보 조회
        headers = {"Authorization": my_token}
        response = requests.get('https://api.iamport.kr/payments/'+imp_uid, data=data, headers = headers)
        data = response.json()

        # // DB에서 결제되어야 하는 금액 조회 const
        order_amount = 100
        amountToBePaid = data['response']['amount']  # 아임포트에서 결제후 실제 결제라고 인지 된 금액
        status = data['response']['status']  # 아임포트에서의 상태

        if order_amount==amountToBePaid:
            # DB에 결제 정보 저장
            # await Orders.findByIdAndUpdate(merchant_uid, { $set: paymentData}); // DB에
            if status == 'ready':
                # DB에 가상계좌 발급정보 저장
                return HttpResponse(json.dumps({'status': "vbankIssued", 'message': "가상계좌 발급 성공"}),
                                    content_type="application/json")

            elif status=='paid':
                return HttpResponse(json.dumps({'status': "success", 'message': "일반 결제 성공"}),
                                    content_type="application/json")

            else:
                pass

        else:
            return HttpResponse(json.dumps({'status': "forgery", 'message': "위조된 결제시도"}), content_type="application/json")
    else:
        return render('payment_complete.html', locals())

처음에 저장해놓은 key와 secret을 넣으면 된다.

view에서 하는 일은 html을 통해 결제가 될 때, 결제 정보를 받아서 imp_uid를 통해 iamport 서버에서 조회하여 확인하는 과정을 거친다. 

 

'Back-End > Django' 카테고리의 다른 글

Django Signals  (0) 2021.10.22
Django ORM  (0) 2021.10.21
Django Bulma 적용 방법  (0) 2021.08.24
Django template 태그 모음  (0) 2021.08.12
Django Admin Custom 3(간단한 Custom 연습)  (0) 2021.08.09

Wagtail은 Django 기반의 CMS다. 이 말인 즉슨 이미 풍부한 Django기능들을 그대로 쓸 수 있다는 뜻이다. 

이번에는 Django에서 중복로그인을 방지하는 Session처리에 대해서 기술해보려고 한다. 물론 Wagtail에서도 그대로 적용된다.

https://planjang.tistory.com/167

위의 블로그를 참고해서 작성했다. 생소한 내용이니 세세하게 공부해서 작성해볼 생각이다. 

 

먼저 쿠키와 세션에 대해서 알아보자

 

https://hahahoho5915.tistory.com/32

쿠키 : HTTP의 일종으로 사용자가 어떠한 웹 사이트를 방문할 경우,
그 사이트가 사용하고 있는 서버에서 사용자의 컴퓨터에 저장하는 작은 기록 정보 파일이다.

HTTP에서 클라이언트의 상태 정보를 클라이언트의 PC에 저장하였다가
필요시 정보를 참조하거나 재사용할 수 있다.

 

세션 : 일정 시간동안 같은 사용자(브라우저)로부터 들어오는
일련의 요구를 하나의 상태로 보고, 그 상태를 일정하게 유지시키는 기술이다.

여기서 일정 시간은 방문자가 웹 브라우저를 통해
웹 서버에 접속한 시점으로부터 웹 브라우저를 종료하여 연결을 끝내는 시점을 말한다.

 

즉, 로그인을 했을 때, 사용자 세션을 발생시키게 된다. 그래서 우리는 이 세션을 가지고 로그아웃을 관리할 수 있다. 다른 사이트에서 로그인이 되었을 때, 기존의 세션을 종료시키면 된다. 그렇다면 로그인이 되었다는 것을 어떻게 알 수 있을까? 그것은 signal을 사용하면 된다. 

 

Signal이란?

django 프레임워크는 어떤 특정한 일을 수행할 때마다 알려줄 것을 설정하고, 그 때에 지정한 동작을 수행할 수 있게 하는 신호(signal)를 발생하는 기능을 가지고 있다.
출처: https://dgkim5360.tistory.com/entry/django-signal-example [개발새발로그]

 

쉽게 얘기하자면 login을 할 때 signal을 발생시키게 되고 우리는 이를 이용하여 로그인을 한 것을 알 수 있다는 것이다. 

그렇다면 코드를 작성해보면서 지금의 내용을 구현해보자.

 

1. 현재 사용자의 세션을 저장하는 모델 객체 생성

user/models.py

from django.contrib.auth.models import AbstractUser
from django.db import models
from django.contrib.auth.signals import user_logged_in
from importlib import import_module
from django.conf import settings


# User 커스텀
class User(AbstractUser):
    nickname = models.CharField(max_length=20, null=True, default='None')


class UserSession(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, editable=False)
    session_key = models.CharField(max_length=40, editable=False)
    created_at = models.DateTimeField(auto_now_add=True)

나는 user app에서 User을 커스텀해서 사용중이었다. 먼저 사용자의 session을 관리하는 model을 만들어 준다. 각자 자신의 User을 가져와서 적용하면될 것이다. 

 

2. 세션 관리 리시버 구현

SessionStore = import_module(settings.SESSION_ENGINE).SessionStore


def kicked_my_other_sessions(sender, request, user, **kwargs):
    for user_session in UserSession.objects.filter(user=user):
        session_key = user_session.session_key
        session = SessionStore(session_key)
        session.delete()

    session_key = request.session.session_key
    UserSession.objects.create(user=user, session_key=session_key)

user_logged_in.connect(kicked_my_other_sessions, dispatch_uid='user_logged_in')
  • user_logged_in 시그널에 기존에 로그인한 세션을 삭제해주는 리시버를 연결해주자.
  • 뷰가 아닌 곳에서 세션을 사용하려면 공식문서(링크)에 나와있는대로 SessionStore를 사용하면 된다.
  • kicked_my_other_sessions 리시버를 만들었는데, 인자로 쓰이는 것들에 대해 알아보자.
    1. sender: 방금 로그인을 마친 유저의 클래스
    2. request: 현재 HttpRequest 인스턴스
    3. user: 방금 로그인을 마친 유저 인스턴스
  • 따라서 함수에서 구현한 사항은
    1. 방금 로그인을 마친 유저가 가지고 있는 UserSession 클래스의 레코드를 모두 읽어온다.
    2. 세션을 삭제하여 로그아웃한다.
    3. 현재 세션에 대한 레코드를 생성하여 저장한다.

위에서 소개한 블로그에 자세하게 나와 있어서 인용해왔다. 

 

분석하자면 SessionStore을 가져와서 user에 맞는 session을 찾아서 삭제한다. (로그아웃)

그리고 현재 세션을 Session에 다시 저장해주는 방법이다. 이렇게 되면 현재 요청들어온 세션만 유지되고 나머지 세션은 전부 삭제되어서 다른 곳에서 로그아웃이 가능해진다. 

 

추가) 로그아웃이 될 때 메세지 출력

로그아웃이 될 때, 로그아웃 메세지를 표현해주는 방법에 대해서 기술하려고 한다. Wagtail에 적용했는데 Wagtail admin login 페이지에만 표현 되어 이후 수정이 필요해보인다. 일단은 블로그 내용을 정리하는데 중점을 맞췄으니 이 내용도 정리하려고 한다.

 

settings에 다음 코드를 추가한다. django는 settings.py이고 wagtail은 settings/base.py이다.

MIDDLEWARE = [
    # ...
    'django.contrib.messages.middleware.MessageMiddleware',
    'user.middleware.KickedMiddleware',
    # ...
]

'user.middleware.KickedMiddleware'은 자신의 middleware(메세지발생) 코드가 위치하는 곳이다.

 

user/middleware

from django.conf import settings
from django.contrib import messages
from django.contrib.auth import logout as auth_logout
from django.shortcuts import redirect
from django.utils.deprecation import MiddlewareMixin

class KickedMiddleware(MiddlewareMixin):
    def process_request(self, request):
        kicked = request.session.pop('kicked', None)
        if kicked:
            messages.info(request, '동일 아이디로 다른 브라우저 웹사이트에서 로그인이 감지되어, 강제 로그아웃되었습니다.')
            auth_logout(request)
            return redirect(settings.LOGIN_URL)
  • settings.py에 등록한 이름으로 클래스를 생성한다.
  • 현재 세션에 'kicked'가 True면 메시지를 띄우고 로그아웃을한다.

 

코드는 간단하다. 세션에 kicked라는 이벤트가 발생했을 때, 메세지를 호출하고 로그아웃 하고 redirect하라는 것이다. models.py에 kicked를 발생시켜주면 된다. 

 

user/models.py

def kicked_my_other_sessions(sender, request, user, **kwargs):
    for user_session in UserSession.objects.filter(user=user):
        session_key = user_session.session_key
        session = SessionStore(session_key)
        session['kicked'] = True
        session.save()
        user_session.delete()

    session_key = request.session.session_key
    UserSession.objects.create(user=user, session_key=session_key)

user_logged_in.connect(kicked_my_other_sessions, dispatch_uid='user_logged_in')
  • 미들웨어 추가에 따른 코드 변경이 필요하다.
  • 기존에 로그인했던 세션(user_session)에 kicked = True를 할당한다.
  • 원래 session middleware에서 save() 처리를 하는데, 지금은 middleware를 쓰지 않으니 직접 save를 호출 해야한다.

위와 같이 수정해주면 된다. 

'Back-End > Wagtail, Django' 카테고리의 다른 글

Wagtail StyleGuide + 추가 모듈  (0) 2021.10.18
Wagtail Iamport 결제, 결제 페이지  (0) 2021.10.14
Wagtail Video  (0) 2021.10.01
Wagtail Custom Widget (+Model)  (0) 2021.09.29
Wagtail Custom Field, Panel  (0) 2021.09.29

Wagtail 문서에서는 Emebed Panel을 통해서 동영상 주소를 추가 시키는 방법으로 동영상을 추가시켰다. 

하지만 비디오 자체를 업로드 하여 관리하기 쉽게 만들어보려고 한다. 

Wagtail에는 Wagtailvideo라는 모듈이 있다. 

모듈 사용법은 https://pypi.org/project/wagtailvideos/을 참고하자

 

wagtailvideos

A wagtail module for uploading and displaying videos in various codecs.

pypi.org

 

플레이어를 Custom 하는 방법에 대해서 기술해보려고 한다. 

 

{% wagtailvideos_tags %}

{% video self.video autoplay controls controlsList=nodownload id=myvideo height=300 width=512 %}

wagtailvideo 문서를 확인하면 확인할 수 있겠지만 html에 영상 추가는 위와 같이 하면 된다. 

 

여기서 문서와 다른 내용이 추가가 되었을텐데 id 값과 download 방지 기능을 추가한 것이다.

(간단하게 이렇게 속성만 추가하면 되었었는데 굳이 백엔드에서 상속받아서 재정의를 하려는 뻘짓을 저질렀다.. 덕분에 공부를 잘했다..) 

 

html에 video 태그가 생성되어 동영상이 표시가 되는데 태그에 요소 값을 주고 싶으면 입력하면 된다. 

여기서 id = "myvideo"가 아닌 id=myvideo로 태그 추가를 해야한다. 

 

그리고 controlsList속성을 nodownload를 주게 되면 플레이어에서 다운로드 버튼이 사라질 것이다. 

 

하지만 오른쪽 마우스 클릭으로 다운로드와 f12개발자 모드로 다운 받는 것은 막을 수가 없다. 여기선 jquery를 통해 방지하는 방법을 소개하려고 한다. 

 

https://server-engineer.tistory.com/563

위 블로그를 참고했다. 

 

<script type="text/javascript"> // F12 버튼 방지 
        $(document).ready(function(){ 
            $(document).bind('keydown',function(e){ 
                if ( e.keyCode == 123 /* F12 */) { 
                    e.preventDefault(); e.returnValue = false; 
                } 
            }); 
        }); // 우측 클릭 방지 

        document.onmousedown=disableclick; 
        status="Right click is not available."; 

        function disableclick(event){ 
            if (event.button==2) { 
                alert(status); return false; 
            } 
        } 
</script>

위 코드를 head태그에 추가하면 된다. 

 

CSS 커스텀을 위해 위에서 id 값을 추가해주었다. CSS 적용 방법은 일반 html css적용이랑 같다. 

 

https://jusths.tistory.com/92 다음 블로그를 참고하자

 

 

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