Javascript

동적으로 생성 (append)

curPost 값을 사용해서 div.content-container에
jQuery의 append 메서드를 사용해 새 HTML 요소를 동적으로 생성

app/index.html

$('.content-container').append('<div>' + curPost.pk + '</div>')

.fail(function(data) {
$('.content-container').append('<div>Fail</div>')
});

인덱스 페이지 동적으로 생성

app/index.html

$.ajax(url)
  .done(function(data) {
  // ajax 요청의 응답에서 next 값을 가져와 변수에 할당
  next = data.next;
  // 응답에 results를 results의 길이만큼 순회하며
  for(var i=0; i < data.results.length; i++) {
    // 매 loop마다 result[i]의 값을 curPost 변수에 할당
    var curPost = data.results[i];
    console.log(curPost.pk);

    // curPost 값을 사용해서 div.content-container에
    // jQuery의 append 메서드를 사용해 새 HTML 요소를 동적으로 생성

    // dummy 요소를 만들고 해당 요소를 clone()한 다음 clone()한 객체의 값을 바꾸는 방법도 가능
    var newDom = $('<article class="post"></article>');

    // article 요소의 header.post-header 생성
    newDom.append('<header class="post-header"></header>');
    var newDomHeader = newDom.find('header.post-header');
    newDomHeader.append('<span class="header-username"></span>');
    newDomHeader.append('<form action="" class="inline"></form>');
    newDomHeader.append('<span class="header-date"></span>');

    newDomHeader.find('span.header-username').text(curPost.author.username)

    // 문자열이 아닌 moment js를 사용해서 parse > format 한 형태를 대입
    var day = moment(curPost.created_date)
    var strDay = moment(day).format('YYYY년 M월 D일')
    var createDate = newDomHeader.find('span.header-date').text(strDay)

    // article 요소의 post-image-container 생성
    newDom.append('<div class="post-image-container"></div>');
    newDom.find('.post-image-container').append('<div class="swiper-container"></div>');
    newDom.find('.swiper-container').append('<div class="swiper-wrapper"></div>');
    var newDomSwiperWrapper = newDom.find('.swiper-wrapper');

    // article 요소의 post-bottom-container 생성
    newDom.append('<div class="post-bottom-container"></div>');

    $('.post-list-container').append(newDom);
  }
})
  .fail(function(data) {
  console.log('fail');
  console.log(data);
  $('.post-list-container').append('<div>Fail</div>')
});

시간 변환

$ bower install moment
$ bower init
<script src="../bower_components/jquery/dist/jquery.min.js"></script>
<!-- 추가 -->
<script src="../bower_components/moment/moment.js"></script>
<script src="../bower_components/swiper/dist/js/swiper.jquery.min.js"></script>
<script>

app/index.html

// 문자열이 아닌 moment js를 사용해서 parse > format 한 형태를 대입
var day = moment(curPost.created_date)
var strDay = moment(day).format('YYYY년 M월 D일')
var createDate = newDomHeader.find('span.header-date').text(strDay)

사진

// curPost.postphoto_set loop
// .swiper-wrapper 내부에 postphoto.photo 값을 사용해 .swiper-slide img 태그 생성
// img의 src 속성과 같이 속성을 변화 시킬 때는 .text() 대신 .attr('속성명', '속성값')을 사용
for (var j = 0; j < curPost.photo_list.length; j++) {
  var curPostPhoto = curPost.photo_list[j];
  // PostPhoto하나당 .swiper-slide > img만들고 src값을 PostPhoto의 photo속성으로 대입
  var newSwiperSlide = $('<div class="swiper-slide"></div>');
  newSwiperSlide.append('<img src="" alt="" />');
  newSwiperSlide.find('img').attr('src', curPostPhoto.photo);


  // 만들어진 swiper-slide를 swiper-wrapper내부에 append
  newDomSwiperWrapper.append(newSwiperSlide);
}

함수로 분리 + swiper 적용

