Vue 와 Django(DRF) 를 이용하여 Todo 리스트 만들기 - 8 (프론트 todoList Create)
Project/Django & Vue.js

Vue 와 Django(DRF) 를 이용하여 Todo 리스트 만들기 - 8 (프론트 todoList Create)

뉴비뉴 2020. 8. 4.

안녕하세요.

 

newbiecs.tistory.com/309

 

Vue 와 Django(DRF) 를 이용하여 Todo 리스트 만들기 - 7 (프론트 todoList GET)

안녕하세요. 이번 포스팅에서는 CRUD 기능 구현을 해보도록 하겠습니다. 전 포스팅에서 우리는 DRF(Djang REST Framework) Server 에서 데이터를 가져오는 것을 실습해보았습니다. 우리는 Django 에서 GET 만

newbiecs.tistory.com

저번 포스팅에서는 List 를 가져와 화면에 보여지는 것을 만들어보았습니다.

오늘은 TodoList 를 생성하고, Clear 를 이용하여 입력폼을 비우는 기능을 구현하도록 하겠습니다.

 

구현하기 전에 제가 생각한 Create 방법은 자식 컴포넌트(TodoContent.vue) 에서 부모 컴포넌트(App.vue) 에게 입력한 데이터를 전달하여 모든 요청응답은 App.vue 에서 하는게 어떨까 라는 생각과 자식 컴포넌트에서 바로 입력한 폼의 요청을 백엔드 서버로 보내는 방법 중에 고민을 했습니다. 정확하지 않지만 알아본 결과 EventBus 가 많으면 좋지 않다 라는 것을 확인하고, 자식 컴포넌트에서 받은 폼을 바로 백엔드 서버로 전송하는 것으로 방향을 잡았습니다. (Vue.js 를 공부하면서 포스팅을 하기에 확실한 정보가 아닐 수 있습니다. 피드백은 환영입니다. *^^*)

 

시작하기전에 코드를 한번 맞추고 시작하겠습니다. (혼자 이것저것 해보면서 변경되었을 부분이 있을수도 있기에..)

<!-- App.vue -->

<template>
  <div>
    <todo-header></todo-header>
    <todo-content v-bind:propsdata="todoList"></todo-content> <!-- v-bind:하위컴포넌트 속성명="상위 컴포넌트 전달할 데이터명"  -->
    <todo-footer></todo-footer>
  </div>
</template>

<script>
import axios from "axios";
import TodoHeader from "./components/TodoHeader.vue";
import TodoContent from "./components/TodoContent.vue";
import TodoFooter from "./components/TodoFooter.vue";

let url = "http://localhost:8000/api/todo/";  // drf server addr

export default {
  data: () => {
    return {
      todoList: [],
    };
  },
  components: {
    "todo-header": TodoHeader,
    "todo-content": TodoContent,
    "todo-footer": TodoFooter
  },
  mounted() { // DOM 객체 생성 후 drf server 에서 데이터를 가져와 todoList 저장
    axios({
      method: "GET",
      url: url 
    })
      .then(response => {
        this.todoList = response.data;
      })
      .catch(response => {
        console.log("Failed to get todoList", response);
      });
  },
  methods: {  // CRUD 로직이 들어갈 부분
    getTodoList: function() {},
    updateTodoList: function() {},
    deleteTodoList: function() {}
  }
};
</script>

<style>
</style>

여기까진 달라진 부분이 없습니다.

 

Vuetify input 레이아웃 변경

아직 완성본을 아니지만 조금씩 이것저것 알아보며 디자인을 개선해나아가고 있습니다.

Vuetify 에서는 12 구획으로 나누어진 그리드 시스템을 지원합니다. 즉 한 row(열)은 12개의 column으로 구성된다고 보면 됩니다.

사이즈에 맞지 않으면 아래로 커지게 됩니다.

v-container -> 컨텐츠를 담을 컨테이너를 정의

v-layout -> 단일 컨텐츠 항목인 v-flex 를 포함하는 레이아웃을 정의

 

Reference Vuetify Grid


자세한 내용은 여기에 정리해두었습니다.

 

크게 v-container, v-layout, v-flex 라는 것을 활용하여 레이아웃을 잡을 수 있는데

이것을 이용하여 input 레이아웃을 변경하였습니다. 아래 코드를 확인해보시죠.

<!-- TodoContent.vue -->

