정보
와 서비스
를 제공하는 컴퓨터 시스템class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
# articles/models.py
class Article(models.Model):
# user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# my_api/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('articles.urls')),
...
]
# articles/urls.py
urlpatterns = [
path('articles/', views.article_list),
path('articles/<int:article_pk>/', views.article_detail),
path('comments/', views.comment_list),
path('comments/<int:comment_pk>/', views.comment_detail),
path('articles/<int:article_pk>/comments/', views.comment_create),
...
]
# migrations 정보는 이미 있음
$ python manage.py migrate
# articles와 comments 동시 삽입 > N:1 관계 유지
$ python manage.py loaddata articles.json comments.json
views/ArticleView.vue
component 확인 및 route 등록// src/views/ArticleView.vue
<template>
<div>
<h1>Article Page</h1>
<hr>
</div>
</template>
<script>
export default {
name: 'ArticleView',
...,
}
</script>
// src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import ArticleView from '@/views/ArticleView'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'ArticleView',
component: ArticleView
},
]
...
src/App.vue
router-link 작성// src/App.vue
<template>
<div id="app">
<nav>
<router-link :to="{ name: 'ArticleView' }">Articles</router-link> |
</nav>
<router-view/>
</div>
</template>
components/ArticleList.vue
확인// components/ArticleList.vue
<template>
<div class="article-list">
<h3>Article List</h3>
</div>
</template>
<script>
export default {
name: 'ArticleList',
components: {
},
computed: {
}
}
</script>
<style>
.article-list {
text-align: start;
}
</style>
views/ArticleView.vue
에 ArticleList
하위 컴포넌트 등록// views/ArticleView.vue
<template>
<div>
<h1>Article Page</h1>
<ArticleList/>
<hr>
</div>
</template>
<script>
import ArticleList from '@/components/ArticleList.vue'
export default {
name: 'ArticleView',
components: {
ArticleList
},
computed:{
},
created() {
},
methods: {
}
}
</script>
components/ArticleListItem.vue
확인// components/ArticleListItem.vue
<template>
<div>
<h5>PK</h5>
<p>제목</p>
<hr>
</div>
</template>
<script>
export default {
name: 'ArticleListItem',
}
</script>
components/ArticleList.vue
에 ArticleListItem
하위컴포넌트 등록// components/ArticleList.vue
<template>
<div class="article-list">
<h3>Article List</h3>
<ArticleListItem/>
</div>
</template>
<script>
import ArticleListItem from '@/components/ArticleListItem.vue'
export default {
name: 'ArticleList',
components: {
ArticleListItem,
},
computed: {
}
}
</script>
store/index.js
의 state에 articles 배열 정의하고 화면 표현 체크용 데이터 생성// store/index.js
export default new Vuex.Store({
state: {
articles: [
{
id: 1,
title: '제목',
content: '내용'
},
{
id: 2,
title: '제목2',
content: '내용2'
},
],
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
components/ArticleList.vue
코드 수정// components/ArticleList.vue
<template>
<div class="article-list">
<h3>Article List</h3>
<ArticleListItem
v-for="article in articles"
:key="article.id"
:article="article"/>
</div>
</template>
<script>
import ArticleListItem from '@/components/ArticleListItem.vue'
export default {
name: 'ArticleList',
components: {
ArticleListItem,
},
computed: {
articles() {
return this.$store.state.articles
}
}
}
</script>
components/ArticleListItem.vue
수정// components/ArticleListItem.vue
<template>
<div>
<h5>{{ article.id }}</h5>
<p>{{ article.title }}</p>
<hr>
</div>
</template>
<script>
export default {
name: 'ArticleListItem',
props: {
article: Object
}
}
</script>
<style>
</style>
설치
$ npm install axios
store/index.js
에서 불러오기
- 요청 보낼 API server 도메인 변수에 담기
// store/index.js
import axios from 'axios'
const API_URL = 'http://127.0.0.1:8000'
getArticles
메서드 정의// store/index.js
export default new Vuex.Store({
...
actions: {
getArticles(context) {
axios({
method: 'get',
url: `${API_URL}/api/v1/articles/`,
})
.then(res =>
console.log(res, context)
)
.catch(err => console.log(err))
}
},
getArticles
actions 호출created()
hook 사용<script>
import ArticleList from '@/components/ArticleList.vue'
export default {
name: 'ArticleView',
components: {
ArticleList,
},
computed:{
},
created() {
this.getArticles()
},
methods: {
getArticles() {
this.$store.dispatch('getArticles')
}
}
}
</script>
동일 출처 정책(SOP)
에 의해 다른 출처의 리소스와 상호작용 하는 것을 제한 함URL의 Protocol, Host, Port를 모두 포함
하여 출처라고 부름HTTP Header
를 사용하여, 특정 출처에서 실행 중인 웹 어플리케이션이 다른 출처의 자원에 접근할 수 있는 권한
을 부여하도록 브라우저에 알려주는 체제CORS header
를 포함한 응답을 반환해야 함응답에 CORS header를 추가
해주는 라이브러리requirements.txt
업데이트$ pip install django-cors-headers
$ pip freeze > requirements.txt
App
추가 및 MIDDLEWARE
추가# my_api/settings.py
INSTALLED_APPS = [
...
# CORS policy
"corsheaders",
...
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
"corsheaders.middleware.CorsMiddleware",
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
CORS_ALLOWED_ORIGINS
에 교차 출처 자원 공유를 허용할 Domain 등록# my_api/settings.py
# 특정 Origin만 선택적으로 허용
CORS_ALLOWED_ORIGINS = [
'http://localhost:8080',
]
# 만약 모든 Origin을 허용하고자 한다면
# 모든 Origin 허용
CORS_ALLOW_ALL_ORIGINS = True
data Array
에 각 게시글 객체store/index.js
수정// store/index.js
export default new Vuex.Store({
state: {
articles: []
},
getters: {
},
mutations: {
GET_ARTICLES(state, articles) {
state.articles = articles
}
},
actions: {
getArticles(context) {
axios({
method: 'get',
url: `${API_URL}/api/v1/articles/`,
})
.then(res =>
// console.log(res, context)
context.commit('GET_ARTICLES', res.data)
)
.catch(err => console.log(err))
}
},
modules: {
}
})
v-model.trim
을 활용해 사용자 입력 데이터에서 공백 제거.prevent
를 활용해 form의 기본 이벤트 동작 막기// views/CreateView.vue
<template>
<div>
<h1>게시글 작성</h1>
<form @submit.prevent="createArticle">
<label for="title">제목 : </label>
<input type="text" id="title" v-model.trim="title"><br>
<label for="content">내용 : </label>
<textarea id="content" cols="30" rows="10" v-model="content"></textarea><br>
<input type="submit" id="submit">
</form>
</div>
</template>
alert
를 통해 경고창을 띄우고 AJAX 요청을 보내지 않도록 return
시켜 함수를 종료// views/CreateView.vue
export default {
name: 'CreateView',
data() {
return {
title: null,
content: null,
}
},
methods: {
createArticle() {
const title = this.title
const content = this.content
if (!title) {
alert('제목을 입력해주세요.')
return
} else if (!content) {
alert('내용을 입력해주세요')
return
}
}
}
}
// views/CreateView.vue
<script>
import axios from 'axios'
const API_URL = 'http://127.0.0.1:8000'
export default {
name: 'CreateView',
data() {
return {
title: null,
content: null,
}
},
methods: {
createArticle() {
const title = this.title
const content = this.content
if (!title) {
alert('제목을 입력해주세요.')
return
} else if (!content) {
alert('내용을 입력해주세요')
return
}
axios({
method : 'post',
url: `${API_URL}/api/v1/articles/`,
data: {title, content}
})
.then((res) => {
console.log(res)
})
.catch(err => console.log(err))
}
}
}
</script>
// router/index.js
import CreateView from '@/views/CreateView'
Vue.use(VueRouter)
const routes = [
...
{
path: '/create',
name: 'CreateView',
component: CreateView
},
]
// views/ArticleView.vue
<template>
<div>
<h1>Article Page</h1>
<router-link :to="{ name:'CreateView' }">
[CREATE]
</router-link>
<ArticleList/>
<hr>
</div>
</template>
// views/CreateView.vue
export default {
...
methods: {
...
axios({
method : 'post',
url: `${API_URL}/api/v1/articles/`,
data: {title, content}
})
.then(() => {
this.$router.push({name: 'ArticleView'})
})
.catch(err => console.log(err))
}
}
}
// views/DetailView.vue
<template>
<div>
<h1>Detail</h1>
</div>
</template>
<script>
export default {
name: 'DetailView',
data() {
},
created() {
},
methods: {
}
}
</script>
// router/index.js
...
import DetailView from '@/views/DetailView'
...
const routes = [
...
{
path: '/:id',
name: 'DetailView',
component: DetailView,
},
]
// components/ArticleListItem.vue
<template>
<div>
<h5>{{ article.id }}</h5>
<p>{{ article.title }}</p>
<router-link :to="{ name: 'DetailView', params: { id: article.id }}">
[DETAIL]
</router-link>
<hr>
</div>
</template>
this.$route.params
를 활용해 컴포넌트가 create될 때, 넘겨받은 id로 상세 정보 AJAX 요청// views/DetailView.vue
<script>
import axios from 'axios'
const API_URL = 'http://127.0.0.1:8000'
export default {
name: 'DetailView',
data() {
return{
article: null
}
},
created() {
this.getArticleDetail()
},
methods: {
getArticleDetail() {
axios({
method : 'get',
url: `${API_URL}/api/v1/articles/${this.$route.params.id}/`,
})
.then((res) => {
console.log(res)
})
.catch(err => console.log(err))
}
}
}
</script>
// views/DetailView.vue
<template>
<div>
<h1>Detail</h1>
<!-- ? : optional chaining -->
<p>글 번호 : {{ article?.id }}</p>
<p>제목 : {{ article?.title }}</p>
<p>내용 : {{ article?.content }}</p>
<p>작성시간 : {{ article?.created_at }}</p>
<p>수정시간 : {{ article?.updated_at }}</p>
</div>
</template>
<script>
import axios from 'axios'
const API_URL = 'http://127.0.0.1:8000'
export default {
name: 'DetailView',
data() {
return{
article: null
}
},
created() {
this.getArticleDetail()
},
methods: {
getArticleDetail() {
axios({
method : 'get',
url: `${API_URL}/api/v1/articles/${this.$route.params.id}/`,
})
.then((res) => {
// console.log(res)
this.article = res.data
})
.catch(err => console.log(err))
}
}
}
</script>
https://github.com/mjieun0956/TIL/tree/master/Vue/13.%20Vue%20with%20DRF/article-app