[KT Aivle AI] 14주차 장고를 활용한 WebApp2

hyowon·2024년 5월 28일
0

KtAivle

목록 보기
36/39

개요

장고를 활용하여 WebApp을 만들어보자

5. 장고 Form

    1. HTML FORM
    1. CSRF
    1. HttpRequest
    1. 장고 Form
    1. URL Reverse
    1. ModelForm
    1. 유효성 검사

1) HTML FORM

  • Form 태그는 하나 이상의 위젯으로 구성
  • action : 처리 요청 URL
  • method : 처리 요청 방식
    • GET 방식 : Query String 이 요청정보 헤더에 포함되어 전달
    • POST 방식 : Query String 이 요청정보 바디에 포함되어 전달
  • enctype : POST 방식에서만 유효
    • application/x-www-form-urlencoded (default)
    • multipart/form-data : 파일 업로드 가능

아래 HTML 코드는 다양한 입력 유형을 사용하여 사용자의 데이터를 수집하는
폼(form) 을 만듭니다. 아래는 각 입력 유형과 해당하는 위젯의 설명입니다.

1. HTML 코드

<form action="" method="post">
    아이디 : <input type="text" name="id"><br>
    비밀번호 : <input type="password" name="pwd"><br>
    사진 : <input type="file" name="image"><br>
    성별 :
    <input type="radio" name="gender" value="male"> 남자
    <input type="radio" name="gender" value="female"> 여자<br>
    관심분야:
    <input type="checkbox" name="interest" value="AI">인공지능
    <input type="checkbox" name="interest" value="BD">빅데이터
    <input type="checkbox" name="interest" value="DA">데이터 분석<br>
    연령대:
    <select name="age">
        <option value="2">20대</option>
        <option value="3">30대</option>
        <option value="4">40대</option>
    </select><br>
    참고 사항: <br>
    <textarea cols="30" rows="10" name="remark"></textarea><br>
    <input type="submit" value="확인">
</form>

2. 각 위젯의 설명

  1. 텍스트 입력 필드 (<input type="text">):
    • 아이디: 사용자가 텍스트를 입력할 수 있는 단일 줄 입력 필드입니다.
    아이디 : <input type="text" name="id"><br>
  1. 비밀번호 입력 필드 (<input type="password">):
    • 비밀번호: 비밀번호 입력을 위한 단일 줄 입력 필드로, 입력 시 텍스트가 마스킹 처리됩니다.
    비밀번호 : <input type="password" name="pwd"><br>
  1. 파일 선택 필드 (<input type="file">):
    • 사진: 사용자가 파일을 선택할 수 있도록 하는 필드입니다.
    사진 : <input type="file" name="image"><br>
  1. 라디오 버튼 (<input type="radio">):
    • 성별: 사용자가 선택할 수 있는 여러 옵션 중 하나를 선택하게 합니다. 동일한 name을 공유하며, 각기 다른 value를 가집니다.
    성별 :
    <input type="radio" name="gender" value="male"> 남자
    <input type="radio" name="gender" value="female"> 여자<br>
  1. 체크박스 (<input type="checkbox">):
    • 관심분야: 사용자가 복수의 옵션을 선택할 수 있게 합니다. 각 체크박스는 동일한 name을 가지며, 각기 다른 value를 가집니다.
    관심분야:
    <input type="checkbox" name="interest" value="AI">인공지능
    <input type="checkbox" name="interest" value="BD">빅데이터
    <input type="checkbox" name="interest" value="DA">데이터 분석<br>
  1. 드롭다운 메뉴 (<select><option>):
    • 연령대: 사용자가 여러 옵션 중 하나를 선택할 수 있게 합니다.
    연령대:
    <select name="age">
        <option value="2">20대</option>
        <option value="3">30대</option>
        <option value="4">40대</option>
    </select><br>
  1. 텍스트 영역 (<textarea>):
    • 참고 사항: 사용자가 여러 줄의 텍스트를 입력할 수 있는 영역입니다.
    참고 사항: <br>
    <textarea cols="30" rows="10" name="remark"></textarea><br>
  1. 제출 버튼 (<input type="submit">):
    • 확인: 폼의 데이터를 서버로 제출하는 버튼입니다.
    <input type="submit" value="확인">
  • 이와 같이 HTML 폼 위젯을 사용하면 다양한 유형의 사용자 입력을 받을 수 있으며, 각각의 입력 필드는 특정 목적에 맞게 설계되어 사용자의 데이터를 효율적으로 수집할 수 있습니다.

