Django #5 - Django로 todo 앱 코딩하기 (for DjangoMixin)

Corner·2022년 4월 23일
0

django

목록 보기
7/14
post-thumbnail

개발 방향 요약하기

  1. todo-html
    • html 파일에 vue.js 코드를 작성하는 방식
    • todo 데이터가 사라지는 단점이 있어, 불완전한 웹 프로그램임
  2. todo-vue-only
    • 장고에 (1.번)의 html 파일을 그대로 수용하는 방식
    • todo 데이터가 사라지는 단점이 있어, 불완전한 웹 프로그램임
  3. todo-django-only
    • vue.js 코드 없이 장고 코드만으로 todo 앱을 개발하는 방식
    • 완전한 웹 프로그램, 페이지 이동 자주 발생 (3개 html)
  4. todo-django-mixin
    • 장고 클래스형 뷰의 믹스인 기능을 활용하는 방식
    • 페이지 이동을 축소함

클래스형뷰 Mixin 사용하기

코딩 순서는 settings.py -> models.py -> urls.py -> views.py -> templates 순으로 해왔지만,

이번에는 settings.py와 models.py는 변경사항이 없어 urls과 templates 부터 작업하고 이후 views의 작업을 어떻게할지 고민하는 작업을 진행한다.

URL 패턴뷰 이름템플릿 파일명
/admin/(장고 기본제공)
/todo/vonly/TodoVueOnlyTV(TemplateView)todo_vue_only.html
/todo/create/TodoCV(CreateView)todo_form.html
/todo/list/TodoLV(ListView)todo_list.html
/todo/{number}/delete/TodoDelV(DeleteView)todo_confirm_delete.html
/HomeView(TemplateView)home.html
/todo/mixin/TodoMOMCV(MultipleObjectMixin, CreateView)todo_form_list.html
/todo/{number}/delete2/TodoMixinDelV(DeleteView)팝업

todo/urls.py에 아래 코드를 추가한다.

    path('mixin/', views.TodoMOMCV.as_view(), name='mixin'),
#     MultipleObjectMixin, CreateView

todo/views.py에 임시 코드를 추가한다.

class TodoMOMCV(CreateView):
    template_name = 'todo/todo_form_list.html'

todo/todo_form_list.html

기존의 todo_form.html 복사해서 코드를 정리한다.

{% extends 'base.html' %}
{% block title %}
    todo_form.html
{% endblock %}
{% block extra-style %}
    <style>
        body {
            text-align: center;
            background-color: #ddd;
        }

        .inputBox {
            margin: auto;
            width: 70%;
            background: white;
            height: 50px;
            border-radius: 50px;
            line-height: 50px;
        }

        .inputBox .name {
            border-style: none;
            border-bottom: 1px solid #ddd;
            width: 90px;
            padding-left: 20px;
            line-height: 20px;
        }

        .inputBox .item {
            border-style: none;
            border-bottom: 1px solid #ddd;
            width: 400px;
            margin-left: 50px;
            padding-left: 20px;
            line-height: 20px;
        }

        .todoList li {
            display: flex;
            height: 50px;
            line-height: 50px;
            margin: 0.5rem 0;
            padding: 0 0.9rem;
            background: white;
            border-radius: 5px;
        }

    </style>
{% endblock %}

