logo
Search검색어를 포함하는 게시물들이 최신순으로 표시됩니다.
    Table of Contents
    [Django] Django Form

    이미지 보기

    [Django] Django Form

    • 22.04.06 작성

    • 읽는 데 19

    TOC

    Form

    • 사용자의 데이터를 직접 받을 때 입력된 데이터의 유효성을 검증해야 한다.

    • 필요시에 입력된 데이터를 검증 결과와 함께 다시 표시 해야한다.

    • 사용자가 입력한 데이터는 개발자가 요구한 형식이 아닐 수 있음을 고려

    • 유효성 검증 : 사용자가 입력한 데이터를 검증

    • 유효성 검증을 모두 구현하는 것은 많은 노력이 필요

    • Django From : Django가 과중한 반복 코드를 줄여줌으로써 유효성 검증을 보다 쉽게!


    Django Form

    • Django의 유효성 검사 도구 중 하나

    • 외부의 악의적 공격 및 데이터 손상에 대한 중요한 방어 수단

    • Form과 관련된 유효성 검사를 단순화/자동화 기능 제공

    • 개발자가 직접 작성하는 코드보다 더 안전하고 빠르게 수행하는 코드 작성 지원


    Django의 form에 관련된 3가지 작업

    1. 렌더링을 위한 데이터 준비 및 재구성
    2. 데이터에 대한 HTML forms 생성
    3. 클라이언트로부터 받은 데이터 수신 및 처리

    Django Form Class

    • Django Form 관리 시스템의 핵심

    • Form 내 field, field 배치, 디스플레이 widget, label, 초기값, 유효하지 않은 field에 관련된 에러 메세지 결정

    • 사용자의 데이터를 받을 때 해야 하는 과중한 작업과 반복 코드 줄여줌

      • 데이터 유효성 검증
      • 필요시 입력된 데이터 검증 결과 재출력 등

    Django Form 사용

    Form 선언

    # articles/forms.py
    from django import forms
    
    
    class ArticleForm(forms.Form):
        title = forms.CharField(max_length=10)
        content = forms.CharField()              # Model과 달리 TextField가 아니다!! 자동완성에도 없다.
    
    • App 폴더는 forms.py가 없으므로 새로 만들어준다.
    • Model을 선언하는 것과 유사, 같은 필드타입 사용 (일부 매개변수도 유사)
    • forms 라이브러리에서 파생된 Form 클래스를 상속

    Form 사용

    • views.py에 ArticleForm을 import한다.
    # articles/views.py
    from .forms import ArticleForm
    

    # articles/views.py
    def new(request):
        return render(request, 'articles/new.html')
    

    # articles/views.py
    def new(request):
        form = ArticleForm()
        context = {
            'form': form
        }
        return render(request, 'articles/new.html', context)
    

    {%- raw -%}
    <!-- articles/templates/articles/new.html -->
    
    <form action="{% url 'articles:create' %}" method="POST">
        {% csrf_token %}
        <label for="title">Title: </label>
        <input type="text" id="title" name="title"><br />
        <label for="content">Content: </label>
        <textarea name="content" id="content" cols="30" rows="10"></textarea>
        <input type="submit">
      </form>
    {% endraw -%}
    
    • new template의 기존 form에 대한 html 태그이다.
    • 위의 코드를 아래처럼 바꿔보자.

    {%- raw -%}
    <!-- articles/templates/articles/new.html -->
    
    <form action="{% url 'articles:create' %}" method="POST">
        {% csrf_token %}
        {{ form }}
        <input type="submit">
      </form>
    {% endraw -%}
    
    image

    Form rendering options

    • label과 input 쌍에 대한 3가지 출력 옵션

    as_p()

    • 각 필드가 단락(p 태그)으로 감사져서 렌더링
    • 줄바꿈이 되는 것!

    as_ul()

    • 각 필드가 목록 항목(li 태그)으로 감싸져서 렌더링됨
    • ul 태그는 직접 작성

    as_table()

    • 각 필드가 테이블 행(tr 태그) 행으로 감싸져서 렌더링
    • table 태그는 직접 작성

    {%- raw -%}
    <!-- articles/templates/articles/new.html -->
    
    <form action="{% url 'articles:create' %}" method="POST">
        {% csrf_token %}
        {{ form.as_p }}
        <input type="submit">
      </form>
    {% endraw -%}
    

    image

    Widgets

    form fields

    • input에 대한 유효성 검사 로직을 처리
    • 템플릿에서 직접 사용

    Widgets

    • 웹 페이지의 HTML input element 렌더링

    • GET/POST 딕셔너리에서 데이터 추출

    • idget은 단독적으로 사용되지 못한다 : field에 의존적

    # articles/forms.py
    from django import forms
    
    class ArticleForm(forms.Form):
        title = forms.CharField(max_length=10)
        content = forms.CharField(widget=forms.Textarea)
    

    form fields의 괄호 내에 widget= option으로 부여


    주의사항

    • form fields와 혼동되어서는 안된다!

      • form fields : input 유효성 검사를 처리
      • widgets : 웹페이지에서 input element의 단순 raw한 렌더링 처리
    • 참고자료 : django 공식문서 - Widgets


    Widgets 응용

    드롭다운 select element를 만들어보자

    # articles/forms.py
    from django import forms
    
    class ArticleForm(forms.Form):
        title = forms.CharField(max_length=10)
        content = forms.CharField(widget=forms.Textarea)
        region = forms.ChoiceField(widget=forms.Select())
    
    • 위처럼 region parameter에 ChoiceField 설정
    • widget은 forms.Select (forms.Select()도 가능)
    • field는 자료형, widget은 user들에게 보이는 형식
    image
    • 아무 option이 없기 때문에 rendering하면 아래와 같은 모습

    • select의 선택항목인 option을 추가해보자
    # articles/forms.py
    from django import forms
    
    class ArticleForm(forms.Form):
        REGION_A = 'sl'
        REGION_B = 'dj'
        REGION_C = 'gj'
        REGION_CHOICES = [
            (REGION_A, '서울'),
            (REGION_B, '대전'),
            (REGION_C, '광주'),
        ]
        
        
        title = forms.CharField(max_length=10)
        content = forms.CharField(widget=forms.Textarea)
        region = forms.ChoiceField(choices=REGION_CHOICES, widget=forms.Select())
        # forms.Select도 되고 forms.Select()도 됨... 왜지?
    
    • ChoiceField의 경우 widget이 Select가 기본이기 때문에 괄호 내 widget 항목은 생략 가능
    • option들은 위와 같이 tuple로 작성
    • tuple 사용은 django coding style - model style 참고

    결과

    image
    • 드롭다운에 필요한 option들이 select element 내부에 있는 것처럼 들어갔다.

    ModelForm

    • forms.py에서 Form에 대한 항목들을 하나하나 작성해주었다.
    • Django Form을 사용하다보면 Model에 정의한 필드를 유저로부터 입력받기 위해 Form에서 Model 필드를 재정의하는 행위가 중복될 수 있다.
    • Django는 Model을 통해 Form Class를 만들 수 있는 ModelForm이라는 Helper 제공

    ModelForm Class

    • Model을 통해 Form Class를 만들 수 있는 Helper
    • 일반 Form Class와 완전히 같은 방식으로 view에서 사용 가능

    # articles/forms.py
    from django import forms
    from .models import Article         # 사용할 모델을 import
    
    class ArticleForm(forms.ModelForm): # ⭐ form.Form과 forms.ModelForm이 다름에 주의할 것
        
        class Meta:
            model = Article             # 어떤 모델을 사용할 것인지 지정
            fields = '__all__'          # 모델의 어떤 field들을 사용할 것인지 지정
            # exclude = ('title',)      # 출력에서 제외하는 것. tuple과 list 모두 가능
    
    • 정의한 class 내에 Meta 클래스 선언
    • 어떤 model을 기반으로 form을 작성할 것인지에 대한 정보 지정
    • widgets을 별도 설정하지 않아도 field의 type을 model에 의거해 지정

    exclude?

    • 모든 field 중에 제외할 field를 명시해서 제외
    • field가 많고, 제외할 field가 적은 경우 사용
    • class 변수 field와 exclude는 동시에 사용 불가

    Meta Class

    • Model에 대한 정보를 작성

    Meta 데이터

    • 데이터에 대한 데이터

    Inner Class

    • 클래스 내에 선언된 다른 클래스
    • 관련 클래스를 함께 그룹화 → 가독성 및 프로그램 유지 관리 지원(논리적으로 묶어 표현)
    • 외부에서 내부 클래스에 접근 불가 → 코드 복잡성 감소

    유효성 검사

    • 요청한 데이터가 특정 조건에 충족하는지 확인하는 작업
    • 데이터베이스 각 필드 조건에 올바르지 않은 데이터가 서버로 전송/저장 되지 않도록 하는 것

    is_valid() method

    • 유효성 검사를 실행하고, 데이터가 유효한지 여부를 boolean으로 반환
    • 데이터 유효성 검사를 보장하기 위한 많은 테스트에 대해 Django는 is_valid()를 제공

    save() method

    • 주의 : 기존 model의 save method와 다르다!! ModelForm 고유의 save method
    • 그냥 form은 save method가 없다!
    • Form에 바인딩된 데이터에서 DB 객체를 만들고 저장
    • ModelForm의 하위클래스(subClass)는 기존 모델 인스턴스를 키워드 인자 instance로 수용 가능
      • 이것이 제공되면 save()는 해당 인스턴스를 수정(UPDATE)
      • 제공되지 않으면 save()는 지정된 모델의 새 인스턴스를 만듦(CREATE)
    • Form의 유효성이 확인되지 않은 경우, save()를 호출하면 form.errors를 확인하여 에러 목록 확인 가능

    예시 코드

    # POST data로부터 form 인스턴스 생성
    form = ArticleForm(request.POST)
    
    # CREATE
    new_article = form.save()
    
    # UPDATE
    article = Article.objects.get(pk=1)
    # instance 값이 있으면 수정인 것으로 django가 인식
    form = ArticleForm(reqeust.POST, instance=article)
    form.save()
    

    실습 코드

    기존 코드

    # articles/views.py
    def create(request):
        title = request.POST.get('title')
        content = request.POST.get('content')
    
        article = Article(title=title, content=content)
        article.save()
    
        return redirect('articles:detail', article.pk)
    

    form 사용 및 유효성 검사

    # articles/views.py
    def create(request):
        form = ArticleForm(request.POST)  # request.POST 딕셔너리 내에 모든 정보가 있음
        # 유효성 검사
        if form.is_valid():     # form의 데이터가 유효하다면(True)
            # form 자체를 DB에 레코드로 저장
            # save() 메서드는 저장된 객체를 반환
            # 이를 article에 저장
            article = form.save()    
            return redirect('articles:detail', article.pk)
        return redirect('articles:new')
    
    • ArticleForm 클래스에 request.POST를 인자로 넣어 form 인스턴스를 생성한다.
    • is_valid 메서드를 이용하면 form 내부의 데이터를 유효성 검사
    • 이를 통과하여 True라면 form을 DB에 save
    • 이는 저장된 객체를 반환하므로 이를 article에 저장
    • article을 활용하여 redirect, 유효성 검사 통과하지 않았다면 다른 return

    Form VS ModelForm

    Form

    • 어떤 Model에 저장해야 하는지 알 수 없으므로 유효성 검사 이후 cleaned_data 딕셔너리 생성
    • cleaned_data 딕셔너리에서 데이터를 가져온 후 .save() 호출
    • Model에 연관되지 않은 데이터를 받을 때 사용

    ModelForm

    • Django가 해당 model에서 양식에 필요한 대부분의 정보를 이미 정의
    • 어떤 레코드를 만들어야 할 지 알고 있으므로 바로 .save() 호출 가능

    cleaned_data 구조 예시

    def create(request):
        if request.method == 'POST':
            form = ArticleForm(request.POST)
            if form.is_valid():
                title = form.cleaned_data.get('title')
                content = form.cleaned_data.get('content')
                article = Article.objects.create(title=title, content = content)
        else:
            form = ArticleForm()
        context = {
            'form': form,
        }
        return render(request, 'articles/create.html', context)
    

    함수 구조 합체

    CREATE

    new와 create는 CREATE를 위해 힘쓰는 두 함수... 합쳐보자!

    • view 함수 new가 흡수되므로 urls.py에서 new 부분을 제거해준다.
    • template 중에서도 new를 호출하는 url tag들의 주소를 모두 create로 바꿔준다.
    path('new/', views.new, name='new'),
    

    기존 분리 코드

    # articles/views.py
    def new(request):
        form = ArticleForm()
        context = {
            'form': form
        }
        return render(request, 'articles/new.html', context)
    
    
    def create(request):
        form = ArticleForm(request.POST)
        if form.is_valid():
            article = form.save()    
            return redirect('articles:detail', article.pk)
        return redirect('articles:new')
    

    합체 코드

    # articles/views.py
    def create(request):
        # create
        if request.method == 'POST':
            form = ArticleForm(request.POST)    
            if form.is_valid(): 
                article = form.save()    
                return redirect('articles:detail', article.pk)
        # new
        else:
            form = ArticleForm()
        context = {
            'form': form,
        }
        return render(request, 'articles/create.html', context)
    
    • request method가 new는 GET, create는 POST

    • .method 메서드를 이용해 request의 메서드가 무엇인지 확인

    • GET일 때, POST일 때의 상황에 따라 상황과 처리 구분

    • is_valid 함수를 통과하지 못하면 else를 거치지 않고 return

    • method가 GET일 때는 아예 빈 form, is_valid에서 비었다면 error message를 담은 form을 context에 전달

    • return문은 context를 필요로 하므로 else(기존 new)문의 context를 함수 전역으로 내어쓰기


    UPDATE

    어, edit도 update와 합칠 수 있다!


    def edit(request, pk):
        article = Article.objects.get(pk=pk)
        context = {
            'article': article,
        }
        return render(request, 'articles/edit.html', context)
    
    
    def update(request, pk):
        article = Article.objects.get(pk=pk)
        article.title = request.POST.get('title')
        article.content = request.POST.get('content')
        article.save()
        return redirect('articles:detail', article.pk)
    
    • urls.py의 edit을 제거해준다.

    위의 코드를 아래처럼 합칠 수 있다.

    def update(request, pk):
        article = Article.objects.get(pk=pk)
        # update
        if request.method == 'POST':
            form = ArticleForm(request.POST, instance=article)
            if form.is_valid():
                article = form.save()
                return redirect('articles:detail', article.pk)
        # edit
        else:
            form = ArticleForm(instance=article)
        context = {
            'article': article,
            'form': form,
        }
        return render(request, 'articles/update.html', context)
    
    • update이므로, 과거의 데이터이자 수정을 원하는 instance를 지정한다.
    • article이 해당 instance이므로 이를 값으로 넣어준다.

    기타 코드의 비밀

    form 태그의 action

    • form의 action 속성의 값이 없다면 현재 url을 기준으로 HTTP를 전송
    • 하지만 명시를 해주는 것을 권장

    forms.py의 파일 위치

    • Form class는 forms.py 뿐만 아니라 다른 어느 위치에 두어도 상관 없다.
    • 하지만 되도록 app폴더/forms.py에 작성하는 것이 일반적인 구조

    왜 method를 POST 먼저 확인하는가.

    • POST는 DB를 조작하는 method
    • 때문에 method가 POST일 때에만 DB를 조작하도록 해야 한다.
    • method를 [POST]와 [POST가 아닌 것]으로 구분하여 확인해야 한다.

    Widget 활용하기

    가. Meta class 내에 정의(비권장)

    # articles/forms.py
    
    class ArticleForm(forms.ModelForm): 
        
        class Meta:
            model = Article        
            fields = '__all__'     
            widgets = {
                'title': forms.TextInput(attrs={
                    'class':'my-title',
                    'placeholder': 'Enter the title',
                    'maxlength': 10,
                    }
                )
            }
    

    Django에서 권장하는 스타일은 아님


    나. ModelForm class 내에 정의(권장)

    # articles/forms.py
    
    class ArticleForm(forms.ModelForm): # ⭐ form.Form과 forms.ModelForm이 다름에 주의할 것
        title = forms.CharField(
            label='제목',
            widget=forms.TextInput(
                attrs={
                    'class': 'my-title second-class',
                    'placeholder': 'Enter the title',
                }
            ),
        )
        content = forms.CharField(
            label='내용',
            widget=forms.Textarea(
                # 인라인 속성 지정
                attrs={
                    'class': 'my-content',
                    'placeholder': 'Enter the content',
                    'rows': 5,
                    'cols': 50,
                }
            ),
            # error message customizing
            error_messages={
                'required': 'Please enter your content', # required 요소가 채워지지 않았을 때의 오류 메시지
            }
        )
        
        
        class Meta:
            model = Article    
            fields = '__all__' 
    

    Rendering Fields Manually

    form field를 template에서 어떻게 출력해야 할까요?


    수동으로 Form 작성하기

    Rendering fields manually

    {%- raw -%}
    <form action="" method="POST">
      {% csrf_token %}
      <div>
        {{ form.title.errors }}
        {{ form.title.label_tag }}
        {{ form.title }}
      </div>
      <div>
        {{ form.content.errors }}
        {{ form.content.label_tag }}
        {{ form.content }}
      </div>
      <input type="submit">
    </form>
    {% endraw -%}
    

    참고자료 : django 공식문서 - forms : rendering fields manually


    Looping over the form's fields

    {%- raw -%}
    <form action="" method="POST">
      {% csrf_token %}
      {% for field in form %}
        {{ field.errors }}
        {{ field.label_tag }}
        {{ field }}
      {% endfor %}
      <input type="submit">
    </form>
    {% endraw -%}
    

    참고자료 : django 공식문서 - forms : looping over the form's fields


    부트스트랩과 함께 사용하기

    Bootstrap class with Widgets

    • Bootstrap Form을 사용해 적용
    • keyword : form-control
    • widget의 class에 form-control 클래스를 입력해주면 된다.

    에러메시지 with bootstrap alert 컴포넌트

    • looping 방식으로 사용
    {%- raw -%}
    <form action="" method="POST">
      {% csrf_token %}
      {% for field in form %}
        {% if field.errors %}
          {% for error in field.errors %}
            <div class="alert alert-warning" role="alert">{{ error|escape }}</div>
          {% endfor %}
        {% endif %}
        <div class="form-group">
          {{ field.label_tag }}
          {{ field }}
        </div>
      {% endfor %}
      <input type="submit">
    </form>
    {% endraw -%}
    

    ⭐ Django Bootstrap Library : django-bootstrap v5

    가. pip install

    $ pip install django-bootstrap-v5
    

    나. 앱 등록

    #  settings.py
    
    INSTALLED_APPS = [
        ...
        'bootstrap5',
        ...
    ]
    

    다. base.html에 load

    • bootstrap5 CDN을 base.html 템플릿에 load해준다.
    • 부트스트랩의 css와 js CDN의 간소화가 가능해진다.
    {%- raw -%}
    <!-- articles/base.html -->
    
    {% load bootstrap5 %}   ⭐ bootstrap5 load
    
    <!DOCTYPE html>
    <html lang="ko">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      {% bootstrap_css %}  ⭐ css CDN의 간소화 기능
      <title>Document</title>
    </head>
    <body>
      <div class="container">
        {% block content %}
        {% endblock content %}
      </div>
      {% bootstrap_javascript %}  ⭐ javascript CDN의 간소화 기능
    </body>
    </html>
    {% endraw -%}
    

    라. form에 적용

    • base.html을 extends하는 하위 template
    {%- raw -%}
    <!-- article/update.html -->
    
    {% extends 'base.html' %}
    {% load bootstrap5 %}
    
    ...
    
    <form action="" method="POST">
      {% csrf_token %}
      {% bootstrap_form form layout='horizontal' %}
      {% buttons submit="Submit" reset="Cancel" %}{% endbuttons %}
    </form>
    
    ...
    {% endraw -%}
    
    profile

    FE Developer 박승훈

    노력하는 자는 즐기는 자를 이길 수 없다