3. HTML FORM 과 POST FORM

  • HTML FormGET 방식과 POST 방식은 클라이언트(웹 브라우저)가 서버에 데이터를 전송하는 두 가지 주요 방법입니다.
  • 각각의 방식은 다음과 같은 특성을 가지고 있습니다:
  1. GET 방식:
  • 데이터를 URL의 쿼리 문자열( query string )에 포함하여 전송합니다.
  • 데이터가 URL 에 노출되므로 보안에 취약합니다.
  • URL 에 데이터가 노출되기 때문에 주로 데이터의 길이가 짧고 민감하지 않은 경우에 사용됩니다.
  • 브라우저에 의해 캐시될 수 있어, 같은 요청이 반복될 때 서버로부터 재요청하지 않고 캐시된 데이터를 사용할 수 있습니다.
  • 주로 데이터를 검색하거나 가져오는 용도로 사용됩니다.
  1. POST 방식:
  • 데이터를 HTTP 요청의 본문( body )에 담아서 전송합니다.
  • 데이터가 URL 에 노출되지 않으므로 GET 방식보다 보안적으로 우수합니다.
  • 대용량의 데이터나 민감한 정보를 전송할 때 사용됩니다.
  • 브라우저에 의해 캐시되지 않습니다.
  • 데이터의 길이에 제한이 없습니다.
  • 주로 데이터를 생성하거나 업데이트하는 용도로 사용됩니다.

따라서, 데이터 전송 시 보안과 데이터의 길이, 캐시 여부 등을 고려하여 GET 방식과 POST 방식 중 적합한 방식을 선택해야 합니다.

2) CSRF

CSRF(Cross-Site Request Forgery) 공격과 방어 방법

  • CSRF(Cross-Site Request Forgery)는 사이트 간 요청 위조 공격으로, 사용자가 의도하지 않은 요청을 서버에 보내는 공격입니다.
  • 주로 사용자의 권한을 이용하여 악의적인 행위가 이루어집니다.
  • 이를 방지하기 위해 다음과 같은 방법을 사용합니다
  1. CSRF 토큰 사용: 웹 애플리케이션이 사용자에게 발급하는 랜덤한 문자열로, GET 요청시에는 HTML 폼 안에 숨겨진 필드로 넣어주고, POST 요청시에는 함께 전송합니다.
  1. CsrfViewMiddleware : 이 미들웨어는 CSRF 토큰을 사용하여 요청의 유효성을 검사합니다. 여기서 CsrfViewMiddleware의 동작을 간단히 설명하겠습니다:
  • GET 요청시 CSRF 토큰 발급:
    • 사용자가 웹 애플리케이션에 GET 요청을 보내면, 서버는 CSRF 토큰을 생성하고 이를 HTML 폼 안에 숨겨진 필드로 포함시킵니다.
    • 이 토큰은 사용자 세션에 저장되며, 이후의 요청에서 이 토큰을 사용하여 요청의 유효성을 검사합니다.
  • POST 요청시 CSRF 토큰 체크:
    • 사용자가 폼을 제출하면(POST 요청), 폼 데이터와 함께 CSRF 토큰도 전송됩니다.
    • 서버는 전송된 CSRF 토큰과 세션에 저장된 토큰을 비교하여 요청의 유효성을 검사합니다.
  • 토큰 체크 오류시 403 Forbidden 응답:
    • 전송된 CSRF 토큰이 없거나 세션에 저장된 토큰과 일치하지 않으면, 서버는 해당 요청을 거부하고 403 Forbidden 응답을 반환합니다.
    • 이를 통해 CSRF 공격을 방지하고, 사용자 데이터의 안전성을 보장합니다.
  1. CSRF의 특성: CSRF 공격은 현재 요청이 유효한지 여부만 판단하므로, 사용자 인증 상태에서 공격이 발생할 수 있습니다.

