[Django] redirect시에 데이터 넘기고 싶어요(feat. HTTP)

상황

페이스북 로그인을 하고, 페이스북 연결 해제버튼을 눌렀을 때 비밀번호가 설정이 되어있지 않다면 비밀번호 설정 페이지로 리다이렉트 시키고 싶다.
이로 인해서 들어온 비밀번호 설정 페이지에서는 그냥 들어왔을 때와는 다르게 “페이스북 연결을 해제하려면 비밀번호를 설정해주셔야 합니다”라는 안내멘트를 보여주고 싶은데, 이를 위해 리다이렉트 시켰을 때 데이터를 넘기고 싶다.

첫 시도

Django docs에서 redirect섹션을 보고,

def my_view(request):
    ...
    return redirect('some-view-name', foo='bar')

이렇게 뒤에 foo=’bar’로 넘기면 되겠다 생각했다. (허접이 스키밍을 했을때의 폐해) (여기서 뒤에 붙는 인자는 kwargs이다.)
하지만 인자는 넘어가지 않았고, ‘django pass data with redirect’ 키워드로 구글링을 해보았다.
스택오버플로우링크의 답변을 보았는데,

redirect is merely a wrapper around HttpResponseRedirect that automatically calls reverse for you to create the URL to redirect to. As a result, the parameters you pass to it, aren’t arbitrary, they must be same you would pass to reverse and, specifically, only those required to create the URL.

Many people seem to have troubles understanding that data can’t just be arbitrarily passed to a view. HTTP is a stateless protocol: each request exists on it’s own, as if user had never been to any other page of the site. The concept of a session was created to provide a sense of “state” to a cohesive unit such as a site. With sessions, data is stored in some form of persistent storage and a “key” to look up that data is given to the client (typically the user’s browser). On the next page load, the client sends the key back to the server, and the server uses it to look up the data to give the appearance of state.

As a result, if you need data from one view available in another, you need to add it to the session, do your redirect, and look up the data in the session from the next view.

내가 찾던 답변같은데 이해하기 쉽지 않았다.
대략 HTTP의 stateless한 특성으로 reverse로는 데이터를 넘길 수 없어, 다른 방안을 찾으라는 것 같아 또 이것 저것 읽어본 결과,
1. Session을 사용하여 넘기기
2. GET으로 뒤에 Parameter달아 넘기기
3. django messages 사용하기

정도의 방법이 있는데, 지금 상황에서 어떤 방법을 써야 좀 더 세련될지 조언을 구하고자 P모님께 질문을 하였다. 결론은 역시 나의 HTTP에 대한 이해부족에서 나온 삽질이었다는 것이다.

HTTP의 Stateless성

P모님: HTTP는 왜 상태가 없죠?
나: (???)

한 초밥집이 있다. 이 초밥집은
1. 손님이 30분간 밥을 먹을동안 주방장이 계속 붙어서 손님에게 초밥을 내어줌
2. 테이크아웃
두가지 방식으로 운영될 수 있다.
1은 서로가 서로의 상태를 바로바로 알 수 있지만 한 손님이 먹는동안 주방장이 다른 손님을 응대하지 못한다는 단점이 있다.
2는 서로의 상태를 유지하고 있지 않지만, 한 주방장이 여러 손님을 순차적으로 응대할 수 있다는 장점이 있다.

보통 채팅이나 게임같은 실시간성 서비스가 1의 형태, 웹은 2의 형태를 취한다.

만약 2 방식에서 주방장이 손님의 단골메뉴를 기억해야 한다 해보자.
주방장이 손님별로 단골메뉴를 적어두거나, 손님이 각자 단골메뉴 쿠폰을 들고있다가 주방장에게 주어 기억하는 방법이 있을것이다. session을 사용하는것이 여기에 속한다. 이 방식의 특징은 서버의 디비와 사용자의 브라우저에 데이터를 저장한다는 것이다. 굳이 세션에 저장할 필요 없는 데이터를 저장하면 불필요하게 디비를 한 번 더 다녀오는 비용이 들게 된다. 그리고 django의 messages도 세션을 사용해서 구현한 것이니 위의 3가지 방법 중 1은 3을 포함하게 된다.
그리고 2번, GET으로 뒤에 파라미터를 달아 넘기는 것은, 아예 처음부터 다른 주소로 들어가는 것이다. 지금 상황처럼 이 url자체로 사람들이 많이 들어올 걱정을 안 해도 된다면 충분히 GET으로 데이터를 넘겨도 되겠다.

참고 – 쿠키와 세션 개념

render, redirect, reverse의 차이

그렇다면 왜 redirect에서 데이터를 인자로 넘기는게 안되는 것일까? 이는 HTTP의 response때문이다.
HTTP의 requestPOST혹은 GET등으로 데이터를 뭍혀서 보낸다.
그리고 온 response엔 200, 404 등 많은 종류가 있다. 여기서 200 ok같은 경우는 헤더영역에 200 등이 써있고, 그 밑에 html문서가 오던지, 혹은 요청한 데이터들이 좌라락 오던지 한다. 하지만 300번대인 redirect는, 밑에 redirect될 주소만 띡 하고 온다. 데이터를 뭍힐 곳이 없는것이다. (참고: HTTP 상태 코드)
(참고: 301은 permanent한 이동, 302는 임시 이동)

그리고 redirect, reverse의 관계는 아래와 같다.

  • redirect(‘welcome’) : 원래 redirect('/some/url/')처럼 url을 주소로 써주는데, url name을 쓰면 redirect 내부에서 자동으로 reverse를 호출하여 이름을 매칭해서 보내준다.
  • reverse(‘welcome’): url name welcome을 찾아서 보내준다.
  • redirect(reverse(‘welcome’)): 즉 이렇게 안쓰고 그냥 redirect만 써도 된다.

render는 템플릿과 컨텍스트를 합쳐서 HttpResponse 오브젝트를 리턴한다. 여기서 합친다는 말은, 예를 들어 컨텍스트에서 {"foo": "bar"}를 넘겼다고 하면, html template에서 {{ foo }}를 찾아서 bar로 치환해 섞어서 HttpResponse로 리턴해준다는 말이다.

그래서 HttpResponseRedirect를 리턴하는 redirect()는 합칠 컨텍스트를 못 받는 것이다.
참고: [Django] HttpResponse VS HttpResponseRedirect

결론

HTTP 부들부들

Advertisements

angularjs http post 405 error

Problem

angular http로 data를 담아 post를 날렸다. 헤더엔 넣은 데이터의 json key value값이 잘 들어가있는데(사실 그런것처럼 보이는 것이다) 데이터가 오지 않았다는 에러가 난다.

$http.defaults.xsrfCookieName = 'csrftoken';
        $http.defaults.xsrfHeaderName = 'X-CSRFToken';

        $http.post('/api/stock/'+$('.stock-select').val()+'/'+operationName+'/', {
            inventory: $('.inventory-select').val(),
            quantity: $('.number-select').val()
        }, {
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
            }
        }).then(function(res){
            console.log("success", res);
        }, function() {
            alert("error");
        });

Solution

key value가 아닌, json을 통으로 인식해서 생기는 문제이므로, 데이터를 $.param으로 감싸서 보내준다. 앵귤러 웨저러는고야~~

$http.defaults.xsrfCookieName = 'csrftoken';
        $http.defaults.xsrfHeaderName = 'X-CSRFToken';

        $http.post('/api/stock/'+$('.stock-select').val()+'/'+operationName+'/', $.param({
            inventory: $('.inventory-select').val(),
            quantity: $('.number-select').val()
        }), {
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
            }
        }).then(function(res){
            console.log("success", res);
        }, function() {
            alert("error");
        });

참고

ajax로 보내기

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;
        }
        var csrftoken = getCookie('csrftoken');

        $.ajax({
            type: 'POST',
            url: '/api/stock/'+$('.stock-select').val()+'/'+operationName+'/',
            data: {
                inventory: $('.inventory-select').val(),
                quantity: $('.number-select').val()
            },
            headers: {
                "X-CSRFToken": csrftoken
            },
            success: function() {
                console.log("success")
            }
        });

Refer

http://stackoverflow.com/questions/27387467/angularjs-http-post-method-405-error