본문 바로가기
WebFramework/Python Django

[Python Django] 11. Django Form / Model Form 사용하기 (1)

by Rosmary 2024. 4. 15.
728x90
반응형

 

 

 

 

 

최근 며칠 간 Django 프로젝트를 만들고 지우고를 반복하면서 웹 프로그래밍 연습을 하다보니, Django가 조금씩 손에 익어가는 중이다. 물론 필자는 Front End 쪽을 거의 5년 전에 독학하고 난 뒤로 제대로 사용한 적이 없어 디자인은 봐 줄 수 없을 만큼 처참하지만, 적어도 가장 중요한 Django의 기본적인 로직이 머리에 제대로 박히게 된 상태라 프론트 쪽은 천천히 배우면 된다고 생각하면서, 최근 들어 전례없이 여유로운 기분을 느끼고 있다.

 

여러 번 프로젝트를 만들고 폭파시키고 다시 만들고를 반복하다보니, 한 가지 불편한 점이 있다. 아래에 필자가 다시 만든 프로젝트의 결과 화면을 보자. 

 

 

 

간단한 게시판 기능이 포함되어 있는 프로젝트를 하나 만들어서 구동했고, 위와 같이 데이터도 잘 들어가는 것을 확인까지 한 상태다. 여기서 새 글 작성 버튼(New Article)을 클릭하면 DB에 저장할 Article 데이터를 저장할 입력 페이지가 나타난다.

 

 

 

이 페이지에서 입력으로 받는 것은 새 글의 제목(Title)과 내용(Contents) 뿐이다. 물론, 필자의 의도대로 사용자가 데이터를 제대로 입력하고 Post 버튼을 누른다면 아무도 고통받는 사람이 없겠지만, 아래와 같이 title을 빈 값으로 입력 폼을 제출하는 사용자가 있다면 ...

 

 

 

완전하지 않은 저런 데이터를 일일이 관리자가 확인하고 지워야하는, 즉 일이 늘어나는 상황이 발생하게 된다. 물론, request 객체로 넘어온 입력값을 views.py에서 검증과정을 거쳐 아무 문제가 없는 값에 대해서만 DB에 추가하는 방안도 있긴 하지만, 웹이 게시판 하나의 기능만 있는 것도 아니고 로그인이나 회원가입 등지에서도 값의 입력은 꾸준히 사용하기 때문에 모든 <form> 객체로부터 넘어온 값들에 대해 일일이 검증 코드를 작성하는 것도 상당한 일이 된다. 

 

이번 포스팅에서는 웹에서의 입력값 검증을 쉽게 할 수 있도록 만드는 Django 모듈인 forms에 대해 간략하게 알아보려 한다.

 

 

 

 

1. django.forms.Form 클래스

 

Django는 HTML에서 사용자가 입력한 값을 검증할 수 있도록 django.forms.Form 클래스를 제공한다. 이 Form 클래스 파일의 내용을 열어보면 BaseForm이라는 클래스만 상속받을 뿐, 아무런 내용도 작성된 것이 없다.

 

 

주석이 달려있는 내용을 대략적으로 보면 입력값의 필드를 지정하기 위한 클래스라는 설명이 있다. 이 클래스가 상속하는 BaseForm은 별도로 입력값과 관련된 정보를 받는 부분이 없기 때문에, HTML의 입력값을 클래스로 정리하고자 한다면 이 Form 클래스를 사용해야한다. 

 

입력값을 정의하기 위한 Form 클래스는 보통 개별 app 폴더 내 forms.py 라는 파일에 작성한다. 현재 필자의 프로젝트에는 게시글 작성 기능을 위한 articles 앱이 존재하는데 articles 폴더 내부에 forms.py 파일을 만들고 해당 파일 내에 Forms 클래스 및 기타 클래스 사용을 위해 django.forms 모듈을 import 해 준다.

 

 

 

 

본격적으로 Form을 입력받을 수 있는 기능을 담당하는 클래스를 만들어보자. 클래스 이름은 새 Article의 생성에 사용할 데이터를 받을 폼이라는 의미에서 ArticleForm으로 지정하려한다. 

 

 

 

