반응형
SMALL

오늘의 목표 : 사진 안나오니까 사진 첨부해서 나오게 하기!


1. 초기 상태

jsp로 연결되어 있었던 파일들을 전부 html로 수정하였다.

연결시켰던 상품 등록 input 에 파일을 추가하고, 그걸 DB로 받아오게 한다.

 

2. img 카테고리 만들기

귀찮으니까 쿼리문 안쓰고 그냥 sql에 등록

img 추가

 

3. input 타입 file 추가

파일 DB에 올리는 코드 추가. 

이미 table을 감싸는 form에 encodetype까지 설정 해둠.


  
<form action="add_product" method="post" name="add_product" id="add_product" enctype="multipart/form-data">

△form 태그 action


  
<tr>
<th>* 이미지 업로드</th>
<td><input type="file" name='file' id='file' maxlength="500" style="width:50%;">
<input type="hidden" name='product_ip' value="<%=request.getRemoteAddr()%>"> </td>
</tr>

△원래 코드


  
<tr>
<th>* 이미지 업로드</th>
{% csrf_token %}
<td><input type="file" name='file1' id='file1' maxlength="500" style="width:50%;"></td>
</tr>

바뀐 코드

 

4. add_product에 추가(view.py)

원래 add_product는 product_info에 담아 데이터를 보내는데에 썼다.

그 데이터에 파일도 추가해야하는데, 그 파일의 파라미터를 처리하는 if문이 필요하다. -> 파일 업로드 링크에 저장

DB에는 단순히 파일의 이름만 들어간다.

 


  
@csrf_exempt
def add_product(request):
product_info = (request.POST['product_name'],
request.POST['category_gender_id'], request.POST['category_type_id'],
request.POST['buy_price'], request.POST['sell_price'],
request.POST['product_info'], request.POST['stock'], request.POST['buy_place'])
product_insert(product_info)
return redirect('/shoppingmallPrj/manager')

△원래 코드


  
# 업로드 링크
UPLOAD_DIR = '/home/kosmo_03/PycharmProjects/myapps/shoppingmallPrj/static/upload/'
@csrf_protect
def add_product(request):
if 'file1' in request.FILES:
# 파라미터 처리
file = request.FILES['file1']
file_name = file.name # 첨부파일 이름
# 파일 오픈 - wb모드(binary)
fp = open("%s%s" % (UPLOAD_DIR, file_name), 'wb')
# 파일을 1바이트씩 조금씩 읽어서 저장
for chunk in file.chunks():
fp.write(chunk)
fp.close() # 파일 닫기
else:
file_name = '-'
product_info = (request.POST['product_name'],
request.POST['category_gender_id'], request.POST['category_type_id'],
request.POST['buy_price'], request.POST['sell_price'],
request.POST['product_info'], request.POST['stock'], request.POST['buy_place'], file_name)
product_insert(product_info)
return redirect('/shoppingmallPrj/manager', {'file_name': file_name})

바뀐 코드

 

5. model.py 수정

파일 이름이 컬럼으로 추가되었으므로, insert 데이터를 9개로 늘려준다.

 


  
def product_insert(product_info):
print(product_info)
conn = ora.connect(database)
cursor = conn.cursor()
sql = "insert into product_info values(Product_info_seq.nextVal, :1,:2,:3,:4,:5,:6,:7,:8)"
cursor.execute(sql, product_info)
cursor.close()
conn.commit()
conn.close()

△원래 코드


  
def product_insert(product_info):
print(product_info)
conn = ora.connect(database)
cursor = conn.cursor()
sql = "insert into product_info values(Product_info_seq.nextVal, :1,:2,:3,:4,:5,:6,:7,:8,:9)"
cursor.execute(sql, product_info)
cursor.close()
conn.commit()
conn.close()

바뀐 코드

 

6. 결과

이미지가 잘 들어간다.

이미지가 설정했던 경로에 추가됨

 

7. 상품 세부 내역에 첨부파일 연결

첨부파일 자리에 엑박이 뜬다.

 

파일이 추가되었다. 어차피 DB에는 이름만 저장되어있으므로, 그 이름만 꺼내오면 사진이 꺼내질 것이다.

img 태그로 사진을 불러오자.

 

원래는 jsp 파일이기 때문에  {% for %}를 사용해서 i라는 변수를 출력, 모든 컬럼값들이 차례대로 나오게 했다.

사진까지 추가되었으므로 이미지 파일은 따로 출력하게 하고, 헤드라인도 출력값에 맞게 맞춰준다.


  
<div class="shop__cart__table">
<table>
<thead>
<tr>
<th>상품명</th>
<th>입고가</th>
<th>판매가</th>
<th>매입처</th>
<th>입고</th>
<th>재고</th>
<th>재입고여부</th>
<th>파일이름</th>
<th>파일용량</th>
</tr>
</thead>
<tbody>
<c:forEach items="${product_infoList }" var="product_detail">
{% for p in product_detail%}
<tr>
{% for i in p%}
<td>{{i}}</td>
{% endfor%}
<td>
<c:choose>
<c:when test="${product_info.product_fsize==0}">첨부파일 없음</c:when>
<c:otherwise>
<img src="resources/upload/${product_info.product_fname}" width='50'>
</c:otherwise>
</c:choose>
</td>
<td>${product_info.product_fsize}byte</td>
</tr>
{% endfor %}
</c:forEach>
</table>
</div>

△원래 코드


  
<div class="shop__cart__table">
<table>
<thead>
<tr>
<th>번호</th>
<th>상품명</th>
<th>성별</th>
<th>옷타입</th>
<th>입고가</th>
<th>판매가</th>
<th>입고</th>
<th>재고</th>
<th>매입처</th>
<th>파일이름</th>
<th>이미지</th>
</tr>
</thead>
{% for p in product_detail%}
<tr>
{% for i in p%}
<td>{{i}}</td>
{% endfor%}
<!-- 이미지파일 출력 -->
<td><img src="{{baseUrl}}/upload/{{p.9}}" width='50'></td>
</tr>
{% endfor %}
</table>
</div>

바뀐 코드

사진이 잘 나온다

8. 쇼핑하기 기능에서 사진 띄우기

사진이 나오게 코드를 수정해주자.

