취미삼아 배우는 프로그래밍

Django로 개인 업무(ERP) 홈페이지 만들기-3 본문

파이썬(장고)

Django로 개인 업무(ERP) 홈페이지 만들기-3

Nadure 2020. 11. 10. 23:27

진행상황

뭐가 많이 추가됐다.

 

 

pdf 뷰

 저번에 만들었었던 pdf_view를 iframe에 담기로 했다. 그치만, iframe에는 same_origin 등의 클릭재킹 공격에 대한 공격대비가 들어있기 때문에 이에대한 방비를 풀어줘야한다.

@xframe_options_exempt
def pdf_view(request, id):
    file_obj = UploadedFiles.objects.get(id=id)
    file_path = os.path.join(settings.MEDIA_ROOT, str(file_obj.uploaded_file))
    
    try:
        return FileResponse(open(file_path, 'rb'), content_type='application/pdf')
    except FileNotFoundError:
        raise Http404()


@xframe_options_sameorigin
def pdf_view_test(request):
    try:
        return FileResponse(open('test.pdf', 'rb'), content_type='application/pdf')
    except FileNotFoundError:
        raise Http404()
        
# xframe_options_exempt : 무차별 허락
# xframe_options_sameorigin : sameorigin일시 허락

 

 그리고 materialize를 깔끔하게 쓰기 위해서 modal페이지를 많이 사용했다.

 

 이와 같은 형태인데, 여러 개를 사용했다.

<div class="container">
    <div class="row">
        <div class="col s12">
            <h4 class="header center-align">미분류 도면</h2>

            <div class="row">

                {% for component in page_obj %}
                <div class="card horizontal col s12 m6">

                    <div class="card-stacked">
                    <div class="card-content">
                        <p>{{ component.extract_filename }}</p>
                    </div>
                    <div class="card-action center-align">

                        <button 
                            data-target="pdf_view_modal" 
                            class="btn modal-trigger"
                            onclick="load_pdf(this)"
                            data-id="{{ component.id }}">
                            상세
                        </button>

                        <a  
                            class="btn modal-trigger"
                            href="{% url 'pdf_delete' id=component.id %}">
                            삭제
                        </a>

                    </div>
                    </div>
                </div>
                {% empty %}
                <h3>아무 파일이 없습니다.</h3>
                {% endfor %}
            </div>
        </div>
    </div>


    
    {% include "modals/search_location_modal.html" %}
    {% include "modals/pdf_view_modal.html" %}
    {% include "modals/create_order_modal.html" %}
</div>

 

 아무래도 모달페이지가 너무 길면, 코드를 보는데 너무 힘들기 때문에 조금 만들어졌다 싶으면 바로 옮기면서 작업했다.

스크립트

더보기

그리고 생각보다 자바스크립트를 많이 사용했다.