HTML에서 받아올 입력값에 대해 클래스 내부에 정의하기 전에 Form 클래스가 어떤 방식으로 동작하는지 먼저 알아보자.

 

 

위의 내용은 Django 공식 문서에서 Form에 대해 소개한 내용의 일부를 발췌한 것이다. 잘 보면 Django의 forms는 

 

- 정의한 데이터에 대한 HTML 구문 준비

- 준비한 HTML의 Rendering

- 사용자로부터 입력받은 값을 수신 및 처리

 

라는 3 가지 기능을 크게 담당한다. 즉, Form 클래스를 활용하면, 데이터에 대한 입력값 지정 + HTML 랜더링 + 사용자로부터의 입력값 수거까지 모두 진행할 수 있다는 이야기다.

 

해당 공식 문서의 아랫부분을 보면 Form 클래스에 정의된 데이터가 어떻게 HTML로 변환되는지 설명되어 있다. 

 

 

 

Form 클래스를 상속받는 클래스 내부에 입력값과 관련된 정보를 작성하면, 기본적으로 <form> 태그 내부에서 필요한 <label> 태그와 <input> 태그가 생성된다. 클래스 내에 작성된 정보 중, 변수명은 입력값에 대한 input 태그의 ID, Name을 지정하며, CharField는 input의 type을 text 형태로 입력받음을 알려주는 것이라 보면 된다. CharField 내부 클래스 인자는 label과 max_length가 있는데, label은 <label> 태그 내부에 작성할 값을 지정하며, max_length는 text input의 최대 길이를 지정하는 옵션에 적용된다. 

 

Form은 클래스 내부에 정의된 데이터 정보를 HTML에 Rendering 해야 하므로, 이 Form 클래스 역시 view에서 HTML Rendering 시 context 데이터에 함께 넘어가야한다. 여기까지 하나씩 진행해보자.

 

*  forms.py

 

*  views.py

 

 

이제, Article을 생성하는 HTML 정보가 랜더링 페이지인 articles/create.html로 넘어간다. 따라서 articles/create.html 내에 DTL을 사용하여 전달되는 form 정보를 호출하면 아래와 같이 create.html 페이지에 form이 생성되는 것을 확인할 수 있다.

 

 

Form에 지정한 내용대로 title과 Contents에 대한 입력 폼은 완성이 되었지만, 아직 HTML 파일에 추가 작업이 필요하다. 우선 Form에 지정된 데이터 정의 내용은 <form>과 <button> 태그까지 랜더링 페이지에 포함하지 않기 때문에 이 태그들은 직접 HTML에서 생성해주어야 한다.

 

 

 

 

2. django.forms.widgets

 

어느정도 입력과 관련된 페이지가 완성되기는 했지만, 글 작성에 필요한 contents는 한 줄 입력이 가능한 input이 아니라 textarea와 같은 태그가 오는 것이 아무래도 더 좋아보인다. 그런데 Django의 forms 모듈에서는 <textarea>를 직접적으로 호출하는 Field 클래스가 존재하지 않는다.

 

아랫부분에 더 많은 Field가 있지만 Text와 관련된 Field는 존재하지 않는다.

 

 

그럼, Django의 Form 관련 작업은 Django에서 지원하지 않는 HTML 태그를 사용할 수 없는 것인지 궁금해진다. 다행히 Django는 이들 Tag를 지원하지 않는 것이 아니라 조금 다른 방식으로 호출하도록 만들어놓았다. 각각의 Field는 연관이 있는 태그들을 묶어서 사용하는 개념이라 보면 되고, 세부 태그는 widget이라 불리는 클래스를 Field 내에서 호출해서 사용하도록 만들어놓았다.

 

 

 

위의 예시에서는 comment 입력을 위해 widget을 forms.TextArea로 설정해놓았는데, 이 클래스를 열어보면 Textarea 위젯이 참조하는 HTML 파일명이 확인된다.

 

 

template_name에 지정된 파일을 열어보면 이 파일은 동일 경로의 input.html 파일을 Import하고 있는데, 이 input.html 파일은 필자가 앞서서 사용한 CharField의 Input 태그와 동일한 형태를 띄고 있음을 알 수 있다.

 