모델에서 img 파일을 불러온다고 하지 않았기 때문에 img를 추가해주어야한다.


  
def getProduct():
conn = ora.connect(database)
cursor = conn.cursor()
sql = "select product_id, product_name, sell_price from product_info order by product_id"
cursor.execute(sql)
result = cursor.fetchall()
cursor.close()
conn.close()
return result

△원래 코드


  
def getProduct():
conn = ora.connect(database)
cursor = conn.cursor()
sql = "select product_id, product_name, sell_price, img from product_info order by product_id"
cursor.execute(sql)
result = cursor.fetchall()
cursor.close()
conn.close()
return result

바뀐 코드

 

그 후 html로 들어가 4번째 순서에 있는 img를 맞게 불러와준다.


  
<div class="product__item">
<div class="product__item__pic set-bg">
<div class="label new">New</div>
<ul class="product__hover">
<!-- 추가 -->
<div><img src="{{baseUrl}}/upload/{{i.3}}" width='500'></div>
<!-- 추가 -->
<li><a href="{{baseUrl}}/upload/{{i.3}}" class="image-popup"><span class="arrow_expand"></span></a></li>
<li><a href="product_details?product_id={{i.0}}"><span class="icon_heart_alt"></span></a></li>
<li><a href="product_details?product_id={{i.0}}"><span class="icon_bag_alt" ></span></a></li>
</ul>
</div>

사진이 잘 나온다.

 

9. 상세보기 페이지에서 이미지 링크 수정

위처럼 모델에 이미지를 추가해주어야한다.


  
def getProduct_details(product_id):
conn = ora.connect(database)
cursor = conn.cursor()
sql = "select product_name, product_info, sell_price, img from product_info where product_id=:product_id"
cursor.execute(sql, product_id=product_id)
result = cursor.fetchone()
print(result)
cursor.close()
conn.close()
return result

spring 기준으로 만들었던 이미지 링크를 경로에 맞게 수정해준다.


  
<img alt="상세보기" src="resources/upload/{{res.0}}" width='80%'>

△원래 코드


  
<img alt="상세보기" src="{{baseUrl}}/upload/{{res.3}}" width='80%'>

바뀐 코드

 

결과

반응형
LIST
반응형
SMALL

스키장에서 물건을 주웠다. 그 물건을 분실물센터에 올려야하는데, 이미지를 올려야 한다. 어떤걸 개발해야할까?

 

1) 분실물 카테고리

2) 클라이언트가 사용할 앱

3) 올리는 순간 위치도 찍도록

4) 기타 등등

 

가장 중요한 것은 물건을 찾거나 찾아줄 때를 위한 알바생이 필요하다.

다행히 우리에게는 이중분류 또는 다중분류로 불리는 알고리즘 친구가 있다.

이번엔 분류 알고리즘을 써서 훈련 -> 머신러닝까지는 안하고, 준비된 파일을 이용해서 분류 앱만 만들어 보도록 하자.

 

준비된 파일에는 이미지 분류를 위한 h5 파일과 json 파일이 있다.

 

▼파일 받기

 


[ fileup  웹 페이지 기능 구현하기 ]


#자세한 세팅은 앞에 포스팅 참조

1. fileup 앱 만들기

늘 하던대로 fileup > templates > fileup > upform1.html 파일 생성


  
python manage.py startapp fileup

2. upform1.html 파일 만들기

3. 뷰 추가(view.py)

4. urls.py 만들고 내용 추가

5. config/setting 추가

6. config/urls 추가

7. 서버 구동 및 upform1.html 파일 열기

 

기본 세팅 끝!


본격적으로 첫 페이지의 파일 업로드 폼을 만들러 가자.


8. catdog 테이블 생성

개나 고양이의 별명(aname), 작성자, 카테로리, 데이터를 추가하기 위한 (cont clob) 등등으로 이루어져 있다.

▼clob란?

더보기

CLOB 이란?

사이즈가 큰 데이터를 외부 파일로 저장하기 위한 데이터 타입이다.

 

  • 문자열 데이터를 DB 외부에 저장하기 위한 타입이다.
  • CLOB 데이터의 최대 길이는 외부 저장소에서 생성 가능한 파일 크기이다.
  • CLOB 타입은 SQL 문에서 문자열 타입으로 입출력 값을 표현한다. 즉, CHAR(n), VARCHAR(n), NCHAR(n), NCHAR VARYING(n) 타입과 호환된다. 단, 명시적 타입 변환만 허용되며, 데이터 길이가 서로 다른 경우에는 최대 길이가 작은 타입에 맞추어 절삭(truncate)된다.
  • CLOB 타입 값을 문자열 값으로 변환하는 경우, 변환된 데이터는 최대 1GB를 넘을 수 없다. 반대로 문자열을 CLOB 타입으로 변환하는 경우, 변환된 데이터는 CLOB 저장소에서 제공하는 최대 파일 크기를 넘을 수 없다.

즉, 문자형 대용량 파일 저장 하는데 유용하고 가변길이로 잘려서 저장이 된다. 라고 생각하시면 되겠네요 ㅎㅎ 

 

BLOB이란?

바이너리 데이터를 DB 외부에 저장하기 위한 타입이다.

 

  • BLOB 데이터의 최대 길이는 외부 저장소에서 생성 가능한 파일 크기이다.
  • BLOB 타입은 SQL 문에서 비트열 타입으로 입출력 값을 표현한다. 즉, BIT(n), BIT VARYING(n) 타입과 호환되며, 명시적 타입 변환만 허용된다. 데이터 길이가 서로 다른 경우에는 최대 길이가 작은 타입에 맞추어 절삭(truncate)된다.
  • BLOB 타입 값을 바이너리 값으로 변환하는 경우, 변환된 데이터는 최대 1GB를 넘을 수 없다. 반대로 바이너리를 BLOB 타입으로 변환하는 경우, 변환된 데이터는 BLOB 저장소에서 제공하는 최대 파일 크기를 넘을 수 없다. 

즉, 컴퓨터가 인식하는 모든 파일(이진 데이터)을 저장하는 타입이다.

 

대용량 -> 4GB까지 저장이 된다.(long의 2배)

LOB 타입은 데이터를 저장하는게 아니라 DB에 저장된 LOB값의 위치 포인터를 저장하기 때문에 일반적으로 읽어오는 SELECT 구문을 사용하면 위치값만 나오게 된다.

 