<script>
    // console.log('%c Oh my heavens! ', 'background: #222; color: #bada55; font-size:40px;')
    document.addEventListener('DOMContentLoaded', function() {
        var options = {};
        var elems = document.querySelectorAll('.modal');
        var instances = M.Modal.init(elems, options);
    });

    function load_pdf(e) {
        var base_url = "{% url 'pdf_view' id=0 %}";
        var url_arr = base_url.split('/');

        url_arr[url_arr.length-2] = e.dataset.id;
        var url = url_arr.join('/');
        
        modal_pdf_iframe.dataset.pdf_id = e.dataset.id;

        modal_pdf_iframe.src = url;
        modal_header.innerText = e.parentElement.parentElement.childNodes[1].innerText;

    }


    function delete_pdf_file(id) {
        // 삭제시 메세지 후 삭제
    }

    function calculate_sum(){
        // 값 입력 시 자동 합계 산출
    }

    function createCell(cell, i) {
        if(i==0) {
            cell.outerHTML = "<th contenteditable='true'>세대</th>";
            return
        }
        var txt = document.createTextNode("");
        cell.setAttribute('contenteditable', true);
        cell.appendChild(txt);
    }


    function delete_order_table_column() {
        var i, j,
        lastCol = order_table.rows[0].cells.length - 2;
        for (i = 0; i < order_table.rows.length; i++) {
            order_table.rows[i].deleteCell(lastCol);
        }
    }

    function append_order_table_column(){
        var i;

        for (i = 0; i < order_table.rows.length; i++) {
            console.log(order_table.rows[i]);
            createCell(order_table.rows[i].insertCell(order_table.rows[i].cells.length-1), i);
        }
    }

    function delete_order_table_row() {
        var len = document.getElementsByClassName("order_row").length;
        var last_elem = document.getElementsByClassName("order_row")[len-1];
        if(len != 1){
            last_elem.remove();
        }
    }

    function append_order_table_row() {
        var cln = document.getElementsByClassName("order_row")[0].cloneNode(true);
        var new_elem = order_table.appendChild(cln);

        // init values
        var i;
        var tds = new_elem.getElementsByTagName("td");
        var len = tds.length;
        for(i=0; i<len; i++) {
            if(i==0){
                var input = tds[i].getElementsByTagName("input");
                input[0].value = "";
                input[1].value = 2400;
                input[2].value = "";
                continue
            }

            else if(i==len-1) {
                continue
            }

            else {
                tds[i].innerText ="";
            }
        }

    }

    function init_wall_ordertable() {
        $.ajax({
            url:"{% url 'ajax_ordering_table_component' %}",
            method:"POST",
            type:"json",
            data:null,
        }).done(function(data){
            location_searcher_btn.outerHTML = '<button data-target="location_searcher" class="btn waves-effect waves-green modal-trigger" id="location_searcher_btn">현장검색</button>';
            order_table.outerHTML = data;
        }).fail(function(xhr,data){
            console.log(xhr, data);
            alert('초기화 실패. 새로고침 해주세요.');
        })

    }

    function gather_data() {
        // 데이터 취합
        /*
        datastructure:
        {
            'fk_id':1,
            'pages':[List],
            'gens':[List],
            0:[[10,20,30,0,0,0,22], {
                'filar':200,
                'panel':2400,
                'rocker':50,
            }]
        }
        */

        var i, j, data, temp;
        var cell_length = order_table.rows[0].cells.length;
        
        if(location_searcher_btn.getAttribute("data-id") === null) {
            toast_msg('현장 선택 안됨.');
            return
        }

        var pdf_id = modal_pdf_iframe.dataset.pdf_id;
        var fk_id = location_searcher_btn.dataset.id;

        data = {};
        data['pdf_id'] = pdf_id;
        data['fk_id'] = fk_id;
        data['pages'] = [];
        data['gens'] = [];
        for (i = 0; i < order_table.rows.length; i++) {
            temp = [{}, []];

            for (j=0; j < cell_length-1; j++) {
                var cell = order_table.rows[i].cells[j]; 
                var txt = cell.textContent;
                if(i == 0) {
                    if(j==0 || j==cell_length-1){
                        continue
                    }
                    if(j==0){
                        // blah blah .. 필라데이터 입력
                    }
                    data['gens'].push(txt);
                }
                else if(j==0) {
                    var panel = cell.getElementsByTagName("input")
                    temp[0]['filar'] = panel[0].value || 0
                    temp[0]['panel'] = panel[1].value || 0
                    temp[0]['rocker'] = panel[2].value ||0
                }
                else if(txt == "") {
                    temp[1].push(0);
                }
                else {
                    temp[1].push(txt);
                }
                if( i!=0 ) {
                    data[i] = temp;
                }
                
            }
        }
        
        return data
    }

    

    function ajax_data_send() {
        // ajax 요청 보냄

        if (confirm('저장하시겠습니까?')) {
            // Save it!
        } else {
            // Do nothing!
            console.log('Not Save.');
            return
        }
        var send_data = gather_data();
        if(send_data === undefined){
            return
        }
        $.ajax({
            url:"{% url 'ajax_standardwall_register' %}",
            method:"POST",
            type:"json",
            data:JSON.stringify(send_data),
        }).done(function(data){
            // console.log('success:', data);
            init_wall_ordertable(); // 오더 적는 곳 초기화
            ajax_search_location(); // 현장검색도 초기화
            
            alert('저장완료');
        }).fail(function(xhr,data){
            console.log(xhr, data);
            alert('저장 실패. 새로고침 해주세요.');
        })
    }

    function ajax_search_location() {
        var send_data = {
            'company_name':search_company_name.value,
            'location_name':search_location_name.value,
            'distrcit_name':search_district_name.value,
        };
        $.ajax({
            url:"{% url 'ajax_search_location_data_response' %}",
            method:"POST",
            type:"json",
            data:JSON.stringify(send_data),
        }).done(function(data){
            result_for_location.outerHTML = data;
        }).fail(function(xhr,data){
            console.log(xhr, data);
            alert('저장 실패. 새로고침 해주세요.');
        })
    }

    function location_selected(e) {
        location_searcher_btn.innerText = e.dataset.string;
        location_searcher_btn.dataset.id = e.dataset.id;
        var elem = location_searcher;
        var instance = M.Modal.getInstance(elem);
        instance.close();
        toast_msg('현장 선택됨');
    }

    /*
        데이터를 전송하는 페이지(HTML), 데이터를 전송받는 페이지(django view)
        HTML - 회원가입, 좋아요 데이터 전송 
        -> 장고측 뷰가 데이터를 받아 모델 처리, 로직 처리
        -> 모델처리가 완료되면 메시지, 혹은 처리 결과 데이터를 다시 HTML로 전송
        -> HTML은 응답받은 데이터를 가지고 화면에 정보 출력

        $.ajax({
            url: "클라이언트가 요청 보낼 서버의 URL 주소",
            method:"POST",          // HTTP 요청 방식(GET, POST)
            type: "json",           // 서버에서 보내줄 데이터의 타입(default값으로 json으로 되어있다.)
            data: {name: "홍길동"}   // HTTP 요청과 함께 서버로 보낼 데이터

        }).done(function(data) {    // HTTP 요청 성공 시, 요청한 데이터가 done() 메소드로 전달

        }).fail(function(xhr, data) {     // HTTP 요청 실패 시, 오류와 상태에 관한 정보가 fail() 메소드로 전달

        }).always(function(xhr, data) {   // HTTP 요청의 성공여부와는 상관없이 언제나 always()메소드 실행

        });
    */
    function toast_msg(msg) {    
        M.toast({html: msg})
    }