<template>
  <div>
  <!-- 변경 -->
    <v-container fluid>  <!-- 전체 너비를 사용하고 싶은 경우 fluid 사용-->
      <v-layout column> <!-- column 으로 잡고 아래 cols="4" 와 같은 옵션을 주면 반응형이 적용이 안되는 것 같음 -->
        <v-flex>
          <h3 class="subject">what is your plan?</h3>
        </v-flex>
        <v-flex column>
          <v-row>
            <v-col cols="4" md="3"> <!-- 합이 12가 되면 전체 화면을 사용한다는 의미입니다. -->
              <v-text-field v-model="data.title" :counter="32" label="Title" required></v-text-field>
            </v-col>
            <v-col cols="4" md="3">
              <v-text-field v-model="data.author" :counter="64" label="Author" required></v-text-field>
            </v-col>
            <v-col cols="4" md="6">
              <v-text-field v-model="data.description" :counter="255" label="Content" required></v-text-field>
            </v-col>
          </v-row>
          <v-row justify="center">
            <v-date-picker v-model="data.due_date"></v-date-picker>
          </v-row>
          <v-btn @click="sendForm" style="background: green">create</v-btn>
          <v-btn @click="clearForm" style="background: red">clear</v-btn>
        </v-flex>
  <!-- 변경 -->        
        
        <v-flex class="todoList" column>
          <v-card max-width="600" tile>
            <v-list-item v-for="(data, index) in propsdata" v-bind:key="index">
              <v-list-item-content>
                <v-list-item-title>{{ data.title }}</v-list-item-title>
                <v-list-item-subtitle>{{ data.description }}</v-list-item-subtitle>
              </v-list-item-content>
            </v-list-item>
          </v-card>
        </v-flex>
      </v-layout>
    </v-container>
  </div>
</template>

좋은 참고자료가 있어 아래 Reference 에 추가해두었습니다.

 

v-container 의 Props로는

 

1. fluid

- 화면에 따라 사이즈가 바뀌는게 아니라 항상 최대 넓이를 가짐

- 사이즈 조절 안해도 도는 경우 사용

2. justify-center

- 컨텐츠를 가운데로 정렬 

3. grid-list-(size)

- 각 v-flex 아이템들이 세로 및 가로로 몇 픽셀만큼 떨어져 있는지

- 2~24px 까지 지정이 가능

xs = 2px

xl = 24px  

 

나머지 v-layout 과 CSS 의 flex 에 대한 추가 정보는 아래 Vuetify Grid를 참고하시면 되겠습니다.

 

다음으로 model 은 어떻게 생성되는지와 POST 로 보낼 데이터들은 어떻게 채워지는지 알아보겠습니다.

<v-flex column>
	<v-row>
		<v-col cols="4" md="3"> <!-- 합이 12가 되면 전체 화면을 사용한다는 의미입니다. -->
			<v-text-field v-model="data.title" :counter="32" label="Title" required></v-text-field>
	    </v-col>
		<v-col cols="4" md="3">
			<v-text-field v-model="data.author" :counter="64" label="Author" required></v-text-field>
		</v-col>	
		<v-col cols="4" md="6">
			<v-text-field v-model="data.description" :counter="255" label="Content" required></v-text-field>
		</v-col>
	</v-row>
	<v-row justify="center">
		<v-date-picker v-model="data.due_date"></v-date-picker>
	</v-row>
	<v-btn @click="sendForm" style="background: green">create</v-btn>
	<v-btn @click="clearForm" style="background: red">clear</v-btn>
</v-flex>

v-model=data.title 아래 정의한 data.title 에 입력한 값을 넣는다는 의미입니다.

값을 입력하면 Vue 인스턴스의 data 에 데이터가 전달되고, create 버튼을 누르게되면 데이터가 백엔드 서버로 전송됩니다.

<!-- TodoContent.vue -->


import axios from "axios";
let url = "http://localhost:8000/api/todo/";

export default {
  data: () => {
    return {
      data: {
        title: "",
        author: "",
        description: "",
        due_date: new Date().toISOString().substr(0, 10),
      },
    };
  },
  props: ["propsdata"],
  methods: {
    sendForm: function() {
      axios({
        method: "POST",
        url: url,
        data: this.data
      })
        .then(response => {
          this.todoList = response.data;
        })
        .catch(error => {
          console.log("Failed to get todoList", error.response);
        });
    },
    clearForm: function() {
        this.data.title = '',
        this.data.description = '',
        this.data.author = ''
    }
  }
};

이렇게 코드를 작성하고 Create 버튼을 클릭하고 개발자도구의 console을 확인해보면 오류가 발생한 것을 볼 수 있습니다.

왜 오류가나는지 찾아보니 due_date의 형식이 다르다는 것 입니다. DatePicker 로 생성한 데이터를 보면 2020-08-04 와 같은 포맷을 나타내고 있는데 백엔드 서버에서는 DateTimeField 라서 날짜와 시간을 다 받으려 하기 때문에 오류가 발생한 것 입니다.

 

하지만 곰곰히 생각해보면 우리는 그런 예외처리를 한 적이 없습니다. 이런 예외처리가 나오는 이유는 우리가 생성해주었던 Serializer 가 validations 을 검사해주기 때문입니다.

 

백엔드 서버로 잠깐 넘어가겠습니다.