3) HttpRequest

1. Django의 HttpRequest 객체와 주요 속성

Django 에서 HttpRequest 객체는 클라이언트로부터 전송된 요청 정보를 처리하는 데 사용됩니다. 주요 속성들은 다음과 같습니다.

  • 클라이언트로부터 전송된 요청정보를 처리하는 객체: 클라이언트의 모든 요청 정보를 포함합니다.
  • View : HttpRequest 객체는 뷰 함수나 클래스 기반 뷰의 첫 번째 인자로 전달됩니다.
  • Form 처리 관련 속성:
    • request.method : 요청 방식(GET, POST 등)을 나타냅니다.
    • request.GET : GET 방식으로 전달된 질의 문자열 정보를 담고 있으며, QueryDict 타입입니다.
    • request.POST : POST 방식으로 전달된 폼 데이터를 담고 있으며, QueryDict 타입입니다.
    • request.FILES : POST 방식으로 업로드된 파일에 대한 정보를 담고 있으며, MultiValueDict 타입입니다.

2. Django의 MultiValueDict와 QueryDict 설명

2-1. MultiValueDict

  • 사전을 상속함: 파이썬의 기본 사전(dict)을 상속합니다.

  • 기본 dict는 key의 중복을 허용하지 않음: 동일한 키를 여러 번 가질 수 없습니다.

  • MultiValueDict은 동일 key를 허용함: 동일한 키에 대해 여러 값을 가질 수 있습니다.

    • 예: color=red&color=blue&color=yellow 같은 쿼리 문자열 처리.
    from django.utils.datastructures import MultiValueDict
    
    data = MultiValueDict({'color': ['red', 'blue', 'yellow']})
    print(data.getlist('color'))  # ['red', 'blue', 'yellow']

2-2.QueryDict

  • MultiValueDict을 상속: MultiValueDict를 상속하여 더 많은 기능을 추가한 자료 구조입니다.

  • 수정 불가능한(Immutable) MultiValueDict: 기본적으로 불변이지만, mutable 속성을 True로 설정하여 수정 가능하게 만들 수 있습니다.

    from django.http import QueryDict
    
     q = QueryDict('color=red&color=blue&color=yellow')
     print(q.getlist('color'))  # ['red', 'blue', 'yellow']
    
     q = q.copy()  # make it mutable
     q['color'] = 'green'
     print(q.getlist('color'))  # ['green']
  • dict와 MultiValueDict은 수정이 가능함: 기본적으로 값을 추가하거나 변경할 수 있습니다.

3. HttpRequest 속성

4) 장고 Form

  • Model 클래스와 비슷하게 Form 클래스 정의
  • Django의 Form 클래스는 사용자 입력을 처리하고 검증하는 강력한 도구입니다.

1. Django Form의 주요 기능

  1. 입력 폼 HTML 생성:
    • Django Form 클래스는 입력 폼을 HTML로 쉽게 생성할 수 있는 메서드를 제공합니다.
      • as_table(): 폼 필드를 <table> 태그로 변환합니다.
      • as_p(): 폼 필드를 <p> 태그로 변환합니다.
      • as_ul(): 폼 필드를 <ul> 태그로 변환합니다.
form = ContactForm()
form.as_table()
form.as_p()
form.as_ul()
  1. 입력 폼의 값들을 검증 및 값 변경:
    • 폼 데이터의 유효성을 검증하고 필요에 따라 값을 변경합니다.
  1. 검증을 완료한 값들을 사전 타입으로 제공(cleaned_data):
    • 폼 검증이 성공하면 유효한 데이터를 cleaned_data 딕셔너리로 제공합니다.
if form.is_valid():
    cleaned_data = form.cleaned_data
    name = cleaned_data['name']
    email = cleaned_data['email']

2. Django Form 처리 방식

  • Django는 동일한 요청에 대해 GET과 POST 방식을 구분하여 처리합니다.