{% block content %}
    <div id='app'>

        <h1>My Todo App !</h1>
        <strong>서로 할 일이나 의견을 공유해 봅시다.</strong>
        <br>


        {#    csrf 토큰 공격을 방지하는 csrf 토큰을 넣는다. 장고에서 제공해주는 템플릿 태그이다. #}
        <form class="inputBox" action="." method="post"> {% csrf_token %}
            <input class="name" type="text" placeholder="name ..." name="name">
            <input class="item" type="text" placeholder="type anything welcomed ..."
                   name="todo">
            <button class="btn btn-info btn-sm" type="submit">ADD</button>
        </form>

        <ul class="todoList">
            {#        v-for문 대신에, 장고에서 제공하는 템플릿 태그로 바꾼다. #}
            {% for todo in object_list %}
                <li>
                    <span>{{ todo.name }}::: {{ todo.todo }}</span>
                    <span class="removeBtn"><a href="{% url 'todo:delete' todo.id %}">&#x00D7</a></span>
                </li>
            {% endfor %}
        </ul>

    </div>
{% endblock %}

이렇게되면 폼과 리스트가 한 화면에 같이 보여지게 된다. form은 createView가 담당하고 list는 ListView가 담당하는데 이 기능을 종합하는 Mixin이 필요하다.

Templates을 코딩하면서 또 한가지 확인해야 될 사안이 View에서 templates로 넘겨줘야 될 context 변수가 어떤것이 있는지 체크해야한다.

list에서는 object_list라는 변수를 사용한다. view에서 templates로 넘겨주어야 한다.

views.py

파이썬에서는 다중 상속을 받아야할 때 List를 먼저쓸지, Create를 먼저 쓸지 상속받는 순서도 중요하다. 이에 대한 설명은 Docs 참고하면 된다.

공식홈페이지를 봐야되지만, https://ccbv.co.uk/ 이 사이트는 아주 잘 정리된 사이트이기 때문에 알아두면 좋은 사이트이다.

Edit 메뉴에서 CreateView를 클릭한다.

바로 보이는 Hierarchy diagram 버튼을 클릭하면

CreateView에 대한 상속계층들을 볼 수 있는데, 여러가지 믹스인 기능들을 조합해서 CreateView를 만들고 있다.

똑같이 ListView의 다이어그램을 보면

ListView의 상속계층을 볼 수 있다.

이렇게 장고의 클래스형 뷰는 공통적으로 사용되는 단위 기능들을 Mixin 클래스로 만들고나서 이 Mixin 클래스 기능들을 조합을 해서 만드는 방식으로 구성되어있다.

CreateView와 비교해보면 CreateView가 훨씬 복잡한 구성을 이루고 있는데, 그 이유는 form 처리 기능을 다루고 있기 때문이다.

form 처리는 주로 process form view에서 이루어지는데 form 처리는 GET, POST 방식 요청을 한다.

CreateView를 메인으로 두고, ListView는 믹스인으로 두고 처리하는 것이 에러 가능성을 줄여주는 방법이다.

그리고 코딩을 할 때는 (mixin, main) 믹스인 클래스를 먼저 써주고, 메인 클래스를 나중에 써준다.

믹스인(Mixin)의 단어 의미

Main 재료에 추가되는 첨가물을 의미한다.

우리가 지금 필요로하는 기능은 object_list라는 context 변수를 templates에 넘겨주는 기능이다. 이 기능은 ListView의 다이어그램을 살펴보면 MultipleObjectMixin에 들어있다.

ListView의 Methods중 에서는

def get_context_data(self, **kwargs):가 있는데, context 변수를 만들고 그 중에 object_list context변수는 queryset으로, queryset은 self.object_list로 부터 만들고 있는것을 확인할 수 있다. 즉, self.object_list 변수를 미리 준비해두고, get_context_data 메소드만 호출하면 object_list라는 context변수가 templates로 넘어가게 된다.

결국 이러한 기능들이 MultipleObjectMixin에 정의가 돼있다는 것을 알 수 있다.

정리 :

MultipleObjectMixin을 먼저 상속받고 CreateView를 상속받는다.

대부분 기능은 CreateView에서 처리하고, 없는 기능인 object_list라는 context 변수를 templates로 넘겨주는 기능은 MultipleObjectMixin에서 처리한다.

(MultipleObjectMixin, CreateView)

views 소스에 반영한다.

views.py

class TodoMOMCV(MultipleObjectMixin, CreateView):
    model = Todo
    fields = '__all__'
    template_name = 'todo/todo_form_list.html'
    success_url = reverse_lazy('todo:mixin')

    def get(self, request, *args, **kwargs):
        self.object_list = self.get_queryset() 
        return super().get(request, *args, **kwargs)
    
    def post(self, request, *args, **kwargs):
        self.object_list = self.get_queryset()
        return super().post(request, *args, **kwargs)

base.html에 메뉴바에 링크하나를 추가한다.

<li class="nav-item mx-1 btn btn-primary">
                <a class="nav-link text-white" href="{% url 'todo:mixin' %}">DjangoMixin</a></li>

테스트해서 확인해본다.

DjangoMixin 메뉴 url을 들어가서 테스트 해보면 작성을 하고나면 현재 페이지에서 처리가 이루어지고 있지만,

삭제 처리 후 리다이렉션이 list url로 가기 때문에 mixin으로 수정한다.

urls.py

path('<int:pk>/delete2/', views.TodoDelV2.as_view(), name='delete2'), # 추가

todo_form_list.html 에서 a 태그의 removeBtn 링크 역할을 해주는 곳에 url을 todo:delete2로 수정한다.

<span class="removeBtn"><a href="{% url 'todo:delete2' todo.id %}">&#x00D7</a></span> <!-- 수정 -->

views.py에 코드 추가 작업을 한다.

class TodoDelV2(DeleteView):
    model = Todo
    template_name = 'todo/todo_confirm_delete.html'
    success_url = reverse_lazy('todo:mixin')
추가사항

삭제 처리를 하면서 GET 요청이 왔을 때 폼을 보여주는 대신에 Delete처리를 하도록 변경을 해버린다.

views.py delete 처리하도록 Override get메소드 추가하고, template_name 속성은 필요없기 때문에 지운다.

class TodoDelV2(DeleteView):
    model = Todo
    # template_name = 'todo/todo_confirm_delete.html'
    success_url = reverse_lazy('todo:mixin')

    def get(self, request, *args, **kwargs):
        return self.delete(request, *args, **kwargs)

bootstrap 팝업창 만들기

todo_form_list.html에서 삭제버튼을 누르면 팝업창이 뜨도록 만들것이다.

Bootstrap 공식 홈페이지에서 모달 컴포넌트 메뉴를 찾는다.

https://getbootstrap.com/docs/5.1/components/modal/

Modal은 팝업을 의미한다. Alert과는 다른 스타일 기능을 하며, 프론트의 기능을 담당하기도 한다.

먼저 todo_form_list.html에서 작업을 진행하는데

컨텐츠 블럭과 스크립트 블럭을 작업한다.

{% extends 'base.html' %}
{% block title %}
    todo_form.html
{% endblock %}
{% block extra-style %}
    <style>
        body {
            text-align: center;
            background-color: #ddd;
        }

        .inputBox {
            margin: auto;
            width: 70%;
            background: white;
            height: 50px;
            border-radius: 50px;
            line-height: 50px;
        }

        .inputBox .name {
            border-style: none;
            border-bottom: 1px solid #ddd;
            width: 90px;
            padding-left: 20px;
            line-height: 20px;
        }

        .inputBox .item {
            border-style: none;
            border-bottom: 1px solid #ddd;
            width: 400px;
            margin-left: 50px;
            padding-left: 20px;
            line-height: 20px;
        }

        .todoList li {
            display: flex;
            height: 50px;
            line-height: 50px;
            margin: 0.5rem 0;
            padding: 0 0.9rem;
            background: white;
            border-radius: 5px;
        }

        .removeBtn {
            margin-left: auto;
            font-size: 20px;
        }

        .removeBtn:hover {
            color: #d24444;
        }
        .modal-footer a {
            color: #ffffff;
        }
    </style>
{% endblock %}

{% block content %}
    <div id='app'>

        <h1>My Todo App !</h1>
        <strong>서로 할 일이나 의견을 공유해 봅시다.</strong>
        <br>


        {#    csrf 토큰 공격을 방지하는 csrf 토큰을 넣는다. 장고에서 제공해주는 템플릿 태그이다. #}
        <form class="inputBox" action="." method="post"> {% csrf_token %}
            <input class="name" type="text" placeholder="name ..." name="name">
            <input class="item" type="text" placeholder="type anything welcomed ..."
                   name="todo">
            <button class="btn btn-info btn-sm" type="submit">ADD</button>
        </form>

        <ul class="todoList">
            {#        v-for문 대신에, 장고에서 제공하는 템플릿 태그로 바꾼다. #}
            {% for todo in object_list %}
                <li>
                    <span>{{ todo.name }}::: {{ todo.todo }}</span>
                    <span class="removeBtn" data-toggle="modal" data-target="#myModal"
                          data-id="{{ todo.id }}" data-name="{{ todo.name }}" data-todo="{{ todo.todo }}">&#x00D7</span>
                </li>
            {% endfor %}
        </ul>

        <!-- Modal -->
        <div class="modal" id="myModal" tabindex="-1" role="dialog">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title" id="exampleModalLabel">Are you sure to delete ?</h5>
                    </div>
                    <div class="modal-body">
                        body-text
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">Cancel</button>
                        <button type="button" class="btn btn-danger btn-sm">
                            <a href="">Delete</a></button>
                    </div>
                </div>
            </div>
        </div>

    </div>
{% endblock %}
{% block extra-script %}
    <script>
        $('#myModal').on('show.bs.modal', function (event) {
            let button = $(event.relatedTarget)
            let id = button.data('id')
            let name = button.data('name')
            let todo = button.data('todo')

            let modal = $(this)
            modal.find('.modal-body').text(name + '::: ' + todo)
            {# modal-body의 텍스트로 채워질 부분이다. #}
            modal.find('.modal-footer a').attr('href', '/todo/' + id + '/delete2/')
            {#    attr 속성의 url은 a 태크의 링크로 채워진다. #}
        })
    </script>
{% endblock %}

GitHub Source

👉🏻깃허브 소스

profile
Full-stack Engineer. email - corner3499@kakao.com,

0개의 댓글