[Django] MTV patterns (Form, Template 활용) #1

Django : Form class

Django의 MTV pattern에서 내가 계속 다뤄왔던건 Model과 View였다. Template은 UI쪽 즉, 프론트엔드의 영역으로 구분짓고 굳이 다루지 않았던것.
이번에 Template도 다뤄보기로 했다. (이후 DRF 하면서 맛보기로 진행하기로 했다.)
이때 사용되는 것이 Form이다.

Form ?
HTML에서 form 이란

<form> ... </form>

태그 내에서 웹사이타를 사용하는 유저가 데이터를 입력할수 있도록 하고, 서버로 데이터를 보내주는 역활을 한다.
즉, form이 갖고있어야 하는 정보는 2가지가 있다.
1. 어느 주소로 보낼지 (to server)
2. 어떤 방법으로 보낼지 (HTTP methods) => 참고로 데이터를 보내는 것은 POST이다.


참고 : https://junlab.tistory.com/193

Django의 Form 을 사용하면 다음과 같은 부분들을 단순화 시켜주고 자동화 기능을 수행한다.

  • 사용자에게 보여줄 많은 데이터 타입들
  • 서버로 넘어온 값들에 대한 유효성 검증
  • 유효성 검증을 통과한 데이터들을 처리할 로직

또한 Form을 사용할때 활용해야 하는것이 Django의 MTV pattern에서 Template 부분이다.
기존의 활용단계에서는 Template은 건드리지 않고 View와 Model만 작업했었는데 이번 경험을 통해 Django로 화면까지 띄워 보았다!


1. models.py

user의 회원가입시 필요한 값들에 대해 field를 생성한다.

deleted_at ?
예제 코드를 참고한 것인데 보면 deleted_at field가 있다. 삭제시간을 기록한다는것!
해당 코드의 의도는 계정 삭제 시 실제 db에서 삭제하지 않고 soft_delete 를 사용하기 위해 삭제시간을 null=True에서 삭제당시의 시간을 기록하는 방식으로 바꾸는것 같다.
이후 유효한 계정만 불러올땐 이 값이 null값인 계정만 불러오면 될 것 같다.

from django.db import models

class User(models.Model):
    name       = models.CharField(max_length=100)
    email      = models.EmailField(max_length=50) 
    password   = models.CharField(max_length=200) 
    birth_date = models.DateField() 
    created_at = models.DateTimeField(auto_now_add=True) 
    updated_at = models.DateTimeField(auto_now=True) 
    deleted_at = models.DateTimeField(null=True)

    class Meta:
        db_table = 'users'


2. forms.py

Django의 MVT pattern에 따라 models.py에 작성했던 것 처럼 forms.py에 코드를 작성한다.
기존에 models.py에 작성된 class들은 django.db로부터 import된 models.Model을 상속받는 형태였다.

from django.db import models.Model

class User(models.Model):
  name = models.CharField(max_length=10)

이와 같이 forms.py 의 class도 django의 forms 로부터 상속받는다.

from django import forms

class UserForm(forms.Form):
    name       = forms.CharField(label='Name', max_length=100)
    email      = forms.EmailField(label='Email', max_length=50) 
    password   = forms.CharField(label='Password', widget=forms.PasswordInput(), max_length=200) 
    birth_date = forms.DateField()

HTML code : form element tag 생성됨

<form action="" method="post">
	<label for="id_name">Name:</label>
	<input type="text" name="name" maxlength="100" required="" id="id_name">
	<label for="id_email">Email:</label>
	<input type="email" name="email" maxlength="50" required="" id="id_email">
	<label for="id_password">Password:</label>
	<input type="password" name="password" maxlength="200" required="" id="id_password">
	<label for="id_birth_date">Birth date:</label>
	<input type="text" name="birth_date" required="" id="id_birth_date">
	<input type="submit" value="submit">
</form>

forms.py에 입력한 코드들이 html에서 각각 하나의 form element tag화 된 모습이다. 보면 forms.py에서 label로 설정한 이름이 화면에서 나타나는 목록의 이름으로 label 태그 사이에 작성된다. 그리고 input tag안에 type과 name, 최대길이(maxlength)에 대한 부분이 작성된 것을 확인할 수 있다.

일단 id_ 관련된 부분은 아직 이해하지 못했다.

method : GET

위 HTML의 form element tag 를 보면 method="post"라고 되어있다. 하지만 GET요청으로 들어올 시 각 항목에 대해 비어있는 form instance들을 생성하게 되고 비어있는 값에 대해 html로 랜더링 하여 반환되는데 이때 빈칸으로 표시된다. (흔히 입력하기 전의 빈칸 상태)

method : POST

비어있는 칸에 유효한 값을 입력하고 전송하면(request) POST요청에 의해 처리된다. 이에 대한 처리를 위해 views.py를 작성해야 한다.



3. views.py