<body>
  <div id=wrap>
       <div class="content-container">
         <div class="post-list-container">
         </div>
       </div>
       <button id="load-post-list">Load PostList</button>
   </div>
    <script src="../bower_components/jquery/dist/jquery.min.js"></script>
    <script src="../bower_components/moment/moment.js"></script>
    <script src="../bower_components/swiper/dist/js/swiper.jquery.min.js"></script>

    <script>
    $('#load-post-list').click(function(){
      loadPostList();
    })
    // loadPostList ajax 요청의 response data에 'next'값이 있으면 해당 값을 할당할 변수
    var next;

    // PostList GET 요청 API 함수
    function loadPostList() {
      // 첫 loadPostList API 요청 URL
      var url = 'http://localhost:8000/api/post/';

      // next 값이 있다면 요청 url은 해당 값을 사용
      if(next != undefined && next != null) {
        url = next;
      }
      console.log(url)
      $.ajax(url)
       .done(function(data) {
        // ajax 요청의 응답에서 next 값을 가져와 변수에 할당
         next = data.next;
        // 응답에 results를 results의 길이만큼 순회하며
         for(var i=0; i < data.results.length; i++) {
          //  console.log(data)
           // 매 loop마다 result[i]의 값을 curPost 변수에 할당
           var curPost = data.results[i];
           console.log(curPost.pk);
           addArticle(curPost)
         }
       })
       .fail(function(data) {
         console.log('fail');
         console.log(data);
         $('.post-list-container').append('<div>Fail</div>')
       });
    }
    function addArticle(curPost) {
      // curPost 값을 사용해서 div.content-container에
      // jQuery의 append 메서드를 사용해 새 HTML 요소를 동적으로 생성
      var newDom = $('<article class="post"></article>');
      // Post가 ID를 갖게됨
      newDom.attr('id', 'post-' + curPost.pk);

      // article 요소의 header.post-header 생성
      newDom.append('<header class="post-header"></header>');
      var newDomHeader = newDom.find('header.post-header');
      newDomHeader.append('<span class="header-username"></span>');
      newDomHeader.append('<form action="" class="inline"></form>');
      newDomHeader.append('<span class="header-date"></span>');

      newDomHeader.find('span.header-username').text(curPost.author.username)

      // 문자열이 아닌 moment js를 사용해서 parse > format 한 형태를 대입
      var day = moment(curPost.created_date)
      var strDay = moment(day).format('YYYY년 M월 D일')
      var createDate = newDomHeader.find('span.header-date').text(strDay)

      // article 요소의 post-image-container 생성
      newDom.append('<div class="post-image-container"></div>');
      newDom.find('.post-image-container').append('<div class="swiper-container"></div>');
      newDom.find('.swiper-container').append('<div class="swiper-wrapper"></div>');
      var newDomSwiperWrapper = newDom.find('.swiper-wrapper');

      // curPost.postphoto_set loop
      // .swiper-wrapper 내부에 postphoto.photo 값을 사용해 .swiper-slide img 태그 생성
      // img의 src 속성과 같이 속성을 변화 시킬 때는 .text() 대신 .attr('속성명', '속성값')을 사용
      for (var j = 0; j < curPost.photo_list.length; j++) {
        var curPostPhoto = curPost.photo_list[j];
        // PostPhoto하나당 .swiper-slide > img만들고 src값을 PostPhoto의 photo속성으로 대입
        var newSwiperSlide = $('<div class="swiper-slide"></div>');
        newSwiperSlide.append('<img src="" alt="" />');
        newSwiperSlide.find('img').attr('src', curPostPhoto.photo);


        // 만들어진 swiper-slide를 swiper-wrapper내부에 append
        newDomSwiperWrapper.append(newSwiperSlide);
      }

      // article 요소의 post-bottom-container 생성
      newDom.append('<div class="post-bottom-container"></div>');
      $('.post-list-container').append(newDom);

      // 새로만든 aritlce내부의 .swiper-container에 id값을 추가
      newDom.find('.swiper-container').attr('id', 'post-swiper-' + curPost.pk);
      // .swiper-container의 id로 Swiper초기화
      new Swiper('#post-swiper-' + curPost.pk);
    }
    </script>
</body>

DRF - Authentication

DRF의 request는 Django에서 사용하는 request와는 다르다. DRF의 request는 Django의 request를 상속받아 만든 것이다. request.auth는 추가하여 만든 정보이다.

인증에는 크게 2가지 방식이 있다.

  1. BasicAuthentication : 테스트용으로만 사용한다. 헤더에 ID, Password를 스트링으로 전달하기 때문에 위험하다. 내용이 탈취당하면, 유저가 비밀번호를 바꿔야만 한다.
  2. SessionAuthentication : 장고를 이용해 통신할 때만 사용가능하다. 그래서 많이 사용하지는 않는다.
  3. TokenAuthentication : 주로 많이 사용하는 방식이다. 서버에서 토큰을 생성해 보내주고, 클라이언트가 토큰을 가지고 있다 인증에 사용하면 된다. 토큰은 탈취당해도 다시 발급하면 된다.

토큰 유효기간은 설정하기 나름이다. 일반적으로 로그아웃 시 토큰을 폐기한다. 필요에 따라, 각 디바이스에서 다르게 설정할 수 있다. 아이폰에서는 로그인 유지, 웹페이지에서는 로그아웃하는 등…

