My Profile Photo

DongChanS's blog


수학과 학생의 개발일지


쟝고(Django) 5편 - Django Form

Form태그를 쟝고를 이용해서 효율적으로 작성하는 법에 대해서 알아보도록 하겠습니다.

1) 서론

1-1. Form 태그의 불편함

흔히 HTML에서 제공하는 Form태그는 이렇게 생겼다.


<div class="row">
    <div class="col-md-6 offset-md-3">
        <form action="" method="POST">
            {% csrf_token %}
            <div class="form-group">
                <label for="username">Username: </label>
                <input type="text" name="username" id="username" class="form-control" placeholder="Enter username">
            </div>
            <div class="form-group">
                <label for="password">Password: </label>
                <input type="password" name="password" id="password" class="form-control" placeholder="Password">
            </div>
            <button type="submit" class="btn btn-primary">Submit</button>
        </form>    
    </div>
</div>

C,R,U,D 중에서 무려 C와 U를 할 때마다 이 form을 작성해야하는데 이게 여간 귀찮은 일이 아니다.

더 큰 문제는 데이터의 유효성 검사가 매우 약하다는 점이다.

22

예를 들어서 고객센터에 불편사항을 문의할 때 누군가가 악의적으로 빈 데이터를 넣는다면 데이터를 관리하기가 힘들 것이다.

물론, HTML차원에서 이걸 해결하기 위해 required라는 속성을 집어넣을 수 있다.

<form action="/action_page.php">
    Username: <input type="text" name="usrname" required>
    <input type="submit">
</form>

그런데 이 required의 단점은, 개발자 도구에서 지워버려도 된다는 점이다.

그 이외에도 여러가지 유효성 검사가 필요한 경우가 많은데, 일일이 form을 만들때마다 이런 것들을 정의해줘야한다는게 매우 힘들다.

1-2. 요약

Form태그의 문제점

  1. C,U를 하기 위해서 Form을 계속 작성해줘야 한다.
  2. 유효성 검사에 매우 취약하다.
  3. Form에서의 데이터를 받을 때 변수 하나하나마다 정의해줘야하는 불편함이 있다.

그래서 Django에서는 Form과 ModelForm이라는 기능을 제공한다.

2) Django Form

Django form은 Models.py의 특정 테이블과 연계해서 Form을 자동으로 만들어주는 기능을 한다.

2-1. forms.py

먼저 forms.py를 새로 만들어준다.

# Form Class
from django import forms
from .models import Shout

# ShoutForm : Shout 모델에 기반하여 django가 만들어주는 form
class ShoutForm(forms.Form):
    title = forms.Textfield()
    content = forms.Textfield()

Shout라는 테이블과 연계되는 ShoutForm 클래스를 만들어준다.

2-2. ShoutForm을 표현하기

그저 Form이라는 객체를 생성해서 보여주면 된다.

def create(request):
    # form을 보여줌
    form = ShoutForm()
    return render(request, 'shouts/create.html', {'form': form})

form이라는 변수를 DTL내에 넣어주면 알아서 form태그 형식을 만들어주는데,

as_p는 p태그처럼 만드는 것이다. 비슷하게 as_table, as_ul도 가능하다.


{% extends 'todos/base.html' %}

{% block content %}
    <form method="POST">
        {{form.as_p}}
        <input type="submit">
    </form>
{% endblock %}

결과화면) 매우 간단한 형태의 form이 등장했다.

21

물론 커스터마이징도 가능한데, 이건 다음에 ModelForm을 살펴보면서 알아보도록 하자.

3) Model Form

3-1. forms.py

  1. 클래스 정의

    from django import forms
    from .models import Shout
       
    class ShoutModelForm(forms.ModelForm):
        model = Shout
    

    model을 Shout클래스로 쓰겠다는 말만 해주면, 알아서 Shout 모델의 멤버변수들을 참고해서 Form을 만들어준다.

  2. 물론 모든 멤버변수를 Form을 통해서 받을 필요가 없기 때문에

    form 태그를 통해서 받을 테이블의 field들을 정의해줄 수 있다.

    class ShoutModelForm(forms.ModelForm):
        class Meta:
        	model = Shout
            fields = ['title', 'content']
    

    Meta라는 클래스를 감쌌는데, 그냥 메타정보들을 넣는다고 받아들이면 될 것 같다.

  3. 약간의 커스터마이징도 가능하다.

    class Meta:
        #....
        widgets = {
            'title' : forms.TextInput(
                attrs = {
                    'class':'form-control',
                    'placeholder':'제목을 입력해 주세요.'
                }
            ),
            'content' : forms.Textarea(
                attrs = {
                    'class':'form-control',
                    'placeholder':'내용을 입력해 주세요.'
                }
            )
        } 
    

    widgets라는 멤버변수로 커스터마이징이 가능한데,

    대표적으로 인풋태그의 타입과 속성을 정의가능하다.

    1. 타입

      • TextInput

      • NumberInput

      • Textarea

        …. 등등

    2. 속성

      속성은 attrs 매개변수를 통해서 딕셔너리형식으로 전달하면 된다.

      Boolean attribute는 True나 False를 통해 렌더할 수 있다.

3-2. Views.py

