Backend + Frontend
- Vue에서 사용자에게 데이터를 보여주는 과정은
- HTML 뼈대 생성 ⇒ AJAX를 활용하여 Django Server에 데이터 요청 ⇒ 받은 데이터를 이용해 화면 렌더링
[실습 준비]
- 주어진 zip파일을 압축 해제한다. Skeleton을 활용한다.
- Django(기본 세팅)
- venv생성
$ pip install -r requirements.txt
- articles/views.py
from .models import Article, Comment
from .serializers import ArticleSerializer, CommentSerializer
from rest_framework.response import Response
from rest_framework.decorators import api_view
from accounts.serializers import UserSerializer
@api_view(['GET', 'POST'])
def article_list(request):
if request.method == 'GET':
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)
elif request.method == 'POST':
data = request.data
serializer = ArticleSerializer(data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=201)
@api_view(['GET', 'PUT', 'DELETE'])
def article_detail(request, id):
if request.method == 'GET':
article = Article.objects.get(id=id)
serializer = ArticleSerializer(article)
return Response(serializer.data)
elif request.method == 'PUT':
article = Article.objects.get(id=id)
data = request.data
serializer = ArticleSerializer(article, data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data)
elif request.method == 'DELETE':
article = Article.objects.get(id=id)
article.delete()
return Response(status=204)
@api_view(['POST'])
def comment_list(request, id):
data = request.data
article = Article.objects.get(id=id)
serializer = CommentSerializer(data=data)
if serializer.is_valid(raise_exception=True):
serializer.save(article=article)
return Response(serializer.data)
@api_view(['PUT', 'DELETE'])
def comment_detail(request, article_id, comment_id):
comment = Comment.objects.get(id=comment_id)
if request.method == 'PUT':
data = request.data
serializer = CommentSerializer(comment, data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data)
elif request.method == 'DELETE':
comment.delete()
return Response(status=204)
- articles/urls.py
from django.urls import path
from articles import views
urlpatterns = [
path('', views.article_list),
path('<int:id>/',views.article_detail),
path('<int:id>/comments/', views.comment_list),
path('<int:article_id>/comments/<int:comment_id>/', views.comment_detail),
]
- articles/serializers.py
from rest_framework import serializers
from .models import Article, Comment
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ("id", "article", "content")
extra_kwargs = {"article": {"read_only": True}}
class ArticleSerializer(serializers.ModelSerializer):
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ("id", "content")
comment_set = CommentSerializer(many=True, required=False)
class Meta:
model = Article
fields = ['id', 'title', 'content', 'comment_set']
- articles/models.py
from django.db import models
from django.conf import settings
from accounts.models import User
# Create your models here.
class Article(models.Model):
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.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
- Vue
- npm install
- 차후 개인 프로젝트를 진행할 때는 axios를 따로 다운받아야 한다.
$ npm install axios
- App.vue
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<header>
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink :to="{name : 'articles'}">Articles</RouterLink>
<RouterLink :to="{name : 'articles-create'}">Create</RouterLink>
</nav>
</header>
<RouterView />
</template>
<style scoped>
header{
margin-bottom: 3rem;
}
</style>
- main.js
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')
- stores/articles.js
import { ref } from 'vue'
import { defineStore } from 'pinia'
import axios from 'axios'
export const useArticleStore = defineStore('article', () => {
return { }
})
- router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import ArticleView from '../views/ArticleView.vue'
import ArticleCreateView from '../views/ArticleCreateView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/articles',
name: 'articles',
component: ArticleView
},
{
path: '/articles/create',
name: 'articles-create',
component: ArticleCreateView
},
]
})
export default router
[실습]
- ArticleView에서 Django에게 Articles에 대한 데이터를 가져와 보여주자.
- django, vue 서버 실행
- django
$ python manage.py runserver
- vue
$ npm run dev
- store에 Article에 대한 변수 및 가져오는 함수 정의하기
- stores/articles.js
import { ref } from 'vue'
import { defineStore } from 'pinia'
import axios from 'axios'
export const useArticleStore = defineStore('article', () => {
const articles = ref([])
function fetchArticles() {
axios({
url : 'http://127.0.0.1:8000/articles/'
})
.then(response => {
console.log(response.data)
articles.value = response.data
})
.catch(error => console.error(error))
}
return { articles, fetchArticles}
})
- ArticleView에서 store에 정의된 fetchArticles 실행하기
- 버튼을 클릭한다.
<template>
<div>
<button @click="fetchArticles">articles 가져오기</button>
</div>
</template>
<script setup>
import { useArticleStore } from '/src/stores/articles'
const articleStore = useArticleStore()
function fetchArticles() {
articleStore.fetchArticles()
}
</script>
CORS
- CORS(Cross-Origin Resource Sharing)는 다른 도메인, 프로토콜, 또는 포트를 가진 리소스에 대한 웹 페이지의 요청이 보안 상의 이유로 차단될 때 발생한다.
- 이는 웹 브라우저가 동일 출처 정책(Same-Origin Policy)을 강제하기 때문인데, 이 정책은 웹 보안의 핵심 원칙 중 하나로 웹 애플리케이션을 다양한 웹 기반 공격으로부터 보호하는 역할을 한다.
- 동일 출처 정책이란, 같은 도메인을 가진 웹페이지의 요청만 받아드리는, 즉 자기 자신에 대한 요청만 허용하는 보안 정책이다.
- 즉, 출처를 모르는 client에 대해서 server가 정보를 주지 않는 보안 정책이다.
django-cors-headers
django-cors-headers
django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS).
pypi.org
- django 서버의 설정을 변경하여 다른 웹페이지의 요청을 받아드릴 수 있다.
$ pip install django-cors-headers
- settings.py
INSTALLED_APPS = [
...,
"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_ALLOW_ALL_ORIGINS = true
# 허용 가능한 출처를 정의한다.
# CORS_ALLOWED_ORIGINS = [
# "https://example.com",
# "https://sub.example.com",
# "http://localhost:8080",
# "http://127.0.0.1:9000",
# ]
- 다시 버튼을 클릭하면 응답이 정상적으로 도착한다.
- Article.view에서 articles 보여주기
- views/ArticleView.vue
<template>
<div>
<button @click="fetchArticles">articles 가져오기</button>
<ul>
<li v-for="article in articleStore.articles">
<h2>{{ article.title }}</h2>
<p>{{ article.content }}</p>
</li>
</ul>
</div>
</template>
<script setup>
import { useArticleStore } from '/src/stores/articles'
const articleStore = useArticleStore()
function fetchArticles() {
articleStore.fetchArticles()
}
</script>
- 일반적인 사이트는 버튼을 눌러 게시글을 가져오지 않고 페이지에 접속했을 때 게시글이 자동으로 보여진다.
onMounted()
- 컴포넌트가 렌더링된 후 input에 정의된 함수를 실행시킨다.
- 데이터를 가져오는 로직에 주로 사용된다.
- views/ArticleView.vue
<template>
<div>
<button @click="fetchArticles">articles 가져오기</button>
<ul>
<li v-for="article in articleStore.articles">
<h2>{{ article.title }}</h2>
<p>{{ article.content }}</p>
</li>
</ul>
</div>
</template>
<script setup>
import { useArticleStore } from '/src/stores/articles'
import { onMounted } from 'vue'
const articleStore = useArticleStore()
function fetchArticles() {
articleStore.fetchArticles()
}
onMounted(() => {
fetchArticles()
})
</script>
<style lang="scss" scoped>
</style>
게시글 작성
- store에 Article에 게시글을 작성하는 함수 정의하기
- stores/articles.js
import { ref } from 'vue'
import { defineStore } from 'pinia'
import axios from 'axios'
export const useArticleStore = defineStore('article', () => {
const articles = ref([])
function fetchArticles() {
axios({
url : 'http://127.0.0.1:8000/articles/'
})
.then(response => {
console.log(response.data)
articles.value = response.data
})
.catch(error => console.error(error))
}
function createArticle(data) {
axios({
url : 'http://127.0.0.1:8000/articles/',
method : 'POST',
data : data
})
}
return { articles, fetchArticles, createArticle}
})
- ArticleCreateView.vue 수정
- form을 사용해 data를 입력받은 뒤 store에 정의한 createArticle에 data를 전달하여 실행시킨다.
<template>
<div>
<form @submit.prevent="createArticle">
<label for="title">title : </label>
<input type="text" name="title" id="title" v-model="data.title"><br>
<label for="content">content : </label>
<textarea name="content" id="content" cols="30" rows="10" v-model="data.content"></textarea><br>
<button type="submit">submit</button>
</form>
</div>
</template>
<script setup>
import { useArticleStore } from '/src/stores/articles'
import { reactive } from 'vue'
const articleStore = useArticleStore()
const data = reactive({
title: '',
content: ''
})
function createArticle() {
articleStore.createArticle(data)
}
</script>
- 데이터가 정상적으로 작성된다.
'Vue' 카테고리의 다른 글
Vue library : Vue Router (0) | 2024.03.28 |
---|---|
Pinia (0) | 2024.03.27 |
Vue 실습(2) (0) | 2024.03.27 |
Vue : Component, Props, Emit (0) | 2024.03.27 |
Vue 실습(1) (0) | 2024.03.27 |
Backend + Frontend
- Vue에서 사용자에게 데이터를 보여주는 과정은
- HTML 뼈대 생성 ⇒ AJAX를 활용하여 Django Server에 데이터 요청 ⇒ 받은 데이터를 이용해 화면 렌더링
[실습 준비]
- 주어진 zip파일을 압축 해제한다. Skeleton을 활용한다.
- Django(기본 세팅)
- venv생성
$ pip install -r requirements.txt
- articles/views.py
from .models import Article, Comment
from .serializers import ArticleSerializer, CommentSerializer
from rest_framework.response import Response
from rest_framework.decorators import api_view
from accounts.serializers import UserSerializer
@api_view(['GET', 'POST'])
def article_list(request):
if request.method == 'GET':
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)
elif request.method == 'POST':
data = request.data
serializer = ArticleSerializer(data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=201)
@api_view(['GET', 'PUT', 'DELETE'])
def article_detail(request, id):
if request.method == 'GET':
article = Article.objects.get(id=id)
serializer = ArticleSerializer(article)
return Response(serializer.data)
elif request.method == 'PUT':
article = Article.objects.get(id=id)
data = request.data
serializer = ArticleSerializer(article, data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data)
elif request.method == 'DELETE':
article = Article.objects.get(id=id)
article.delete()
return Response(status=204)
@api_view(['POST'])
def comment_list(request, id):
data = request.data
article = Article.objects.get(id=id)
serializer = CommentSerializer(data=data)
if serializer.is_valid(raise_exception=True):
serializer.save(article=article)
return Response(serializer.data)
@api_view(['PUT', 'DELETE'])
def comment_detail(request, article_id, comment_id):
comment = Comment.objects.get(id=comment_id)
if request.method == 'PUT':
data = request.data
serializer = CommentSerializer(comment, data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data)
elif request.method == 'DELETE':
comment.delete()
return Response(status=204)
- articles/urls.py
from django.urls import path
from articles import views
urlpatterns = [
path('', views.article_list),
path('<int:id>/',views.article_detail),
path('<int:id>/comments/', views.comment_list),
path('<int:article_id>/comments/<int:comment_id>/', views.comment_detail),
]
- articles/serializers.py
from rest_framework import serializers
from .models import Article, Comment
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ("id", "article", "content")
extra_kwargs = {"article": {"read_only": True}}
class ArticleSerializer(serializers.ModelSerializer):
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ("id", "content")
comment_set = CommentSerializer(many=True, required=False)
class Meta:
model = Article
fields = ['id', 'title', 'content', 'comment_set']
- articles/models.py
from django.db import models
from django.conf import settings
from accounts.models import User
# Create your models here.
class Article(models.Model):
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.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
- Vue
- npm install
- 차후 개인 프로젝트를 진행할 때는 axios를 따로 다운받아야 한다.
$ npm install axios
- App.vue
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<header>
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink :to="{name : 'articles'}">Articles</RouterLink>
<RouterLink :to="{name : 'articles-create'}">Create</RouterLink>
</nav>
</header>
<RouterView />
</template>
<style scoped>
header{
margin-bottom: 3rem;
}
</style>
- main.js
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')
- stores/articles.js
import { ref } from 'vue'
import { defineStore } from 'pinia'
import axios from 'axios'
export const useArticleStore = defineStore('article', () => {
return { }
})
- router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import ArticleView from '../views/ArticleView.vue'
import ArticleCreateView from '../views/ArticleCreateView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/articles',
name: 'articles',
component: ArticleView
},
{
path: '/articles/create',
name: 'articles-create',
component: ArticleCreateView
},
]
})
export default router
[실습]
- ArticleView에서 Django에게 Articles에 대한 데이터를 가져와 보여주자.
- django, vue 서버 실행
- django
$ python manage.py runserver
- vue
$ npm run dev
- store에 Article에 대한 변수 및 가져오는 함수 정의하기
- stores/articles.js
import { ref } from 'vue'
import { defineStore } from 'pinia'
import axios from 'axios'
export const useArticleStore = defineStore('article', () => {
const articles = ref([])
function fetchArticles() {
axios({
url : 'http://127.0.0.1:8000/articles/'
})
.then(response => {
console.log(response.data)
articles.value = response.data
})
.catch(error => console.error(error))
}
return { articles, fetchArticles}
})
- ArticleView에서 store에 정의된 fetchArticles 실행하기
- 버튼을 클릭한다.
<template>
<div>
<button @click="fetchArticles">articles 가져오기</button>
</div>
</template>
<script setup>
import { useArticleStore } from '/src/stores/articles'
const articleStore = useArticleStore()
function fetchArticles() {
articleStore.fetchArticles()
}
</script>
CORS
- CORS(Cross-Origin Resource Sharing)는 다른 도메인, 프로토콜, 또는 포트를 가진 리소스에 대한 웹 페이지의 요청이 보안 상의 이유로 차단될 때 발생한다.
- 이는 웹 브라우저가 동일 출처 정책(Same-Origin Policy)을 강제하기 때문인데, 이 정책은 웹 보안의 핵심 원칙 중 하나로 웹 애플리케이션을 다양한 웹 기반 공격으로부터 보호하는 역할을 한다.
- 동일 출처 정책이란, 같은 도메인을 가진 웹페이지의 요청만 받아드리는, 즉 자기 자신에 대한 요청만 허용하는 보안 정책이다.
- 즉, 출처를 모르는 client에 대해서 server가 정보를 주지 않는 보안 정책이다.
django-cors-headers
django-cors-headers
django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS).
pypi.org
- django 서버의 설정을 변경하여 다른 웹페이지의 요청을 받아드릴 수 있다.
$ pip install django-cors-headers
- settings.py
INSTALLED_APPS = [
...,
"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_ALLOW_ALL_ORIGINS = true
# 허용 가능한 출처를 정의한다.
# CORS_ALLOWED_ORIGINS = [
# "https://example.com",
# "https://sub.example.com",
# "http://localhost:8080",
# "http://127.0.0.1:9000",
# ]
- 다시 버튼을 클릭하면 응답이 정상적으로 도착한다.
- Article.view에서 articles 보여주기
- views/ArticleView.vue
<template>
<div>
<button @click="fetchArticles">articles 가져오기</button>
<ul>
<li v-for="article in articleStore.articles">
<h2>{{ article.title }}</h2>
<p>{{ article.content }}</p>
</li>
</ul>
</div>
</template>
<script setup>
import { useArticleStore } from '/src/stores/articles'
const articleStore = useArticleStore()
function fetchArticles() {
articleStore.fetchArticles()
}
</script>
- 일반적인 사이트는 버튼을 눌러 게시글을 가져오지 않고 페이지에 접속했을 때 게시글이 자동으로 보여진다.
onMounted()
- 컴포넌트가 렌더링된 후 input에 정의된 함수를 실행시킨다.
- 데이터를 가져오는 로직에 주로 사용된다.
- views/ArticleView.vue
<template>
<div>
<button @click="fetchArticles">articles 가져오기</button>
<ul>
<li v-for="article in articleStore.articles">
<h2>{{ article.title }}</h2>
<p>{{ article.content }}</p>
</li>
</ul>
</div>
</template>
<script setup>
import { useArticleStore } from '/src/stores/articles'
import { onMounted } from 'vue'
const articleStore = useArticleStore()
function fetchArticles() {
articleStore.fetchArticles()
}
onMounted(() => {
fetchArticles()
})
</script>
<style lang="scss" scoped>
</style>
게시글 작성
- store에 Article에 게시글을 작성하는 함수 정의하기
- stores/articles.js
import { ref } from 'vue'
import { defineStore } from 'pinia'
import axios from 'axios'
export const useArticleStore = defineStore('article', () => {
const articles = ref([])
function fetchArticles() {
axios({
url : 'http://127.0.0.1:8000/articles/'
})
.then(response => {
console.log(response.data)
articles.value = response.data
})
.catch(error => console.error(error))
}
function createArticle(data) {
axios({
url : 'http://127.0.0.1:8000/articles/',
method : 'POST',
data : data
})
}
return { articles, fetchArticles, createArticle}
})
- ArticleCreateView.vue 수정
- form을 사용해 data를 입력받은 뒤 store에 정의한 createArticle에 data를 전달하여 실행시킨다.
<template>
<div>
<form @submit.prevent="createArticle">
<label for="title">title : </label>
<input type="text" name="title" id="title" v-model="data.title"><br>
<label for="content">content : </label>
<textarea name="content" id="content" cols="30" rows="10" v-model="data.content"></textarea><br>
<button type="submit">submit</button>
</form>
</div>
</template>
<script setup>
import { useArticleStore } from '/src/stores/articles'
import { reactive } from 'vue'
const articleStore = useArticleStore()
const data = reactive({
title: '',
content: ''
})
function createArticle() {
articleStore.createArticle(data)
}
</script>
- 데이터가 정상적으로 작성된다.
'Vue' 카테고리의 다른 글
Vue library : Vue Router (0) | 2024.03.28 |
---|---|
Pinia (0) | 2024.03.27 |
Vue 실습(2) (0) | 2024.03.27 |
Vue : Component, Props, Emit (0) | 2024.03.27 |
Vue 실습(1) (0) | 2024.03.27 |