Django ModelForm - 폼 자동화와 URL 통합
Django ModelForm - 폼 자동화와 URL 통합
Django ModelForm - 폼 자동화와 URL 통합
개요
CRUD 로직을 더욱 효율적으로 개선합니다:
- URL 통합: create(new, create) / update(edit, update)를 하나의 URL로 통합
- Form 자동화: forms.py를 통한 폼 자동 생성
- GET/POST 방식 통합: 하나의 뷰에서 GET과 POST 요청을 모두 처리
1. URL 통합
기존 방식 (URL 분리)
1
2
3
4
5
# 기존: 2개의 URL 필요
path('new/', views.new, name='new'), # 폼 표시
path('create/', views.create, name='create'), # 데이터 저장
path('edit/<int:id>/', views.edit, name='edit'), # 수정 폼 표시
path('update/<int:id>/', views.update, name='update'), # 데이터 업데이트
개선된 방식 (URL 통합)
1
2
3
# 개선: 1개의 URL로 통합
path('create/', views.create, name='create'), # GET: 폼 표시, POST: 데이터 저장
path('update/<int:id>/', views.update, name='update'), # GET: 수정 폼, POST: 데이터 업데이트
2. Form 자동화 (forms.py)
forms.py 생성
articles/forms.py:
1
2
3
4
5
6
7
8
9
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = '__all__' # 모든 필드 포함
# 또는 특정 필드만: fields = ['title', 'content']
ModelForm의 장점
- 자동 폼 생성: 모델의 필드를 기반으로 폼 자동 생성
- 검증 자동화: 모델의 제약조건에 따른 자동 검증
- 유지보수성: 모델 변경 시 폼도 자동으로 업데이트
3. GET/POST 방식 통합 처리
Create 기능 통합
articles/views.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from django.shortcuts import render, redirect, get_object_or_404
from .models import Article
from .forms import ArticleForm
def create(request):
# POST 요청: 사용자가 폼을 제출한 경우
if request.method == 'POST':
# 사용자가 입력한 데이터로 폼 생성
form = ArticleForm(request.POST)
# 폼 검증
if form.is_valid():
# 데이터 저장
article = form.save()
# 상세 페이지로 리다이렉트
return redirect('articles:detail', id=article.id)
# GET 요청: 사용자에게 빈 폼을 보여주는 경우
else:
# 빈 폼 생성
form = ArticleForm()
# 공통: 폼을 템플릿에 전달
context = {
'form': form,
}
return render(request, 'articles/create.html', context)
처리 흐름
- GET 요청: 빈 폼을 사용자에게 표시
- POST 요청 (유효하지 않은 데이터): 검증 실패 시 폼을 다시 표시
- POST 요청 (유효한 데이터): 데이터 저장 후 리다이렉트
4. 템플릿 작성
base.html 업데이트
templates/base.html:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Django Board{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{% url 'articles:index' %}">Django Board</a>
<div class="navbar-nav">
<a class="nav-link" href="{% url 'articles:index' %}">Home</a>
<a class="nav-link" href="{% url 'articles:create' %}">Create</a>
</div>
</div>
</nav>
<div class="container mt-4">
{% block body %}
{% endblock %}
</div>
</body>
</html>
create.html
articles/templates/articles/create.html:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{% extends 'base.html' %}
{% block title %}새 글 작성 - Django Board{% endblock %}
{% block body %}
<h1>새 글 작성</h1>
<form action="" method="POST">
{% csrf_token %}
{% raw %}{{ form.as_p }}{% endraw %}
<button type="submit" class="btn btn-primary">작성</button>
<a href="{% url 'articles:index' %}" class="btn btn-secondary">취소</a>
</form>
{% endblock %}
폼 렌더링 옵션
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 기본 렌더링 -->
{% raw %}{{ form }}{% endraw %}
<!-- 각 필드를 <p> 태그로 감싸기 -->
{% raw %}{{ form.as_p }}{% endraw %}
<!-- 각 필드를 <table> 태그로 감싸기 -->
{% raw %}{{ form.as_table }}{% endraw %}
<!-- 각 필드를 <ul> 태그로 감싸기 -->
{% raw %}{{ form.as_ul }}{% endraw %}
<!-- 개별 필드 렌더링 -->
{% raw %}{{ form.title }}{% endraw %}
{% raw %}{{ form.content }}{% endraw %}
5. Update 기능 통합
views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def update(request, id):
# 기존 게시물 가져오기
article = get_object_or_404(Article, id=id)
# POST 요청: 사용자가 수정 폼을 제출한 경우
if request.method == 'POST':
# 기존 데이터 + 사용자 입력 데이터로 폼 생성
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
# 데이터 저장
form.save()
return redirect('articles:detail', id=article.id)
# GET 요청: 수정 폼을 보여주는 경우
else:
# 기존 데이터로 폼 생성
form = ArticleForm(instance=article)
context = {
'form': form,
}
return render(request, 'articles/update.html', context)
update.html
articles/templates/articles/update.html:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{% extends 'base.html' %}
{% block title %}글 수정 - Django Board{% endblock %}
{% block body %}
<h1>글 수정</h1>
<form action="" method="POST">
{% csrf_token %}
{% raw %}{{ form.as_p }}{% endraw %}
<button type="submit" class="btn btn-primary">수정</button>
<a href="{% url 'articles:detail' article.id %}" class="btn btn-secondary">취소</a>
</form>
{% endblock %}
6. 완전한 CRUD 구현
URL 설정
articles/urls.py:
1
2
3
4
5
6
7
8
9
10
11
12
from django.urls import path
from . import views
app_name = 'articles'
urlpatterns = [
path('', views.index, name='index'),
path('create/', views.create, name='create'),
path('<int:id>/', views.detail, name='detail'),
path('<int:id>/update/', views.update, name='update'),
path('<int:id>/delete/', views.delete, name='delete'),
]
모델 정의
articles/models.py:
1
2
3
4
5
6
7
8
9
10
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
모든 뷰 함수
articles/views.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from django.shortcuts import render, redirect, get_object_or_404
from .models import Article
from .forms import ArticleForm
def index(request):
articles = Article.objects.all()
context = {'articles': articles}
return render(request, 'articles/index.html', context)
def detail(request, id):
article = get_object_or_404(Article, id=id)
context = {'article': article}
return render(request, 'articles/detail.html', context)
def create(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
article = form.save()
return redirect('articles:detail', id=article.id)
else:
form = ArticleForm()
context = {'form': form}
return render(request, 'articles/create.html', context)
def update(request, id):
article = get_object_or_404(Article, id=id)
if request.method == 'POST':
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
form.save()
return redirect('articles:detail', id=article.id)
else:
form = ArticleForm(instance=article)
context = {'form': form}
return render(request, 'articles/update.html', context)
def delete(request, id):
article = get_object_or_404(Article, id=id)
article.delete()
return redirect('articles:index')
7. 폼 커스터마이징
forms.py 고급 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'content']
widgets = {
'title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '제목을 입력하세요'
}),
'content': forms.Textarea(attrs={
'class': 'form-control',
'rows': 10,
'placeholder': '내용을 입력하세요'
})
}
labels = {
'title': '제목',
'content': '내용'
}
help_texts = {
'title': '제목은 100자 이내로 입력해주세요.',
'content': '내용을 자세히 작성해주세요.'
}
폼 검증 추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'content']
def clean_title(self):
title = self.cleaned_data.get('title')
if len(title) < 5:
raise forms.ValidationError('제목은 5자 이상 입력해주세요.')
return title
def clean_content(self):
content = self.cleaned_data.get('content')
if len(content) < 10:
raise forms.ValidationError('내용은 10자 이상 입력해주세요.')
return content
8. 실무 팁
1. 폼 필드 제외
1
2
3
4
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
exclude = ['created_at', 'updated_at'] # 특정 필드 제외
2. 조건부 필드 표시
1
2
3
4
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk: # 수정 모드
self.fields['title'].widget.attrs['readonly'] = True
3. 폼 에러 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<form method="POST">
{% csrf_token %}
{% raw %}{{ form.as_p }}{% endraw %}
{% if form.errors %}
<div class="alert alert-danger">
<ul>
{% for field, errors in form.errors.items %}
{% for error in errors %}
<li>{% raw %}{{ error }}{% endraw %}</li>
{% endfor %}
{% endfor %}
</ul>
</div>
{% endif %}
<button type="submit">제출</button>
</form>
이렇게 Django ModelForm을 활용하면 폼 처리가 훨씬 간단하고 효율적이 됩니다!
This post is licensed under CC BY 4.0 by the author.