[Django]ajax(POST)를 활용한 javascript와 views.py 데이터 통신 파이차트만들기(feat. csv파일 업로드)

2021. 1. 6. 20:10python 전문가로/Django

반응형

안녕하세요. 

오늘은 ajax 함수를 이용하여 Django에서 javascript와 views.py의 데이터 통신 결과물로 파이차트를 만든 것을 공유하려고 합니다! 

먼저 결과물을 보여드리면,

동영상 촬영이다 보니, 파일선택 button이 녹화되지 않았네요ㅜㅜ 

아래 첨부된 이미지 파일처럼 '파일선택' 버튼을 누르면 파일 선택할 수 있는 화면이 띄워집니다. 

업로드하는 csv 파일 내용은 아래와 같습니다. 

여기서 Column인 country, currency, sector, amount_issued 4개의 컬럼만 사용해서 파이차트를 만들거에요.

country(국가)별, currency(통화)별, sector(색터)별 amount_issued를 더해서 파이차트로 시각화를 하는 작업을 했습니다. 

먼저, 저는 canvasjs라는 차트 오픈소스를 활용했어요~ 자세한 내용은 아래 내용을 참조해주세요!

shiningyouandme.tistory.com/22

 

[Django]Canvasjs으로 멀티 라인차트(Line Chart) 보여주기(feat.무료)

위와같이 Django 웹페이지에서 차트를 띄우는 방법을 공유드리고자 합니다! 차트는 오픈소스인 canvasjs를 사용했습니다. canvasjs.com/ ◀canvasjs 홈페이지에 들어가면 다양한 Demo가 있으니 한번 확인해

shiningyouandme.tistory.com

 

 

 

먼저 views.py코드 입니다. 

from board.models import *
from django.http import HttpResponse, HttpResponseRedirect
import pandas as pd
import matplotlib.pyplot as plt
import json
from django.core.serializers.json import DjangoJSONEncoder
from datetime import datetime


@csrf_exempt
def ajax_method(request):
#POST 방식은 GET 방식과 달리, 데이터 전송을 기반으로 한 요청
    if request.method == "POST":

#javascrpit에서 데이터를 가져와 DataFrame으로 변형(데이터분석을 위해)
    uploaded = request.POST.get('upload_data', None)
    uploaded_list = json.loads(uploaded)
    uploaded_pd = pd.DataFrame.from_dict(uploaded_list)
    uploaded_pd = uploaded_pd.apply(pd.to_numeric, errors='ignore')


#COUNTRY별 amount_issued를 더해서 amount_country칼럼에 넣기
    port_country = pd.DataFrame(columns=("country", "amount_country"))
    port_country['country'] = uploaded_pd['country'].drop_duplicates()
    port_country = port_country.reset_index(drop=True)

    for i in range(len(port_country)):
        port_country.loc[i, 'amount_country'] = (
            uploaded_pd.loc[uploaded_pd['country'] == list(port_country['country'])[i], 'amount_issued'].sum())

    port_country_json = port_country.to_json(orient='records')

#CURRENCY별 amount_issued를 더해서 amount_country칼럼에 넣기
    port_currency = pd.DataFrame(columns=("currency", "amount_currency"))
    port_currency['currency'] = uploaded_pd['currency'].drop_duplicates()
    port_currency = port_currency.reset_index(drop=True)

    for i in range(len(port_currency)):
        port_currency.loc[i, 'amount_currency'] = (
            uploaded_pd.loc[uploaded_pd['currency'] == list(port_currency['currency'])[i], 'amount_issued'].sum())

    port_currency_json = port_currency.to_json(orient='records')

#SECTOR별 amount_issued를 더해서 amount_country칼럼에 넣기
    port_sector = pd.DataFrame(columns=("sector", "amount_sector"))
    port_sector['sector'] = uploaded_pd['sector'].drop_duplicates()
    port_sector = port_sector.reset_index(drop=True)
    for i in range(len(port_sector)):
        port_sector.loc[i, 'amount_sector'] = (
            uploaded_pd.loc[uploaded_pd['sector'] == list(port_sector['sector'])[i], 'amount_issued'].sum())

    port_sector_json = port_sector.to_json(orient='records')

