LostCatBox

프론트엔드 기본편 4

Word count: 3.5kReading time: 22 min
2020/01/02 Share

Ajax with Django #4

이미지 썸네일 처리

큰 이미지를 CSS로 이미지 크기만 줄이는 것은 도움이 되지 않습니다.실제 서버에서 다운받을 때부터 적절히 조절하는 것이 좋습니다.

  • 이미지 업로드 받을 때 미리 조절해서 한 버전 혹은 여러 버전으로 저장 해두거나
  • 이미지를 서빙받을 때 동적으로 조절해서 내려주거나

Image Libraries

  • sorl-thumbnail
  • easy-thumbnails
1
2
3
4
pip3 install easy-thumbnails

settings.py 에 easy_thumbnails 추가
python3 manage.py migrate 필수

사용법은 예시를 통해!

예시

1
2
3
4
5
6
7
{% load thumbnail %}


<li id="comment-{{ comment.pk }}">
{% if comment.photo %}
<img src="{{ thumbnail comment.photo.url }}" style="width: 100px;"/>
{% endif %}

위에 코드를 아래처럼 바꿀수있다

1
2
3
4
5
6
7
{% load thumbnail %}


<li id="comment-{{ comment.pk }}">
{% if comment.photo %}
<img src="{% thumbnail comment.photo 100x100 crop %}"/> //인자 3개넘김
{% endif %}
1
2
3
4
5
6
7
8
9
10
11
#post_detail.html 에서

$.get('{% url "blog:comment_list" post.pk %}')
.done(function (html) {
$('#comment-list').html(html);
})
.fail(function (xhr, textStatus, error) {
alert('failed:' + error);
})

추가해준다면 현재 html에서 id= 'comment-list'를 찾아서 거기안에 html을 get요청으로 받아온 html을 넣어줌 즉, ajax로 댓글 리스트를 구현함.

댓글 레이아웃 개선

bootstrap에서 양식따와서 수정함

왼쪽 사진 오른쪽 댓글로 구성됨.

댓글 페이징처리

blog템플릿에 post_detail에 있던내용중 comment-list를 따로 blog템플릿Comment_list.html 뺌

1
2
3
4
5
<div id="'comment-list">
{% for comment in comment_list %}
{% include "blog/_comment.html" %}
{% endfor %}
</div>

따로 만들어줌

댓글 Ajax 새로고침

Comment-id를 추출해서 이 id값이 더 높은 값이 있다면 새로고침을 하는 방식으로 구현

1
2
3
4
#blog/templates/_comment.html
<div id="comment-{{ comment.pk }}" class="media comment" data-comment-id="{{ comment.id }}">

data-comment-id속성을 추가하므로서 댓글 구별, 삭제, 새로고침들에 이용가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# post_detail.html 글쓰기 아래 부분에 추가 


<a id="check-comment" class="btn btn-primary btn-block">
새 댓글 체크
</a>






$('#check-comment').click(function(e) {
e.preventDefault();

var comment_id = $('#comment-list .comment:first').data('comment-id');
console.log(comment_id);

$.get('{% url "blog:comment_list" post.pk %}', {last_comment_id: comment_id}) //get요청의 인자로 보냄
.done(function(html) {
console.log(html);
$('#comment-list').prepend(html); //최상단에 html넣기

})
.fail(function(xhr, textStatus, error) {
alert('failed:' + error);
});

});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
view.py 

class CommentListView(ListView):
model = Comment

def get_queryset(self):
#self.kwargs를 가져오면 url에서 post_ argu를 다 가져옴!!!
qs = super().get_queryset()
qs = qs.filter(post__id = self.kwargs['post_pk'])

latest_comment_id = self.request.GET.get('latest_comment_id', None)
if latest_comment_id:
# lt : less than <
# gt : greater than >
qs = qs.filter(id__gt=latest_comment_id)
return qs

comment_list = CommentListView.as_view()

사용자 인증 연동

로그인 로그아웃. next인자를 넣어준다면 그전 url로 자동으로 돌아감

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="{% url "blog:index" %}">Home</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">

{% if not user.is_authenticated %}
<li><a href="{% url "login" %}"?next={{ request.path }}>로그인</a></li>
<li><a href="{% url "signup" %}">회원가입</a></li>
{% else %}

<li><a href="{% url "profile" %}">프로필</a></li>
<li><a href="{% url "logout" %}?next={{ request.path }}" class="disabled">로그아웃</a></li>
{% endif %}

<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</div>

