Form태그를 쟝고를 이용해서 효율적으로 작성하는 법에 대해서 알아보도록 하겠습니다.
1) 서론
1-1. Form 태그의 불편함
흔히 HTML에서 제공하는 Form태그는 이렇게 생겼다.
<div class="row">
<div class="col-md-6 offset-md-3">
<form action="" method="POST">
{% csrf_token %}
<div class="form-group">
<label for="username">Username: </label>
<input type="text" name="username" id="username" class="form-control" placeholder="Enter username">
</div>
<div class="form-group">
<label for="password">Password: </label>
<input type="password" name="password" id="password" class="form-control" placeholder="Password">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
C,R,U,D 중에서 무려 C와 U를 할 때마다 이 form을 작성해야하는데 이게 여간 귀찮은 일이 아니다.
더 큰 문제는 데이터의 유효성 검사가 매우 약하다는 점이다.
예를 들어서 고객센터에 불편사항을 문의할 때 누군가가 악의적으로 빈 데이터를 넣는다면 데이터를 관리하기가 힘들 것이다.
물론, HTML차원에서 이걸 해결하기 위해 required라는 속성을 집어넣을 수 있다.
<form action="/action_page.php">
Username: <input type="text" name="usrname" required>
<input type="submit">
</form>
그런데 이 required의 단점은, 개발자 도구에서 지워버려도 된다는 점이다.
그 이외에도 여러가지 유효성 검사가 필요한 경우가 많은데, 일일이 form을 만들때마다 이런 것들을 정의해줘야한다는게 매우 힘들다.
1-2. 요약
Form태그의 문제점
- C,U를 하기 위해서 Form을 계속 작성해줘야 한다.
- 유효성 검사에 매우 취약하다.
- Form에서의 데이터를 받을 때 변수 하나하나마다 정의해줘야하는 불편함이 있다.
그래서 Django에서는 Form과 ModelForm이라는 기능을 제공한다.
2) Django Form
Django form은 Models.py의 특정 테이블과 연계해서 Form을 자동으로 만들어주는 기능을 한다.
2-1. forms.py
먼저 forms.py를 새로 만들어준다.
# Form Class
from django import forms
from .models import Shout
# ShoutForm : Shout 모델에 기반하여 django가 만들어주는 form
class ShoutForm(forms.Form):
title = forms.Textfield()
content = forms.Textfield()
Shout라는 테이블과 연계되는 ShoutForm 클래스를 만들어준다.
2-2. ShoutForm을 표현하기
그저 Form이라는 객체를 생성해서 보여주면 된다.
def create(request):
# form을 보여줌
form = ShoutForm()
return render(request, 'shouts/create.html', {'form': form})
form이라는 변수를 DTL내에 넣어주면 알아서 form태그 형식을 만들어주는데,
as_p는 p태그처럼 만드는 것이다. 비슷하게 as_table, as_ul도 가능하다.
{% extends 'todos/base.html' %}
{% block content %}
<form method="POST">
{{form.as_p}}
<input type="submit">
</form>
{% endblock %}
결과화면) 매우 간단한 형태의 form이 등장했다.
물론 커스터마이징도 가능한데, 이건 다음에 ModelForm을 살펴보면서 알아보도록 하자.
3) Model Form
3-1. forms.py
-
클래스 정의
from django import forms from .models import Shout class ShoutModelForm(forms.ModelForm): model = Shout
model을 Shout클래스로 쓰겠다는 말만 해주면, 알아서 Shout 모델의 멤버변수들을 참고해서 Form을 만들어준다.
-
물론 모든 멤버변수를 Form을 통해서 받을 필요가 없기 때문에
form 태그를 통해서 받을 테이블의 field들을 정의해줄 수 있다.
class ShoutModelForm(forms.ModelForm): class Meta: model = Shout fields = ['title', 'content']
Meta라는 클래스를 감쌌는데, 그냥 메타정보들을 넣는다고 받아들이면 될 것 같다.
-
약간의 커스터마이징도 가능하다.
class Meta: #.... widgets = { 'title' : forms.TextInput( attrs = { 'class':'form-control', 'placeholder':'제목을 입력해 주세요.' } ), 'content' : forms.Textarea( attrs = { 'class':'form-control', 'placeholder':'내용을 입력해 주세요.' } ) }
widgets라는 멤버변수로 커스터마이징이 가능한데,
대표적으로 인풋태그의 타입과 속성을 정의가능하다.
-
-
TextInput
-
NumberInput
-
Textarea
…. 등등
-
-
속성
속성은 attrs 매개변수를 통해서 딕셔너리형식으로 전달하면 된다.
Boolean attribute는 True나 False를 통해 렌더할 수 있다.
-
3-2. Views.py
이제 이렇게 틀을 만들었으면 어떤 식으로 활용하는지 알아보겠습니다.
-
Create
from .forms import ShoutModelForm def home(request): if request.method == "POST": pass else: shouts = Shouts.objects.all() form = ShoutModelForm() return render(request, 'shouts/home.html', { 'shouts': shouts, 'form':form, })
모든 Shout 인스턴스들을 보여줌과 동시에 새로운 Shout를 추가할 수 있는 home함수를 만든다고 생각해봅시다.
get method로 주어진 home 링크에 접근했을 때는 새로운 Shout를 만드는 form과 Shout 인스턴스들만 보여주면 되기 때문에 이런식으로 작성하면 됩니다.
이런식으로 작성할 수 있을 것입니다.
그다음에는, 위의 form을 통해 데이터를 입력할 때만 POST메소드로 작동하기 때문에 이때는 새로운 데이터를 생성해줘야 합니다.
from .forms import ShoutModelForm def home(request): if request.method == "POST": form = ShoutModelForm(request.POST) form.save() return redirect('shouts:home') # 생략..
그저 FORM태그를 통해 들어온 데이터 전부를 넣어줘서 save만 하면 됩니다.
request.POST를 뜯어볼 필요가 없이 Django가 알아서 지원해줍니다.
-
Update
업데이트를 위해서는 일단 어떤 Shout 객체를 업데이트할건지를 정해야 하기 때문에
그것을 instance 매개변수로 지정하였습니다.
from .models import Shout def update(request, shout_id): shout = Shout.objects.get(id=shout_id) if request.method == "POST": pass else: form = ShoutModelForm(instance=shout) return render(request, 'shouts/update.html', { 'form' : form })
즉, instance는 shout의 정보를 가지고 있습니다.
그러면 shout의 정보를 가지고 value를 미리 띄워주는 기능을 합니다.
def update(request, shout_id): shout = Shout.objects.get(id=shout_id) if request.method == "POST": form = ShoutModelForm(request.POST,instance=shout) form.save() return redirect('shouts:home') # 생략..
위의 form에서 데이터를 수정해서 POST메소드를 통해 전달하면 그냥 save만 하면 됩니다.
주어진 instance에 request.POST를 잘 저장해둔다는 의미라고 생각하면 됩니다.
결론적으로, 하나의 ShoutModelForm만 가지고도 Create와 Update를 동시에 할 수 있습니다.
그리고 그 절차도 매우 간단해졌습니다.
4) User Form, validator
저번 포스트에서 로그인기능은 만들었으나, 정작 유저들 자기자신이 회원가입을 하지는 못하고 우리가 직접 추가해줘야 했습니다.
이러한 유저 회원가입 기능도 UserCreationForm으로 할 수 있습니다.
4-1. UserCreationForm()
from django.contrib.auth.forms import UserCreationForm
# 이건 저번에 임포트했던것들
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
django.contrib.auth.forms에서 UserCreationForm을 임포트할 수 있습니다.
생각해보니 django.contrib.auth는 models도 가지고있고, forms도 가지고있는걸로 보아서, 하나의 쟝고 앱과 비슷한 구조인것으로 추측할 수 있습니다.
def register(request):
if request.method == "POST":
form = UserCreationForm(request.POST)
form.save()
else:
form = UserCreationForm()
return render(request, 'users/register.html', {
'form' : form
})
매우 간단합니다. 이런식으로만 작성하면 회원가입을 할 수 있습니다.
물론 커스터마이징도 상속을 통해서 가능하지만 이번에는 다루지 않겠습니다.
4-2. 유효성 검사
그런데 회원가입은 반드시 유효성검사가 필요합니다.
이런 경우에는 유효성검사를 매우 간단하게 할 수 있습니다.
def register(request):
if request.method == "POST":
form = UserCreationForm(request.POST)
if form.is_valid():
form.save()
#user = User.objects.get(id=form.instance.id)
#이렇게 user를 명시하지 않고도 form의 인스턴스가 생성된 유저이므로 이거만 하면 된다.
login(request, form.instance)
messages.success(request, "환영합니다 " + form.instance.username + "님")
return redirect('todos:home')
else:
return redirect('users:register')
# 생략...
form.is_valid()로 유효성을 검사함.
위에 링크에 들어가보면 validation은 여러가지 단계로 이루어져있는데,
대략적으로는 다음과 같습니다.
-
to_python : model에 정의된 필드의 타입에 부합하는가?
-
validate : model에 정의된 필드의 validatior에 부합하는가?
- validator : https://docs.djangoproject.com/en/2.1/ref/validators/
is_valid()는 model에서 정의된 테이블의 validators에 따라서 유효성을 검사합니다.
validator는 RegexValidator , EmailValidator 등등이 있으며, 이런 validator를 지정해놓으면 불필요한 데이터를 잘 걸러낼 수 있습니다.
-
만약 validation error가 발생하면 clean()메소드를 통해서 데이터를 정제해준다고 합니다.