# models/todo.py

# 변경 전
due_date = models.DateTimeField(
	verbose_name="투두 마감일",
	help_text="투두 마감일을 나타냅니다."
)

# 변경 후
due_date = models.DateField(
	verbose_name="투두 마감일",
    help_text="투두 마감일을 나타냅니다."
)

Model 에 변화가 생기면 Database 에 우리의 모델이 변경되었고 변경 된 것으로 DB를 수정해줘! 라는 명령을 내려야합니다.

 

> python manage.py makemigrations # migrations 파일을 생성한 뒤

> python manage.py migrate # DB에 반영합니다.

 

이제 다시 데이터를 넣어 요청을 해보겠습니다.

데이터를 넣고 POST 요청 우측 하단의 console 확인

생성은 잘 되었지만 갑자기 작성하였던 TodoList 가 안가져와지는 것을 확인할 수 있습니다.

(여기서 테스트데이터를 생성한 적이 없던 분이면 아래 오류가 발생하지 않습니다.)

오류를 한번 살펴보겠습니다.

 

사진의 에러가 나오면서 TodoList 가 가져와지지 않음

오류코드를 보고 한번 문제를 해결해보시고 안되면 아래 내용을 확인해보겠습니다.

 

 

문제를 알아내셨나요? 문제는 에러코드를 잘 살펴보면 알 수 있습니다.

우리는 전에 due_date 라는 필드를 DateTimeField 로 정의하여 테스트 데이터를 생성한 적이 있습니다.

사진의 맨 아래줄은 보면 시간이 잘려 나온 것이 힌트입니다. 정리해보면 DateTimeField 로 생성 된 Row 들을 가져오지 못하는 것 입니다.

왜냐하면 DateField 로 변경되었기 때문이죠.

 

해결방법] 

가장 간단하게 해결하는 방법은 문제의 투두 Row를 삭제하고, 생성하는 것 입니다.

귀찮으니 전부 다 삭제해주고 다시 생성해보겠습니다.

 

> python manage.py shell

> from todo.models import *

> Todo.objects.all().delete()

Todo 테이블 row 삭제

그리고 다시 위 사진처럼 투두리스트를 생성해주면! TodoList Create와 GET 기능구현이 끝났습니다!

TodoList Create, GET 완료

Button(Create, Clear)

1. Create

<v-btn @click="sendForm" style="background: green">create</v-btn>

버튼을 클릭하게되면 @click.prevent="sendForm" 이라는 것이 실행됩니다. @가 뭔지 모르시는 분들은 v-on:click 와 같다 라고 생각하시면 됩니다. 추가로 @click.prevent 는 기본 이벤트의 실행을 중지시킨다는 용도로 사용할 수 있습니다. btn 에 submit 이 붙는 경우 prevent를 이용하여 새로고침을 막을 수 있습니다.

 

sendForm 메소드를 살펴보도록 하겠습니다.

methods: {
    sendForm: function() {
      axios({
        method: "POST",
        url: url,
        data: this.data
      })
        .then(response => {
		  console.log("Success", response)
        })
        .catch(error => {
          console.log("Failed to create todoList", error.response);
        });
    },

생성을 할 것이니 method 는 POST 가 되어야하구요, GET 에서는 볼 수 없던 data 가 생겼습니다. 여기에 우리가 입력받은 데이터를 담아 백엔드 서버로 보내겠습니다.

 

2. Clear

Clear 는 입력할 수 있는 Form 을 비우는 것인데 매우 간단합니다.

<v-btn @click="clearForm" style="background: red">clear</v-btn>

Create 에서 click에 대해서 알아봤으니 바로 메소드를 보겠습니다.

clearForm: function() {
        this.data.title = '',
        this.data.description = '',
        this.data.author = ''
    }

사용자가 title을 입력하였다 하면 title 에 어떠한 값이 저장되어있을텐데 Clear 버튼을 누르게되면 '' 공백으로 처리하여

사용자가 입력한 텍스트가 없어지게 됩니다.

 

아직 부족한게 많지만 Create와 GET 까지 생성하였으니 다음은 Update와 Delete 하는 게시글을 포스팅해보도록 하겠습니다.

포스팅하면서 느낀점은 간단한 포트폴리오라도 설계를 하고 블로그 포스팅 간에 오류를 잡는 과정을 줄이면 좋겠다는 것과

Vue.js를 공부하면서 포스팅을 하기 때문에 중간중간 수정되는 부분이 많아 진행이 매끄러워지지 않다라는 것 입니다.

 

감사합니다.

 

Reference

- Vuetify Grid https://chansbro.github.io/vue/vuetify_tutorial1

- Vue.js Axios response error catch https://yamoo9.github.io/axios/guide/error-handling.html

- Vuetify DatePicker https://vuetifyjs.com/ko/components/date-pickers/

 

댓글

💲 추천 글