2021.03 ์ด๊ธฐ Django๋ก ๊ฐ๋ฐ
2021.06 Django REST frame work + Vue.js ๋ก ์ ๋ฐ์ดํธ
- ์น๊ฐ๋ฐ ์ฐ์ต
- ๊ฐ๋จํ toy project ๊ตฌํ, ์ ๋๋ฉ์ด์ ์ฐ์ต
- ํ์ต ๋ด์ฉ ๋ณต๊ธฐ
- Vue.js
- axios๋ก ์๋ฒ API์ ์์ฒญ
- props, emit์ผ๋ก ๋ฐ์ดํฐ ์ฒ๋ฆฌ
- CSS ์ ๋๋ฉ์ด์
- Django REST frame work
- ๋๋ง์๊ธฐ ๊ท์น(์ด์ ๋จ์ด์ ๋ง์ง๋ง ๊ธ์ ํ์ธ, ๋์๋ฒ์น, ์ค๋ณต๋จ์ด, ์ต์ ,,)
- ๊ตญ๋ฆฝ๊ตญ์ด์ API ํ์ฉ
"axios": "^0.21.1",
"bootstrap": "^5.0.1",
"bootstrap-vue": "^2.21.2",
"core-js": "^3.6.5",
"vue": "^2.6.11"
- ๋ ์ด์์ & ์ปจ์ : 90's ๋ํ ๋ ๊ฒ์๊ธฐ(ํํฐ๋ ์คํธ ์ฐธ๊ณ )
- font : ๋ฅ๊ทผ๋ชจ๊ผด
- App.vue : ์ต์๋จ ์ปดํฌ๋ํธ
- components
- Option
- OnOffToggle.vue : ์ธ๋์ด ์ต์ ํ ๊ธ ๋ฒํผ
- SelectTextLen.vue : ์ต์ ๊ธ์์ ์ต์ ๋ผ๋์ค ๋ฒํผ
- Introduce.vue : ๋งจ ์ฒ์ ๋ฃฐ ์ค๋ช ํ๋ ์ปดํฌ๋ํธ (1)
- SelectOption.vue : Next ํด๋ฆญ์ ๋์ค๋ ์ต์ ์ ํ ์ปดํฌ๋ํธ (2)
- GameStage.vue : ๋๋ง์๊ธฐ ์งํ์ค ๋ณด์ฌ์ง๋ ์ปดํฌ๋ํธ (3)
- Lose.vue : ํจ๋ฐฐ ํ๋ฉด (4)
- Win.vue : ์น๋ฆฌ ํ๋ฉด (5)
- Option
โ App.vue
- ํ๋ฉด์ ๋จ๊ณ๊ฐ ์งํ๋ ๋๋ง๋ค emit์ผ๋ก ํ์ฌ ์งํ ์ํ๋ฅผ ์ต์๋จ์ธ app์ ์ ๋ฌํ๋ค.
status
๋ฅผ ํตํด ํ์ฌ ํ๋ฉด์ ๋ณด์ฌ์ง ์ปดํฌ๋ํธ๊ฐ ๊ฒฐ์ ๋๋ค.
<div class="realScreenArea">
<Introduce v-if="status === 1" @status-change="StatusChange"/>
<SelectOptions v-else-if="status === 2" @status-change="StatusChange" @set-game-options="setGameOption"/>
<GameStage v-else-if="status === 3"
:foreign="foreign"
:textLen="textLen"
@status-change="StatusChange"
/>
<Lose v-else-if="status === 4"/>
<Win v-else-if="status === 5"/>
</div>
โ Introduce.vue
- ์ฒซ ํ๋ฉด์์ ๊ฒ์ ๊ท์น์ ์ค๋ช ํ๊ณ , Next ๋ฒํผ์ ๋๋ฅผ ์ ์๋ค.
- ๋ฒํผ์ ํด๋ฆญํ๋ฉด ์ด๋ฒคํธ๋ฅผ ๋ฐ์์์ผ ๋ถ๋ชจ ์ปดํฌ๋ํธ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ค.
- ๋ฒํผ์ ๋ง์ฐ์ค ์ค๋ฒ์ ์ ๋ณํ, ๊ทธ๋ฆผ์ ํจ๊ณผ๋ฅผ ์ค๋ค.
<template>
<div>
<div>
<h3 class="subHeadline">์ฟต์ฟต๋ฐ</h3>
<div>
<div>1. ์
๋ ฅํ๋ ๋จ์ด๊ฐ ๊ตญ๋ฆฝ๊ตญ์ด์ ์ฌ์ ์ ๋ฑ์ฌ๋ ๋จ์ด์ฌ์ผ ํฉ๋๋ค.</div>
<div>2. ํ๋ฐฉ ๋จ์ด ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค</div>
<div>3. ๋์ ๋ฒ์น์ด ์ ์ฉ๋ฉ๋๋ค. (ex. ๋ฆผ -> ์) </div>
<div>4. ์ค๋ณต ๋จ์ด ์ฌ์ฉ ๋ถ๊ฐ </div>
</div>
<br>
<br>
<div class="Button" @click="Next">Next</div>
</div>
</div>
</template>
<script>
export default {
name: 'Introduce',
methods: {
Next() {
this.$emit('status-change', 2)
}
}
}
</script>
<style scoped>
.Button {
color: white;
font-size: 30px;
background-color: rgb(81, 112, 24);
border-radius: 10px;
text-align: center;
width: 300px;
height: 60px;
padding-top: 5px;
margin: 0px auto;
transform: translate(0px, 20px);
transition-duration: 0.1s;
cursor: pointer;
}
.Button:hover {
background-color: rgb(101, 139, 30);
box-shadow: 1px 5px 0px rgba(0, 0, 0, 0.119);
}
</style>
โ SelectOption.vue > OnOffToggle.vue
- OnOff toggle ๊ธฐ๋ฅ
- ๋ท ๋ฐฐ๊ฒฝ, ๊ธ์, ์ ์ธ๊ฐ์ง์ ์์น ์ค์ ์ ์ํด z-index๋ฅผ ์ฌ์ฉํ๋ค. z-index๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ๋ฐ๋์ position ์ค์ ์ด ํ์ํ๋ค.
- ์์ ๋ถ๋๋ฌ์ด ์ด๋์ ์ํด transition ์์ฑ์ ์ค์ ํ๋ค. ๊ธฐ๋ณธ์ด ๋๋ ์คํ์ผ ์ค์ ์ transition ์์ฑ์ ์์ฑํด๋๋ฉด, ์คํ์ผ์ด ๋ณ๊ฒฝ๋ ๋ ์ ๋๋ฉ์ด์ ์ฒ๋ผ ๋ณด์ฌ์ง๋ค.
<template>
<div style="height: 2rem; position: relative;">
<div :class="{ optionBody: true, optionBodyTrue: option, optionBodyFalse: !option}" @click="toggleOption">
<div class="optionText">
<p v-if="option">On</p>
<p v-else>Off</p>
</div>
<div :class="{ optionCircle: true, optionCircleTrue: option, optionCircleFalse: !option}"></div>
</div>
</div>
</template>
<script>
export default {
name: 'OnOffToggle',
data() {
return {
option: 1,
}
},
methods: {
toggleOption() {
this.option = (this.option + 1) % 2 // 1 -> 0, 0 -> 1
this.$emit('foreign-option', this.option)
}
}
}
</script>
<style>
.optionBody{
width: 5.5rem;
height: 2rem;
border-radius: 15px;
z-index: 0;
position: absolute;
cursor: pointer;
transition-duration: 0.5s;
}
.optionBodyTrue{
background-color: #FFC107;
}
.optionBodyFalse{
background-color: #bebebe;
}
.optionText{
padding-top: 4px;
padding-left: 34px;
z-index: 1;
position: absolute;
}
.optionCircle {
margin-top: 3px;
margin-right: 3px;
margin-left: 3px;
width: 1.5rem;
height: 1.5rem;
border-radius: 50%;
background-color: white;
z-index: 2;
position: relative;
transition-duration: 0.5s;
}
.optionCircleTrue {
transform: translate(57px, 0px)
}
.optionCircleFalse {
transform: translate(1px, 0px)
}
</style>
โ SelectOption.vue > SelectTextLen.vue
- ์ฒดํฌ ๋ฐ์ค์ ์์๋ฅผ ํ๊ฐ๋ง ์ ํํ๊ฒ ํ ๋๋ radio๋ก ํ๋ฉด ๋๋ค. ์ค๋ณต ์ ํ์ด ๊ฐ๋ฅํ๊ฒ ํ๋ ค๋ฉด checkbox๋ก ์ค์ ํ๋ฉด ๋๋ค.
- type์ radio๋ก, name์ ๋ชจ๋ ๋์ผํ๊ฒ ํ๊ณ v-model์ ์ค์ ํ๋ฉด, ์ ํ๋ ์์๋ฅผ ๋ณ์ ํ๊ฐ๋ก ๋ฐ์ ์ ์๋ค.
- str์ num์ผ๋ก ๋ฐ๊พธ๋ ๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ str*1์ ํด์ ์๋ํ๋ณํ ํ๋ ๊ฒ์ด๋ค.
- watch๋ก ์ ํ๋ radio ์์๊ฐ ๋ฐ๋ ๋๋ง๋ค emit ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํจ๋ค.
<template>
<div>
<div class="checkBox">
<div>
<label for="2">2๊ธ์</label>
<input id="2" name="textLen" v-model="selectedValue" value="2" type="radio">
</div>
<div>
<label for="3">3๊ธ์</label>
<input id="3" name="textLen" v-model="selectedValue" value="3" type="radio">
</div>
<div>
<label for="4">4๊ธ์</label>
<input id="4" name="textLen" v-model="selectedValue" value="4" type="radio">
</div>
<div>
<label for="5">5๊ธ์</label>
<input id="5" name="textLen" v-model="selectedValue" value="5" type="radio">
</div>
</div>
</div>
</template>
<script>
export default {
name: 'SelectTextLen',
data(){
return {
selectedValue: 2
}
},
watch: {
selectedValue() {
this.$emit('textlen-option', this.selectedValue*1) // ์๋ํ๋ณํ!
}
}
}
</script>
โ GameStage.vue
- ๋งํ์ ์ ์ด ์ฌ์ดํธ๋ฅผ ์ฐธ๊ณ ํ๋ค.
- ์ฌ์ฉ์ ์ ๋ ฅ์ v-model๋ก ์ฐ๊ฒฐํ๋ค.
- ์ํฐ๋ฅผ ๋๋ฅด๋ฉด ์๋ฒ๋ก ์์ฒญ์ ๋ณด๋ธ๋ค. ์๋ฒ์์ ๋์์ค๋ ์๋ต์ ์ํ์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๋ฅผ ๋ค๋ฅด๊ฒ ํ์ํ๋ค.
- props๋ก ๋ฐ์ ์ธ๋์ด ํ์ฉ ์ต์ ๊ณผ ์ต์ ๊ธ์์ ์ต์ ์ axios ์์ฒญ์ ํ๋ผ๋ฏธํฐ๋ก ์ฌ์ฉํ๋ค.
<template>
<div>
<h3 class="subHeadline">์ฟต์ฟต๋ฐ</h3>
<div class="d-flex justify-content-between">
<div class="d-flex flex-column align-items-start">
<span class="player-speech-bubble">{{ inputTxt }}</span>
<div class="d-flex flex-column align-items-center">
<img src="@/assets/player.png" alt="">
<div>player</div>
</div>
</div>
<div class="d-flex flex-column align-items-end justify-content-between">
<span class="ai-speech-bubble"> {{ AItxt }} </span>
<div class="d-flex flex-column align-items-center">
<img src="@/assets/AI.png" alt="">
<div>AI</div>
</div>
</div>
</div>
<br>
<div class="input-group mb-3">
<input @keyup.enter="UserInput" type="text" class="form-control" v-model="inputTxt" aria-describedby="button-addon" autofocus>
<button @click="UserInput" class="btn btn-outline-success" type="button" id="button-addon">์ฟต์ฟต๋ฐ!</button>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'GameStage',
data() {
return {
inputTxt: '',
AItxt: '...'
}
},
props: {
foreign: {
type: Number
},
textLen: {
type: Number
}
},
methods: {
UserInput(){
axios.post(`${process.env.VUE_APP_API_URL}word/user/${this.foreign}/${this.textLen}/`, {content: this.inputTxt})
.then(res => {
// ํต๊ณผ
if (res.data.result === 1) {
this.AItxt = '...' // AI ์๊ฐ์ค
this.AIInput()// AI์ ์
๋ ฅ ์์ฒญ ๋ณด๋ด๊ธฐ
} else { // Lose ํ๋ฉด ๋์ฐ๊ธฐ
this.$emit('status-change', 4)
console.log('๋น์ ์ด ์ก์ต๋๋ค.', res.data.msg)
}
})
.catch(err => {
console.log(err)
})
},
AIInput(){
axios.post(`${process.env.VUE_APP_API_URL}word/ai/${this.foreign}/${this.textLen}/`)
.then(res => {
// ํต๊ณผ
if (res.data.result === 1) {
this.AItxt = res.data.word // AI ์
๋ ฅ ๋งํ์ ์ ์
๋ฐ์ดํธ
this.inputTxt = '' // ์ธํ ๋น์ฐ๊ธฐ
} else { // Win ํ๋ฉด ๋์ฐ๊ธฐ
this.$emit('status-change', 5)
console.log('๋น์ ์ด ์ด๊ฒผ์ต๋๋ค.', res.data.msg)
}
})
.catch(err => {
console.log(err)
})
}
}
}
</script>
django-cors-headers==3.7.0
djangorestframework==3.12.4
bs4==0.0.1
requests==2.25.1
...
corsheaders
- ์๋ก ๋ค๋ฅธ origin (ip๋ ๊ฐ์ง๋ง, ํฌํธ๋ฒํธ๊ฐ ๋ค๋ฆ. ํ๋ก ํธ 8080, ์๋ฒ 8000) ์์ ์์ฒญ๊ณผ ์๋ต์ ์ฃผ๊ณ ๋ฐ์ ๋ ํ์
- ์๋ฒ๋ ์๊ด ์๋๋ฐ ๋ธ๋ผ์ฐ์ ์์ CORS ์ค๋ฅ๊ฐ ๋จ๊ธฐ ๋๋ฌธ์ ๋ฐ๋์ ์ค์ ํด์ผ ํ๋ค.
INSTALLED_APPS = [
'word',
'corsheaders',
'rest_framework',
...
]
MIDDLEWARE = [
..
'corsheaders.middleware.CorsMiddleware', # CORS ์ถ๊ฐ
'django.middleware.common.CommonMiddleware',
..
]
CORS_ALLOW_ALL_ORIGINS = True
urlpatterns = [
path('user/<int:foreign>/<int:txtlen>/', views.user_input), # ์ฌ์ฉ์ ์
๋ ฅ ๊ฒ์ฆ ๋ฐ ์ ์ฅ
path('ai/<int:foreign>/<int:txtlen>/', views.ai_input), # AI ์
๋ ฅ ๊ฒ์ฆ ๋ฐ ์ ์ฅ
path('reset/', views.reset), # ๋ฆฌ์
]
-
์ฌ์ฉ์ ์ ๋ ฅ
- ์ค๋ณต ๋จ์ด๊ฐ ์๋์ง
- ๋๋ง์๊ธฐ ๊ท์น์ ์๋์ง
- ๊ตญ๋ฆฝ๊ตญ์ด์์ ๋ฑ์ฌ๋์ด ์๊ณ , ์ ํํ ์ต์ ์ ๋ง๋์ง
- ์ 3๊ฐ์ง๊ฐ ๋ชจ๋ ๋ง์กฑํ๋ ๊ฒฝ์ฐ DB์ ์ ์ฅ, ๊ทธ๋ ์ง ์์ผ๋ฉด ์ฌ์ฉ์์ ํจ๋ฐฐ!
-
AI ์ ๋ ฅ : ์ฌ์ฉ์ ์ ๋ ฅ์ ๋ ๊ธ์๋ก ํ์
- ๊ทธ ๋จ์ด๋ก ์์ํ๋ ๊ธ์๊ฐ ์๋์ง
- ์๋ค๋ฉด ๊ทธ ์ค์ ๋๋ค 1๊ฐ ์ถ๋ ฅ
- ์๋ค๋ฉด, ๋์ ๋ฒ์น ์ ์ฉ ํ๊ธฐ ( ใน -> ใ
, ใด -> ใ
)
- ๋์๋ฒ์น ์ ์ฉํ ๊ธ์๋ก ์์ํ๋ ๋จ์ด๊ฐ ์๋ค๋ฉด ๊ทธ์ค์ ๋๋ค 1๊ฐ ์ถ๋ ฅ
- ๋์๋ฒ์น ๋ถ๊ฐ or ๋์๋ฒ์นํด๋ ์๋ค๋ฉด -> ์ฌ์ฉ์์ ์น๋ฆฌ!
- ๊ทธ ๋จ์ด๋ก ์์ํ๋ ๊ธ์๊ฐ ์๋์ง
- ํ๋ก ํธ ๊พธ๋ฏธ๋๋ฐ ์๊ฐ์ด ๊ต์ฅํ ์ค๋ ๋ค์๋ค. ๋ ์ด์์์ ์์ง๊ณ ๊พธ๋ฏธ๋ฉด์ ๋์์ ํ๋๊น ์ง๋๊ฐ ์๋๊ฐ๋ค.
- ํ๋ก์ ํธ์์๋ ๊ธฐํ ๋จ๊ณ์์ ๋์์ธ์ด๋ ๋ ์ด์์์ ๋ฌด์กฐ๊ฑด ๋จผ์ ์ง๊ณ ๋ ํผ๋ฐ์ค๋ก ๋ง๋ค์ด์ ๊ทธ๋๋ก ๋ฐ๋ผ ๊ฐ๋ ๊ฒ ์ข์ ๊ฒ ๊ฐ๋ค.
- onoff ํ ๊ธ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์์์ง๋ง, ์ง์ ๋ง๋ค์ด๋ณด๊ธฐ๋ก ํ๊ณ ์ฑ๊ณตํ๋ค. ํจ์จ์ ์ธ์ง๋ ๋ชจ๋ฅด๊ฒ ์ง๋ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ง์ ๋ง๋ค์ด๋ณด๋ ๊ฒ๋ ์ข์ ๊ฒ ๊ฐ๋ค.
- ํ๋ก ํธ ํ๋จ์ ๋ฒํผ๋ค๋ ์ง์ js, css, html๋ก ๋ง๋ค์ด์ ๋ง์ฐ์ค ์ด๋ฒคํธ๋ฅผ ์ฃผ๊ณ ์ถ์์ง๋ง, ์๊ฐ๊ด๊ณ์ ํฌํ ์ต์ผ๋ก ์ด๋ฏธ์ง ๋ง๋ค์ด์ ๋ถ์ฌ๋ฃ์๋ค.
- vue, django ์์ด์ง ํ๋ฌ ์๋์๋๋ฐ ๋ฒ์จ ๋ช๋ช ๊ฐ๋ฌผ๊ฐ๋ฌผํ๋ค. ์ฃผ๋ง์๋ ํ๋ก์ ํธ ํ๋์ฉ ํ๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค.