Wagtail StreamField는 기존 Django Field와는 다르게 JSON 형식으로 이루어져 있다. 

그래서 Django Field 처럼 query문으로 create, add 등을 쓸 수가 없다. StreamField는 Query set을 어떻게 사용해야하는지 알아보자.

 

참고 사이트

https://stackoverflow.com/questions/40612658/add-modify-blocks-value-in-wagtail-streamfield-from-shell

https://docs.wagtail.io/en/stable/topics/streamfield.html#streamfield

1. Models

user_management/models.py

@register_snippet
class UserPyCoin(index.Indexed, ClusterableModel):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    py_coin_count = models.IntegerField(default=0, verbose_name="파이코인 총갯수")
    py_coin = StreamField(UserPyCoinBlock(), verbose_name="파이코인", blank=True)

    panels = [
        FieldPanel('user'),
        FieldPanel('py_coin_count', widget=CountWidget()),
        StreamFieldPanel('py_coin'),
    ]

일단 사용할 모델에 대해서 살펴보자. user 별로 streamfield인 py_coin을 추가해주고 py_coin의 갯수를 py_coin_count에 추가해주려고 한다. 그렇다면 streamfield인 py_coin이 어떤 구조인지 살펴보자.

 

user_management/blocks.py

class PyCoinBlock(StructBlock):
    coin_explanation = CharBlock(required=False, label="코인 설명")
    coin = CoinBlock(required=False, label='코인 개수', default=0)
    date = DateBlock(required=True, label='획득 날짜')

coin_explanation은 어떻게 코인을 사용했고 얻었는지 설명을 적어준다.

coin은 얻은 코인의 갯수이다.

date는 coin을 얻은 날짜이다. 

 

2. Ajax 통신

model을 보면 알 수 있듯이 user에게 코인을 적립해주는 기능이다. user가 quiz를 맞추면 ajax통신을 통해 quiz 정보와 갯수를 저장해주려고 한다. 

 

js/card.js

jQuery.ajax({ 
    url: card_page_url + "answer_check/", // 클라이언트가 HTTP 요청을 보낼 서버의 URL 주소 
    headers:{'X-CSRFToken':card_page_token},
    data: { 
    title : card_page_title,
    py_coin : 1,
  }, // HTTP 요청과 함께 서버로 보낼 데이터 
    method: "POST", // HTTP 요청 메소드(GET, POST 등) 
    dataType: "json" // 서버에서 보내줄 데이터의 타입 
  }).done(function (data) {
    if (data.status=='success') {
    alert('성공입니다. 파이 코인이 적립되었습니다.');
  }
    else {
    alert("서버 오류입니다. 새로고침 후 다시 실행해주세요.");
  }
    }).fail(function () {
    alert("서버 오류입니다. 새로고침 후 다시 실행해주세요.");

  }).always(function () {

});

Jquery Ajax를 통해 작성하였다. 요즘은 fetch를 사용하기 때문에 추후에 바꿔서 수정할 계획이다. 

퀴즈를 맞추게 되면 Ajax의 POST를 통해서 서버와 통신하려고 한다. 

 

3. StreamField Query Set

card/models.py

    @route(r'^answer_check/$', name='answer_check')
    def answer_check(self, request):
        if request.method == 'POST' and request.is_ajax():
            page_title = request.POST.get('title')
            py_coin = request.POST.get('py_coin')

            add_pycoin = [
                ('py_coin',{
                'coin_explanation' : page_title,
                'date' : timezone.localdate(),
                'coin' : py_coin
                },)
            ]

            instance_user = UserPyCoin.objects.get(user=request.user)
            instance_user.py_coin_count += int(py_coin)
            instance_user.py_coin.append(add_pycoin[0])
            instance_user.save()

            return HttpResponse(json.dumps({'status':'success'}), content_type="application/json")

Wagtail이라서 route를 통해서 Ajax 통신을 구현하였다. 앞서 얘기 했던 것 처럼 StreamField는 JSON 형태의 데이터이다. 먼저 어떻게 데이터가 저장되어 있는지 데이터베이스를 통해서 확인해보자. 

 

데이터베이스 구조

StreamField는 JSON 형식인 것을 확인 할 수 있다. 

구조를 보자면

type, value, id 값으로 나누어져 있고 type에는 StreamField의 변수명 (py_coin), value는 각 변수의 값이 JSON 형식으로 저장되어 있고 id 값은 block의 고유 값이다. 

 

우리는 type과 value만 추가해주면 된다. (아이디 값을 wagtail에서 자동으로 추가)

 