공식 문서 예시만 보면 헷갈릴 수 있는데 CharField와 CharField(widget=forms.TextInput)은 동일한 것이다.

 

필자의 경우, 글의 내용을 작성해야하기 때문에 한 줄 작성만 가능한 TextInput 대신, Textarea를 widget으로 지정하여 사용해보려한다. Textarea를 widget으로 지정한 뒤,

 

 

 

다시 웹 페이지로 접속하면 이전과 달리 내용 입력 창이 여러 줄을 입력할 수 있는 형태로 변경됨을 알 수 있다.

 

 

 

"혹시 Form에서 지정한 Field와 Widget이 생성하는 Tag에 대해 option도 넣을 수 있나요?"

 

당연히 넣을 수 있다. widget 계열 클래스는 인자로 attrs를 제공하는데, 인자에 지정할 옵션을 Python Dictionary 형태로 작성하면 된다.

 

 

 

Field 계열의 클래스에서는 직접적으로 태그 옵션을 지정할 수 있는 방법이 존재하지 않는데, 이 방법은 본 포스팅의 후반부에 설명하려 한다.

 

 

 

3. django.forms.ModelForm

 

현재 필자가 조잡하게 만든 게시판 앱은 Models.py가 이미 생성되어 있는 상태다 (Models.py 생성과 관련된 기본적인 부분은 이 포스팅을 참고하자). 

 

row의 ID는 자동으로 PK 및 auto increament 처리 된다.

 

 

writer와 created_dt, modified_dt는 우선 기본값 또는 자동으로 현재 시간을 반영되도록 만들어 놓았기 때문에 실질적으로 HTML에서 사용자가 입력한 Title과 Contents 내용만 참조하여 DB를 업데이트하게 된다. 

 

그럼, Form에서 지정하는 데이터 정보와, Model에서 필요한 데이터 정보 역시 동일하게 맞춰줘야한다는 문제가 생긴다. 만약 필자가 Article Model의 title은 최대 허용 길이를 20자로 지정하고 Form에서는 25자로 지정한다면, 사용자가 25자의 Title로 새 글을 올리게 되면 추후 문제가 발생할 수도 있다.

 

따라서 Form의 데이터 정의 내용과 Model의 각 필드 정의 내용을 동일하게 맞춰주어야 할 필요가 있는데, 개발 도중에는 DB의 조건 변경이 워낙 흔하게 벌어지다보니, DB에서 변경 하나만 일어나도 관련된 Form 데이터의 내용도 함께 바꾸어주어야한다는 문제가 발생한다. 그럼, "저 Form과 Model을 한 번에 처리할 수 있는 기능을 Django에서 제공해주지 않을까"라는 생각까지 다다르게 된다.

 

Django는 이러한 문제를 예방하기 위해 django.forms모듈 내에 ModelForm이라는 클래스를 제공한다. ModelForm 역시 Form 클래스의 일종이긴 하지만 DB Model과 연결되는 부분을 고려해야한다는 차이점이 존재하기 때문에 사용법 역시 조금은 다르다. 

 

 

ModelForm의 실제 코드를 보면 metaclass 라는 클래스 인자로 ModelFormMetaClass가 설정된 것이 보이는데, 

 

 

 

 

이 클래스는 Meta라는 이름의 클래스에서 연결하고자 하는 Model의 정보를 호출하여 opts라는 변수에 저장하도록 설정이 되어 있다. 따라서, ModelForm의 기본적인 형태는 아래와 같이 나타나야 한다.

 

 

 

HTML로부터 입력받을 값과 관련된 Model은 Article이며 Model의 fields 값은 title과 contents로 지정한다는 의미다. 이제 views.py에 새로 생성한 클래스 파일을 form으로 적용하고 HTML 랜더링 데이터로 함께 전달하도록 만들자. 

 

 

 

앞서 Form만 사용했을 때, contents의 입력 폼은 widget으로 Textarea를 적용해야만 커다란 입력창이 폼으로 나타났던 것에 반해, 이번에는 별도로 widget 설정을 진행하지 않았음에도 정상적으로 <textarea> 태그가 적용된 것이 확인된다. 이는

