💻 프로그래밍/Django

validation 체크 노가다를 줄여주는 Django form, modelform, formset 사용

피트웨어 제이 (FitwareJay) 2019. 4. 27. 19:21

안녕하세요! 코딩하는 JAY입니다. 오랜만의 포스팅이네요.. 항상 오랜만 ㅋㅋㅋ 

오늘은 제가 회사 프로젝트를 하면서 진짜 고생 많이 하고, 배우게 돼서 정말 다행인 Form, ModelForm, Formset에 대해서 설명하겠습니다. 일단 각각의 정의(?)를 알아보겠습니다.

 

1. Form(in HTML): HTML폼은 사용자와 웹사이트 또는 어플리케이션이 서로 상호 작용하는 것 중 중요한 기술 중에 하나입니다. form을 사용하여 사용자는 웹사이트에서 웹서버로 데이터를 전송할 수 있습니다.(submit) 일반적으로 데이터는 웹 서버로 전송되지만 웹페이지가 데이터를 사용하기 위하여 사용할 수 도 있습니다.

- 관련 참고글 :  http://www.nextree.co.kr/p8428/

 

HTML : 폼(form) 이해

폼은 알게 모르게 웹에서 많이 사용합니다. 사용자 의견이나 정보를 알기 위해 입력할 큰 틀을 만드는 데 사용되기 때문입니다. 폼은 입력된 데이터를 한 번에 서버로 전송합니다. 전송한 데이터는 웹 서버가 처리하고, 결과에 따른 또 다른 웹 페이지를 보여줍니다. 이번 글에서 우리가 잘 모르는 폼의 내부적인 동작과정부터 폼의 큰 틀을 구성하는 엘리먼트에

www.nextree.co.kr

 

2.Form(in Django): Django Fomr Class는 위에서 설명한 HTML Form을 좀 더 쉽게 만들 수 있고, 각 데이터들의 유효성 검사를 쉽게, 아~주~쉽게 해주는 class입니다.

 

3.ModlelForm(in Django): Modelform은 Form과 거의 동일한 기능을 합니다. Form과 다른 점은 이름에서 알 수 있듯이, model을 기반으로 form을 생성합니다. 기본적으로 field는 모델에서 선언한 field를 사용하며, 추가적으로 필요한 field를 선언하여 추가해 사용할 수 있습니다.

 

4.FormSet(in Django): FormSet은 form들의 말 그대로 폼들의 집합입니다. Form이 여러 개 사용될 경우 FormSet을 이용하면 아주 쉽게 여러 form에 대한 유효성 검사를 처리할 수 있습니다.

 

-Django Form 관련 참고 글:  https://docs.djangoproject.com/en/2.2/topics/forms/#forms-in-django

 

Working with forms | Django documentation | Django

Django The web framework for perfectionists with deadlines.

docs.djangoproject.com

Djang에서 Form은 결국 HTML Form 사용을 좀 더 쉽게 만들 수 있게 해주는 기능입니다. 하지만 그것보다도 Django의 Form이 정말 유용한 이유는 필드의 유효성 체크를 자동으로 해준다는 것입니다.

물론 프론트단에서 각 필드 내용에 대한 유효성 체크를 할 수 있지만, 프론트를 믿지마라..ㅋㅋ

무튼, 프론트단에서 백엔드로 데이터를 전송할 때 A라는 필드에 숫자만 들어가야 하는데, 문자열이 들어간다던지, 텍스트가 100글자 미만만 들어와야 하는데 1000글자가 들어온다던지 이런 것들에 대한 검사를 해줘야 합니다.

이때마다 view에서 이런 것들을 처리하는 코드를 작성하면.. 정말 방대하죠?! 귀찮고..

form은 이 유효성 검사를 아주 쉽게 처리해줍니다. 아래 코드를 보면서 설명할게요!

- Form

1
2
3
4
5
6
7
8
9
from django import forms
 
 
class CompanyForm(forms.Form):
    name = forms.CharField(max_length=10, requried=True)
    date_establish = forms.DateField()
 
    class Meta:
        fields = ['name''date_establish']
 