loginview, logoutview에 인자 넘기는 법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
urls.py 에서

from django.contrib.auth import views as auth_views
from django.conf import settings
from django.urls import path, include
from . import views

urlpatterns = [
path('signup/', views.SignupFormView.as_view(), name="signup"),
path('login/', auth_views.LoginView.as_view(template_name='accounts/login.html'), name="login"),
path('logout/', auth_views.LogoutView.as_view(next_page=settings.LOGIN_URL), name="logout"),
path('profile/', views.profile, name='profile'),

]

로그인 창 또한 ajax로 처리하는 방법

  • modal을 이용해야하는데 이미 앞에서 class=comment-form-btn을 click이벤트가 발생하면 ajax로 처리하는 일반화된 것을 만들어놓음 따라서 로그인 버튼에 class=comment-form-btn이라고 지정만 해주면 처리가 될것이다 (앞에서는 detail안에만 있지만 이것을 일반화시키면 모두 modal로 띄워줄수있다)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
comment-form에 관한 모든것을 다 modal-form으로 바꾸고 일반화
# 프젝/layout.html에 아래내용 추가 (기존 detail내용들은 삭재)


<script>
$(document).on('click', '.modal-form-btn', function(e) {
e.preventDefault();

var action_url = $(this).attr('href');
var target_id = $(this).data('target-id');

$.get(action_url)
.done(function (form_html) {
var $modal = $('#modal-form-modal');

$modal.find('.modal-body').html(form_html);

$form = $modal.find('.modal-body form');
$form.attr('action', action_url);

if ( target_id ) { //target_id값 존재시
// modal form에 data-target-id속성을 기록
// - $form.data('target-id', target_id); 를 써봤으나,
// 지정이 되지않아서 attr로 변경
$form.attr('data-target-id', target_id);
}
else {
$form.removeData('target-id'); //target-id정보 제거
}

$modal.modal();
})
.fail(function (xhr, textStatus, error) {
alert('failed:' + error);

});

// $('#comment-form-modal').modal();

})


$(document).on('submit', '#modal-form-modal form', function(e) {
e.preventDefault();
console.log("Submit");

// jQuery Form Plugin의 ajaxSubmit을 활용 : ajax로 파일까지 모두 전달
$(this).ajaxSubmit({
success: function(response, statusText, xhr, $form) {
console.log("---- done ----");
var html = response;
console.log(html);

var $resp = $(html);
var target_id = $form.data('target-id');

if ( $resp.find('.has-error').length > 0 ) {
var fields_html = $resp.html();
$('#modal-form-modal .modal-body form').html(fields_html);
}
else {
if (target_id) {
$('#' + target_id).html($resp.html());
}
else {
$resp.prependTo('#comment-list');
}

$('#modal-form-modal').modal('hide');
$form[0].reset();
}
},
error: function(xhr, textStatus, error) {
alert('failed : ' + error);
},
complete: function(xhr, textStatus) {
}
});

$(document).on('click', '.ajax-post-confirm', function (e) {
e.preventDefault();

var url = $(this).attr("href");
var target_id = $(this).data('target-id'); //현재 링크에 data속성을 가져올떄
var message = $(this).data('message');

if (confirm(message)) { //confirm자체가 확인 취소를 물어보고 이는 true false로 입력된다.
$.post(url)
.done(function () {
$('#' + target_id).remove();
})
.fail(function (xhr, textStatus, error) {
alert("failed")
});
}

alert('clicked:' + message);
});

})




</script>

위에 처럼 바꾸면 로그인을 하는순간 ajax post로 보냈으므로 html로 응답되기를 기대되는데 로그인 성공하면 post_detail로 redirect가 일어나 일어난것에 대해 ajax요청이니까 post에 대한 json응답을 받아버림

  • ajax로 처리하는과정
1
2
3
4
5
6
7
8
9
10
11
12
class LoginView(AuthLoginView):

def form_valid(self, form):
response = super().form_valid(form)
if self.request.is_ajax():
return JsonResponse({'next_url': self.get_success_url()})
return response

def get_template_names(self):
if self.request.is_ajax():
return ['accounts/_login.html']
return ['accounts/login.html']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
프젝/layout.html  내용 추가

