[Django] Django Form
작성:    
업데이트:
카테고리: Django
태그: BE Framework, Django
Form
- 사용자의 데이터를 직접 받을 때 입력된 데이터의 유효성을 검증해야 한다.
- 필요시에 입력된 데이터를 검증 결과와 함께 다시 표시 해야한다.
-
사용자가 입력한 데이터는 개발자가 요구한 형식이 아닐 수 있음을 고려
- 유효성 검증 : 사용자가 입력한 데이터를 검증
- 유효성 검증을 모두 구현하는 것은 많은 노력이 필요
- Django From : Django가 과중한 반복 코드를 줄여줌으로써 유효성 검증을 보다 쉽게!
Django Form
- Django의 유효성 검사 도구 중 하나
-
외부의 악의적 공격 및 데이터 손상에 대한 중요한 방어 수단
- Form과 관련된 유효성 검사를 단순화/자동화 기능 제공
- 개발자가 직접 작성하는 코드보다 더 안전하고 빠르게 수행하는 코드 작성 지원
Django의 form에 관련된 3가지 작업
- 렌더링을 위한 데이터 준비 및 재구성
- 데이터에 대한 HTML forms 생성
- 클라이언트로부터 받은 데이터 수신 및 처리
Django Form Class
-
Django Form 관리 시스템의 핵심
- Form 내 field, field 배치, 디스플레이 widget, label, 초기값, 유효하지 않은 field에 관련된 에러 메세지 결정
- 사용자의 데이터를 받을 때 해야 하는 과중한 작업과 반복 코드 줄여줌
- 데이터 유효성 검증
- 필요시 입력된 데이터 검증 결과 재출력 등
Django Form 사용
Form 선언
# articles/forms.py
from django import forms
class ArticleForm(forms.Form):
title = forms.CharField(max_length=10)
content = forms.CharField() # Model과 달리 TextField가 아니다!! 자동완성에도 없다.
- App 폴더는 forms.py가 없으므로 새로 만들어준다.
- Model을 선언하는 것과 유사, 같은 필드타입 사용 (일부 매개변수도 유사)
- forms 라이브러리에서 파생된 Form 클래스를 상속
Form 사용
- views.py에 ArticleForm을 import한다.
# articles/views.py
from .forms import ArticleForm
# articles/views.py
def new(request):
return render(request, 'articles/new.html')
# articles/views.py
def new(request):
form = ArticleForm()
context = {
'form': form
}
return render(request, 'articles/new.html', context)
<!-- articles/templates/articles/new.html -->
<form action="{% url 'articles:create' %}" method="POST">
{% csrf_token %}
<label for="title">Title: </label>
<input type="text" id="title" name="title"><br>
<label for="content">Content: </label>
<textarea name="content" id="content" cols="30" rows="10"></textarea>
<input type="submit">
</form>
- new template의 기존 form에 대한 html 태그이다.
- 위의 코드를 아래처럼 바꿔보자.
<!-- articles/templates/articles/new.html -->
<form action="{% url 'articles:create' %}" method="POST">
{% csrf_token %}
{{ form }}
<input type="submit">
</form>
Form rendering options
- label과 input 쌍에 대한 3가지 출력 옵션
as_p()
- 각 필드가 단락(p 태그)으로 감사져서 렌더링
- 줄바꿈이 되는 것!
as_ul()
- 각 필드가 목록 항목(li 태그)으로 감싸져서 렌더링됨
- ul 태그는 직접 작성
as_table()
- 각 필드가 테이블 행(tr 태그) 행으로 감싸져서 렌더링
- table 태그는 직접 작성
<!-- articles/templates/articles/new.html -->
<form action="{% url 'articles:create' %}" method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit">
</form>
Widgets
form fields
- input에 대한 유효성 검사 로직을 처리
- 템플릿에서 직접 사용
Widgets
- 웹 페이지의 HTML input element 렌더링
-
GET/POST 딕셔너리에서 데이터 추출
- idget은 단독적으로 사용되지 못한다 : field에 의존적
# articles/forms.py
from django import forms
class ArticleForm(forms.Form):
title = forms.CharField(max_length=10)
content = forms.CharField(widget=forms.Textarea)
form fields의 괄호 내에 widget= option으로 부여
주의사항
- form fields와 혼동되어서는 안된다!
- form fields : input 유효성 검사를 처리
- widgets : 웹페이지에서 input element의 단순 raw한 렌더링 처리
- 참고자료 : django 공식문서 - Widgets
Widgets 응용
드롭다운 select element를 만들어보자
# articles/forms.py
from django import forms
class ArticleForm(forms.Form):
title = forms.CharField(max_length=10)
content = forms.CharField(widget=forms.Textarea)
region = forms.ChoiceField(widget=forms.Select())
- 위처럼 region parameter에 ChoiceField 설정
- widget은 forms.Select (forms.Select()도 가능)
- field는 자료형, widget은 user들에게 보이는 형식
- 아무 option이 없기 때문에 rendering하면 아래와 같은 모습
- select의 선택항목인 option을 추가해보자
# articles/forms.py
from django import forms
class ArticleForm(forms.Form):
REGION_A = 'sl'
REGION_B = 'dj'
REGION_C = 'gj'
REGION_CHOICES = [
(REGION_A, '서울'),
(REGION_B, '대전'),
(REGION_C, '광주'),
]
title = forms.CharField(max_length=10)
content = forms.CharField(widget=forms.Textarea)
region = forms.ChoiceField(choices=REGION_CHOICES, widget=forms.Select())
# forms.Select도 되고 forms.Select()도 됨... 왜지?
- ChoiceField의 경우 widget이 Select가 기본이기 때문에 괄호 내 widget 항목은 생략 가능
- option들은 위와 같이 tuple로 작성
- tuple 사용은 django coding style - model style 참고
결과
- 드롭다운에 필요한 option들이 select element 내부에 있는 것처럼 들어갔다.
ModelForm
- forms.py에서 Form에 대한 항목들을 하나하나 작성해주었다.
- Django Form을 사용하다보면 Model에 정의한 필드를 유저로부터 입력받기 위해 Form에서 Model 필드를 재정의하는 행위가 중복될 수 있다.
- Django는 Model을 통해 Form Class를 만들 수 있는 ModelForm이라는 Helper 제공
ModelForm Class
- Model을 통해 Form Class를 만들 수 있는 Helper
- 일반 Form Class와 완전히 같은 방식으로 view에서 사용 가능
# articles/forms.py
from django import forms
from .models import Article # 사용할 모델을 import
class ArticleForm(forms.ModelForm): # ⭐ form.Form과 forms.ModelForm이 다름에 주의할 것
class Meta:
model = Article # 어떤 모델을 사용할 것인지 지정
fields = '__all__' # 모델의 어떤 field들을 사용할 것인지 지정
# exclude = ('title',) # 출력에서 제외하는 것. tuple과 list 모두 가능
- 정의한 class 내에 Meta 클래스 선언
- 어떤 model을 기반으로 form을 작성할 것인지에 대한 정보 지정
- widgets을 별도 설정하지 않아도 field의 type을 model에 의거해 지정
exclude?
- 모든 field 중에 제외할 field를 명시해서 제외
- field가 많고, 제외할 field가 적은 경우 사용
- class 변수 field와 exclude는 동시에 사용 불가
Meta Class
- Model에 대한 정보를 작성
Meta 데이터
- 데이터에 대한 데이터
Inner Class
- 클래스 내에 선언된 다른 클래스
- 관련 클래스를 함께 그룹화 → 가독성 및 프로그램 유지 관리 지원(논리적으로 묶어 표현)
- 외부에서 내부 클래스에 접근 불가 → 코드 복잡성 감소
유효성 검사
- 요청한 데이터가 특정 조건에 충족하는지 확인하는 작업
- 데이터베이스 각 필드 조건에 올바르지 않은 데이터가 서버로 전송/저장 되지 않도록 하는 것
is_valid() method
- 유효성 검사를 실행하고, 데이터가 유효한지 여부를 boolean으로 반환
- 데이터 유효성 검사를 보장하기 위한 많은 테스트에 대해 Django는 is_valid()를 제공
save() method
- 주의 : 기존 model의 save method와 다르다!! ModelForm 고유의 save method
- 그냥 form은 save method가 없다!
- Form에 바인딩된 데이터에서 DB 객체를 만들고 저장
- ModelForm의 하위클래스(subClass)는 기존 모델 인스턴스를 키워드 인자 instance로 수용 가능
- 이것이 제공되면 save()는 해당 인스턴스를 수정(UPDATE)
- 제공되지 않으면 save()는 지정된 모델의 새 인스턴스를 만듦(CREATE)
- Form의 유효성이 확인되지 않은 경우, save()를 호출하면 form.errors를 확인하여 에러 목록 확인 가능
예시 코드
# POST data로부터 form 인스턴스 생성
form = ArticleForm(request.POST)
# CREATE
new_article = form.save()
# UPDATE
article = Article.objects.get(pk=1)
# instance 값이 있으면 수정인 것으로 django가 인식
form = ArticleForm(reqeust.POST, instance=article)
form.save()
실습 코드
기존 코드
# articles/views.py
def create(request):
title = request.POST.get('title')
content = request.POST.get('content')
article = Article(title=title, content=content)
article.save()
return redirect('articles:detail', article.pk)
form 사용 및 유효성 검사
# articles/views.py
def create(request):
form = ArticleForm(request.POST) # request.POST 딕셔너리 내에 모든 정보가 있음
# 유효성 검사
if form.is_valid(): # form의 데이터가 유효하다면(True)
# form 자체를 DB에 레코드로 저장
# save() 메서드는 저장된 객체를 반환
# 이를 article에 저장
article = form.save()
return redirect('articles:detail', article.pk)
return redirect('articles:new')
- ArticleForm 클래스에 request.POST를 인자로 넣어 form 인스턴스를 생성한다.
- is_valid 메서드를 이용하면 form 내부의 데이터를 유효성 검사
- 이를 통과하여 True라면 form을 DB에 save
- 이는 저장된 객체를 반환하므로 이를 article에 저장
- article을 활용하여 redirect, 유효성 검사 통과하지 않았다면 다른 return
Form VS ModelForm
Form
- 어떤 Model에 저장해야 하는지 알 수 없으므로 유효성 검사 이후 cleaned_data 딕셔너리 생성
- cleaned_data 딕셔너리에서 데이터를 가져온 후 .save() 호출
- Model에 연관되지 않은 데이터를 받을 때 사용
ModelForm
- Django가 해당 model에서 양식에 필요한 대부분의 정보를 이미 정의
- 어떤 레코드를 만들어야 할 지 알고 있으므로 바로 .save() 호출 가능
cleaned_data 구조 예시
def create(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
title = form.cleaned_data.get('title')
content = form.cleaned_data.get('content')
article = Article.objects.create(title=title, content = content)
else:
form = ArticleForm()
context = {
'form': form,
}
return render(request, 'articles/create.html', context)
함수 구조 합체
CREATE
new와 create는 CREATE를 위해 힘쓰는 두 함수… 합쳐보자!
- view 함수 new가 흡수되므로 urls.py에서 new 부분을 제거해준다.
- template 중에서도 new를 호출하는 url tag들의 주소를 모두 create로 바꿔준다.
path('new/', views.new, name='new'),
기존 분리 코드
# articles/views.py
def new(request):
form = ArticleForm()
context = {
'form': form
}
return render(request, 'articles/new.html', context)
def create(request):
form = ArticleForm(request.POST)
if form.is_valid():
article = form.save()
return redirect('articles:detail', article.pk)
return redirect('articles:new')
합체 코드
# articles/views.py
def create(request):
# create
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
article = form.save()
return redirect('articles:detail', article.pk)
# new
else:
form = ArticleForm()
context = {
'form': form,
}
return render(request, 'articles/create.html', context)
- request method가 new는 GET, create는 POST
- .method 메서드를 이용해 request의 메서드가 무엇인지 확인
-
GET일 때, POST일 때의 상황에 따라 상황과 처리 구분
- is_valid 함수를 통과하지 못하면 else를 거치지 않고 return
- method가 GET일 때는 아예 빈 form, is_valid에서 비었다면 error message를 담은 form을 context에 전달
- return문은 context를 필요로 하므로 else(기존 new)문의 context를 함수 전역으로 내어쓰기
UPDATE
어, edit도 update와 합칠 수 있다!
def edit(request, pk):
article = Article.objects.get(pk=pk)
context = {
'article': article,
}
return render(request, 'articles/edit.html', context)
def update(request, pk):
article = Article.objects.get(pk=pk)
article.title = request.POST.get('title')
article.content = request.POST.get('content')
article.save()
return redirect('articles:detail', article.pk)
- urls.py의 edit을 제거해준다.
위의 코드를 아래처럼 합칠 수 있다.
def update(request, pk):
article = Article.objects.get(pk=pk)
# update
if request.method == 'POST':
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
article = form.save()
return redirect('articles:detail', article.pk)
# edit
else:
form = ArticleForm(instance=article)
context = {
'article': article,
'form': form,
}
return render(request, 'articles/update.html', context)
- update이므로, 과거의 데이터이자 수정을 원하는 instance를 지정한다.
- article이 해당 instance이므로 이를 값으로 넣어준다.
기타 코드의 비밀
form 태그의 action
- form의 action 속성의 값이 없다면 현재 url을 기준으로 HTTP를 전송
- 하지만 명시를 해주는 것을 권장
forms.py의 파일 위치
- Form class는 forms.py 뿐만 아니라 다른 어느 위치에 두어도 상관 없다.
- 하지만 되도록 app폴더/forms.py에 작성하는 것이 일반적인 구조
왜 method를 POST 먼저 확인하는가.
- POST는 DB를 조작하는 method
- 때문에 method가 POST일 때에만 DB를 조작하도록 해야 한다.
- method를 [POST]와 [POST가 아닌 것]으로 구분하여 확인해야 한다.
Widget 활용하기
가. Meta class 내에 정의(비권장)
# articles/forms.py
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = '__all__'
widgets = {
'title': forms.TextInput(attrs={
'class':'my-title',
'placeholder': 'Enter the title',
'maxlength': 10,
}
)
}
Django에서 권장하는 스타일은 아님
나. ModelForm class 내에 정의(권장)
# articles/forms.py
class ArticleForm(forms.ModelForm): # ⭐ form.Form과 forms.ModelForm이 다름에 주의할 것
title = forms.CharField(
label='제목',
widget=forms.TextInput(
attrs={
'class': 'my-title second-class',
'placeholder': 'Enter the title',
}
),
)
content = forms.CharField(
label='내용',
widget=forms.Textarea(
# 인라인 속성 지정
attrs={
'class': 'my-content',
'placeholder': 'Enter the content',
'rows': 5,
'cols': 50,
}
),
# error message customizing
error_messages={
'required': 'Please enter your content', # required 요소가 채워지지 않았을 때의 오류 메시지
}
)
class Meta:
model = Article
fields = '__all__'
Rendering Fields Manually
form field를 template에서 어떻게 출력해야 할까요?
수동으로 Form 작성하기
Rendering fields manually
<form action="" method="POST">
{% csrf_token %}
<div>
{{ form.title.errors }}
{{ form.title.label_tag }}
{{ form.title }}
</div>
<div>
{{ form.content.errors }}
{{ form.content.label_tag }}
{{ form.content }}
</div>
<input type="submit">
</form>
참고자료 : django 공식문서 - forms : rendering fields manually
Looping over the form’s fields
<form action="" method="POST">
{% csrf_token %}
{% for field in form %}
{{ field.errors }}
{{ field.label_tag }}
{{ field }}
{% endfor %}
<input type="submit">
</form>
참고자료 : django 공식문서 - forms : looping over the form’s fields
부트스트랩과 함께 사용하기
Bootstrap class with Widgets
- Bootstrap Form을 사용해 적용
- keyword : form-control
- widget의 class에 form-control 클래스를 입력해주면 된다.
에러메시지 with bootstrap alert 컴포넌트
- looping 방식으로 사용
<form action="" method="POST">
{% csrf_token %}
{% for field in form %}
{% if field.errors %}
{% for error in field.errors %}
<div class="alert alert-warning" role="alert">{{ error|escape }}</div>
{% endfor %}
{% endif %}
<div class="form-group">
{{ field.label_tag }}
{{ field }}
</div>
{% endfor %}
<input type="submit">
</form>
⭐ Django Bootstrap Library : django-bootstrap v5
- form class에 bootstrap을 적용시켜주는 라이브러리
- django-bootstrap-v5 설치 사이트
가. pip install
$ pip install django-bootstrap-v5
나. 앱 등록
# settings.py
INSTALLED_APPS = [
...
'bootstrap5',
...
]
다. base.html에 load
- bootstrap5 CDN을 base.html 템플릿에 load해준다.
- 부트스트랩의 css와 js CDN의 간소화가 가능해진다.
<!-- articles/base.html -->
{% load bootstrap5 %} ⭐ bootstrap5 load
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% bootstrap_css %} ⭐ css CDN의 간소화 기능
<title>Document</title>
</head>
<body>
<div class="container">
{% block content %}
{% endblock content %}
</div>
{% bootstrap_javascript %} ⭐ javascript CDN의 간소화 기능
</body>
</html>
라. form에 적용
- base.html을 extends하는 하위 template
<!-- article/update.html -->
{% extends 'base.html' %}
{% load bootstrap5 %}
...
<form action="" method="POST">
{% csrf_token %}
{% bootstrap_form form layout='horizontal' %}
{% buttons submit="Submit" reset="Cancel" %}{% endbuttons %}
</form>
...
댓글남기기