장고(Django) 개발: JQuery를 사용한 Ajax 구현 및 Autocomplete 예제

웨드_ |

장고(Django) 프레임워크의 From 사용 방법에 이어서 Ajax를 활용한 서버 통신을 알아보고, Autocomplete 예제도 구현해 보았다.


작년에 장고(Django) 프레임워크로 프로젝트를 진행하면서 공부했던 개발 지식을 블로그에 다시 정리하려고 합니다. 개인 위키에 정리했던 것을 옮기는 수준이라, 친절한 설명은 기대하기 어렵고, 프로그래밍 언어와 파이썬 지식이 어느정도 있어야 이해할 수 있을 것입니다.


아래 내용들은 Django 1.6~1.7 버전을 기준으로하고, "쉽고 빠른 웹개발 Django"란 책에서 1/3, 공식 위키에서 1/3, 그리고 나머지는 구글링을 바탕으로 정리한 지식입니다. 말투가 존대와 반말이 섞여있어도 이해바랍니다 :)



1 Ajax 란?

2 JQuery를 사용하여 Ajax 구현하기

3 Ajax와 Form의 csrf_token 문제

4 Autocomplete 구현 예제





1 Ajax 란?

  • Asynchronous JavaScript and XML
  • 페이지 안의 데이터에 접근하여 조작
  • XMLHttpRequest를 사용하여 페이지 리로드와 무관하게 서버와 통신
  • 통신하는 데이터 포멧은 XML, JSON, 일반 HTML, 일반 텍스트 등

Ajax 라이브러리

브라우저의 차이를 극복하고, 자바스크립트 기본 함수를 묶어서 Ajax를 편리하게 사용할 수 있도록 지원

Django와 Ajax

Django에서는 어떤 Ajax 프레임워크를 써도 무방하지만, 여기에서는 JQuery를 사용

기본적으로 Ajax 인터페이스를 구현하기 전에, Ajax가 없어도 기능이 구동하도록 먼저 만들어 놓은 후, Ajax를 구현하는 것이 중요함




2 JQuery를 사용하여 Ajax 구현하기

  1. Ajax가 없어도 구동하는 인터페이스를 먼저 구현
  2. views.py 에서 Ajax 요청인 경우, 템플릿 파일 변경
  3. Ajax 기능을 위한 JavaScript 추가

1. Ajax가 없어도 구동하는 인터페이스를 먼저 구현

  1. forms.py에 사용자 정의 Form 만들기
  2. views.py에 페이지 컨트롤러 함수 만들기
  3. 해당 페이지 템플릿 만들기
  4. urls.py에 주소 등록하기

2. views.py 에서 Ajax 요청인 경우, 템플릿 파일 변경

request.is_ajax() 메서드를 사용하여 해당 요청이 Ajax인 경우를 가릴 수 있음

기존의 템플릿을 그대로 쓰기보다, 변경시켜줘야 하는 (더 작은) 템플릿만을 지정해줘도 됨

# views.py의 search_page(request) 함수
    if request.is_ajax():
        return render_to_response('bookmark_list.html', variables)
    else:
        return render_to_response('search.html', variables)

3. Ajax 기능을 위한 JavaScript 추가