forms.Form 클래스를 상속받는 CompanyForm이라는 클래스를 정의했습니다. 

name과 date_establish라는 변수를 선언하고 두 변수를 필드로 선언했습니다. 이렇게 하면 name, date_establish가 HTML form 안에 input 태그로 생성됩니다.

이제 간단하게 view와 html 파일을 만들어 보겠습니다.

1. View 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class LandingView(generic.TemplateView):
    template_name = 'landing.html'
 
    def post(self, request, *args, **kwargs):
        form = CompanyForm(request.POST)
 
        if form.is_valid():
            instance = Company() #Company 모델 인스턴스 선언
            # form 유효성이 검증된 data 저장
            instance.name = form.cleaned_data.get('name', None)
            instance.date_establish = form.cleaned_data.get('date_establish', None)
            instance.save()
 
            return render(request, 'landing.html')
        else:
            return HttpResponseNotFound("Validation Faild")
 
    def get(self, request, *args, **kwargs):
        form = CompanyForm()
 
        return render(request, 'landing.html', {'form': form})
 

2. HTML 코드

View 코드를 보면, LandingView에 get요청 시  CompanyForm()을 context로 넘겨줍니다.

HTML에서는 {{  form.as_p }} 로 넘겨받은 form을 생성해줍니다. as_p는 <p> 태그 사용해서 다음 단락으로 form 출력하는 option입니다. 추가적으로
HTML form으로 감싸져 있는 걸 볼 수 있는데, 여기서 submit 버튼을 클릭하면 form의 method에 따라 서버로 form을 전송합니다. 현재 method=POST이고, action은 보낼 url인데, "."으로 되어있는 경우 현재와 동일한 URL을 사용합니다.

실제 동작을 확인해보겠습니다.

Name 필드를 입력하지 않고 '저장'버튼을 눌렀을 경우

 

이미지를 보면 날짜 format을 잘못 입력했을 경우 validation error가 나는 모습을 볼 수 있습니다. 저는 따로 날짜 format을 주지 않았지만 기본적으로'% Y-%m-%d'가 default 인 것 같습니다. 날짜 format 또한 form 필드 선언 시 지정해줄 수 있습니다.

디버그 모드에서 실제로 데이터가 잘 넘어오는지 확인해보겠습니다.

request.POST를 보면 form을 통해 넘어온 데이터들을 확인할 수 있습니다.

그리고 이 각 필드의 유효성 체크(form.is_valid())가 통과하면 

form.cleand_data로 유효성이 통과된 데이터를 가져올 수 있습니다. 절대 request.POST로 넘어온 데이터를 바로 사용하시면 안 돼요~ㅎㅎ

post 메소드를 보면 유효성검사가 통과된 데이터를 Company 모델에 저장합니다. DB를 확인하면?!

자! 이렇게 생성된 데이터를 확인할  수 있습니다. 참 쉽쥬~ 근데 여기서 더 쉬운 방법이 있습니다.

- ModelForm

바로 모델 폼입니다!! 모델 폼은 form과 비슷하지만 model을 기반으로 하기떄문에 더욱더 편하게 사용할 수 있습니다.

1
2
3
4
5
class CompanyModelForm(forms.ModelForm):
 
    class Meta:
        model = Company
        fields = '__all__'
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class LandingView(generic.TemplateView):
    template_name = 'landing.html'
 
    def post(self, request, *args, **kwargs):
        form = CompanyModelForm(request.POST)
 
        if form.is_valid():
            form.save(commit=True)
 
            return render(request, 'landing.html')
        else:
            return HttpResponseNotFound("Validation Faild")
 
    def get(self, request, *args, **kwargs):
        form = CompanyModelForm()
 
        return render(request, 'landing.html', {'form': form})
 
 