GET 방식

  • 입력 폼 출력:

    • 사용자가 GET 요청을 보내면 입력 폼을 출력합니다.

      def contact_view(request):
          form = ContactForm()
          return render(request, 'contact.html', {'form': form})

POST 방식

  • 데이터의 유효성을 검증:

    • 사용자가 폼을 제출하면 POST 요청이 발생합니다.
    • 서버는 폼 데이터를 유효성 검증합니다.
  • 검증 성공 시:

    • 유효한 데이터가 제출되면 데이터를 처리하고 성공 URL로 리다이렉트합니다.

      def contact_view(request):
          if request.method == 'POST':
              form = ContactForm(request.POST)
              if form.is_valid():
                  # 데이터 처리 로직
                  return redirect('success_url')
          else:
              form = ContactForm()
          return render(request, 'contact.html', {'form': form})
  • 검증 실패 시:

    • 유효성 검증에 실패하면 오류 메시지와 함께 입력 폼을 다시 출력합니다.

      def contact_view(request):
          if request.method == 'POST':
              form = ContactForm(request.POST)
              if not form.is_valid():
                  return render(request, 'contact.html', {'form': form})
          else:
              form = ContactForm()
          return render(request, 'contact.html', {'form': form})

3. Django Form 오류 처리

  1. GET 방식의 입력 페이지 응답을 위한 구현:
    • Form 클래스를 선언하고 필드 속성으로 입력 폼을 처리합니다.
  1. POST 방식의 서비스 처리 응답을 위한 구현:
    • HttpRequest.POST 값을 Form 객체에 바인딩합니다.
    • is_valid() 메소드를 호출하여 유효성을 검사합니다.
    • 유효성 검사 실패 시, 오류 메시지와 함께 입력 페이지를 응답합니다.
    • 유효성 검사 성공 시, cleaned_data 변수에 저장된 값을 기반으로 서비스 처리를 진행합니다.

4. Django Form 필드와 위젯 변환

Django Form 클래스는 다양한 필드 타입을 제공하며, 이는 HTML 폼 필드와 매핑됩니다. 주요 필드 타입은 다음과 같습니다:

  • BooleanField, CharField, ChoiceField, DateField, DateTimeField, EmailField, FileField, ImageField, FloatField, IntegerField, RegexField 등.

폼 필드를 HTML 태그로 변환할 때는 다음 메서드를 사용합니다:

  • as_p(): 폼 필드를 <p> 태그로 변환합니다.
  • as_ul(): 폼 필드를 <ul> 태그로 변환합니다.
  • as_table(): 폼 필드를 <table> 태그로 변환합니다.

실습

Django 프로젝트에서 블로그 포스트 작성을 위한 폼을 구현하고 사용하는 방법을 단계별로 정리해보겠습니다. 다음은 blog 애플리케이션의 폼, URL, 템플릿, 뷰 파일에 대한 내용입니다.

1. 폼 정의 (blog/forms.py)

from django import forms

class PostForm(forms.Form):
    title = forms.CharField(label='제목')
    body = forms.CharField(label='내용', widget=forms.Textarea)

2. URL 정의 (blog/urls.py)

from django.urls import path
from . import views

urlpatterns = [
    path('new/', views.post_create, name='post_create'),
]

3. 템플릿 작성 (blog/templates/blog/post_form.html)

{% extends "layout.html" %}

{% block title %} Post 등록 {% endblock %}

{% block content %}
<h1>POST 등록</h1>
<form action="" method="post">
    {% csrf_token %}
    <table>
        {{ form.as_table }}
    </table>
    <input type="submit" value="등록">
</form>
{% endblock %}

4. 블로그 리스트에 새 글 쓰기 링크 추가 (blog/templates/blog/list.html)

{% extends "layout.html" %}

{% block content %}
{% for post in post_all %}
    <a href="{{ post.url }}">{{ post.title }}</a><br>
{% endfor %}
<a href="/blog/new/">새 글 쓰기</a>
{% endblock %}

5. 뷰 정의 (blog/views.py)

from django.shortcuts import render, redirect
from django.http import HttpResponse
from .forms import PostForm
from .models import Post

