티스토리 뷰

Python

[Django 입문] 폼클래스 만들기

Alledy 2019. 11. 12. 15:03

폼 클래스 만들기

장고에서 기본적으로 폼을 처리할 때에는 html 템플릿에서 form 태그 통해서 요청을 보내고, URL conf로 연결된 뷰에서 데이터를 받아서 리다이렉트하는 식이다.

그런데 html 템플릿에서 이 데이터를 받을 input을 일일이 만드는게 번거롭다. 받을 항목 수가 많을 수록 더욱 귀찮은 작업이 된다. 이를 조금 더 간단하게 할 수 있는 방법이 폼 클래스를 만드는 것이다.

우리가 모델 클래스 필드를 정의해서 데이터베이스 필드와 매핑하듯이, 폼 클래스 필드도 HTML input 엘리먼트에 매핑된다. 즉 모델 클래스를 만들면 SQL문을 직접 적지 않아도 데이터베이스 필드가 생성되는 것처럼, 폼 클래스를 만들면 HTML input 엘리먼트를 직접 일일이 만들지 않아도 생성할 수 있다.

폼 클래스를 생성, 적용하기 위해서는

  1. forms.py에 클래스 정의
  2. 폼을 렌더링할 템플릿 변경
  3. 뷰에서 폼 클래스 인스턴스 생성 및 객체 리턴

순서대로 정리해보겠다.

 

forms.py 생성 및 클래스 정의

폼을 사용하고자 하는 앱 디렉토리 하위에 forms.py를 생성한다. 그리고 받고 싶은 데이터 input을 생각하면서 클래스를 정의한다. 예를 들어 나는 어떤 영화에 대해서 한줄평과 평점을 받아 DB에 저장하고 싶다.

# models.py
from django.db import models

class Score(models.Model):
    content = models.CharField(max_length=100)
    score = models.IntegerField(default=0)
    movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
# forms.py
from django import forms
from .models import Score, Movie

class ScoreForm(forms.Form):
    content = forms.CharField(
        max_length=100,
        label='한줄평',
        widget=forms.TextInput(attrs={
            'class': 'score-content',
            'placeholder': '한줄평을 작성해주세요'
        })
    )
    score = forms.IntegerField(
        required=True,
        label='평점',
        min_value=0,
        widget=forms.NumberInput(attrs={
            'class': 'score'
        })
    )

스코어 모델 클래스에는 content, score 필드가 정의돼 있다. (여기서 movie는 외래키로 스코어와 연결되어 있지만, 여기서는 중요하지 않으니 신경쓰지 않아도 된다. )

스코어 테이블에 데이터를 생성하려면 content(한줄평)과 score(평점)을 받아야 하므로 이를 폼 클래스로 정의한다.

모델 클래스와 달라 보이는 점은 labelwidget 인데, label은 HTML에서 label 태그를 의미하고 widget은 input 이라고 생각하면 된다.

예를 들어 content는 CharfFiled로 정의됐는데, 이 때 디폴트 위젯은 TextInput이다. 이는 HTML에서 <input type="text"> 를 의미한다. textarea 태그를 원하면 widget = froms.Textarea 라고 설정하면 된다.

그리고 attrs={} 안에 지정하고자 하는 class나 placeholder 등을 부가적으로 설정해준다. 이 또한 HTML 엘리먼트의 속성들이다.

폼클래스에서 content를 설정하면 HTML에서는 이렇게 렌더링된다.

<label for="id_content">한줄평:</label>
<input type="text" name="content" class="score-content" placeholder="한줄평을 작성해주세요" maxlength="100" required id="id_content">

 

템플릿 변경

기존 템플릿에서는 폼을 정의하려면 이런식으로 길게 써야 한다.

<!-- detail.html -->
<form action="{% url 'movies:scores_new' movie.pk %}" method="POST">
    <p>
    	<label for="id_content">한줄평:</label>
		<input type="text" name="content" class="score-content" placeholder="한줄평을 작성해주세요" maxlength="100" required id="id_content">
	</p>
	<p>
    	<label for="id_score">평점:</label>
		<input type="number" name="score" class="score" min="0" required id="id_score">
	</p>
    <button type="submit">
        등록
    </button>