#COUNTRY, CURRENCY, SECTOR를 concat하여 모아 json형태로 변형한다. 
    port_total = pd.concat([port_country, port_currency, port_sector], axis=1)
    port_total.fillna("")
    port_total_json = port_total.to_json(orient='records')

    context = {
        'port_total_json' : port_total_json,
    }
#javascript에 json형태로 데이터를 보낸다.
    return HttpResponse(port_total_json)


def index(request):
    movevixlibor = Movevixlibor.objects.values()
    movevixlibor_json = json.dumps(list(movevixlibor), cls=DjangoJSONEncoder)
    
    context = {
    #html로 보내는 필드명을 ''(따옴표)안에 넣어주면 됩니다.
    'movevixlibor_json': movevixlibor_json,
    }
 
 
##html로 값을 보내는 부분    
    return render(request, 'board/index.html', context)

여기서 함수명이 2개가 있죠? ajax_method()와 index()가 있어요.

index()함수는 html로 데이터를 보내는 함수이고

ajax_method()는 javascript로 데이터를 보내는 함수 입니다.

if request.method == "POST": uploaded = request.POST.get('upload_data', None)

POST방식으로 데이터를 통신하는데 데이터명이 'upload_data'라고 적혀있는거 보이시죠? 

이 'upload_data'명을 가진 데이터는 어디에서 받을까요?? 바로 javascrpit로 이동해볼게요!! 

 

javascript 코드입니다. 

var chart1;

//파일 업로드 버튼 누르면 handleFileSelect 함수 호출 
$(function init(){
    document.getElementById('fileUpload').addEventListener('change', handleFileSelect, false);
});


function handleFileSelect(event){
    const reader = new FileReader()
    //handleFileLoad 함수 호출
    reader.onload = handleFileLoad;
    reader.readAsText(event.target.files[0])
}


//csv 데이터 형식을 json으로 변경 
function csvJSON(csv){
    var lines = csv.split("\n");
    var result = [];
    var headers=lines[0].split(",");
    for(var i=1;i<lines.length;i++){
        var obj = {};
        var currentline=lines[i].split(",");
        for(var j=0;j<headers.length;j++){
            obj[headers[j]] = currentline[j];
        }
        result.push(obj);
    }
    return JSON.stringify(result); //JSON
}