출처 : http://www.cubrid.org/ko_manual41/entry/BLOB%7CCLOB



  
create table catndog(
num number constraint shop_num_pk primary key,
aname varchar2(100),
writer varchar2(30),
categories varchar2(10),
cont clob,
img varchar2(50),
reip varchar2(50),
mdate date);
create sequence catndog_seq
increment by 1
start with 1;

 

 

9. unform1.html에 제대로 body 채워주기


  
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>upform1.html</title>
</head>
<body>
<h1>파일 업로드 예제</h1>
<div id="wrap">
<form method="post" id="form1" enctype="multipart/form-data">
{% csrf_token %}
<p>이미지 등록</p>
<p><input type="file" name="file1" id="file1"></p>
<p><input type="button" value="등록" id="writeBtn"></p>
</form>
</div>
</body>
</html>

 

10. 구글 CDN 링크를 하단 script에 입력해주기(3.5버전)

▼CDN이란?

더보기

CDN이란?

CDN(콘텐츠 전송 네트워크)은 서버와 사용자 사이의 물리적 거리를 줄여 웹 페이지 콘텐츠 로드 지연을 최소화하는, 촘촘히 분산된 서버로 이루어진 플랫폼이다.

CDN을 사용하지 않으면 콘텐츠 오리진 서버들은 모든 엔드유저의 요청에 일일이 응답해야 한다. 이는 오리진과 오리진에 막대한 트래픽을 유발하고 이후에도 엄청난 부하를 유발하여 트래픽이 과도하게 증가하거나 부하가 끊임없이 들어오는 경우 오리진에서 장애가 발생할 확률을 높인다.CDN은 오리진을 대신하여 엔드유저와 가까운 물리적 위치 및 네트워크에서 엔드유저 요청에 응답함으로써 콘텐츠 서버의 트래픽 부하를 오프로드하고 웹 경험을 개선하여 콘텐츠 제공업체와 엔드유저 모두에게 막대한 이점을 제공한다.

 

인터넷 트래픽의 절반 이상이 CDN(콘텐츠 전송 네트워크)을 통해 전송된다. CDN의 목표는 웹 페이지에 대한 요청이 이동해야 하는 물리적 거리를 줄여 요청 제출 시간과 장치에 완전히 로딩되는 웹 페이지 간의 지연 시간을 줄이는 것이다.

출처:www.akamai.com/kr/ko/cdn/what-is-a-cdn.jsp


이거 붙여넣기


  
<!--구글 CDN 링크 -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

헤드에 넣었다

 

11. static 아래 images 디렉토리 만들기

이제 파일 업로드 하면 거기에 사진이 들어갈 수 있도록 만들 것.

 

12. upform1 하단에 제이쿼리 쓰자.


  
<script>
$(function(){
$('#writeBtn').click(function(){
$('#form1').attr('action','upload_success');
$('#form1').submit();
});
});
</script>

 

실행 후 F12 개발자 모드 들어가서 요소를 보면(form 부분) 전송이 되는지 확인해 볼 수 있음. -> ????

 

13. 파이어폭스에서 강아지와 고양이 사진을 다운받아 보자.

14. views 가서 정의하자.

만든 것 중에 upload_success를 만들어야함.

파일 업로드 하는 방법이다.

이렇게하면 파일을 업로드 했을 때 위에서 설정한 링크로 들어간다.(이미지 폴더로)


  
# 업로드 링크
UPLOAD_DIR = '/home/kosmo_03/PycharmProjects/myapps/fileup/static/images/'
@csrf_protect
def upload_success(request):
# request.FILES : enctype으로 전소오디어 온 파일 파라미터 객체
# .name 등의 파일의 정보를 받을 수 있다.
if 'file1' in request.FILES:
# 파라미터 처리
file = request.FILES['file1']
file_name = file.name # 첨부파일 이름
# 파일 오픈 - wb모드(binary)
fp = open("%s%s" % (UPLOAD_DIR, file_name), 'wb')
# 파일을 1바이트씩 조금씩 읽어서 저장
for chunk in file.chunks():
fp.write(chunk)
fp.close() # 파일 닫기
else:
file_name = '-'
return render(request, "fileup/success.html", {'file_name': file_name})

15. urls 등록하기


  
path('upload_success',views.upload_success),

16. success.html 파일만 대충 만들어놓고 사진 업로드 해보자

잘 들어갔다

17. success.html 수정하기

등록버튼을 누르면 사진이 나온다.


  
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>success.html</title>
<style>
image { width:300px }
</style>
</head>
{% load static %}
{% static "" as baseUrl %}
<body>
<img src="{{baseUrl}}/images/{{file_name}}">
</body>
</html>

 

18. upform2.html 만들기


  
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>upform2.html</title>
<style>
#wrap { width:350px; margin:auto }
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<h1>파일 업로드 예제</h1>
<div id="wrap">
<form method="post" id="form1" enctype="multipart/form-data">
{% csrf_token %}
<p>강아지/고양이 이미지 등록</p>
<p><input type="file" name="file1" id="file1"></p>
<div>
<img src="" id="targetImg" style="width:300px">
</div>
<p><input type="button" value="등록" id="writeBtn"></p>
</form>
<script>
$(function(){
$('#writeBtn').click(function(){
$('#form1').attr('action','upload_success')
$('#form1').submit()
});//클릭함수
//html5 파일 기능
$('#file1').change(function(){
//업로드 파일 읽기
let fileInfo = document.getElementById("file1").files[0];
console.log(fileInfo)
let reader = new FileReader();
//readAsDataURL()을 통해 파일을 읽어들일 때 onload가 실행
reader.onload=function(){
document.getElementById("targetImg").src = reader.result;
};
if(fileInfo){
//readAsDataURL()을 통해 파일의 URL을 읽어온다.
reader.readAsDataURL(fileInfo);
}
})//chang 함수
})//시작함수
</script>
</div>
</body>
</html>

19. 경로와 views에 main2 등록

urls.py
views.py

main2로 바로 접속하면 잘 된다.

 

20. 설치하자


  
pip install pillow
pip install tensorflow

텐서플로우 설치 못해서 아무것도 못했던게 엊그제 같은데ㅠㅠ

 