Python에서 저장해줄 때는 append를 통해 추가해주면 된다. 그리고 JSON형식은 튜플과 딕셔너리를 통해서 작성해주면 된다. 

add_pycoin = [
  ('py_coin',{
  'coin_explanation' : page_title,
  'date' : timezone.localdate(),
  'coin' : py_coin
  },)
]

type을 정의해주기 위해 'py_coin'을 작성하였다. 그리고 실제 데이터에는 JSON을 형식에 맞게 딕셔너리로 작성하였다. 

그리고 add_pycoin은 리스트 형식이기 때문에 첫번 째 데이터를 append 한다는 의미로 [0]을 붙여주면 된다.

instance_user.py_coin.append(add_pycoin[0])

이제 데이터베이스에 가서 확인하면 정상적으로 저장되는 것을 확인할 수 있다. 

 

추가)

삭제

del instance_user.py_coin[-1]

삭제를 하고 싶다면 위와 같이 작성을 해주면 된다. JSON형식의 데이터로 List안에 저장되므로 파이썬 List와 같게 데이터를 제어하면 될 것이다. 

 

수정

 

instance_user.py_coin[0] = ('heading', "My story")

코드 수정은 직접 리스트 번호로 접근해서 수정해야한다. 

 

instance_user.py_coin[0]를 출력해보면 아래와 같이 나타난다. 

<dl>
    <dt>coin_explanation</dt>
    <dd>코인입니다.</dd>
    <dt>coin</dt>
    <dd>1</dd>
    <dt>date</dt>
    <dd>2021-10-26</dd>
</dl>

그렇다면 데이터를 접근하려면 어떻게 해야할까?

 

page.body[0].block_type and page.body[0].value

위와 같이 접근할 수 있다. 다만 데이터의 직접 접근은 불가능하다고 한다. (tuple값이라서 바꿀 수가 없음)

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

Wagtail Ajax  (0) 2021.10.29
Wagtail Custom Admin Menu  (0) 2021.10.28
알아두면 좋을 것들  (0) 2021.10.22
Wagtail StyleGuide + 추가 모듈  (0) 2021.10.18
Wagtail Iamport 결제, 결제 페이지  (0) 2021.10.14

dev_front와 dev_backend를 main에다 merge 하면서 전체적으로 코드를 검토하게 되었다. 그동안 기능 만들기에 집중하여 애매하게 넘어갔던 부분이나 잘 몰랐던 부분을 검토하면서 정리하려고 한다. 

 

1. @Property

    @property
    def cards_of_course(self):
        cards = [ n.card for n in self.course_card_relationship.all()]
        return cards

Page에서 특정 필드 값을 Template에 다양한 방법으로 표현하고 싶을 때, 사용하는 방법이다. get_context로도 보낼 수 있지만 @property가 모듈화 되어 있어서 사용하기에는 더 좋아보인다.

context와 property를 사용하는 가장 큰 차이점!context는 request를 사용하고 싶을 때, property는 request가 필요 없을 때! property 메소드 함수에 request를 쓰면 오류가 난다. 

(Template에서는 메소드 명으로 가져와야한다.)

 

https://www.youtube.com/watch?v=5OyFch8_4fk

https://dojang.io/mod/page/view.php?id=2476 

@property에 대한 설명은 위에 잘 나와있다. 이해한대로 정리를 하자면 class에서 값을 가져오는 getter와 값을 설정해주는 setter가 있는데 @property는 getter, @함수명.setter는 setter를 뜻한다. 클래스의 변수명은 직접 변수명으로 접근해도 되지만 getter와 setter를 통해서 접근하면 원하는 조건을 입력해줄 수 있다. 

 

예를들어 어떤 경우에는 나이가 10살보다 적은 사람에만 적용되는 기능을 만들고 싶을 때, getter, setter 메소드를 통해서 기능을 적용할 수가 있다는 뜻이다. setter에서 조건에 맞게 기능을 추가해주고 getter로 가져오면 된다. 

 

(@property를 안적어도 기능 수행에는 문제가 없는 것을 확인할 수 있다. 하지만 getter 메소드이고 나중에 setter 메소드를 사용할 수도 있으므로 @property를 적어주자.)

 

2. git 명령어

git branch --delete 브랜치 이름 : git branch 삭제

git push origin --delete 브랜치 이름 : 원격 저장소 git branch 삭제

git merge 브랜치 명 : git merge

git pull origin 브랜치 명 : 특정 branch pull

 

 