$(document).on('submit', '#modal-form-modal form', function(e) {
e.preventDefault();
console.log("Submit");


// jQuery Form Plugin의 ajaxSubmit을 활용 : ajax로 파일까지 모두 전달
$(this).ajaxSubmit({
success: function(response, statusText, xhr, $form) {
if ( response.next_url ) { //response안에 nex_url인자 존재시
window.location = response.next_url //window.location에 주소로이동임
return; // 함수종료
}

지금까지 modal 사용, ajax연결 등을 자세히 배웠음

JavaScript Chart 데이터 연동

다양한 JavaScript 차트

  • Chart.js #home
  • D3.js
  • Highcharts.js
  • Chartist.js
  • Google Chart
  • 이 외에도 수많은 라이브러리가 있습니다.

장고와의 연동에서 주안점

  1. Chart 라이브러리 static 연결
    • CDN 버전 연결
    • static 직접 호스팅 1
  2. 데이터 연동 : 데이터가 고정된 차트를 보고 싶지 않습니다.
    • Inline JavaScript를 통한 데이터 공급
    • Ajax를 통한 데이터 공급

Tip: 참고: [장고 기본편] StaticFiles - CSS/JavaScript 파일을 어떻게 관리해야할까요?

django chart 앱

  • django-chartjs: Django Class Based Views to generate Ajax charts js parameters. This is compatible with Chart.js and Highcharts JS libraries.
  • django-jchart: This Django app enables you to configure and render Chart.JS charts directly from your Django codebase.
  • django-chart-tools: django-chart-tools is a simple app for creating charts in django templates using Google Chart API.
  • django-rest-pandas: Serves up Pandas dataframes via the Django REST Framework for use in client-side (i.e. d3.js) visualizations and offline analysis (e.g. Excel)

javascript chart 활용법을 먼저 익히세요.

  • django chart 앱을 통해, 장고에서 손쉽게 차트를 사용하실 수는 있습니다.
  • 하지만 django chart 앱과 연동된 차트 외에 더 많은 JavaScript 차트가 있습니다.
  • 게다가 django chart 앱에서는 본연의 JavaScript의 모든 기능을 활용하 지 못하고 있을 가능성도 있습니다.
  • JavaScript 차트를 직접 활용하실 줄 아셔야 합니다.

백엔드 도움없이 프론트엔드 단에서만 차트 그리기

Chart.js 간단 샘플

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<script src="http://www.chartjs.org/dist/2.7.0/Chart.bundle.js"></script>
</head>
<body>

<canvas id="canvas"></canvas> //웹에서의 그림판임!! 여기에다가 chart.js가 그림

<script>
var chartData = { // 현재 chartData 객체에 들어있는 지금값들은 chart.js가 요구하는 스펙으로 구성함 (label과 data만 넣어도 작동은함)
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'Dataset 1',
backgroundColor: "rgba(255, 99, 132, 0.5)",
borderColor: "rgba(255, 99, 132, 1)",
pointBackgroundColor: "rgba(255, 99, 132, 1)",
pointBorderColor: "#fff",
data: [
parseInt(Math.random() * 100), parseInt(Math.random() * 100), parseInt(Math.random() * 100), parseInt(Math.random() * 100),
parseInt(Math.random() * 100), parseInt(Math.random() * 100), parseInt(Math.random() * 100)
]
}]
};

window.onload = function() { //현재 jquery를 안썻으므로 이방식으로 함수지정해서쓰면 페이지로딩다되면 함수호출됨

var ctx = document.getElementById('canvas').getContext('2d'); //2d context가져옴

// chart.js에서 지원해주는 Chart로 ctx넘겨주고, 필수 객체넘겨주면 인자만들어줌
window.chart = new Chart(ctx, {

type: 'line',
data: chartData
});
};
</script>
</body>
</html>

백엔드에서 데이터 넘겨주기 (1) 템플릿 렌더링 시에 데이터 넘겨주기

유틸리티 코드) 웹툰 평점 크롤링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin


def get_comic_info(comic_id, comic_title):
ep_list = []


for page in range(1, 3): # 최대 5페이지
params = {
'titleId': comic_id,
'page': page,
}
resp = requests.get('http://comic.naver.com/webtoon/list.nhn', params=params)
html = resp.text
soup = BeautifulSoup(html, 'html.parser')



for tr in soup.select('#content table tr'): # 아래 크롤링 하는목록 죄회하
try:
link = tr.select('.title a[href*=detail]')[0] #.title이 class 이름이고 a에 href가 포함된것을 긁어옴
rating = tr.select('.rating_type strong')[0].text
date = tr.select('.num')[0].text
except IndexError:
continue

title = link.text
url = urljoin(resp.request.url, link['href'])
ep = {
'title': title,
'url': url,
'rating': rating,
'date': date,
}
if ep in ep_list: #같은게 나와버리면 지금 함수 끝냄
return ep_list

ep_list.append(ep)

return {
'title': comic_title,
'ep_list': ep_list,
'soup': soup,
}

뷰 코드

차트 데이터들을 server side에서 javascript 형식으로 렌더링하기

1
2
3
4
5
6
7
8
9
from django.shortcuts import render
from .utils import get_comic_info

def index(request):
comic = get_comic_info(20853,
'마음의 소리')
return render(request, 'mychart/index.html', {
'comic': comic,
})

뷰 render에서 넘겨진 comic 사전 활용 템플릿

1
{{ comic }}

위에 호출은 django단에서 처리되고 script태그 안에있는것은 django한테는 그냥 문자열이지

django문법

1
{% %}

를 써서 javascript문자열들을 만들어줌

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<script src="http://www.chartjs.org/dist/2.7.0/Chart.bundle.js"></script>
</head>
<body>

<canvas id="canvas"></canvas>

<script>
var chartData = {
labels: [
{% for ep in comic.ep_list %}
'{{ ep.title }}'
{% if not forloop.last %},{% endif %} //마지막 loop의 마지막이 아닐경우 ,를 붙인다
{% endfor %}
],
datasets: [{
label: '평점',
backgroundColor: "rgba(255, 99, 132, 0.5)",
borderColor: "rgba(255, 99, 132, 1)",
pointBackgroundColor: "rgba(255, 99, 132, 1)",
pointBorderColor: "#fff",
data: [
{% for ep in comic.ep_list %}
{{ ep.rating }}
{% if not forloop.last %},{% endif %} //마지막 loop의 마지막이 아닐경우 ,를 붙인다
{% endfor %}
]
}]
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
<script>
window.onload = function() {
var ctx = document.getElementById('canvas').getContext('2d');
var chart = new Chart(ctx, {
type: 'line',
data: chartData
});
};
</script>
</body>
</html>
EP 9. JavaScript Chart

백엔드에서 데이터 넘겨주기 (2) Ajax 활용

urls.py

1
2
3
4
urlpatterns = [
# 중략
url(r'^data.json$', views.data_json, name='data_json'),
]

뷰 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from django.shortcuts import render
from .utils import get_comic_info
from django.http import JsonResponse

def index(request):

comic = get_comic_info(20853, '마음의 소리')

return render(request, 'mychart/index.html', {'soup': comic})


def data_json(request):
comic = get_comic_info(20853, '마음의 소리')

return JsonResponse({
'labels': [ep['title'] for ep in comic['ep_list']],
'datasets': [{
'label': '평점',
'backgroundColor': "rgba(255, 99, 132, 0.5)",
'borderColor': "rgba(255, 99, 132, 1)",
'pointBackgroundColor': "rgba(255, 99, 132, 1)",
'pointBorderColor': "#fff",
'data': [ep['rating'] for ep in comic['ep_list']]
}],

})

템플릿 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{% load static %}
<!doctype html>

<html>
<head>
<meta charset="utf-8" />
<script src="http://www.chartjs.org/dist/2.7.0/Chart.bundle.js"></script>
<script src="{% static 'jquery/dist/jquery.min.js' %}"></script>

</head>
<body>

<canvas id="canvas"></canvas>

<ul>
{# {{ soup }}#} //로우데이터보고싶을때 활용
</ul>


<script>
$(function() {

$.get('{% url "mychart:data_json" %}')
.done(function(chartData) {
console.log(chartData)
var ctx = document.getElementById('canvas').getContext('2d');
window.chart = new Chart(ctx, {
type: 'line',
data: chartData
});
})
.fail(function (xhr, textStatus, error) {
alert('failed:'+ error);

});
});
</script>
</body>
</html>

백엔드에서 데이터 넘겨주기 (3) django-chartjs 활용(추천안함)

django-chartjs 활용 뷰코드

data_json 뷰를 django-chartjs를 통해 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from chartjs.views.lines import BaseLineChartView

class WebtoonChartJSONView(BaseLineChartView):
def __init__(self):
super().__init__()
self.comic = get_comic_info(20853, '마음의 소리')

def get_labels(self):
return [ep['title'] for ep in self.comic['ep_list']]

def get_providers(self):
return ['평점']

def get_data(self):
return [
[ep['rating'] for ep in self.comic['ep_list']],
]

def get_colors(self):
yield (255, 99, 132)

data_json = WebtoonChartJSONView.as_view()

Tip

javascript chart를 장고와 연동하기 전에, html/css/javascript만으로 javascript chart를 우선 익혀보세요.

그래야만 자유자재로 chart를 활용하실 수 있습니다.

그 후에, django chart 앱을 활용하시며, 소스코드도 까보세요.

크롤링 로우 데이터

1
{'title': '마음의 소리', 'ep_list': [{'title': '1203. 그 동상', 'url': 'https://comic.naver.com/webtoon/detail.nhn?titleId=20853&no=1207&weekday=tue', 'rating': '9.73', 'date': '2019.12.30'}, {'title': '1202. 당숙 어르신', 'url': 'https://comic.naver.com/webtoon/detail.nhn?titleId=20853&no=1206&weekday=tue', 'rating': '9.77', 'date': '2019.12.23'}, {'title': '1201. 겨울 옷차림', 'url': 'https://comic.naver.com/webtoon/detail.nhn?titleId=20853&no=1205&weekday=tue', 'rating': '9.85', 'date': '2019.12.16'}, {'title': '1200. 양심의소리', 'url': 'https://comic.naver.com/webtoon/detail.nhn?titleId=20853&no=1204&weekday=tue', 'rating': '9.95', 'date': '2019.12.09'}, {'title': '1199. 백야', 'url': 'https://comic.naver.com/webtoon/detail.nhn?titleId=20853&no=1203&weekday=tue', 'rating': '9.81', 'date': '2019.12.02'}, {'title': '1198. 성함이…?', 'url': 'https://comic.naver.com/webtoon/detail.nhn?titleId=20853&no=1202&weekday=tue', 'rating': '9.93', 'date': '2019.11.25'}, {'title': '1197. 학예회', 'url': 'https://comic.naver.com/webtoon/detail.nhn?titleId=20853&no=1201&weekday=tue', 'rating': '9.85', 'date': '2019.11.18'}, {'title': '1196. 키아누 X브스', 'url': 'https://comic.naver.com/webtoon/detail.nhn?titleId=20853&no=1200&weekday=tue', 'rating': '9.80', 'date': '2019.11.11'}, {'title': '1195. 상의', 'url': 'https://comic.naver.com/webtoon/detail.nhn?titleId=20853&no=1199&weekday=tue', 'rating': '9.73', 'date': '2019.11.04'}, {'title': '1194. 불법스캔', 'url': 'https://comic.naver.com/webtoon/detail.nhn?titleId=20853&no=1198&weekday=tue', 'rating': '9.54', 'date': '2019.10.28'}]
CATALOG
  1. 1. Ajax with Django #4
    1. 1.1. 이미지 썸네일 처리
      1. 1.1.1. Image Libraries
    2. 1.2. 댓글 레이아웃 개선
    3. 1.3. 댓글 페이징처리
    4. 1.4. 댓글 Ajax 새로고침
    5. 1.5. 사용자 인증 연동
      1. 1.5.1. 로그인 로그아웃. next인자를 넣어준다면 그전 url로 자동으로 돌아감
      2. 1.5.2. loginview, logoutview에 인자 넘기는 법
      3. 1.5.3. 로그인 창 또한 ajax로 처리하는 방법
  2. 2. JavaScript Chart 데이터 연동
    1. 2.1. 다양한 JavaScript 차트
    2. 2.2. 장고와의 연동에서 주안점
      1. 2.2.1. django chart 앱
    3. 2.3. javascript chart 활용법을 먼저 익히세요.
    4. 2.4. 백엔드 도움없이 프론트엔드 단에서만 차트 그리기
      1. 2.4.1. Chart.js 간단 샘플
    5. 2.5. 백엔드에서 데이터 넘겨주기 (1) 템플릿 렌더링 시에 데이터 넘겨주기
      1. 2.5.1. 유틸리티 코드) 웹툰 평점 크롤링
      2. 2.5.2. 뷰 코드
    6. 2.6. 뷰 render에서 넘겨진 comic 사전 활용 템플릿
    7. 2.7. 백엔드에서 데이터 넘겨주기 (2) Ajax 활용
      1. 2.7.1. urls.py
      2. 2.7.2. 뷰 코드
      3. 2.7.3. 템플릿 코드
    8. 2.8. 백엔드에서 데이터 넘겨주기 (3) django-chartjs 활용(추천안함)
      1. 2.8.1. django-chartjs 활용 뷰코드
    9. 2.9. Tip
      1. 2.9.1. 크롤링 로우 데이터