django의 mtv pattern에서 백엔드 API로 models.py와 views.py만을 다루었을때와 달리 추가적인 모듈이 필요하다. 위에서 말했든 html로 랜더링 해주는 것이 그 예시이다.

from django.http.response   import HttpResponse
from django.shortcuts       import render
from .models                import User
from .forms                 import UserForm


def signup(request):
  if request.method == 'POST':
    form = UserForm(request.POST)
    if form.is_valid(): ...1
      #form.cleaned_data = {}
      #form.cleaned_data["name"] = form.name
      #form.cleaned_data["name"] = form.name
      #form.cleaned_data["name"] = form.name
      
      User(
	name     = form.cleaned_data['name'], ...2
	email    = form.cleaned_data['email'],
	password = form.cleaned_data['password']
    ).save() 
      return HttpResponse(201)
  else:
      form = UserForm()
  return render(request, 'user/signup.html', {'form':form}) ...3

view : POST

먼저 signup함수는 parameter로 들어온 request를 받는다. 조건문을 보면 request의 method가 "POST"일시 form 이라는 변수에 UserForm(request.POST)를 담는다. 이때의 값은 dictionary값으로 {"name" : "김아무개"} 식으로 전달된다.

번호 설명 첨부

1: UserForm에 입력된 것에 따라 form instance가 생성되는데 먼저 is_valid에 의해 유효성 검사를 거친다. 이때 is_valid는 django의 forms에서 제공하는 함수이다.

2: 이후 models.py에 작성된 User class의 name, email, password 값에 form의 cleaned_data를 사용한다고 되어있다. 코드를 살펴보니 django의 forms.pyi에서 제공하는 Class BaseForm으로부터 나온것이라더라...

3: 최종적으로 return할때 랜더링 해주어야 한다. 이때에는 3가지 parameter와 함께 넘겨진다.

  • request : 들어온 request를 그대로 넘겨준다.
  • 'user/signup.html' : user app의 sighup.html 파일을 가리킨다.
  • {'form':form} : 'form'이라는 키에 위에서 UserForm을 담아준 변수 form을 값으로 담는다.

🔥 template file 위치선정 주의!

위의 예제에서는 signup.html을 단순히 views.py나 models.py 처럼 app 안에 바로 생성하였다. 하지만 이것이 가능했던것은 signup이라는 이름의 html 파일이 user app에만 존재할 것임을 암묵적으로 알기에 괜찮다고 할 수 있다. 이말은 즉, 동일한 이름의 html파일을 서로 다른 app에 작성할 시 이런식의 경로지정은 좋지 않다는 것이다.

그 이유는 View에서 template을 가져올때 현재위치app의 template부터 찾는것이 아니라, 프로젝트 전반에 걸쳐서 template을 찾기 때문이다. 따라서 순서상 두번째에 존재하는 app안의 aa.html파일을 가져오고자 하여도 순서상 첫번째에 존재하는 app안에도 동일한 이름의 html 파일이 존재한다면 첫번째 app의 html파일을 가져올 수 있다.

이러한 혼란을 막기위해 보통 app(폴더)/template(폴더)/app이름(폴더)/template file 경로로 생성해 준다!

<참고 : 예제로 배우는 파이썬 프로그래밍 >

일단은 이러한 점을 유념해 두고 위의 예제에 맞게 계속 작성하였다.
참고로 예제에서는 root directory에서 template이라는 direcotry를 만들어 관리하였다.



4. template file 작성

1) settings.py에서 TEMPLATE 설정 변경하기

root directory의 프로젝트 앱에 있는 settings.py에 TEMPLATE 설정을 변경해주어야 한다. 이전의 프로젝트 들에선 template을 다룰일이 없어 다루지 않았었다.

'DIRS': [BASE_DIR / 'templates']

다른 부분들은 그대로고 DIRS에 BASE_DIR의 'templates'로 경로를 지정해 준다.
이때 BASE_DIR은 같은 settings.py 안에있는 변수로

BASE_DIR = Path(__file__).resolve().parent.parent

위 코드를 의미한다.

2) templates/user/signup.html

위에서 말했듯 template file의 경로를 위와 같이 지정하였다.

  • root directory에서 templates 라는 folder 생성
  • 해당 폴더 안에 app이름과 같은 이름의 folder 생성
  • 해당 폴더 안에 실제 사용할 template file 생성
<html>
  <head></head>
<body>
  <form action="" method="post">

  (위에 작성예시 들었던 html 코드 그대로 입력)
  
  </form>
</body>
</html>


5. urls.py 작성

1) project의 urls.py

기존방법과 마찬가지로 처음 request가 들어왔을때 경로를 찾는 지점은 project쪽이다. 때문에 project의 urls.py에서 경로를 지정. 특별히 경로설정 할께 없어서 공백으로 지정하였다.

path('', include('user.urls')

2) app의 urls.py