def post_create(request):
    if request.method == 'POST':
        form = PostForm(request.POST)
        if form.is_valid():
            # 데이터 저장
            Post.objects.create(**form.cleaned_data)
            return HttpResponse('추가 작업 완료')
    else:
        form = PostForm()
    return render(request, 'blog/post_form.html', {'form': form})
  • 이렇게 하면 장고에서 블로그 포스트 작성을 위한 폼을 구현하고 사용할 수 있습니다.
  • 폼을 통해 입력된 데이터는 유효성 검사를 거쳐 데이터베이스에 저장되며, 사용자는 새 글 작성 페이지를 통해 글을 등록할 수 있습니다.

5) URLReverse

  • Django에서 URL Reverse 기능을 사용하여 URL을 효율적으로 관리하고 사용하는 방법을 정리하겠습니다.
  • 이는 URL 패턴을 역으로 해석하여 URL을 생성하는 기능으로, 코드 내에서 URL을 하드코딩하지 않도록 도와줍니다.
    Django의 URL Reverse 기능을 사용하여 URL을 효율적으로 관리하고 사용하는 방법에 대해 단계별로 설명하겠습니다. 이는 URL 패턴을 역으로 해석하여 URL을 생성하는 기능으로, 코드 내에서 URL을 하드코딩하지 않도록 도와줍니다.

1. URL 정의 및 네임스페이스 설정

먼저, URL 패턴을 정의하고 네임스페이스를 설정합니다. 네임스페이스를 설정하면 URL 이름이 중복되지 않도록 할 수 있습니다.

blog/urls.py

from django.urls import path
from . import views

app_name = 'blog'  # 앱 네임스페이스 설정

urlpatterns = [
    path('', views.list, name='list'),  # 리스트 페이지 URL
    path('<int:id>/', views.detail, name='detail'),  # 상세 페이지 URL
    path('new/', views.post_create, name='create'),  # 새 글 작성 페이지 URL
]

2. URL 태그 사용하여 링크 생성

  • 템플릿 내에서 URL 태그를 사용하여 동적으로 URL을 생성합니다. 이를 통해 URL 하드코딩을 피할 수 있습니다.

blog/templates/blog/list.html 수정 전

{% for post in post_all %}
    <a href="/blog/{{ post.id }}">{{ post.title }}</a><br>
{% endfor %}
<a href="/blog/new/">새 글 쓰기</a>

blog/templates/blog/list.html 수정 후

{% extends "layout.html" %}

{% block content %}
{% for post in post_all %}
    <a href="{% url 'blog:detail' post.id %}">{{ post.title }}</a><br>
{% endfor %}
<a href="{% url 'blog:create' %}">새 글 쓰기</a>
{% endblock %}

이제 각 링크는 URL 태그를 사용하여 동적으로 생성됩니다. 'blog:detail''blog:create'blog/urls.py에서 정의된 URL 패턴의 이름입니다.

3. URL Reverse 함수 사용

  • Django 쉘에서 URL Reverse 기능을 사용하여 URL을 생성할 수 있습니다.

장고 쉘 사용 예시

>> from django.urls import reverse
>> reverse('blog:list')  # 리스트 페이지 URL 생성
'/blog/'
>> reverse('blog:create')  # 새 글 작성 페이지 URL 생성
'/blog/new/'
>> reverse('blog:detail', args=[1])  # ID가 1인 게시물의 상세 페이지 URL 생성
'/blog/1/'
>> reverse('blog:detail', kwargs={'id': 1})  # ID가 1인 게시물의 상세 페이지 URL 생성 (kwargs 사용)
'/blog/1/'

reverse 함수는 URL 패턴의 이름을 인수로 받아 해당 URL을 문자열로 반환합니다.

4. get_absolute_url 메서드 사용

  • 모델에 get_absolute_url 메서드를 정의하여 모델 인스턴스의 URL을 반환하도록 합니다.

blog/models.py

from django.db import models
from django.urls import reverse

class Post(models.Model):
    title = models.CharField(max_length=250)
    body = models.TextField()
    tag = models.ManyToManyField('Tag', null=True, blank=True)

    def get_absolute_url(self):
        return reverse('blog:detail', args=[self.id])