views.py에 import 해준다.


  
from PIL import Image
import tensorflow as tf
import os,glob
import numpy as np
from tensorflow.python.keras.models import load_model, model_from_json
from tensorflow.python.keras.preprocessing.image import load_img

 

21. success2.html을 만든다.

내용 일단 그냥 복붙해놓고.

 

22. views.py에 success2 추가

파일을 받아서 모델을 열어서 모델가지고 전에 올렸던 강아지나 고양이 사진을 가지고 학습해서 y_pridict(=결과값)을 출력해서 실험해보자. 예측을 하는데 도와주는 것이지, 예측에는 100프로는 없기 때문에 참고용으로 사용할 수 있다.


  
@csrf_protect
def upload_success2(request):
if 'file1' in request.FILES:
file = request.FILES['file1']
file_name = file.name
fp = open("%s%s" % (UPLOAD_DIR, file_name), 'wb')
for chunk in file.chunks():
fp.write(chunk)
fp.close()
# ---추가-- 어차피 개 어려운거 실행이라도 해보는게 의의-------
# model.json 파일 열기
json_file = open('/home/kosmo_03/PycharmProjects/myapps/fileup/static/catdog_model.json')
loaded_model_json = json_file.read()
json_file.close()
# json 파일로부터 model 로드하기
loaded_model = model_from_json(loaded_model_json)
# json model에 가중치 값 로드하기
loaded_model.load_weights("/home/kosmo_03/PycharmProjects/myapps/fileup/static/model.h5")
loaded_model.compile(loss='binary_crossentopy', optimizer='adam', metrics=['accuracy'])
# 가중치와 모델을 로드 완료
image = Image.open("%s%s"%(UPLOAD_DIR,file_name))
width = 64
height = 64
image = image.resize((width,height))
# 이미지를 벡터화
image = np.array(image)
x_test = [image]
x_test = np.array(x_test)
x_test = x_test /255
y_predict = loaded_model.predict(x_test)
category = ""
# 예측해서 50% 이면 cat, 아니면 dog
if y_predict >= 0.5:
category = "cat"
print("cat",y_predict)
else:
category = "dag"
print("dog",y_predict)
# --------------- 추가 완료 -----------------------------
else:
file_name = '-'
return render(request, "fileup/success2.html", {'file_name': file_name, 'y_predict':y_predict, 'category':category})

어떻게 분류할까?

이미지의 경우, 예를 들면 색이 들어간 곳은 1, 아닌곳은 0(물론 실제로 이것보다 복잡하다)으로 두는 등, 이런 식으로 벡터로 array를 만들고, 그걸 수학적으로 훈련시킨다.

그러한 데이터로 확률을 구하는 것.

23. 아까 만든 success2.html 수정

분류된 결과값이 나올 수 있도록 써주기!


  
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>success2.html</title>
<style>
image { width:300px }
</style>
</head>
{% load static %}
{% static "" as baseUrl %}
<body>
<img src="{{baseUrl}}/images/{{file_name}}">
<!-- ----------- 추가 ------------------- -->
{% if 'cat' in category %}
<p>위의 이미지는 {{y_predict}} 값으로 <b> 고양이 [{{category}}]</b>로 예측되었습니다.</p>
{% else %}
<p>위의 이미지는 {{y_predict}} 값으로 <b> 강아지 [{{category}}]</b>로 예측되었습니다.</p>
{% endif %}
<p>
<input type="submit" value="파일업로드" onclick="location='main2'">
</p>
<!-- ----------- 추가 끝----------------- -->
</body>
</html>

 

24. urls.py 패스도 추가해주어야한다.


  
path('upload_success2',views.upload_success2),

 

25. 결과

1) main

2) main2

아래 확률까지 잘 나온다.

 

반응형
LIST
반응형
SMALL

리눅스에서 jupyter notebook를 실행하고 윈도우에 설치된 오라클 11g와 연동하는 것까지 해보았다.

 

▼저번 포스팅에서 해본 파이썬 - 오라클 연동 정리(링크)

더보기

방법을 간단히 요약하자면,

 

1) 오라클 connection 연결

2) connection이 cursor 객체 가져옴(연결도구)

3) cursor 객체의 fetch메서드를 이용해서 데이터를 서버로부터 가져온다.

4) cursor 객체의  execute() 메서드를 사용해서 파이썬에 입력한 SQL 문장을 오라클 DB 서버로 보낸다.

5) 주고받는 것이 완료되면 connection 객체의 commit() 메서드를 사용해서 데이터를 확정한다.

6) connection.close()를 해서 오라클과 연결을 끊는다.

 

즉, 정리하자면

오라클 수입 : import cx_Oracle as ora
오라클 Connection 연결 : conn = ora.connect('kosmorpa/test00@192.168.0.122:1522/orcl')
Cursor 불러오기 :  cursor = conn.cursor()
a) SQL 입력문장 -> 오라클 DB : cursor.execute( 전송할 sql 문장 )
b) 오라클 DB 데이터 -> 파이썬 : cursor.fetch*****()
Cursor 닫기 : cursor.close()
오라클 DB 저장 : conn.commit()
오라클 연결 끊기 : conn.close()

이걸 이용해서 파이참(PyCharm)에서 DB를 원래 했던 sqlite가 아닌 윈도우에 설치 되어있는 오라클과 연동해서 해보도록 하자.


 

준비된 템플릿에 회원가입 창과 아이디 중복 확인 프로그램을 만들었었다.

 

이번엔 로그인 앱을 만들어 web1과 연결해보자.

 

## web1에 있는 static 파일을 이용해서 추후 web1에 템플릿 디자인으로 적용해보기.

 


[ login/logout 웹 페이지 기능 구현하기 ]


1. login 앱 만들기

늘 하던대로 login > templates > login > index.html 파일 생성


  
python manage.py startapp login

 

2. index.html 파일 만들기

회원가입(앞선 포스팅 web1에서 만들었던 tripmember를 링크)과 로그인 폼에는 링크를 걸어두었다.