Articles/models.py에 contents는 model.TextField를 사용하여 값을 처리하도록 되어 있기 때문에, ModelForm이 이를 참조하여 HTML 파일에 <textarea> 태그를 생성했기 때문이다. textaread 태그의 option 설정 값이 Form 클래스에서 widget을 적용했을 때와 달라졌다는 것을 확인해보자. 

 

또한, ModelForm에 정의된 데이터는 반드시 입력해야하는 값에 대한 검증도 가능해진다. 필자가 HTML 페이지에서 반드시 입력해야하는 필드는 Title 뿐인데 (contents는 Model 내에서 blank를 허용하도록 설정되어 있다), 이제 ModelForm의 적용으로 인해 title을 비운 상태로 Post 버튼을 누르게 되면, Title 필드 위에 값을 입력하라는 안내 문구가 출력되게 된다. 

 

저 메세지 변경 방법은 조금 더 찾아봐야한다.

 

 

 

 

 

4. views.py 파일에서 ModelForm 매서드를 이용하여 입력값 다루기

 

 

이제 views.py 파일에서 HTML에서 POST로 전송되어 오는 데이터의 처리 부분 코드를 보자. 

 

 

 

작성한 코드를 보면, POST로 전달되는 데이터를 변수화하는 부분, 그리고 변수화된 데이터를 Model에 적용하여 저장하는 부분으로 나뉘어져 있음을 확인할 수 있다. 그런데, ModelForm을 사용한다는 것은 이 두 객체에 대해 동시에 접근이 가능하다는 의미이므로, 위의 코드 역시 ModelForm만을 사용하여 간단하게 동작하도록 변경할 수 있다. 

 

모든 Form은 BaseForm을 상속받는데 BaseForm은 인자로 HTML에서 돌아오는 정보(request.GET, request.POST 등)를 받을 수 있도록 코드가 구성되어 있다. 

 

 

 

따라서, 아래와 같이 Form을 통해 전달되는 모든 데이터를 단 한 줄의 코드를 사용하여 객체 형태로 받는 것이 가능해진다.

 

 

 

ModelForm의 경우, BaseForm은 물론이고 Model까지 연결되어 있기 때문에 다양한 기능의 매서드를 사용할 수 있다. 그 중 가장 많이 사용되는 것이 입력값의 검증이 완료되었는지를 boolean으로 반환하는 is_valid()와, Form에 반환된 입력값을 바로 DB Model에 업데이트하는 save() 매서드다. 두 매서드는 보통 아래와 같이 사용된다.

 

 

 

즉, 입력값에 대한 검증이 완료된 ModelForm이라면 DB에 적용하라는 코드인데, 단 두 줄의 코드만으로 입력값 검증 및 DB 업데이트가 완료된다는 점에서 매우 효율적이다. 결과는 아래와 같이 나타난다.

 

 

 

 

 

만약, 사용자가 입력한 데이터를 일괄적으로 처리하지 않고, 각 입력 폼마다 입력된 값을 추출하고 싶다면 ModelForm의 clean() 매서드를 사용하면 된다. 이 매서드는 데이터명과 사용자 입력값을 Dictionary 형태로 반환하는데, 이를 활용하여 조금 더 안전한 로직을 구현할 수 있다. 예를 들면 SQL Injection을 막아야 한다던가...

 

 

 

다만 clean() 매서드는 사용 이전에 반드시 get_context()라는 매서드를 호출해야만 오류 없이 사용이 가능한 듯 한데, 이 부분은 조금 더 확인이 필요하다. 원래 이 clean() 매서드가 ModelForm에 cleaned_data() 라는 오버라이드 매서드가 정의되어 있어야만 사용이 가능하도록 설정되어 있는 듯 한데, 이와 관련된 내용은 정리되면 다른 포스팅에서 다루어보려한다.

 

 

 


다음 포스팅에서는 ModelForm의 Meta 데이터에서 입력할 수 있는 정보 중 이번 포스팅에서 다루어보지 않았던 몇 가지에 대해 알아보려한다.

 

 

 

End.

 

 

반응형

댓글