</script>

 위에서 보는것 처럼 생각보다 아약스요청을 많이넣었다. 어떻게든 빨리 하고싶어서 이렇게 했다. 근데 생각보다 건들게 없어서 놀랐다.

def wall_ordering_table_response(request):
    template_name = 'wall_ordering.html'
    return HttpResponse(render(request,template_name,{}))

 위처럼 render된 template파일을 HttpResponse로 보낼 때 render된 것을 주게 되면 텍스트로 날아가게 되는데, 자바스크립트를 통해 outerHTML 속성으로 아예 통째로 HTML 자체를 교체해서 시간을 많이 절약했다. 무식한 방법 같지만 생각보다 유지보수가 더 쉬운 느낌이다.

 

그렇게 해서 얼추 윤곽은 나왔다.

 나오진 않았지만, DB에 저장하는 부분까지 모두 완료됐다.(그러고보니 수정, 삭제는 없다. 아..)

 어느정도 잘 진행되고 있는것 같지만, 가장 큰 문제는 순서를 수정하는 문제가 남아있다. 매번 순서를 재조정하는걸로 해야할까.. 근데 이거도 가장 마지막 오더의 40개 ~ 60개 정도가 왔다갔다 하길래 괜찮은 방법 같기도 하다.

 그리고 저기 위에서 오더작성 모달 페이지를 드래그로 아래위 움직였으면 좋겠는데, 이건 조금 나중에 다시 도전해봐야겠다.

 

Comments