이제 Post 모델의 인스턴스에서 get_absolute_url 메서드를 호출하면 해당 인스턴스의 상세 페이지 URL을 얻을 수 있습니다.

장고 쉘 사용 예시

>> from django.shortcuts import redirect
>> from blog.models import Post
>> post = Post.objects.get(id=5)
>> redirect(post)  # post.get_absolute_url()을 호출하여 리다이렉트
<HttpResponseRedirect status_code=302, "text/html; charset=utf-8", url="/blog/5/">

redirect(post)는 내부적으로 post.get_absolute_url()을 호출하여 해당 URL로 리다이렉트합니다.

5. get_absolute_url 사용하여 뷰 리다이렉션

  • 뷰에서 폼 데이터를 처리한 후, get_absolute_url 메서드를 사용하여 리다이렉트합니다.

blog/views.py

from django.shortcuts import render, redirect
from .forms import PostForm
from .models import Post

def post_create(request):
    if request.method == 'POST':
        form = PostForm(request.POST)
        if form.is_valid():
            post = Post.objects.create(**form.cleaned_data)
            return redirect(post)  # post.get_absolute_url()을 호출하여 리다이렉트
    else:
        form = PostForm()
    return render(request, 'blog/post_form.html', {'form': form})

새 게시물을 생성한 후, redirect(post)를 사용하여 생성된 게시물의 상세 페이지로 리다이렉트합니다.

6. 목록 보기 링크 추가

  • 상세 페이지에서 목록 보기로 돌아가는 링크를 추가합니다.

blog/templates/blog/detail.html

{% extends "layout.html" %}

{% block content %}
<!-- 게시물 상세 내용 -->
<hr>
<a href="{% url 'blog:list' %}">목록 보기</a>
{% endblock %}
  • 이와 같이 Django의 URL Reverse 기능을 사용하면 URL 관리를 더 효율적으로 할 수 있으며, 코드 내에서 URL을 하드코딩하지 않아 유지보수가 용이해집니다.

6) ModelForm

  • Django의 ModelForm을 사용하여 데이터를 추가, 수정, 삭제하는 과정을 전체적으로 정리

1. ModelForm 선언 및 데이터 추가

blog/forms.py

먼저 ModelForm을 정의합니다. ModelForm은 특정 모델의 입력 폼을 자동으로 생성해줍니다.

from django import forms
from .models import Post

class PostModelForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ['title', 'body']

blog/views.py

ModelForm을 사용하여 데이터를 추가하는 뷰를 작성합니다.

from django.shortcuts import render, redirect
from .forms import PostModelForm
from .models import Post

def post_create(request):
    if request.method == 'POST':
        form = PostModelForm(request.POST)
        if form.is_valid():
            post = form.save()  # form의 save 메서드를 사용하여 데이터를 저장
            return redirect(post)  # post.get_absolute_url()로 리다이렉트
    else:
        form = PostModelForm()
    return render(request, 'blog/post_form.html', {'form': form})

2. ModelForm을 사용하여 데이터 수정

blog/views.py

데이터 수정 뷰를 작성합니다.

from django.shortcuts import get_object_or_404

def post_update(request, id):
    post = get_object_or_404(Post, id=id)
    if request.method == 'POST':
        form = PostModelForm(request.POST, instance=post)
        if form.is_valid():
            form.save()  # form의 save 메서드를 사용하여 데이터를 업데이트
            return redirect('blog:detail', id=post.id)
    else:
        form = PostModelForm(instance=post)
    return render(request, 'blog/post_update.html', {'form': form})

blog/urls.py

URL 패턴을 정의합니다.

from django.urls import path
from . import views

app_name = 'blog'

urlpatterns = [
    path('', views.list, name='list'),
    path('<int:id>/', views.detail, name='detail'),
    path('new/', views.post_create, name='create'),
    path('update/<int:id>/', views.post_update, name='update'),
]

blog/templates/blog/post_update.html

데이터 수정을 위한 템플릿을 작성합니다.

{% extends "layout.html" %}

{% block title %}Post 수정{% endblock %}