가장 애먹었던 부분이다. 기존의 Template을 건드리지 않았던 MTV pattern의 프로젝트시에는 views.py에서 django의 View class를 상속받았기 때문에 경로상에도 as_view로 지정해줬었는데 이번에는 그렇게 구현하지 않았다. 일단 함수이름 그대로 경로 설정해보았다.

path('', signup)

결과는 성공 😬



6-1. 결과 : GET

저.. 보라색의 500에러는 흐린눈으로 보기로 하자. 나의 p;ㅠ

위에서 말했듯이 GET method사용시엔 UserForm의 parameter로 아무것도 담기지 않는다. 빈 값의 form이 html로 랜더링 되면서 해당 부분은 빈칸으로 표시되었다.



6-2. 결과 : POST

403 error가 났다!

403 ?
지난 2개월 수도없이 본 에러.. FORBIDDEN 접근권한 없음. 유효하지 않는 유저.

html 코드 작성시에 같이 작성한 token 부분이 있다.

<input type="hidden" name="csrfmiddlewaretoken" value="대충 무슨 값">

저 value에 어떤 값이 들어가야 유효한지 아직 잘 모르겠음.. 일단 에러메세지는 CSRF token이 없거나 잘못되었다고 한다.

Forbidden (CSRF token missing or incorrect.): /
[26/Apr/2021 17:23:23] "POST / HTTP/1.1" 403 2519


🔥 ERROR 발생

1. CSRF token missing or incorrect

이 에러의 해결법으로 검색하니 두가지가 세가지 솔루션이 나왔다.

  • django의 csrf에서 데코레이터 사용 : csrf_protect
  • django의 csrf에서 데코레이터 사용 : csrf_exempt
  • template file에 csrf_token 추가

1) csrf_protect

# views.py
from django.views.decorators.csrf import csrf_protect

@csrf_protect
def signup(request)

2) csrf_exempt

# views.py
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def signup(request)

3) template file에 추가

<form action="." method="post">{% csrf_token %}

이때 pj의 settings.py 에서 MIDDLEWARE에 CSRF가 추가되어있는지 확인한다.

django.middleware.csrf.CsrfViewMiddleware

나의 경우 3안까지 가지 않고 2안에서 해당 에러는 해결이 되었다. 단지 다른 에러 발생

2. OperationalError

django.db.utils.OperationalError: no such table: users
[26/Apr/2021 17:37:50] "POST / HTTP/1.1" 500 148566

이거는 table 자체가 왠지 없는듯 하다 (makemigrations 해준 기억 없음.. Model꺼를 자동으로 처리해줄리가 없을것 같은 느낌)

바로 makemigrations => migrate 진행

해결.. 근데 또 다른 에러가 발생했다.


3. IntegrityError

django.db.utils.IntegrityError: NOT NULL constraint failed: users.birth_date
[26/Apr/2021 17:48:04] "POST / HTTP/1.1" 500 148696

birth_date의 data type은 DateField이다.

DateField ?
DateField의 형식은 YYYY-MM-DD 이다.

내가 입력해준 형식이 맞는데.. 혹시몰라서 해당 날짜를 쌍따옴표로 감싸 주었다.

성공 😮

아근데 문제가.. 저장된 데이터 어떻게 확인하지...? 어드민 계정이 없는데.....



끝!

일단 forms.py에서 작성한 대로 빈값의 입력칸이 생성되었음을 확인했고 ✅
POST 되는거 확인 했다 ✅

이 방법은 DRF가 아니라 기존의 Django의 MTV pattern에서 다루지 않았던 Template부분을 다뤄본 방법이었다. DRF를 사용하면 이보다 훠얼씬 간편하다고 한다!

DRF 맛보기

Django Rest Framework

즉 Django에서 REST API를 쉽고 정확하게 구현할 수 있도록 도와주는 라이브러리이다.


라이브러리 ?
간단하게 말해서 함수들의 집합이다. 재활용 가능하기 때문에 프로그램의 제작 시간과 인력비용을 줄일 수 있다. 라이브러리를 활용해여 내가 원하는 기능을 구현할 수 있다.


프레임워크 ?
이것 역시 함수들의 집합이지만, 단순히 거기서 그치는 것이 아니라 특정한 프로그램 제작에 필요한 필수 함수들을 모두 포함하고 있다 프레임워크 안에서 내가 하고자 하는 기능을 구현하는 개념 즉, 말그래도 이라 생각하면 된다.


Django는 python의 Framework 이고, DRF는 Django의 library 이다.



DRF 주요 특징


  • 검색 가능하다
  • Serialization / Deserialization 하다
    : Queryset / Model Instance -> Json / Native Python data type으로 변환 가능
    : Server로 들어오는 데이터Queryset / Model Instance로 변환 가능
  • 인증 정책 제공 : OAuth1a 및 OAuth2용 패키지 포함 => 내용 추가하기...

좋은 웹페이지 즐겨찾기