서버 구동을 확인한 후 로그인 폼(loginform)을 만들러 갈 예정.


  
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index.html</title>
<style>
ol, ul {list-style:none;}
ul li { float:left; margin-left:2px; background:pink}
</style>
</head>
<body>
<h1>로그인/로그아웃 데모</h1>
<span>추후 web1에 템플릿 디자인으로 적용하세요.</span>
<nav id="menu">
<header class="major">
<ul>
<li><a href="main">Home</a> </li>
<li><a href="survey">설문조사</a> </li>
<li>김길동님 반갑습니다. </li>
<li><a href="logout">로그아웃</a> </li>
<li><a href="/web1/tripmember">회원가입</a> </li>
<li><a href="loginform">로그인폼</a> </li>
</ul>
</header>
</nav>
</body>
</html>

3. 뷰 추가(view.py)

이제 이쯤이야 쉽다. 첫페이지 추가

4. urls.py 만들고 내용 추가

5. config/setting 추가

6. config/urls 추가

7. 서버 구동 및 index.html 파일 열기

대충 이런모양이다.

기본 세팅 끝.


본격적으로 첫 페이지의 로그인 폼을 만들러 가자.


 

8. login.html 만들기

index에 web1에서 썼었던 로그인폼 그대로 가져와서 내용 추가하자.


  
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login.html</title>
<style>
ol, ul {list-style:none;}
ul li { float:left; margin-left:2px; background:pink}
</style>
</head>
<body>
<h1>로그인 페이지</h1>
<span>추후 web1에 템플릿 디자인으로 적용하세요.</span>
<nav id="menu">
<section id="main">
<header>
<h2>StarTrip</h2>
<form method="post" action="">
{% csrf_token %}
<div class="fields">
<div class="field">
<input type="text" name="id" id="id" value="" placeholder="ID"
style="text-transform :lowercase"/>
</div>
<div class="field">
<input type="password" name="pwd" id="pwd" placeholder="Password" />
</div>
</div>
<ul class="actions special">
<li><input type="submit" value="로그인" class="primary"/></li>
</ul>
<div><span style="color:red;">{{error}}</span> </div>
</form>
</header>
</section>
</nav>
</body>
</html>

 

9. views.py로 와서 추가

login.html를 loginform 이름으로 정의하자.

여기서는 if문을 사용해서 loginform에 대해 1) 전송방식 구분 2) Session 유무에 따른 구분 을 두자.

 

▼왜 갑자기 세션이 나와?

더보기

갑자기 세션이 등장했다.(두둥탁)

JSP에서 쓰던 Session에 대한 개념을 여기에 정리해보고자 한다.

 

원래 HTTP는 비연결성 프로토콜(stateless)이기 때문에 전단계의 데이터 요청과 다음 데이터 요청이 관련이 없다. 이렇게 되면 항상 새롭게 매번, 데이터를 요청하고 받아와야 하기 때문에, 세션과 쿠키가 등장했다. 세션은 웹 컨테이너, 쿠키는 웹 브라우저에 상태 유지를 위한 정보를 저장한다.

 

예시를 들어보자.

원래 회원가입 절차는 사용자가 무조건 각 단계를 순차적으로 넘어와야 한다. HTTP로 구현을 하게 되면 모든 페이지에 대해서 URL(/step1/, /step2/, /step3/)로 접근할 수 있다. 하지만 세션을 사용하면 각 정보를 저장하기 때문에 /step1/ => /step2/ => /step3/ 순서로만 접근이 가능하게 바꿀 수 있다. 각 단계의 순서대로 접근하지 않으면 403 Forbidden 페이지를 볼 수 있을 것이다.

 

Django에서는 기본적으로 세션 관리 기능을 제공해주고, request.session 이라는 딕셔너리 객체에 필요한 값을 저장하고 읽어올 수 있다. view.py 에서 return을 걸어주기 전에 if문을 통해 step1에서 step2로만 가게끔(나머지는 false가 되게끔) 만들면 접근 순서를 제어할 수 있다. (출처)


▽코드 문장으로 읽기

더보기

loginform 두가지 if문이 중첩되어 있다.

1) 만약 user_id 가 request.session(=딕셔너리 객체에 필요한 값을 저장하고 읽어옴) 안에 있다면 /login 페이지로 이동해라.

2) 만약 요청방법이(로그인 폼에서 submit 버튼 누르고 id 비번 값이 넘어가는는 방법이) POST이면?

   n -> login/login.html로 가라.

   y -> post 방식으로 id를 요청해서 user_id 안에 넣어라.(비밀번호도) 그 후에 print해라.

   (db_id와 db_pwd 는 아직 db 연동을 하지 않았기 때문에 임의로 정해놓았음)

   2-1) 그 후 또다른 if문

      만약 db 안에 user 의 아이디와 비밀번호가 있다면? y -> 로그인 성공 띄우고 /login으로 가라.

      n -> 로그인 실패 띄우고 에러메시지 띄우고,  login/login.html로 가라.



  
from django.shortcuts import render, redirect
# Create your views here.
from django.views.decorators.csrf import csrf_protect
def home(request):
return render(request, "login/index.html")
# 1. 전송방식 구분
# 2. Session 유무에 따른 구분
@csrf_protect
def ldginform(request):
# 세션에 등록한 값이 존재하는가?
if 'user_id' in request.session:
return redirect('/login')
if request.method == 'POST':
user_id = request.POST['id']
user_pwd = request.POST['pwd']
print('user_id: ', user_id)
print('user_pwd: ', user_pwd)
db_id = 'xman'
db_pwd = '11'
# 중첩 if 문
if user_id in db_id and user_pwd in db_pwd:
print('로그인 성공')
request.session['user_id'] = db_id
request.session['user_name'] = '김길동'
return redirect('/login')
else:
print('로그인 실패')
msg = '아이디나 비밀번호가 잘못되었습니다.'
return render(request,"login/login.html",{"error":msg})
return render(request, "login/login.html")

10. 로그아웃도 추가(view.py)

세션을 삭제하고 나간다.


  
# 로그아웃 처리
def logout(request):
del request.session['user_id']
del request.session['user_name']
return redirect('/login')

당연히 view에 정의했으니 길 터줘야 한다.

11. urls.py에 path 추가

간-단

12. 세션 나눠주기(index.html)

views.py에서 if문으로 상황에 따라 경로를 만들어주었다.

첫 페이지인 index.html에서도 각 부분에 따른 세션을 나눠줘야 한다. 각각 if / else를 나눠 표시해주자.

