Django

Django Form [Django]

경상도상남자 2024. 9. 25. 09:41

지금까지는 HTML의 'Form' 태그를 이용해서 사용자로부터 데이터를 받았다. 그러나 이는 비정상적 혹은 악의적인 요청을 필터링 할 수 없다. (유효한 데이터인지에 대한 확인이 필요)

 

따라서 우리는 수집한 데이터가 정확하고 유효한지 확인하는 과정(유효성 검사)이 필요하다.

유효성 검사를 구현하기 위해서는 입력 값, 형식, 중복, 범위, 보안 등 많은 것들을 고려해야 하는데, Django가 제공하는 Form을 사용하여 구현할 수 있다. 

 

Django Form

사용자 입력 데이터를 수집하고 처리 및 유효성 검사를 수행하기 위한 도구

→ 유효성 검사를 단순화하고 자동화 할 수 있는 기능을 제공

  • 사용자로부터 데이터를 수집하고 처리하기 위한 강력하고 유연한 도구
  • HTML form의 생성, 데이터 유효성 검사를 쉽게 할 수 있도록 도움

 

1. Form class 

 

1.1 Form class의 정의 

기본적으로 앱 폴더 안에 form.py 파일을 만들어서 정의한다. 

모델을 정의할때와 유사한 구조를 가진다. 

# article/form.py

from django import forms

class ArticleForm(forms.Form):
    title= forms.CharField(max_length=10, required=False)
    content=forms.CharField()

 

1.1 view 함수에서 작성혹은 변경

# article/view.py
from .forms import ArticleForm

def new(request):
    # 게시글 작성 페이지 응답
    form = ArticleForm() #인스턴스 생성
    context = {
        'form':form,
    }
    return render(request, 'articles/new.html',context)

 

1.2 new 페이지에서 form 인스턴스 출력

사전에 from 태그안에 form 인스턴스를 넣는다

토큰submit은 지우지 않는다!!

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h1>New</h1>
  <form action="{% url "articles:create" %}" method="POST">
    {% csrf_token %}
    {{form}}
    <input type="submit">
  </form>
</body>
</html>

 

Django form 사용전 new.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h1>New</h1>
  <form action="{% url "articles:create" %}" method="POST">
    {% csrf_token %}
    <div>
      <label for="title">Title: </label>
      <input type="text" name="title" id="title">
    </div>
    <div>
      <label for="content">Content: </label>
      <textarea name="content" id="content"></textarea>
    </div>
    <input type="submit">
  </form>
</body>
</html>

 

Form rendering options

label, input 쌍을 특정 HTML 태그로 감싸는 옵션

  1. form.as_p
  2. form.as_div
  3. form.as_ul
  4. form.as_table

https://docs.djangoproject.com/en/4.2/topics/forms/#form-rendering-options

 

Working with forms | Django documentation

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

Widgets

HTML 'input' element의 표현을 담당

  • 단순히 input의 표현을 바꾸는 용도로 사용한다. 
  • form 필드의 인자로 사용해야 한다. 
from django import forms

class ArticleForm(forms.Form):
    title= forms.CharField(max_length=10)
    content=forms.CharField(widget=forms.Textarea)

 

사용 전

 

widgets 사용후
검사로 열었을 때

 

 

 

Django는 기본적으로 두개의 Form을 제공한다.

 

Form VS ModelForm

Form

사용자 입력 데이터를 DB에 저장하지 않을 때 (ex 검색, 로그인)

 

ModelForm 

사용자 입력 데이터를 DB에 저장해야 할때 (ex 게시글 작성, 회원가입)

 

2. ModelFrom

Form + Model

Model과 연결된 Form을 자동으로 생성해주는 기능을 제공(만들어진 모델을 기반으로 만들 수 있어 코드작성중복을 줄임)

type, content를 재정의 필요 X

 

Meta 클래스라고 하는 이유?

  • Meta가 데이터의 데이터라는 의미이기 때문에 
  • 사진의 예로들면 사진 안에도 위치, 조리개값, 조도, 타입 등이 들어있는데 사진도 데이터이고 그안에 값도 데이터인데 이런 것을 Meta data라고 한다
  • 따라서 모델 폼에 대한 정보를 작성하는 공간이라 이름을 Meta 라고 했다