function handleFileLoad(event){

    let uploaded = event.target.result;
    //csv형식 data를 json으로 변형
    let uploaded_data = csvJSON(uploaded)


//Show Chart 버튼클릭시 차트 보여주는 함수
    $('#cellOneExp').on('shown.bs.collapse', function () {
        chartOne();
        $('#cellOneExp').off(); // to remove the binded event after chart initialization
        renderChartOneEvent();
    });

    function renderChartOneEvent() {
        $('#cellOneExp').on('shown.bs.collapse', function () {
            chart1.render();
        });
    }


//view.py에 data 보내기
    $.ajax({
        //요청이 전송될 URL 주소
        url: 'ajax_method/',
        type: "POST",
        dataType: "JSON",
        data: {
            'upload_data': uploaded_data,
            csrfmiddlewaretoken: '{{ csrf_token }}'
        },
        headers: { "X-CSRFToken": "{{ csrf_token }}" },
        
        success : function(data) {
          
            const port_data = JSON.stringify(data);
            var port_data_json = JSON.parse(port_data);


        //list에 데이터 담기 - 국가별 
            var dataPoint_port_country = [];
            for (var i = 0; i < port_data_json.length-1; i++) {
                if(port_data_json[i].country != null){
                    dataPoint_port_country.push({
                        name: port_data_json[i].country,
                        y: port_data_json[i].amount_country,
                        showInLegend: true,
                    });
                }else{
                    i++;
                }
            }


            var dataPoint_port_currency = [];
            for (var i = 0; i < port_data_json.length-1; i++) {
                if(port_data_json[i].currency != null){
                    dataPoint_port_currency.push({
                        name: port_data_json[i].currency,
                        y: port_data_json[i].amount_currency,
                        showInLegend: true,
                    });
                }else{
                    i++;
                }

            }


            var dataPoint_port_sector = [];
            for (var i = 0; i < port_data_json.length-1; i++) {
                if(port_data_json[i].sector != null){
                    dataPoint_port_sector.push({
                        name: port_data_json[i].sector,
                        y: port_data_json[i].amount_sector,
                                 showInLegend: true,
                    });
                }else{
                    i++;
                }
            }


//국가별 파이 차트 만들기
            chart1 = new CanvasJS.Chart("port_country_chart",{
                animationEnabled: true,
                backgroundColor: "transparent",
                theme: "dark2",
                height: 230,
                title :{
                    fontsize:3,
                    text: "country"
                },
                legend:{
                    cursor: "pointer",
                    itemclick: explodePie,
                    verticalAlign: "bottom"
                },
                toolTip:{
                    enabled: true,       //disable here
                    animationEnabled: true //disable here
                },
                yValueFormatString: "#,##0.##",
                data: [{
                    type: "pie",
                    showInLegend: true,
                    dataPoints: dataPoint_port_country
                }]
            });
            chart1.render();

//통화별 파이 차트 만들기
            chart2 = new CanvasJS.Chart("port_currency_chart",{
                animationEnabled: true,
                backgroundColor: "transparent",
                height: 230,
                theme: "dark2",
                title :{
                    fontsize:3,
                    text: "currency"
                },
                legend:{
                    cursor: "pointer",
                    itemclick: explodePie,
                    verticalAlign: "bottom"
                },
                toolTip:{
                    enabled: true,       //disable here
                    animationEnabled: true //disable here
                },
                yValueFormatString: "#,##0.##",
                data: [{
                    type: "pie",
                    showInLegend: true,
                    dataPoints: dataPoint_port_currency
                }]
            });
            chart2.render();

//섹터별 파이 차트 만들기
            chart3 = new CanvasJS.Chart("port_sector_chart",{
                animationEnabled: true,
                backgroundColor:"transparent",
                height: 230,
                theme:"dark2",
                title :{
                    fontsize:3,
                    text: "sector"
                },
                legend:{
                    cursor: "pointer",
                    itemclick: explodePie,
                    verticalAlign: "bottom"
                },
                toolTip:{
                    enabled: true,       //disable here
                    animationEnabled: true //disable here
                },
                yValueFormatString: "#,##0.##",
                data: [{
                    type: "pie",
                    showInLegend: true,
                    dataPoints: dataPoint_port_sector
                }]
            });
            chart3.render();


            function explodePie (e) {
                if(typeof (e.dataSeries.dataPoints[e.dataPointIndex].exploded) === "undefined" || !e.dataSeries.dataPoints[e.dataPointIndex].exploded) {
                    e.dataSeries.dataPoints[e.dataPointIndex].exploded = true;
                } else {
                    e.dataSeries.dataPoints[e.dataPointIndex].exploded = false;
                }
                e.chart1.render();
                e.chart2.render();
                e.chart3.render();
            }



        },
        error : function(xhr, textStatus, thrownError) {
            alert("Could not send URL to Django. Error: " + xhr.status + ": " + xhr.responseText);
        }
    });
    
}

1) 먼저 javascript에서 fileUpload 버튼을 누르면 가장 먼저 init()함수에서 handleFileSelect()함수를 호출합니다. 

2) handleFileSelect()함수에서는 데이터를 읽어서 handleFileLoad()함수에 넘겨줍니다. 

3) handleFileLoad()함수에서는 많은 일들을 처리하는데요..! 

① csvJSON()를 활용하여 CSV 데이터를 JSON으로 변경한다.

② $.ajax()를 활용하여 views.py로 데이터를 보낸다. ajax의 기본 형태는 아래와 같아요!

veiws.py에 데이터를 보낼때 url주소로 'ajax_method/'를 적는데요! 

'ajax_method/' 이 URL 어디서 많이 봤죠!? 글 바로 위쪽에서 설명드린 views.py의 함수명입니다!