왜??


  
<ul>
<li><a href="main">Home</a> </li>
<li><a href="survey">설문조사</a> </li>
{% if 'user_id' in request.session %}
<li>{{request.session.user_name}}님 반갑습니다. </li>
<li><a href="logout">로그아웃</a> </li>
{% else %}
<li><a href="/web1/tripmember">회원가입</a> </li>
<li><a href="loginform">로그인폼</a> </li>
{% endif %}
</ul>

 

[ 실습 예제 ]

[ cx_oracle과 연동해서 로그인 프로그램 완성하기 ]

def getLoginChk() 함수를 만들어서 인자 정의 및 구현을 한다.

 

13. 로그인을 체크하는 모델 추가(models.py)

오라클과 연결하고 sql문을 전송 및 값을 받아온다. datas의 경우 fetchall()로 모든 결과를 리턴한다.

[ Fatch 종류 ]

fetchall()의 경우 결과를 모두 리턴
fetchone()의 경우 하나의 row를 리턴
fetchmany(num rows)의 경우 rows의 숫자 만큼 리턴을 한다.

 


  
from django.db import models
import cx_Oracle as ora
database = 'kosmorpa/test00@192.168.0.122:1522/orcl'
# Create your models here.
# select count(*) cnt, name from member_table where id='xman' and pwd='121'
# 인자값이 여러개일 경우 함수의 인자를 늘여서 사용하게 되는 부분을 리팩토링한다.
def getLoginChk(**kwargs):
conn = ora.connect(database)
cursor = conn.cursor()
sql_select = "select count(*) cnt, name from member_table" \
" where id=:id and pwd=:pwd group by name"
cursor.execute(sql_select, id=kwargs['id'], pwd=kwargs['pwd'])
datas = cursor.fetchall()
cursor.close()
conn.close()
return datas

 

 

▼kwargs와 args

더보기

kwargs와 args는 파이썬에서 기존의 함수에 파라미터를 추가하여 새로운 함수를 만드는 경우에 사용한다.

별표시(***)의 경우, 여러 개의 인수를 받을 때, 키워드 인수를 받을 때 사용하는 표시이다.

 

1) **kwargs (keyword argument)

 

**kwargs는 {'키워드': '특정 값'} 형태로 함수를 호출할 수 있다. 또는 특정 키워드에 반응하여 함수를 작성하는 방법도 있다.(if문 사용) 단, 신규로 추가할 파라미터를 **kwargs 의 왼쪽에 써야 한다.

ex)def a(**kwargs, a): 은 틀린 문법(SyntaxError)

 

2) *args(argument)

 

*args 여러개의 인자를 함수로 받고자 할 때 쓰인다. 이름은 임시로 지은거라 *a , *banana 등등 다 가능하다. 튜플(tuple) 형태로 여러개의 인자를 함수로 출력하게 될 경우 {'a', 'b', 'c' } 와 같이 튜플 형태로 출력된다.

 

(예시 출처)

 

1. hello( ) 함수가 있습니다. 부서명과 이름을 받아 인사합니다.

 

2. 어느날 여러 번 인사와 함께 just do it! 을 외치는 새로운 함수를 만들어야 합니다.

단순히 아래처럼 같은 내용을 또 선언 할 수도 있습니다.

)

런 경우 hello( ) 내용이 달라지면 hello_repeat도 변경해야하는 번거로움이 있습니다.

 

3. 이런 경우 기존 hello( )를 그대로 사용하면서 파라미터만 추가로 선언하는 방법을 사용할 수 있습니다.

결과가 같습니다. 이제 hello( ) 내용이 변경되더라도 hello_repeat( )는 변경할 필요가 없습니다.

kwargs와 args는 파이썬에서 기존의 함수에 파라미터를 추가하여 새로운 함수를 만드는 경우에 사용한다.

별표시(***)의 경우, 여러 개의 인수를 받을 때, 키워드 인수를 받을 때 사용하는 표시이다.

 

1) **kwargs (keyword argument)

 

**kwargs는 {'키워드': '특정 값'} 형태로 함수를 호출할 수 있다. 또는 특정 키워드에 반응하여 함수를 작성하는 방법도 있다.(if문 사용) 단, 신규로 추가할 파라미터를 **kwargs 의 왼쪽에 써야 한다.

ex)def a(**kwargs, a): 은 틀린 문법(SyntaxError)

 

2) *args(argument)

 

*args 여러개의 인자를 함수로 받고자 할 때 쓰인다. 이름은 임시로 지은거라 *a , *banana 등등 다 가능하다. 튜플(tuple) 형태로 여러개의 인자를 함수로 출력하게 될 경우 {'a', 'b', 'c' } 와 같이 튜플 형태로 출력된다.

 


(예시 출처)

 

1. hello( ) 함수가 있습니다. 부서명과 이름을 받아 인사합니다.


  
def hello(department, name):
print('hello ' + department + ' team ' + name)

 

2. 어느날 여러 번 인사와 함께 just do it! 을 외치는 새로운 함수를 만들어야 합니다.

단순히 아래처럼 같은 내용을 또 선언 할 수도 있습니다.


  
def hello_repeat(num, department, name):
for i in range(num):
print('hello ' + department + ' team ' + name)
print('just do it!')

)

런 경우 hello( ) 내용이 달라지면 hello_repeat도 변경해야하는 번거로움이 있습니다.

 

3. 이런 경우 기존 hello( )를 그대로 사용하면서 파라미터만 추가로 선언하는 방법을 사용할 수 있습니다.


  
def hello_repeat(num, **kwargs):
for i in range(num):
hello(**kwargs)
print('just do it!')

결과가 같습니다. 이제 hello( ) 내용이 변경되더라도 hello_repeat( )는 변경할 필요가 없습니다.


 

 

15. views 가서 바꾸기