TokenAuthentication

  • request.useruser를 가져옴
  • request.authToken을 가져옴
  • Tokenuserkey로 구성

settings.py

아래와 같이 설정 후 마이그레이션 해줘야 한다.

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
         'rest_framework.authentication.TokenAuthentication'
    )
}

INSTALLED_APPS = [
    # ...
    'rest_framework.authtoken',
]

토큰을 생성하는 방식은 3가지가 있다.

  1. By using signals : User가 생성될 때, 해당 User에 대한 토큰을 미리 생성한다.
  2. By exposing an api endpoint : 우리가 사용할 방식

member/urls/apis.py

from django.conf.urls import url
from rest_framework.authtoken import views as authtoken_views

from .. import apis

urlpatterns = [
    url(r'^token-auth/', authtoken_views.obtain_auth_token),
]

urls.py

from member.urls import apis as member_apis_urls


api_urlpatterns = [
    # ...
    url(r'^member/', include(member_apis_urls)),
]

포스트맨 확인

아래와 같이 요청해 응답으로 온 token을 확인한다.

POST 127.0.0.1:8000/api/member/token-auth/
Body form-data
	username : <your_username>
	password : <your_password>

위에서 생성한 토큰을 이용해 아래와 같이 요청하면, 포스트가 생성된 것을 확인 할 수 있다.

POST 127.0.0.1:8000/api/post/
Header
	Authorization : Token <생성된 토큰>

해당 유저에게만 프로필 페이지 노출(+ 코드 분리)

member/apis/user.py

from rest_framework import permissions
from rest_framework.response import Response
from rest_framework.views import APIView

from member.serializer import UserSerializer

__all__ = (
    'ProfileView',
)


class ProfileView(APIView):
    permission_classes = (permissions.IsAuthenticated,)

    def get(self, request, format=None):
        user = request.user
        serializer = UserSerializer(user)
        return Response(serializer.data)

member/urls/apis.py

from django.conf.urls import url
from rest_framework.authtoken import views as authtoken_views

from .. import apis

urlpatterns = [
    url(r'^token-auth/', authtoken_views.obtain_auth_token),
    url(r'^profile/$', apis.ProfileView.as_view()),
]

포스트맨 확인

요청

GET 127.0.0.1:8000/api/member/profile/
Header
	Authorization : Token <생성된 토큰>

응답

{
  "pk": 1,
  "username": "hm"
}

Javascript

파일 분리

app/index.html

<!-- Custom Script, 불러오는 순서 중요 -->
<script src="static/js/cookie.js"></script>
<script src="static/js/dom-utils.js"></script>
<script src="static/js/apis.js"></script>

<script>
  $('#load-post-list').click(function(){
    loadPostList();
  });
</script>

app/static/js/apis.js

// loadPostList ajax 요청의 response data에 'next'값이 있으면 해당 값을 할당할 변수
var next;

// PostList GET 요청 API 함수
function loadPostList() {
  // 첫 loadPostList API 요청 URL
  var url = 'http://localhost:8000/api/post/';

  // next 값이 있다면 요청 url은 해당 값을 사용
  if(next != undefined && next != null) {
    url = next;
  }
  console.log(url)
  $.ajax(url)
   .done(function(data) {
    // ajax 요청의 응답에서 next 값을 가져와 변수에 할당
     next = data.next;
    // 응답에 results를 results의 길이만큼 순회하며
     for(var i=0; i < data.results.length; i++) {
      //  console.log(data)
       // 매 loop마다 result[i]의 값을 curPost 변수에 할당
       var curPost = data.results[i];
      //  console.log(curPost.pk);
       addArticle(curPost)
     }
   })
   .fail(function(data) {
     console.log('fail');
     console.log(data);
     $('.content-container').append('<div>Fail</div>')
   });
}

app/static/js/dom-utils.js