$.ajax({
      //요청이 전송될 URL 주소
      url: 'ajax_method/',
      type: "POST",
      dataType: "JSON",
      data: {
          'upload_data': uploaded_data,
          csrfmiddlewaretoken: '{{ csrf_token }}'
      },
      headers: { "X-CSRFToken": "{{ csrf_token }}" },
      
      success : function(data) {
        
          const port_data = JSON.stringify(data);
          var port_data_json = JSON.parse(port_data);
},
      error : function(xhr, textStatus, thrownError) {
          alert("Could not send URL to Django. Error: " + xhr.status + ": " + xhr.responseText);
      }
  });

 이 ajax는 views.py의 ajax_method함수로 데이터를 통신하게 되는데요!

data:{ 'upload_data': uploaded_data, csrfmiddlewaretoken: '{{ csrf_token }}' }  

우리가 views.py에 보낼때 데이터명을 'upload_data'로 하여 보낸다는 뜻입니다. 

csrfmiddlewaretoken'{{ csrf_token }}' 이 코드는 javascript로 데이터를 통신할때 html등과의 요청위조를 방지하기 위해서 토큰을 담아서 보내야 된다고 하더라구요! 참고하세요~

views.py로 데이터를 보내고, views.py에서 데이터분석한 결과를 다시 javascrpit로 보내기가 성공한다면

success : function(data) {} 여기 함수로 들어가게 되요!

그런데 만약에 성공하지 못한다면, 아래와 같은 error함수로 들어가게 됩니다. 

error : function(xhr, textStatus, thrownError) {
alert("Could not send URL to Django. Error: " + xhr.status + ": " + xhr.responseText);
}

success function(data) 함수 안에서 차트를 불러온다. 코드를 보시면 chart1, chart2, chart3가 render()가 된것을 볼 수 이습니다.

chart1은 html의 port_country_chart를 가리키고 chart2는 html의 port_currency_chart를, chart3는 html의 port_sector_chart를 가리킵니다. 

그럼 바로 html로 가볼까요?

 

index.html입니다. 

{% load static %}
<!DOCTYPE HTML>

<html>
<head>
    <meta charset="UTF-8">
	<title></title>
 	<meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href='{% static "board/bootstrap/bootstrap.css" %}' media="screen">
    <link rel="stylesheet" href='{% static "board/_assets/css/custom.min.css" %}'>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <!-canvasjs 플러그인--->
    <script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>

</head>
<body>
	<h7 style="color:#abb6c2">엑셀(포트폴리오)을 업로드 하거나 [Credit List] Tab에서 관심있는 채권을 선택하여 국가/통화/산업 등 비중을 시각화하여 분석할 수 있는 화면입니다.</h7>
                       <br/>
                           <!--upload에서 온 portfolio-->
                           <div style="width: 530px;">
                               <div onload="init()">
                                   <button class="btn btn-warning" type="button" data-toggle="collapse" data-target="#cellOneExp" aria-expanded="false" aria-controls="cellOneExp" style="margin-left: 4px; margin-right: 2px; font-size:13px; font-family: 'KBFG Text Medium'; padding:2px;">
                                       Show Chart(upload)
                                   </button>
                                   <!--file upload하는 부분-->
                                   <input type="file" id="fileUpload"/>
                               </div>
                               <div>
                                   <tr>
                                       <td colspan="100%" class="hiddenRow">
                                           <div class="collapse" id="cellOneExp">
                                           </div>
                                       </td>
                                       <!--국가별, 통화별, 섹터별 PIE차트-->
                                       <div class="row">
                                           <div id="port_country_chart" style="height: 150px; width: 30%; margin-left: 3px"></div>
                                           <div id="port_currency_chart" style="height: 150px;width: 30%; margin-left: 20px"></div>
                                           <div id="port_sector_chart" style="height: 150px;width: 30%; margin-left:25px"></div>
                                       </div>
                                   </tr>
                               </div>
                           </div>

<!--javascript 경로-->
<script type="text/javascript" src='{% static "board/table_to_charts/upload_file_chart.js" %}'></script>
<script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
</body>
</html>

 

ajax로 views.py와 javascript간의 데이터 통신 어렵지 않아요~~!

궁금한점 있으면 댓글로 달아주세요~ 

그럼 즐거운 개발되시길 바랍니다!! 좋은하루 되세요 :)