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가지 방식이 있다.
- BasicAuthentication : 테스트용으로만 사용한다. 헤더에 ID, Password를 스트링으로 전달하기 때문에 위험하다. 내용이 탈취당하면, 유저가 비밀번호를 바꿔야만 한다.
- SessionAuthentication : 장고를 이용해 통신할 때만 사용가능하다. 그래서 많이 사용하지는 않는다.
- TokenAuthentication : 주로 많이 사용하는 방식이다. 서버에서 토큰을 생성해 보내주고, 클라이언트가 토큰을 가지고 있다 인증에 사용하면 된다. 토큰은 탈취당해도 다시 발급하면 된다.
토큰 유효기간은 설정하기 나름이다. 일반적으로 로그아웃 시 토큰을 폐기한다. 필요에 따라, 각 디바이스에서 다르게 설정할 수 있다. 아이폰에서는 로그인 유지, 웹페이지에서는 로그아웃하는 등…
TokenAuthentication
request.user
가user
를 가져옴request.auth
가Token
을 가져옴Token
은user
와key
로 구성
settings.py
아래와 같이 설정 후 마이그레이션 해줘야 한다.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication'
)
}
INSTALLED_APPS = [
# ...
'rest_framework.authtoken',
]
토큰을 생성하는 방식은 3가지가 있다.
- By using signals : User가 생성될 때, 해당 User에 대한 토큰을 미리 생성한다.
- 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
를 확인할 수 있다.
과제
- DeleteToken View완성
- django-rest-auth패키지 붙여서 api login/logout구현
- 스크롤시 자동으로 PostList추가 로드
- PostCreate를 jQuery로 구현