todo 앱을 생성해주고, 초기설정(superuser)을 해준 뒤
1. 모델 만들기
# todo/models.py
from django.db import models
class Todo(models.Model):
title = models.CharField(max_length=100)
completed = models.BooleanField()
def __str__(self):
return self.title
python manage.py makemigrations
python manage.py migrate
2. 관리자 페이지 등록하기
# todo/admin.py
from django.contrib import admin
from .models import Todo
class TodoAdmin(admin.ModelAdmin):
list_display = ['title','completed']
admin.site.register(Todo, TodoAdmin)
3.뷰 만들기
# todo/views.py
from django.shortcuts import render
from django.http import JsonResponse
from todo.models import Todo
# 저장 된 모든 할 일 데이터를 불러온 후에 하나씩 json 데이터로 가공할 수 있게 사전형 데이터로 저장
# 마지막으로 JsonResponse 형태로 데이터를 반환하면 Vue.js앱에서 데이터를 받아서 보여주게된다.
def todo_fetch(request): # 목록 불러오기
todos = Todo.objects.all()
todo_list = []
for index, todo in enumerate(todos, start=1): # eumerate는 반복문 사용 시 몇 번째 반복문인지 돌려준다.
todo_list.append({'id':index,'title':todo.title,'completed':todo.completed})
return JsonResponse(todo_list, safe=False)
import json
from django.views.decorators.csrf import csrf_exempt
from .forms import TodoForm
@csrf_exempt
def todo_save(request): #할 일 목록 전체 데이털르 받아서 그대로 저장하는 역할을 한다.
if request.body:
data = json.loads(request.body)
if 'todos' in data: #3 그런데 뷰를 호출 할 때 마다 데이터 확인 없이 데이터를 지우게되면 문제가 발생함으로 확인
todos = data['todos']
Todo.objects.all().delete() #2 전체 데이터를 지우고 다시 입력하는 방식 사용
for todo in todos:
print('todo', todo)
form = TodoForm(todo) # 데이터를 저장
if form.is_valid():
form.save() # DB에 저장
return JsonResponse({})
4. 폼 생성
# todo/forms.py
from django import forms
from .models import Todo
class TodoForm(forms.ModelForm):
class Meta:
model = Todo
fields = ['title','completed']
5. URL 연결하기
# config/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('todo.urls'))
]
# todo/urls.py
from django.urls import path
from .views import *
urlpatterns = [
path('fetch/', todo_fetch, name='fetch'),
path('save/', todo_save, name='save'),
]
fetch/ 로 접근하면 미리 관리자 페이지를 통해 만들어둔 할 일 목록이 나타난다.
save/ 로 접근하면 비어있는 사전형 데이터 하나만 나온다.
todo_save 뷰는 POST 메서드로 데이터를 전송해야만 제대로 기능을 하기 때문입니다.
이런 비어있는 뷰를 보여주기 싫고 POST 멧더르르 사용했을 때만 뷰가 동작하도록 하고 싶다면 데코레이터를 이용해서 제약 조건을 만들 수 있습니다.
# todo/urls.py
< 이전 코드 그대로 >
from django.views.decorators.http import require_POST
from .forms import TodoForm
@csrf_exempt
@require_POST # POST 메서드로 접근했을 때만 동작
def todo_save(request):
if request.body:
data = json.loads(request.body)
if 'todos' in data:
todos = data['todos']
Todo.objects.all().delete()
for todo in todos:
form = TodoForm(todo)
if form.is_valid():
form.save()
return JsonResponse({})
6. Vue.js 템플릿 적용
<!-- templates/base.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}Django Todo with Vue.js{% endblock %}</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" type="text/css" href="https://unpkg.com/todomvc-app-css@2.0.6/index.css">
</head>
<body>
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<input type="text" class="new-todo"
autofocus autocomplete="off"
placeholder="What needs to be done?"
v-model="newTodo"
@keyup.enter=addTodo">
</header>
{% block content %}
{% endblock %}
</section>
<footer class="info">
<p>Double-click to edit a todo</p>
<p>Written by <a href="http://evanyou.me">Evan You</a></p>
<p>Edited by <a href="https://github.com/2044smile">2044smile</a>Ver. Django</p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
{% block extra_script_footer %}
{% endblock %}
</body>
</html>
<!-- todo/templates/todo/list.html -->
{% extends 'base.html' %}
{% block content %}
<section class="main" v-show="todos.length" v-cloak>
<input type="checkbox" class="toggle-all" v-model="allDone">
<ul class="todo-list">
<li v-for="todo in filteredTodos"
class="todo"
:key="todo.id"
:class="{ completed: todo.completed, editing: todo == editedTodo}">
<div class="view">
<input type="checkbox" class="toggle" v-model="todo.completed">
<label @dblclick="editTodo(todo)">[[ todo.title ]]</label>
<button class="destroy" @click="removeTodo(todo)"></button>
</div>
<input type="text" class="edit"
v-model="todo.title"
v-todo-focus="todo == editedTodo"
@blur="doneEdit(todo)"
@keyup.enter="doneEdit(todo)"
@keyup.esc="cancelEdit(todo)">
</li>
</ul>
</section>
<footer class="footer" v-show="todos.length" v-cloak>
<span class="todo-count">
<strong>[[ remaining ]]</strong> [[ remaining | pluralize ]] left
</span>
<ul class="filters">
<li><a href="#/all" :class="{ selected: visibility == 'all' }">All</a></li>
<li><a href="#/active" :class="{ selected : visibility == 'active' }">Active</a></li>
<li><a href="#/completed" :class="{ selected: visibility == 'completed'">Completed</a></li>
</ul>
<button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">Clear completed</button>
</footer>
{% endblock %}
{% block extra_script_footer %}
{% load static %}
<script src="{% static 'js/scripts.js' %}"></script>
{% endblock %}
7. 정적 파일 적용
// todo/static/js/scripts.js
// Full spec-compliant TodoMVC with localStorage persistence
// and hash-based routing in ~120 effective lines of JavaScript.
// localStorage persistence
Vue.prototype.$http = axios;
// axios라는 HTTp 클라이언트 라이브러리 입니다. Promise 베이스이기 때문에 사용하기 까다롭지만
// Vue.js의 HTTP 클라이언트로 가장 많이 사용됩니다. axios를 Vue에서 기본으로 사용하도록 prototype에 추가
var todoStorage = {
// 가장 많은 변경이 있는 부분입니다. 원래 코드는 데이터를 웹 스토리지에 저장하는데 이를 장고를 통해
// 데이터베이스에 저장하고 꺼내와야 하기 때문에 axios를 사용해서 데이터를 저장하고 Vue.js의 기본 통신
// 메서드를 사용해 데이터를 얻어오도록 코드를 변경햇습니다. Promise 패턴을 사용하는 fetch부분은
// 자바스크립트가 익숙하지 않으신 분들에게는 어려울 수 있습니다.
fetch: function (app) {
app.fetch('fetch/').then((response) => {
return response.json();
})
.then((todos) => {
todos.forEach(function (todo, index) {
todo.id = index
});
this.uid = todos.length
app.app.todos = todos;
});
return []
},
save: function (todos) {
app.$http.post('save/',{todos:todos});
}
}
// visibility filters
var filters = {
all: function (todos) {
return todos
},
active: function (todos) {
return todos.filter(function (todo) {
return !todo.completed
})
},
completed: function (todos) {
return todos.filter(function (todo) {
return todo.completed
})
}
}
// app Vue instance
var app = new Vue({
// Vus.js 앱을 작성합니다.
// app initial state
delimiters: ['[[', ']]'], // 장고 템플릿 엔진과 충돌을 막기 위해 데이터를 적용하는 부분을 [[ ]] 로 변경
data: {
todos: todoStorage.fetch(this),
newTodo: '',
editedTodo: null,
visibility: 'all'
},
// watch todos change for localStorage persistence
watch: {
todos: {
handler: function (todos) {
todoStorage.save(todos)
},
deep: true
}
},
// computed properties
// http://vuejs.org/guide/computed.html
computed: {
filteredTodos: function () {
return filters[this.visibility](this.todos)
},
remaining: function () {
return filters.active(this.todos).length
},
allDone: {
get: function () {
return this.remaining === 0
},
set: function (value) {
this.todos.forEach(function (todo) {
todo.completed = value
})
}
}
},
filters: {
pluralize: function (n) {
return n === 1 ? 'item' : 'items'
}
},
// methods that implement data logic.
// note there's no DOM manipulation here at all.
methods: {
addTodo: function () {
var value = this.newTodo && this.newTodo.trim()
if (!value) {
return
}
this.todos.push({
id: todoStorage.uid++,
title: value,
completed: false
})
this.newTodo = ''
},
removeTodo: function (todo) {
this.todos.splice(this.todos.indexOf(todo), 1)
},
editTodo: function (todo) {
this.beforeEditCache = todo.title
this.editedTodo = todo
},
doneEdit: function (todo) {
if (!this.editedTodo) {
return
}
this.editedTodo = null
todo.title = todo.title.trim()
if (!todo.title) {
this.removeTodo(todo)
}
},
cancelEdit: function (todo) {
this.editedTodo = null
todo.title = this.beforeEditCache
},
removeCompleted: function () {
this.todos = filters.active(this.todos)
}
},
// a custom directive to wait for the DOM to be updated
// before focusing on the input field.
// http://vuejs.org/guide/custom-directive.html
directives: {
'todo-focus': function (el, binding) {
if (binding.value) {
el.focus()
}
}
}
})
// handle routing
function onHashChange () {
var visibility = window.location.hash.replace(/#\/?/, '')
if (filters[visibility]) {
app.visibility = visibility
} else {
window.location.hash = ''
app.visibility = 'all'
}
}
window.addEventListener('hashchange', onHashChange)
onHashChange()
// mount
app.$mount('.todoapp')
8. 동작하는 뷰 만들기
# todo/views.py
from django.shortcuts import render
def index(request):
return render(request, 'todo/list.html')
index 뷰는 list.html만 렌더링해서 보여주는 역할을 합니다. 실제 화면은 프론트 코드가 모두 로드된 후에 Vue.js가 화면을 렌더링 합니다. 그리고 데이터를 주고 받기 위해 todo_fetch와 todo_save 뷰를 호출하면서 애플리케이션이 동작하는 형태 입니다.
index 뷰도 URL을 연결해줍시다.
# todo/urls.py
urlpatterns = [
path('', index, name='index'),
]
'Web > Django' 카테고리의 다른 글
Django - Nginx설치 배포 (0) | 2019.08.27 |
---|---|
Django - Slug (0) | 2019.08.23 |
Django - 소셜 로그인 추가하기(allauth, naver) (0) | 2019.08.17 |
Django - Form 커스터마이징(CreateView, UpdateView) (0) | 2019.08.14 |
Django - Form(함수형,클래스형) (PyCon Korea) (0) | 2019.08.09 |
댓글