{% block content %}
<h1>POST 수정</h1>
<form action="" method="POST">
    {% csrf_token %}
    <table>
        {{ form.as_table }}
    </table>
    <input type="submit" value="수정">
</form>
{% endblock %}

3. 데이터 삭제

blog/views.py

데이터 삭제 뷰를 작성합니다.

def post_delete(request, id):
    post = get_object_or_404(Post, id=id)
    if request.method == 'POST':
        post.delete()
        return redirect('blog:list')
    return render(request, 'blog/post_delete.html', {'post': post})

blog/urls.py

URL 패턴을 정의합니다.

urlpatterns = [
    path('', views.list, name='list'),
    path('<int:id>/', views.detail, name='detail'),
    path('new/', views.post_create, name='create'),
    path('update/<int:id>/', views.post_update, name='update'),
    path('delete/<int:id>/', views.post_delete, name='delete'),
]

blog/templates/blog/post_delete.html

데이터 삭제를 위한 템플릿을 작성합니다.

{% extends "layout.html" %}

{% block title %}Post 삭제{% endblock %}

{% block content %}
<h1>POST 삭제</h1>
<p>{{ post }} 글을 정말로 삭제하시겠습니까?</p>
<form action="" method="POST">
    {% csrf_token %}
    <input type="submit" value="네, 삭제합니다">
</form>
<a href="{% url 'blog:list' %}">아니오, 취소합니다</a>
{% endblock %}

4. 템플릿 수정

게시글 상세 페이지에 수정 및 삭제 링크를 추가합니다.

blog/templates/blog/detail.html

{% extends "layout.html" %}

{% block content %}
<h1>{{ post.title }}</h1>
<p>{{ post.body }}</p>
<hr>
<a href="{% url 'blog:list' %}">목록 보기</a>
<a href="{% url 'blog:update' post.id %}">글 수정</a>
<a href="{% url 'blog:delete' post.id %}">글 삭제</a>
{% endblock %}

5. commit 지연

commit=False를 사용하여 추가 작업을 수행한 후 저장합니다.

blog/views.py

def post_create(request):
    if request.method == 'POST':
        form = PostModelForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)  # DB에 바로 저장하지 않음
            post.ip = request.META['REMOTE_ADDR']  # 추가 작업 수행
            post.save()  # 최종 저장
            return redirect(post)
    else:
        form = PostModelForm()
    return render(request, 'blog/post_form.html', {'form': form})

마이그레이션

새로운 필드를 추가하거나 모델 변경 사항을 적용하려면 마이그레이션을 수행해야 합니다.

python manage.py makemigrations blog
python manage.py migrate blog
  • 이제 전체적인 데이터 추가, 수정, 삭제 기능과 관련된 코드를 모두 정리했습니다.
  • 각 단계에 필요한 코드를 작성하고 설명했으므로, 이를 참고하여 Django 프로젝트에서 ModelForm을 사용한 CRUD 작업을 구현할 수 있습니다.

7) 유효성 검사

  • Django에서 데이터를 추가, 수정, 삭제할 때 유효성 검사를 적용하고, commit 지연, 사용자 정의 validator를 사용하는 방법을 정리

1. 데이터 추가

ModelForm 선언

ModelForm을 사용하여 모델 기반 폼을 정의합니다.

blog/forms.py

from django import forms
from .models import Post

class PostModelForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ['title', 'body']

데이터 추가 뷰

POST 요청을 받아 데이터를 저장합니다.

blog/views.py

from django.shortcuts import render, redirect
from .forms import PostModelForm

def post_create(request):
    if request.method == 'POST':
        form = PostModelForm(request.POST)
        if form.is_valid():
            post = form.save()  # 유효성 검사를 통과한 데이터 저장
            return redirect(post)
    else:
        form = PostModelForm()
    return render(request, 'blog/post_form.html', {'form': form})

2. 데이터 수정

  • 기존 데이터를 수정할 때는 폼에 기존 데이터를 채운 후 수정합니다.

데이터 수정 뷰

blog/views.py

from django.shortcuts import render, get_object_or_404, redirect
from .models import Post
from .forms import PostModelForm