DB를 연결했으므로 임의로 정한 db_id, db_pwd가 아닌 오라클 DB에 있는 id와 pwd인지 if문을 이용해서 결과값을 리턴한다. 세션에 저장된 user_id가 id와 같을 경우, 로그인 성공창과 함께 0행 1열(2차원 배열 : 즉, 1행2열)을 이름값으로 가져온다.로그인이 실패되면 msg를 띄운다.


  
@csrf_protect
def loginform(request):
# 세션에 등록한 값이 존재하는가?
if 'user_id' in request.session:
return redirect('/login')
if request.method == 'POST':
user_id = request.POST['id']
user_pwd = request.POST['pwd']
print('user_id: ', user_id)
print('user_pwd: ', user_pwd)
# ----------------- 리펙토링한거 ------------------
res = getLoginChk(id=user_id,pwd=user_pwd)
print('='*30)
print(res)
if len(res) > 0:
print('로그인 성공')
request.session['user_id'] = user_id
request.session['user_name'] = res[0][1]
return redirect('/login')
else:
print('로그인 실패')
msg = '아이디나 비밀번호가 잘못되었습니다.'
return render(request, "login/login.html", {"error": msg})
return render(request, "login/login.html")

 

좌 : 기존 우 : 바꾼거

DB와 연동되어 로그인이 잘 되었다.

 

로그인 성공
로그인에 실패했을 경우

▼ 전체 코드 보기

더보기

[전체 코드]

1. index.html


  
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index.html</title>
<style>
ol, ul {list-style:none;}
ul li { float:left; margin-left:2px; background:pink}
</style>
</head>
<body>
<h1>로그인/로그아웃 데모</h1>
<span>추후 web1에 템플릿 디자인으로 적용하세요.</span>
<nav id="menu">
<header class="major">
<ul>
<li><a href="main">Home</a> </li>
<li><a href="survey">설문조사</a> </li>
{% if 'user_id' in request.session %}
<li>{{request.session.user_name}}님 반갑습니다. </li>
<li><a href="logout">로그아웃</a> </li>
{% else %}
<li><a href="/web1/tripmember">회원가입</a> </li>
<li><a href="loginform">로그인폼</a> </li>
{% endif %}
</ul>
</header>
</nav>
</body>
</html>

2. login.html


  
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login.html</title>
<style>
ol, ul {list-style:none;}
ul li { float:left; margin-left:2px; background:pink}
</style>
</head>
<body>
<h1>로그인 페이지</h1>
<span>추후 web1에 템플릿 디자인으로 적용하세요.</span>
<nav id="menu">
<section id="main">
<header>
<h2>StarTrip</h2>
<form method="post" action="">
{% csrf_token %}
<div class="fields">
<div class="field">
<input type="text" name="id" id="id" value="" placeholder="ID"
style="text-transform :lowercase"/>
</div>
<div class="field">
<input type="password" name="pwd" id="pwd" placeholder="Password" />
</div>
</div>
<ul class="actions special">
<li><input type="submit" value="로그인" class="primary"/></li>
</ul>
<div><span style="color:red;">{{error}}</span> </div>
</form>
</header>
</section>
</nav>
</body>
</html>

3. models.py


  
from django.db import models
import cx_Oracle as ora
database = 'kosmorpa/test00@192.168.0.122:1522/orcl'
# Create your models here.
# select count(*) cnt, name from member_table where id='xman' and pwd='121'
# 인자값이 여러개일 경우 함수의 인자를 늘여서 사용하게 되는 부분을 리팩토링한다.
def getLoginChk(**kwargs):
conn = ora.connect(database)
cursor = conn.cursor()
sql_select = "select count(*) cnt, name from member_table" \
" where id=:id and pwd=:pwd group by name"
cursor.execute(sql_select, id=kwargs['id'], pwd=kwargs['pwd'])
datas = cursor.fetchall()
cursor.close()
conn.close()
return datas

4. urls.py


  
from django.urls import path
from login import views
urlpatterns=[
path('',views.home),
path('loginform',views.loginform),
path('logout',views.logout),
]

5. views.py


  
from django.shortcuts import render, redirect
from .models import *
# Create your views here.
from django.views.decorators.csrf import csrf_protect
def home(request):
return render(request, "login/index.html")
# 1. 전송방식 구분
# 2. Session 유무에 따른 구분
@csrf_protect
def loginform(request):
# 세션에 등록한 값이 존재하는가?
if 'user_id' in request.session:
return redirect('/login')
if request.method == 'POST':
user_id = request.POST['id']
user_pwd = request.POST['pwd']
print('user_id: ', user_id)
print('user_pwd: ', user_pwd)
# ----------------- 리펙토링한거 ------------------
res = getLoginChk(id=user_id,pwd=user_pwd)
print('='*30)
print(res)
if len(res) > 0:
print('로그인 성공')
request.session['user_id'] = user_id
request.session['user_name'] = res[0][1]
return redirect('/login')
else:
print('로그인 실패')
msg = '아이디나 비밀번호가 잘못되었습니다.'
return render(request, "login/login.html", {"error": msg})
return render(request, "login/login.html")
# from .models import * 위쪽에 해줘야함
# ---------------------기존 코드 -> 리펙토링 해보기 ------------------------------
# db_id = 'xman'
# db_pwd = '11'
# if user_id in db_id and user_pwd in db_pwd:
# print('로그인 성공')
# request.session['user_id'] = db_id
# request.session['user_name'] = '김길동'
# return redirect('/login')
# else:
# print('로그인 실패')
# msg = '아이디나 비밀번호가 잘못되었습니다.'
# return render(request,"login/login.html",{"error":msg})
# return render(request, "login/login.html")
# ------------------------------------------------------------------------------
# 로그아웃 처리
def logout(request):
del request.session['user_id']
del request.session['user_name']
return redirect('/login')

 

cf) 웹브라우저에 SessionId가 쿠키에 저장되어있는지 확인하는 방법

 

반응형
LIST
반응형
SMALL

쉽게 분석 결과를 시각화해서 첨부할 수 있다.

바로 여기!

링크 : c3js.org/examples.html

 

C3.js | D3-based reusable chart library

Number Format Localization Number format localization using D3 locale settings. View details »

c3js.org

 

아래 코드를 복사해서 써보자

바로 전 포스팅에서 만들었던 설문조사 폼에서 시도해보자.

 

설문 결과창에 파이차트를 띄워보려고 한다.

 

 

[연습]

 

1. chart1.html 파일 만들기

 

2. view에 추가

 

3. urls 추가

 

4. 파이 차트 주소를 바디에 붙여넣기


  
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.11/c3.min.js"></script>
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>

이렇게 받아오는 걸 제이쿼리(선택자)로, 스크립트로, 제이슨으로도 불러올 수 있다.

 