3. Migrations 오류

models을 만들고 테스트하는 과정에서 기존 모델의 데이터를 그대로 남긴채로 모델을 수정을 하고 새로 데이터를 입력하거나 수정하려고 하면 오류가 발생한다. 

 

 

해결방법은 app의 migrations 파일의 있는 파일들을 삭제해주고 계속 문제가 발생되면 원인을 찾아서 수정해주면 되지만 정 안되면 깔끔하게 데이터베이스를 날려버리는게 속편하다. 

 

 

3. Templates에서의 self, page차이 (Wagtail)

templates에서 {{ self.title }}와 {{ page.title }}은 기술적으로 같다. 두개 중 선택해서 사용하면 된다. 

 

 

4. Templates와 Static 파일 관리 (Wagtail)

특별히 정해진 형식은 없지만 보통 기본 프로젝트 폴더에서 통합적으로 관리한다. 

 

 

5. Settings에서 base.py dev.py production.py 차이점 (Wagtail)

Wagtail의 Settings에 보면 base.py, dev.py, production.py를 볼 수 있다. 이들의 차이점은 

 

1)base.py

This file is for global settings that will be used in both development and production. Aim to keep most of your configuration in this file.

 

2)dev.py

This file is for settings that will only be used by developers. For example: DEBUG = True

 

3)production.py

This file is for settings that will only run on a production server. For example: DEBUG = False

 

4)local.py

This file is used for settings local to a particular machine. This file should never be tracked by a version control system.

 

1) base.py 는 공통적으로 사용하는 것을 정의 해 놓는다.

2) dev.py에는 로컬 단계. 즉 개발할 때 사용하는 것을 정의 해 놓는다.

3) production.py는 배포 서버를 위해 사용하는 것을 정의한다.

4) local.py 특정 기기에서 사용할 것을 정의한다. 

 

 

6. __str__(self)의 용도 (Wagtail)

 
  def __str__(self):
        return self.full_name

의 설정은 다른 곳에서 model을 불러왔을 때, 보여지는 변수이다. 즉 위에 코드에서는 객체를 호출했을 때 full_name이 나타난다.

 

 

7. DEFAULT_AUTO_FIELD

https://docs.djangoproject.com/en/3.2/releases/3.2/#customizing-type-of-auto-created-primary-keys

 

Django 3.2 release notes | Django documentation | Django

Django The web framework for perfectionists with deadlines. Overview Download Documentation News Community Code Issues About ♥ Donate

docs.djangoproject.com

AutoField는 Django ORM에서 자동적으로 생성되는 Auto increment Primary key를 말한다. 기본값이 AutoField로 int 크기를 갖게 되는데 거의 매번 bigint 사이즈 사용을 위해 Model을 정의할 때 bigint로 정의 하기 모델마다 별도 선언을 해주었었다.

 

출처: https://www.morenice.kr/263 [morenice's blog]

 

Wagtail에서 apps.py를 확인해보면 BigAutoField로 되어 있다. Django 3.2 버전 이상부터 만들어진 프로젝트들은 주로 BigAutoField를 사용한다. 그래서 기존에 AutoField로 되어 있는 Field을 BigAutoField로 바꿔서 문제를 피하는 것이 바람직할 것이다. 

 

settings/base.py

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

 

 

8. Wagtail Snippets

Wagtail에서 Snippets은 언제 사용할까? 보통 재사용하는 Models을 만들 때 사용한다고 한다. 주로 Django Model이 Snippet에 저장된다. 

 

 

9. __str__(self):

Wagtail에서 Model을 작성할 때, __str__(self)을 정의해준다. 어떤 용도일까? 답은 orm을 사용해서 Model을 불러왔을 때, 표시되는 이름이다. 정의하지 않고 객체를 출력한다면 SampleModel(1), SampleModel(2), SampleModel(3) 이런 식으로 숫자로 구별되어서 전달이 될 것이다. 그래서 __str__(self)의 return 값을 모델의 필드로 한다면 필드 이름으로 보여질 것이다. 

def __str__(self):
	return self.name

 위와 같은 방법으로 사용하면 된다. 

 

 

10. 다른 Port로 서버를 실행하고 싶다면?

python manage.py runserver 포트번호

python manage.py runserver 8001

위와 같이 실행시키면 8001포트로 서버가 열린다. 

그렇게 되면 접속할 주소는 http://127.0.0.1:8001 이 된다. 

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

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 다음 블로그를 참고하자

 

 

+ Recent posts