https://docs.djangoproject.com/en/5.1/topics/forms/modelforms/

 

Creating forms from models | Django documentation

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

사용법

2.1 ModelForm 작성

# article/forms/py

from django import forms
from .models import Article


# class ArticleForm(forms.Form):
#     title= forms.CharField(max_length=10)
#     content=forms.CharField(widget=forms.Textarea)

class ArticleForm(forms.ModelForm):
    class Meta: # 모델 폼에 대한 정보를 메타 클래스안에 작성
        model = Article # 어떤 모델을 기반으로 할 것인지
        fields= '__all__' # 어떤 필드를 사용할 것인지
        # exclude=('title',)

 

Meta class

  • Model Form의 정보를 작성하는 곳
  • 'fields' 는 field를 지정하는 것
  • 'exclude' 는 field를 제외하는 것 
  • 주의 사항
    • Django에서 ModelForm에 대한 추가 정보나 속성을 작성하는 클래스 구조를 Meta 클래스로 작성했을 뿐이니 파이썬의 inner class와 같은 문법적인 관점으로 접근하지 말 것 

 

2.2 view 함수 작성

실질적으로 유효성검사는 view 함수에서 실시 한다.

  1. 모델 폼 인스턴스 생성
  2. if 문과 is_valid() 매서드를 사용해서 유호성 검사
    • 성공시 저장후 redirect (save 메서드에 return값에 저장된 데이터 정보가 담겨있다)
    • 실패시 실패 내용을 담아 다시 rendering (is_valid 메서드에서 실패시에 실패 내역이 담긴다.)
def create(request):
    # 모델폼 사용전
    # title = request.POST.get('title')
    # content = request.POST.get('content')

    # 저장 2 
    # article = Article(title=title, content=content)
    # article.save()


    # 1. 모델폼 인스턴스 생성 (+ 사용자 입력 데이터를 통째로 인자로 작성)
    form = ArticleForm(request.POST)

    # 2. 유효성 검사
    if form.is_valid(): # 실패하면 실패 내역 담김
        article = form.save()
        return redirect('articles:detail', article.pk)
    # 실패시 
    context = {
        'form': form,
    }
    return render(request, 'articles/new.html', context)

 

 

실패시 실패 내역 알려줌

 

참고 

수정의 경우 수정 이전 데이터와 작성한 데이터를 불러와야 하기 때문에 살짝 코드가 다르다

def edit(request, pk):
    # 모델 폼 사용전
    # article = Article.objects.get(pk=pk)
    # context = {
    #     'article': article,
    # }
    # return render(request, 'articles/edit.html', context)
    
    article = Article.objects.get(pk=pk) 
    form =ArticleForm(instance=article)
    context = {
        'article': article,
        'form':form
    }
    return render(request, 'articles/edit.html', context)




def update(request, pk):
    # 모델 폼 사용전
    # # 1. 어떤 게시글 수정할지 조회
    # article = Article.objects.get(pk=pk)
    # # 2. 사용자로부터 받은 새로운 입력 데이터 추출
    # title = request.POST.get('title')
    # content = request.POST.get('content')
    # # 3. 기존 게시글의 데이터를 사용자로 받은 데이터로 새로 할당
    # article.title = title
    # article.content = content
    # # 4. 저장
    # article.save()

    article =Article.objects.get(pk=pk)
    # 1. 모델 폼 인스턴스 생성 (+ 사용자 입력 데이터 & 기존 데이터)
    form = ArticleForm(request.POST, instance=article)
    # 2. 유효성 검사
    if form.is_valid():
        form.save()
        return redirect('articles:detail', article.pk)
    context = {
        'article': article,
        'form': form,
    }
    return render(request, 'articles/edit.html', context)

 

is_valid()

  • 여러 유효성 검사를 내부적으로 실시하고 데이터가 유효한지 여부를 Boolean으로 반환
  • false 리턴시 false 원인도 같이 return을 해준다
  • 과정
    • 별도로 명시하진 않았지만 모델 필드에서 기본적으로 빈 값은 허용하지 않는 제약 조건이 설정되어 있음
    • 빈 값은 is_valid()에 의해 False로 평가되고 form 객체에는 그에 맞는 에러 메시지가 포함되어 다음 코드로 진행

save()

데이터베이스 객체를 만들고 저장하는 ModelForm의 인스턴스 메서드