5. js 파일 붙여넣기

어차피 setTomeout은 필요 없다. 그 앞에까지만 복붙해보자.

스크립트 안 제이쿼리에 붙여넣기.

 

 

컬럼의 차트, 데이터를 컬럼에서 가져와 var로 선언만 해주면 된다.


  
$(function(){
//여기 붙여넣기
var chart = c3.generate({
bindto :'#chart_donut', //이거 쓴거
data: {
columns: [
['data1', 30],
['data2', 120],
['data3', 150],
],
type : 'pie',
}
});

이거를


  
$(function(){
//여기 붙여넣기
var chartData = [
['data1', 30],
['data2', 120],
['data3', 150],
];
var chart = c3.generate({
bindto :'#chart_donut', //이거 쓴거
data: {
columns: chartData,
type : 'pie',
}
});

이렇게 추가해주면 데이터가 변할 때 chartData 부분만 수정해 주면 된다.

 

이런식으로 결과값이 나온다.

 

 

[ 설문조사 결과폼에 적용해보기 ]

1. result.html 파일 하단에 <script> 추가하기

링크를 연결하거나 할 필요는 없다. 결과 확인창에 나오는 값을 그대로 데이터로 가져와서 차트의 데이터(chartData) 부분에 넣어주면 된다.


  
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>result.html</title>
<style>
#wrap { border:1px sloid #000; width:450px; margin:auto; }
table { border-collapse: collapse; width:100% }
th,td { padding:8px; text-align:left;
border-bottom: 1px solid #ddd;
}
tr:hover { background:#f5f5f5; cursor:pointer }
</style>
</head>
<body>
<div id="wrap">
<h1>설문조사 결과보기 예제</h1>
<table id = "datatable">
<thead>
<tr>
<th>문항</th>
<th>응답수</th>
</tr>
</thead>
<tbody>
{% for row, ans in surveyList %}
<tr>
<td>{{ans}}</td>
<td>{{row.sum_num}}</td>
<!--sql에 있는 테이블 row 가져오기-->
</tr>
{%endfor%}
</tbody>
<tr>
<td colspan="2">
<input type="button" value="투표하기" onclick="location='/survey'">
</td>
</tr>
</table>
</div>
<!--================================================================-->
<!--파이차트 들어갈 곳-->
<div id="chart_donut"></div>
<!--파이차트 연결 주소-->
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.11/c3.min.js"></script>
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<!--파이차트 연결 주소 끝-->
<!-- 파이차트 연결하기-->
<script>
$(function(){
var chartData=[
];
var chart = c3.generate({
bindto:'#chart_donut',
data: {
columns: chartData,
type : 'pie',
}
});
});
</script>
<!--================================================================-->
</body>
</html>

 

2. 파이차트의 데이터를 불러오기 위해 값을 넣어준다(input)

hidden으로 숨겨서 들어온다.

sql 테이블에서 가져온 데이터들(ans, row.sum_num) 을 value값으로 지닌 클래스를 설정해준다.


  
{% for row, ans in surveyList %}
<tr>
<!--=================================================================================-->
<input type="hidden" class="ans" name="ans" value="{{ans}}">
<input type="hidden" class="sum_num" name="sum_num" value="{{row.sum_num}}">
<!--=================================================================================-->
<td>{{ans}}</td>
<td>{{row.sum_num}}</td>
<!--sql에 있는 테이블 row 가져오기-->
</tr>
{%endfor%}

위치는 ans와 row.sum_num을 surveyList에서 가져온 디장고 탬플릿 태그(  {% %}  ) 안에 써줘야 한다.

 

3. 데이터를 받아오기 위해 여러 방법으로 받아온다.

답변이 4개이기 때문에 데이터를 여러가지 방법으로 가져올 수 있다.

 

1) 반복문 사용


  
$(function(){
var chartData=[];
for(var i=0;i<$('.ans').length; i++){
chartData.push([$('.ans').eq(i).val(),$('.sum_num').eq(i).val()]);
}
var chart = c3.generate({
bindto:'#chart_donut',
data: {
columns: chartData,
type : 'pie',
}
});
});

- arrayList 생성한다. 이름 : chartData
- id 값이 ans인 태그의 길이만큼 반복문 돌린다.(문항수만큼)
- chartData변수에 배열 값을 넣는다(push)
- push 조건 : id가 ans이거나 sum_num인 태그 중 인덱스번호가 i(=eq)인 경우, 양식(form)의 값을 가져오거나 값을 설정(=val())

2) ans 접근을 위한 each 메소드 사용

클래스 값을 다르게 설정한 경우 가능하다.


  
<input type="hidden" class="ans{{forloop.counter0}}" name="ans" value="{{ans}}">
<input type="hidden" class="sum_num{{forloop.counter0}}" name="sum_num" value="{{row.sum_num}}">

forloop 란?

Template 태그 중 하나로 {% for %} 태그를 사용하면 리스트에 담겨 있는 항목들을 순회하면서 출력할 수 있다.

현재까지 루프를 실행한 루프 카운트를 0부터 카운트한다. ans는 인덱스번호가 0부터 시작하므로 0부터 카운트한다.


  
$(function(){
var chartData=[];
$("[class^='ans']").each(function(){
chartData.push([$(this).val(),$(this).next().val()]);
})
var chart = c3.generate({
bindto:'#chart_donut',
data: {
columns: chartData,
type : 'pie',
}
});
});

- each 메소드를 통해 제이쿼리로 배열을 관리할 수 있다. each() 메서드는 매개 변수로 받은 것을 사용해 for in 반복문과 같이 배열이나 객체의 요소를 검사할 수 있는 메소드로 반복함수이다. 역할은 위의 for문과 동일하다.

- 위에서 카운트되는 값들을 그대로 this 자리에 넣어준다.

- $(this).next() 는 sum_num 값들이다.

 

 

3) 일일히 변수값 지정해주기

 

var ans1 , var ans2 등 일일히 .ans 값과 sum_num 값을 변수로 지정해주어서 배열 속의 배열 [ [배열] ] 로 넣는 방법도 있다.

4. 결과

차트가 잘 출력된다.

반응형
LIST
반응형
SMALL

+ Recent posts

반응형
LIST