def post_update(request, id):
    post = get_object_or_404(Post, id=id)
    if request.method == 'POST':
        form = PostModelForm(request.POST, instance=post)
        if form.is_valid():
            form.save()
            return redirect(post)
    else:
        form = PostModelForm(instance=post)
    return render(request, 'blog/post_update.html', {'form': form})

3. 데이터 삭제

  • 삭제는 확인 페이지를 거쳐 데이터 삭제를 진행합니다.

1. 데이터 삭제 뷰

blog/views.py

def post_delete(request, id):
    post = get_object_or_404(Post, id=id)
    if request.method == 'POST':
        post.delete()
        return redirect('blog:list')
    return render(request, 'blog/post_delete.html', {'post': post})

4. Commit 지연

데이터를 저장할 때 commit=False를 사용하여 객체를 먼저 생성하고 추가 작업을 수행한 후 저장할 수 있습니다.

1. Commit 지연 뷰

blog/views.py

def post_create(request):
    if request.method == 'POST':
        form = PostModelForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)  # commit 지연
            post.ip = request.META['REMOTE_ADDR']  # 추가 작업
            post.save()  # 실제 저장
            return redirect(post)
    else:
        form = PostModelForm()
    return render(request, 'blog/post_form.html', {'form': form})

5. 사용자 정의 Validator

  • 사용자 정의 validator를 사용하여 폼 필드의 유효성을 검사할 수 있습니다.

사용자 정의 validator 함수

member/forms.py

import re
from django import forms
from django.core.exceptions import ValidationError
from django.core.validators import MinLengthValidator

def username_validator(value):
    if re.search(r'[^A-Za-z0-9_@.+-]', value):
        raise ValidationError('8자 이상 150자 이하 문자, 숫자 그리고 @/./+/-/_ 만 가능합니다.')

class UserCreationForm(forms.Form):
    username = forms.CharField(
        label='사용자 이름',
        max_length=150,
        validators=[username_validator, MinLengthValidator(8)]
    )
    password1 = forms.CharField(
        label='비밀번호',
        widget=forms.PasswordInput,
        validators=[MinLengthValidator(8)]
    )
    password2 = forms.CharField(
        label='비밀번호 확인',
        widget=forms.PasswordInput
    )

    def clean_password2(self):
        password1 = self.cleaned_data.get('password1')
        password2 = self.cleaned_data.get('password2')
        if password1 and password2 and password1 != password2:
            raise ValidationError('비밀번호가 일치하지 않습니다.')
        return password2

    def clean(self):
        cleaned_data = super().clean()
        username = cleaned_data.get('username')
        password1 = cleaned_data.get('password1')
        if username and password1 and username in password1:
            raise ValidationError('사용자 이름과 비밀번호가 유사합니다.')
        return cleaned_data

템플릿 예시

1. 데이터 추가 템플릿

blog/templates/blog/post_form.html

{% extends "layout.html" %}

{% block title %}Post 등록{% endblock %}

{% block content %}
<h1>POST 등록</h1>
<form action="" method="POST">
    {% csrf_token %}
    <table>
        {{ form.as_table }}
    </table>
    <input type="submit" value="등록">
</form>
{% endblock %}

2. 데이터 수정 템플릿

blog/templates/blog/post_update.html

{% extends "layout.html" %}

{% block title %}Post 수정{% endblock %}

{% block content %}
<h1>POST 수정</h1>
<form action="" method="POST">
    {% csrf_token %}
    <table>
        {{ form.as_table }}
    </table>
    <input type="submit" value="수정">
</form>
{% endblock %}

3. 데이터 삭제 템플릿

blog/templates/blog/post_delete.html

{% extends "layout.html" %}

{% block title %}Post 삭제{% endblock %}

{% block content %}
<h1>POST 삭제</h1>
<p>{{ post }} 글을 정말로 삭제하시겠습니까?</p>
<form action="" method="POST">
    {% csrf_token %}
    <input type="submit" value="네, 삭제합니다">
</form>
<a href="{% url 'blog:list' %}">아니오, 취소합니다</a>
{% endblock %}
profile
안녕하세요. 꾸준히 기록하는 hyowon입니다.

0개의 댓글