[Django] ModelForm: 모델 기반 폼 자동 생성
Django ModelForm, model form, Meta.fields, Meta.exclude, ModelForm save, clean_field
정의
ModelForm = Model 로부터 Form 자동 생성. 필드 반복 정의 없이 CRUD 폼 완성.
일반 Form 은 django-forms 참고.
왜 ModelForm?
# ❌ 일반 Form: 필드 중복 정의
class UserForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
age = forms.IntegerField(min_value=0)
def save(self):
# 직접 model 저장 코드...
# ✓ ModelForm: 자동
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ['name', 'email', 'age']
# form.save() 자동 지원!
기본 사용
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'body', 'category', 'tags']
widgets = {
'body': forms.Textarea(attrs={'rows': 20}),
}
labels = {
'title': '제목',
'body': '본문',
}
help_texts = {
'title': '100자 이내',
}
error_messages = {
'title': {
'required': '제목은 필수입니다',
'max_length': '제목이 너무 길어요',
},
}
fields vs exclude
class Meta:
model = User
fields = ['name', 'email'] # 명시적 화이트리스트 (권장)
# 또는
fields = '__all__' # 모든 필드 (위험!)
# 또는
exclude = ['password', 'is_staff'] # 블랙리스트 (위험!)
WARNING
fields = '__all__' 또는 exclude = 새 필드 추가 시 자동 노출 → mass assignment 취약점. fields 화이트리스트 권장.
View 에서 사용
def create_article(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
article = form.save() # DB 저장 후 instance 반환
return redirect('article_detail', pk=article.pk)
else:
form = ArticleForm()
return render(request, 'article_form.html', {'form': form})
def edit_article(request, pk):
article = get_object_or_404(Article, pk=pk)
if request.method == 'POST':
form = ArticleForm(request.POST, instance=article) # ← 편집!
if form.is_valid():
form.save()
return redirect('article_detail', pk=article.pk)
else:
form = ArticleForm(instance=article)
return render(request, 'article_form.html', {'form': form})
save(commit=False)
저장 전 수정:
if form.is_valid():
article = form.save(commit=False)
article.author = request.user # 현재 사용자 자동 지정
article.status = 'draft'
article.save() # 이제 DB에
form.save_m2m() # ManyToMany 별도 저장!
IMPORTANT
commit=False + save_m2m() 짝. Many-to-Many 필드가 있으면 필수.
Custom Validation
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'body', 'publish_date']
def clean_title(self):
title = self.cleaned_data['title']
if Article.objects.filter(title=title).exclude(pk=self.instance.pk).exists():
raise forms.ValidationError('이미 존재하는 제목입니다')
return title
def clean(self):
cleaned_data = super().clean()
publish_date = cleaned_data.get('publish_date')
if publish_date and publish_date < timezone.now():
self.add_error('publish_date', '과거로 예약할 수 없습니다')
return cleaned_data
자세한 form 검증은 django-forms.
Class-Based View 와 함께
from django.views.generic.edit import CreateView, UpdateView
class ArticleCreateView(CreateView):
model = Article
form_class = ArticleForm
template_name = 'article_form.html'
success_url = '/articles/'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class ArticleUpdateView(UpdateView):
model = Article
form_class = ArticleForm
template_name = 'article_form.html'
자세한 CBV 는 django-views, django-mixins.
Template 렌더링
<form method="post">
{% csrf_token %}
{{ form.as_p }} <!-- 각 필드 <p> 로 -->
<button type="submit">저장</button>
</form>
<!-- 또는 필드 단위 -->
<form method="post">
{% csrf_token %}
<div>
<label for="{{ form.title.id_for_label }}">제목</label>
{{ form.title }}
{{ form.title.errors }}
</div>
<button>저장</button>
</form>
Django 6.0 의 새 template 문법:
{{ form.as_field_group }} <!-- 필드 그룹으로 (6.0+) -->
필드 override
class ArticleForm(forms.ModelForm):
# 모델 필드보다 우선
title = forms.CharField(
max_length=200,
widget=forms.TextInput(attrs={'class': 'form-control'}),
)
class Meta:
model = Article
fields = ['title', 'body']
Formset (여러 개 동시)
자세한 건 django-formsets.
다른 프레임워크
| Framework | 모델 기반 폼 |
|---|---|
| Django | ModelForm |
| Rails | form_with model: (helper) |
| Spring | @ModelAttribute + Jakarta Validation |
| FastAPI | Pydantic + response_model |
흔한 함정
WARNING
fields = '__all__'= mass assignment 사고.save_m2m()잊음 = ManyToMany 필드 저장 안 됨.instance=obj안 넘김 = create 로 오해되어 새 row 생성.- user 필드 폼에 노출 = 사용자가 다른 사람 ID 조작.
exclude또는 view 에서 override.
관련 위키
이 글의 용어 (5개)
- [Django] Form과 ModelFormdjango
- 정의 Django Form은 사용자 입력 정의·검증·렌더링·바인딩을 통합. 일반 은 임의 필드, 은 모델 자동 매핑. 검증과 보안(CSRF, SQL 인젝션 방지) 내장. Form…
- [Django] Formsets: 여러 폼 동시 처리django
- 정의 Formset = 여러 폼을 한 페이지에서 동시 처리. "상품 3개 한 번에 등록", "주문 상세 여러 줄 편집" 같은 시나리오. 3가지 종류 1. 기본 FormSet Te…
- [Django] Mixin 패턴: LoginRequired, UserPasses, Customdjango
- 정의 Django Mixin은 CBV에 공통 기능을 추가하는 작은 클래스. 다중 상속으로 조합. 인증, 권한, 로깅, 메시지 등에 활용. MRO(Method Resolution …
- [Django] Model과 Field 타입django
- 정의 Django 모델은 DB 테이블의 Python 표현이다. 상속, 클래스 속성 = 컬럼, 메타 클래스 = 테이블 설정. 모델 정의 후 + 로 DB 스키마 자동 생성. 기본 정…
- [Django] View: 함수 기반 vs 클래스 기반django
- 정의 Django View는 HTTP 요청을 받아 응답을 반환하는 callable. 두 스타일이 공존. - FBV (Function-Based View): 일반 함수. 간단하고 …
💬 댓글