코드를 보면 뭐가 좀 더 심플하죠?ㅎㅎ 모델폼은 따로 저장해주고 데이터를 넣어주는 코드 없이 유효성 검사가 끝난 후 , form.save() 하면 바로 db에 저장이 됩니다. commit=True는 바로 저장한다는 의미고, False인 경우에는 좀 나중에 저장하는 기능입니다. commit=False하고 다른 기능들을 하거나 데이터를 추가적으로 처리하는 경우 사용합니다. 자 결과를 확인해 볼까요?

modelform 클래스 생성 시 feilds를 __all__로 모두 사용하겠다 해서 이번에는 모든 필드가 나왔습니다. 추가적으로 필드를 넣고 싶다면, form 클래스에서 필드를 선언했던 것처럼 선언하시면 사용할 수 있습니다. 

- FormSet(ModelFormSet)

마지막으로 FormSet에 대해 알아보도록 하겠습니다. Formset은 말 그대로 Form들의 집합입니다. 코드로 먼저 확인해 보겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class LandingView(generic.TemplateView):
    template_name = 'landing.html'
 
    def post(self, request, *args, **kwargs):
        formset_class = modelformset_factory(Company, extra=0, form=CompanyModelForm)
        formset = formset_class(request.POST)
        if formset.is_valid():
            for form in formset:
                if form.is_valid():
                    form.save()
 
        return render(request, 'landing.html', {'formset': formset})
 
    def get(self, request, *args, **kwargs):
        UserFormSet = modelformset_factory(Company, extra=0, form=CompanyModelForm)
        formset = UserFormSet(queryset=Company.objects.filter(id__lte=2))
 
        return render(request, 'landing.html', {'formset': formset})
 

먼저 view를 보면, modelformset_factroy() 로 모델폼 셋을 만들어 줍니다. 물론 formset_factory()도 있습니다. 

modelformset_factory() 설명 : https://docs.djangoproject.com/en/2.2/ref/forms/models/

 

Model Form Functions | Django documentation | Django

Django The web framework for perfectionists with deadlines.

docs.djangoproject.com

폼셋을 생성하게 되면, 현재 모델에 있는 모든 row에 대한 form을 만들어 줍니다. extra는 모델에 저장된 데이터가 아닌 새로운 form(아무것도 저장되어 있지않은)을 만들어 줍니다.

queryset을 통해 모델을 필터링 할 수 도 있어요!저는 id가 2보다 같거나 작은 row들만 가져와서 템플릿으로 전송했습니다.

html코드를 보면 formset을 for문으로 돌려서 개수만큼 form을 출력했습니다.

여기서 한가지 주의할 점은 {{ formset.management_form }} 코드를 추가해줘야 합니다. 이 코드는 formset을 관리(?)하기 위한 코드이며, 없으면 raise 가 납니다.

자세한 내용 : https://docs.djangoproject.com/en/dev/topics/forms/formsets/#understanding-the-managementform

 

Formsets | Django documentation | Django

Django The web framework for perfectionists with deadlines.

docs.djangoproject.com

동작을 확인해 보면~ 이렇게 쉽게 form을 이용해 db를 데이터를 저장하고 수정하는 동작을 쉽게 할 수 있습니다.

- 마치며

오랜만에 글쓰는거라 두서가 없었는데요, 여기서 요점은 form, modelform을 사용하면 프론트단에서 넘어오는 데이터들에 대한 유효성 검사, form자체를 생성하는 기능들을 쉽게 할 수 있다는 점입니다. 

제가 3주전 처음 웹을 시작했을때 그냥 하드코딩으로 일일히 데이터들에 대한 유효성 검사 삽질..등을 해보면서 form, modelform, formset의 중요성을 깨달았습니다..흑흑... 

여기서 나아가 inline이라는 기능도 있는데 간단히 설명하자면 서로다른 form들을 하나로 사용할 수 있게 해주는 겁니다.

직접 코딩을 해보시면서 이해하시면 좀 더 쉬울것 같아요!

오늘도 전국의 코린이들 화이팅 하시고!(물론저도,,,) 재밌는 코드 생산하세요~ 아디오스~