이제 이렇게 틀을 만들었으면 어떤 식으로 활용하는지 알아보겠습니다.

  1. Create

    from .forms import ShoutModelForm
       
    def home(request):
        if request.method == "POST":
            pass
        else:
            shouts = Shouts.objects.all()
            form = ShoutModelForm()
            return render(request, 'shouts/home.html', {
                'shouts': shouts,
                'form':form,
            })
    

    모든 Shout 인스턴스들을 보여줌과 동시에 새로운 Shout를 추가할 수 있는 home함수를 만든다고 생각해봅시다.

    get method로 주어진 home 링크에 접근했을 때는 새로운 Shout를 만드는 form과 Shout 인스턴스들만 보여주면 되기 때문에 이런식으로 작성하면 됩니다.

    23

    이런식으로 작성할 수 있을 것입니다.

    그다음에는, 위의 form을 통해 데이터를 입력할 때만 POST메소드로 작동하기 때문에 이때는 새로운 데이터를 생성해줘야 합니다.

    from .forms import ShoutModelForm
       
    def home(request):
        if request.method == "POST":
            form = ShoutModelForm(request.POST)
            form.save()
            return redirect('shouts:home')
        # 생략..
    

    그저 FORM태그를 통해 들어온 데이터 전부를 넣어줘서 save만 하면 됩니다.

    request.POST를 뜯어볼 필요가 없이 Django가 알아서 지원해줍니다.

  2. Update

    업데이트를 위해서는 일단 어떤 Shout 객체를 업데이트할건지를 정해야 하기 때문에

    그것을 instance 매개변수로 지정하였습니다.

    from .models import Shout
       
    def update(request, shout_id):
        shout = Shout.objects.get(id=shout_id)
        if request.method == "POST":
            pass
        else:
            form = ShoutModelForm(instance=shout)
            return render(request, 'shouts/update.html', {
                'form' : form
            })
    

    즉, instance는 shout의 정보를 가지고 있습니다.

    24

    그러면 shout의 정보를 가지고 value를 미리 띄워주는 기능을 합니다.

    def update(request, shout_id):
        shout = Shout.objects.get(id=shout_id)
        if request.method == "POST":
            form = ShoutModelForm(request.POST,instance=shout)
            form.save()
            return redirect('shouts:home')
        # 생략..
    

    위의 form에서 데이터를 수정해서 POST메소드를 통해 전달하면 그냥 save만 하면 됩니다.

    주어진 instance에 request.POST를 잘 저장해둔다는 의미라고 생각하면 됩니다.

결론적으로, 하나의 ShoutModelForm만 가지고도 Create와 Update를 동시에 할 수 있습니다.

그리고 그 절차도 매우 간단해졌습니다.

4) User Form, validator

저번 포스트에서 로그인기능은 만들었으나, 정작 유저들 자기자신이 회원가입을 하지는 못하고 우리가 직접 추가해줘야 했습니다.

이러한 유저 회원가입 기능도 UserCreationForm으로 할 수 있습니다.

4-1. UserCreationForm()

from django.contrib.auth.forms import UserCreationForm
# 이건 저번에 임포트했던것들
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User

django.contrib.auth.forms에서 UserCreationForm을 임포트할 수 있습니다.

생각해보니 django.contrib.auth는 models도 가지고있고, forms도 가지고있는걸로 보아서, 하나의 쟝고 앱과 비슷한 구조인것으로 추측할 수 있습니다.

def register(request): 
    if request.method == "POST":
        form = UserCreationForm(request.POST)
            form.save()
    else:
        form = UserCreationForm()
        return render(request, 'users/register.html', {
            'form' : form
        })

매우 간단합니다. 이런식으로만 작성하면 회원가입을 할 수 있습니다.

25

물론 커스터마이징도 상속을 통해서 가능하지만 이번에는 다루지 않겠습니다.

4-2. 유효성 검사

그런데 회원가입은 반드시 유효성검사가 필요합니다.

이런 경우에는 유효성검사를 매우 간단하게 할 수 있습니다.

def register(request):
    if request.method == "POST":
        form = UserCreationForm(request.POST)
        if form.is_valid():
            form.save()
            #user = User.objects.get(id=form.instance.id)
            #이렇게 user를 명시하지 않고도 form의 인스턴스가 생성된 유저이므로 이거만 하면 된다.
            login(request, form.instance)
            messages.success(request, "환영합니다 " + form.instance.username + "님")
            return redirect('todos:home')
        else:
            return redirect('users:register')
        # 생략...

form.is_valid()로 유효성을 검사함.

위에 링크에 들어가보면 validation은 여러가지 단계로 이루어져있는데,

대략적으로는 다음과 같습니다.

  1. to_python : model에 정의된 필드의 타입에 부합하는가?

  2. validate : model에 정의된 필드의 validatior에 부합하는가?

    • validator : https://docs.djangoproject.com/en/2.1/ref/validators/

    26

    is_valid()는 model에서 정의된 테이블의 validators에 따라서 유효성을 검사합니다.

    validator는 RegexValidator , EmailValidator 등등이 있으며, 이런 validator를 지정해놓으면 불필요한 데이터를 잘 걸러낼 수 있습니다.

  3. 만약 validation error가 발생하면 clean()메소드를 통해서 데이터를 정제해준다고 합니다.

comments powered by Disqus