function addArticle(curPost) {
  // curPost 값을 사용해서 div.content-container에
  // jQuery의 append 메서드를 사용해 새 HTML 요소를 동적으로 생성
  var newDom = $('<article class="post"></article>');
  // Post가 ID를 갖게됨
  newDom.attr('id', 'post-' + curPost.pk);

  // article 요소의 header.post-header 생성
  newDom.append('<header class="post-header"></header>');
  var newDomHeader = newDom.find('header.post-header');
  newDomHeader.append('<span class="header-username"></span>');
  newDomHeader.append('<form action="" class="inline"></form>');
  newDomHeader.append('<span class="header-date"></span>');

  newDomHeader.find('span.header-username').text(curPost.author.username)

  // 문자열이 아닌 moment js를 사용해서 parse > format 한 형태를 대입
  var day = moment(curPost.created_date)
  var strDay = moment(day).format('YYYY년 M월 D일')
  var createDate = newDomHeader.find('span.header-date').text(strDay)

  // article 요소의 post-image-container 생성
  newDom.append('<div class="post-image-container"></div>');
  newDom.find('.post-image-container').append('<div class="swiper-container"></div>');
  newDom.find('.swiper-container').append('<div class="swiper-wrapper"></div>');
  var newDomSwiperWrapper = newDom.find('.swiper-wrapper');

  // curPost.postphoto_set loop
  // .swiper-wrapper 내부에 postphoto.photo 값을 사용해 .swiper-slide img 태그 생성
  // img의 src 속성과 같이 속성을 변화 시킬 때는 .text() 대신 .attr('속성명', '속성값')을 사용
  for (var j = 0; j < curPost.photo_list.length; j++) {
    var curPostPhoto = curPost.photo_list[j];
    // PostPhoto하나당 .swiper-slide > img만들고 src값을 PostPhoto의 photo속성으로 대입
    var newSwiperSlide = $('<div class="swiper-slide"></div>');
    newSwiperSlide.append('<img src="" alt="" class="post-image" />');
    newSwiperSlide.find('img').attr('src', curPostPhoto.photo);


    // 만들어진 swiper-slide를 swiper-wrapper내부에 append
    newDomSwiperWrapper.append(newSwiperSlide);
  }

  // article 요소의 post-bottom-container 생성
  newDom.append('<div class="post-bottom-container"></div>');
  $('.post-list-container').append(newDom);

  // 새로만든 aritlce내부의 .swiper-container에 id값을 추가
  newDom.find('.swiper-container').attr('id', 'post-swiper-' + curPost.pk);
  // .swiper-container의 id로 Swiper초기화
  new Swiper('#post-swiper-' + curPost.pk);
}

토큰 받기

app/static/js/apis.js

function obtainAuthToken(username, password) {
  url = 'http://localhost:8000/api/member/token-auth/';
  $.ajax({
    url: url,
    method: 'POST',
    data: {
      username: username,
      password: password,
    }
  })
  .done(function(data){
    var token = data.token;
    console.log(token)
  })
  .fail(function(data){
  });
}

app/index.html

<script>
$('#load-post-list').click(function(){
loadPostList();
});
obtainAuthToken('<username>', '<password>');
</script>

페이지가 로드될 때, API 토큰을 받아온다. 토큰을 개발자도구 - Console에서 확인할 수 있다. 받아온 토큰을 저장하기 위해서 쿠키를 사용해야 한다.

쿠키에 토큰 저장

app/static/js/cookie.js

function setCookie(name, value, expireDays) {
  var exdate=new Date();
  exdate.setDate(exdate.getDate() + expireDays);
  var c_value=escape(value) + ((expireDays==null) ? "" : "; expires="+exdate.toUTCString());
  document.cookie=name + "=" + c_value;
}

app/static/js/apis.js

function obtainAuthToken(username, password) {
  // ...
  .done(function(data){
    var token = data.token;
    // console.log(token)
    setCookie('instagramToken', token);
  })
  .fail(function(data){
  });
}

페이지를 다시 로드한 후, 개발자 도구 - Application - Storage - Cookies에서 확인할 수 있다.

쿠키에 저장한 토큰을 이용해 포스트 작성

app/static/js/apis.js

function postCreate() {
  var url = 'http://localhost:8000/api/post/';
  var token = 'Token ' + getCookie('instagramToken');
  console.log(token);
  $.ajax({
    url: url,
    method: 'POST',
    headers: {
      Authorization: token,
    }
  })
  .done(function(data){
    console.log(data);
  });
}

app/static/js/cookie.js

function getCookie(c_name) {
	var i,x,y,ARRcookies=document.cookie.split(";");
	for (i=0;i<ARRcookies.length;i++) {
	  x=ARRcookies[i].substr(0,ARRcookies[i].indexOf("="));
	  y=ARRcookies[i].substr(ARRcookies[i].indexOf("=")+1);
	  x=x.replace(/^\s+|\s+$/g,"");

	  if (x==c_name) {
			return unescape(y);
		}
	}
}

app/index.html

<script>
    // ...
    postCreate();
    </script>

페이지를 로드하자마자 포스트가 생성된다. 개발자도구 - Console에서도 Object를 확인할 수 있다.

과제

  1. DeleteToken View완성
  2. django-rest-auth패키지 붙여서 api login/logout구현
  3. 스크롤시 자동으로 PostList추가 로드
  4. PostCreate를 jQuery로 구현