</form>

 

이제 폼 클래스를 정의했으니 간단하게

<!-- detail.html -->
<form action="{% url 'movies:detail' movie.pk %}" method="POST">
    {{ form.as_p }}
    <button typ="submit">
        등록
    </button>
</form>

이렇게 해주면 된다. 기본적으로 form 인스턴스를 렌더링할 때 {{ form }} 으로 하는데, 뒤에 as_p가 붙은 것은 <p> 태그로 라벨과 인풋을 감싸라는 것이다. 안 그러면 줄바꿈 없이 일렬로 렌더링되므로 as_p를 쓰는게 좋다. 이 외에도 as_table, as_ul 등이 있다.

그리고 하나 주의할 점은 폼 클래스를 렌더링할 때 서브밋 버튼은 자동으로 만들어주지 않으므로 서브밋 버튼은 직접 써야한다는 것이다.

그러면 이제 템플릿에 사용될 저 form 객체를 뷰에서 보내주는 처리를 해야 한다.

 

뷰에서 폼 인스턴스 생성하고 데이터 받아오기

# views.py
from .forms import ScoreForm

def detail(request, pk):
    movie = Movie.objects.get(pk=pk)
    scores = movie.score_set.all()

    if(len(scores) == 0):
        messages.info(request, "아직 등록된 평점이 없습니다.")
        
	# 폼 처리 로직
    if request.method == 'POST':
        # score = request.POST['score']
        # content = request.POST['content']
        # Score.objects.create(score=score, content=content, movie_id=pk)
        form = ScoreForm(request.POST)
        if form.is_valid():
            score = form.cleaned_data['score']
            content = form.cleaned_data['content']
            Score.objects.create(score=score, content=content, movie_id=pk)
            return redirect('movies:detail', movie.pk)
    else:
        form = ScoreForm()

    return render(request, 'movies/detail.html', {'movie': movie, 'scores': scores, 'form': form})

여기서 중요한 부분은 두 가지다. (주석처리 부분은 폼클래스가 없을 때 로직)

  1. 폼클래스 임포트 및 인스턴스 생성
  2. if request.method == 'POST' ... else

일단 request.method가 POST인지 아닌지에 따라 폼클래스의 인스턴스를 처리하는 방식이 다르다. POST가 아닌 경우, 그냥 URL로 접속하거나 GET방식 요청의 경우에는 else 부분의 로직을 실행한다.

else부분에서는 폼 인스턴스를 생성하는데, 이 때 인자로는 아무 것도 전달하지 않는다. 이는 빈 폼을 생성한 것이므로, POST 요청이 아닌 경우에는 빈 폼을 렌더링할 것이다.

POST 요청인 경우에는

  1. request.POST 를 인자로 넣어 폼 인스턴스 생성
  2. 폼의 유효성 검사 후 모든 필드가 유효하면 is_valid()는 True를 반환. 그리고 유효한 데이터를 cleaned_data 속성에 넣는다.
  3. cleaned_data에서 데이터를 꺼내서 저장 후, 원하는 로직을 처리하고(이 경우 새로운 DB 생성) 리다이렉트 한다.

여기서 유효성 검사를 통과한 경우에만 리다이렉트 되도록 하고 있는데, 만약 일부만 유효성 검사를 통과하고 나머지는 통과하지 못했다면(is_valid()가 False를 리턴했다면) 마지막 줄의 렌더함수가 호출되면서 form 객체에는 직전에 제출된 폼 데이터가 채워진다. 즉 잘못 작성했다고 전부 빈 칸부터 시작하는 것이 아니라, 직전에 작성했던 부분은 남아있다는 것이다.

이렇게 빈 폼을 보여주는 로직(else)과 폼 제출 결과를 처리하는 로직(if request.method == 'POST') 이 하나의 뷰 함수로 처리된다. 이는 장고가 권장하는 방식이다.

폼클래스로 폼 처리하는 것은 여기까지이고 다음은 모델 폼과 부트스트랩 적용 폼을 정리하고자 한다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함