키워드 인자 instance 여부를 통해 생성할 지, 수저할 지 결정한다.

 

 

HTTP 요청 다루기 

 

new 와 create view 함수 간의 공통점 차이점

공통점

  • 데이터 생성을 구현하기 위함

차이점

  • new는 GET method 요청만을
  • create는 POST method 요청만을 처리

따라서 우리는 HTTP request method 차이점을 활용해 동일한 목적을 가지는 2개의 view 함수를 하나로 구조화 할 수 있다!!

 

생성을 위해 만들었던 new와 create 함수의 결합

결합 전

def new(request):
    # 게시글 작성 페이지 응답
    form = ArticleForm() #인스턴스 생성
    context = {
        'form':form,
    }
    return render(request, 'articles/new.html',context)


def create(request):
    # 1. 모델폼 인스턴스 생성 (+ 사용자 입력 데이터를 통째로 인자로 작성)
    form = ArticleForm(request.POST)

    # 2. 유효성 검사
    if form.is_valid(): # 실패하면 실패 내역 담김
        article = form.save()
        return redirect('articles:detail', article.pk)
    # 실패시 
    context = {
        'form': form,
    }
    return render(request, 'articles/new.html', context)

 

결합 후

  • 두 함수의 유일한 차이점이었던 request method에 따른 분기
  • POST 아닐 때는 과거 new 함수에서 진행 했던 form 인스턴스 생성
  • context에 담기는 form은
    1. is_valid()를 통과하지 못해 에러메시지를 담는 form이거나
    2. else 문을 통한 form 인스턴스
def create(request):
    # 요청 메서드가 POST일 때
    if request.method == 'POST':
        # 1. 모델폼 인스턴스 생성 (+ 사용자 입력 데이터를 통째로 인자로 작성)
        form = ArticleForm(request.POST)

        # 2. 유효성 검사
        if form.is_valid(): # 실패하면 실패 내역 담김
            article = form.save()
            return redirect('articles:detail', article.pk)

    # 요청 메서드가 POST가 아닐 때(PUT,DELETE 등 다른 메서드 존재)
    else:
        form = ArticleForm() #인스턴스 생성
    
    context = {
    'form':form,
    }
    return render(request, 'articles/new.html',context)

 

이제 new와 관련된 코드를 모드 수정하면 끝난다(url들 모드 수정)

  • new의 존재들을 모드 지우기- (create)로 바꾼다.

생성을 위해 만들었던 edit와 update 함수의 결합

결합 후

def update(request):
    article = Article.objects.get(pk=pk) 
    if request.mothod == 'POST':
        form = ArticleForm(request.POST, instance=article)
        # 2. 유효성 검사
        if form.is_valid():
            form.save()
            return redirect('articles:detail', article.pk)
    else:
        form =ArticleForm(instance=article)
    context = {
        'article': article,
        'form':form
    }
    return render(request, 'articles/update.html', context)

 

 

참고 자료

ModelForm의 키워드 인자

  • data
    • data는 첫 번째에 위치한 키워드 인자이기 때문에 생략 가능
  • instance
    • instance는 9번째에 위치한 키워드 인자이기 때문에 생략할 수 없었음

Widgets 응용

  • {{form.as_p}}의 표현을 바꿀 수 있도록 하게 하기 위해서 widgets을 사용
  • 필드를 수동으로 렌더링 할 수 도 있다
class ArticleForm(forms.ModelForm):
    # 메타클래스 위에 쓰는 걸 권장
    title = forms.CharField(
        label='제목',
        widget=forms.TextInput(
            attrs={
                'class': 'my-title',
                'placeholder': '제목을 입력해주세요',
                'maxlength':10,
            }
        )
    )
    class Meta:
        model = Article # 어떤 모델을 기반으로 할 것인지
        fields= '__all__' # 어떤 필드를 사용할 것인지

Widgets 적용 후

https://docs.djangoproject.com/en/5.1/ref/forms/widgets/

 

Widgets | Django documentation

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

'Django' 카테고리의 다른 글

Django Template & URLs [Django]  (0) 2024.09.29
Static [Django]  (3) 2024.09.26
Django ORM with view [Django]  (0) 2024.09.24
Django ORM [Django]  (3) 2024.09.23
Django Model 생성하기[Django]  (0) 2024.09.23