submit(0함수를 사용하여, input의 submit 이벤트가 발생했을 때 load()함수를 호출해 줌

load()함수는 URL을 전달받아서, 선택한 엘리먼트에 URL의 페이지 내용을 넣어줌

# load()함수로 검색결과 엘리먼트에 검색 URL의 결과를 넣어줌
function search_submit() {
    var query = $("#id_query").val();
    $("#search-results").load(
        "/search/?ajax&query=" + encodeURIComponent(query)
    );
    return false;
}
 
# submit 이벤트
$(document).ready(function()) {
    $("#search-form").submit(search_submit);
}




3 Ajax와 Form의 csrf_token 문제

Jquery에서 제공하는 ajax() 또는 post() 명령으로 폼 내용을 전달할때, 알 수 없는 오류가 발생하는 경우가 있다. 이는 form 내부에 {% csrf_token %} 가 함께 전송되지 않아서 나타나는 문제이다.

CSRF (Cross Site Request Forgery protection) 
크로스 사이트 요청 위조란, 사용자가 자신의 의지와는 무관하게 공격자가 의도한행위(수정, 삭제, 등록 등)를
특정 웹사이트에 요청하게 하는 공격.
사용자가 웹사이트에 로그인한 상태에서 "크로스 사이트 요청 위조" 공격 코드가 삽입된 페이지를 열면,
이후에는 사용자의 행동과 관계 없이 사용자의 웹 브라우저와 공격 대상 웹사이트 간의 상호작용이 이루어진다.

해결 방법

아래 Javascript 코드(ajax_csrf.js)를 프로젝트에 넣고,

jQuery(document).ajaxSend(function(event, xhr, settings) {
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) == (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
function sameOrigin(url) {
    // url could be relative or scheme relative or absolute
    var host = document.location.host; // host + port
    var protocol = document.location.protocol;
    var sr_origin = '//' + host;
    var origin = protocol + sr_origin;
    // Allow absolute or scheme relative URLs to same origin
    return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
        (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
        // or any other URL that isn't scheme relative or absolute i.e relative.
        !(/^(\/\/|http:|https:).*/.test(url));
}
function safeMethod(method) {
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
 
if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
    xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
});

템플릿에 <head></head> 사이에 ajax_csrf.js 파일을 추가해준다.

<head>
    <script type="text/javascript" src="/경로../ajax_csrf.js"></script>
</head>

참고




4 Autocomplete 구현 예제

Django에서 Autocomplete 기능을 사용하기 위해서 JQuery UI에서 제공하는 Autocomplete 위젯을 사용하는 방법이다. 아래 예제는 Tag 자동 추천에 사용된 코드이다.

(JQuery UI를 사용하는 방법 외에도, 다양한 Autocomplete 솔루션이 존재한다.)

Autocomplete 기본 사용법

$( "#autocomplete" ).autocomplete({
    source: "",
    minLength: ?,
    select: function(event, ui){
        //자동완성이 선택되었을 때, 이벤트 콜백 함수
        //event(Event)
        //ui(Object)=>item(object)는 label과 value 속성으로 구성됨
    },
});

1. models.py에서 DB 데이터 정의

자동완성에 사용할 DB 데이터 정의

class Tag(models.Model):
    name = models.CharField(max_length=64, unique=True)
    bookmarks = models.ManyToManyField(Bookmark)
    def __unicode__(self):
        return self.name

2. views.py의 함수 정의

JQuery의 Autocomplete은 GET 방식으로 term이라는 키에 값을 넣어서 보냄 (예제 ?term="sns")

filter()에, istartswith을 사용하여 입력된 값으로 시작하는 문자열을 찾아내고, 결과값을 json 형태로 변경 (JQuery의 Autocomplete이 받아들이는 json 형태는 id, label, value로 구성됨)

import json
 
def ajax_tag_autocomplete(request):
    if request.GET.has_key('term'):
        tags = Tag.objects.filter(name__istartswith=request.GET['term'])[:10]
        results = []
        for tag in tags:
            tag_json = {}
            tag_json['id'= tag.id
            tag_json['label'= tag.name
            tag_json['value'= tag.name
            results.append(tag_json)
        data = json.dumps(results)
        mimetype = 'application/json'
        return HttpResponse(data, mimetype)
    return HttpResponse()

3. urls.py의 주소 정의

views.py의 함수와 주소를 연결

url(r'^ajax/tag/autocomplete/$', ajax_tag_autocomplete),

4. templates의 html 작성

JQuery의 Autocomplete는 input 또는 textarea 등에서 사용할 수 있고, Form 내부에 존재하는 input은 forms.py에서 작성될 수도 있음

<p>
    <label for="id_tags">태그:</label>
    <input id="id_tags" name="tags" size="64" type="text" class="ui-autocomplete-input" autocomplete="off">
</p>

5. templates의 JS 작성

Autocomplet의 source만 지정해주면, json 방식으로 데이터를 처리함

$(document).ready(function () {
    $('#id_tags').autocomplete({
        source: '/ajax/tag/autocomplete/',
    });
});

참고