AIVLE TIL ('23.05.12) Django (4)

cjkangme·2023년 5월 15일
0

에이블스쿨

목록 보기
72/81
post-thumbnail
post-custom-banner

Django Form

Form은 사용자와 웹사이트를 연결하는 다리라고 할 수 있다.

단순히 서버에서 사용자에게 일방적으로 전달하던 것에서 Form을 통해 사용자가 서버로 정보를 전송할 수 있게 되었다.

Django Form

대부분의 form은 대부분 동일한 작업을 반복한다.

  • form tag 내에 입력받을 값들을 input 태그로 지정하여 입력 받는다.
  • 전달 받은 값을 확인하여 이상 없으면 저장
  • 보안을 위해 백엔드에서 정해진 방법을 사용하여 처리한다.

Django Form은 이를 적은 코드로 쉽게 처리할 수 있도록 해준다.

  • 사용자 입력 처리 + 유효성 검사 및 에러메세지 제공
  • 간단한 데이터 저장 (메서드 제공)
  • CSRF 토큰으로 웹사이트 보호

Django에서 폼을 처리하는 방법

I. 기존 방법대로의 처리 (HTML Form + POST)

기존의 방법대로 HTML에 직접 폼 태그를 작성하고,
백엔드에서는 POST method로 입력된 정보를 받아 처리한다.

1. HTML 폼 입력

<form method="POST">
    {% csrf_token %}
    <div><input type="text" name="title" maxlength="100" required></div>
    <div><textarea name="content" cols="40" rows="10" required></textarea></div>
    <div><input type="submit" value="저장"></div>
</form>
  • name 옵션에 백엔드가 어떤 데이터인지 구분할 수 있도록 이름을 지정해주어야 한다.
  • csrf_token : csrf_token은 임의의 난수값을 생성하여 세션에 저장한다. 또한 사용자의 요청시 난수값을 포함시켜 전송하는 역할을 한다. 이 난수값은 세션에 저장된다.
    서버는 세션에 저장된 난수값과 사용자가 보낸 난수값이 일치하는지 확인하여 일종의 인증과정을 거친다. 이를 통해 CSRF 공격을 방지한다.

2. 백엔드 처리

if req.method == 'POST':
    new_post = models.Post(
        title = req.POST.get('title'),
        content = req.POST.get('content'),
        ) 
    new_post.save()
  • req.POST.get('<name\>') : HTML 태그에 선언했던 <name> 속성의 value를 가져올 수 있다.
  • 가져온 데이터를 ORM을 통해 처리한다. 다만 현재코드에서는 전혀 유효성검사를 하고 있지도 않고, 가져온 데이터가 없다고 해도 무조건 동일하게 처리하기 때문에 오류가 발생할 가능성이 높다.
    이를 해결하기 위해서는 백엔드에서 직접 로직을 짜야한다.

Django Form을 통한 처리

1. forms.py에 폼 정의

# forms.py

class PostForm (forms.Form):
    title = forms.CharField(max_length= 100)
    content = forms.CharField(widget=forms.Textarea())

forms.py를 통해 사용할 django 폼을 한번에 모아 관리할 수 있다.

  • djnago는 모델에 따라 기본적으로 생성되는 input type을 지정해준다.
    예를들어 CharField는 기본적으로type=’text’인 input 태그가 된다.
  • 하지만 원하는 input을 직접 지정하고 싶다면 widget 옵션을 이용하면 된다. 위 코드의 경우 content를 받기 위해 input태그 대신 textarea 태그를 사용할 것이다.

2. 백엔드 처리

from . import forms

def create_form(req):
    form = forms.PostForm()
    if form.is_valid():
        new_post = models.Post(
            title = form.cleaned_data['title'],
            content = form.cleaned_data['content'],
            )
        new_post.save()
    return redirect('/blog/')
  • forms.py에 선언한 폼을 가져와서 이용하면 된다.
  • is_valid() : Form 클래스로 선언한 유효성 검사를 만족하는지 여부를 반환하는 함수
  • cleaned_data : 유효성 검사를 만족한 값만 담겨있다. 또한, DateField에 저장될 값을 일정한 Format으로 변환해주거나, Field에 맞게 형변환을 해주는 등 DB에 일관적인 형태의 데이터가 저장될 수 있도록 정제된 데이터를 제공한다.

이렇게 자동으로 유효성 검사를 수행해주며, DB가 일관성있게 관리될 수 있도록 데이터 정제까지 해준다.

3. HTML에 삽입

<form method="POST">
    {% csrf_token %}
    <table>
        {{form}}
    </table>
    <div><input type="submit" value="저장"></div>
</form>

Django Form을 이미 백엔드에서 정의하여 데이터로 보내주고 있기 때문에, HTML에서 작성할 필요가 없다.
대신 넘겨받은 변수명에 맞게 폼이 들어갈 자리를 지정해주면 된다.

  • 폼이 들어갈 자리를 table 태그로 감싸주면 된다. table 형식으로 보여주기 위해 이런 형식이 필요하며, table외에 다른 태그안에 감싸도 상관 없다.

III. ModelForm

  • 모델과 직접적으로 연결된 폼을 말한다.
  • 폼 자체가 모델과 연결되어 프레임워크가 자동으로 유효성 검사 및 데이터 개체 생성을 담당한다. 즉, 백엔드에서 폼 데이터를 입력받아 따로 처리해야하는 과정이 필요 없다.

1. ModelForm 정의

# forms.py
class PostModelForm(forms.ModelForm):
    class Meta:
        model = models.Post
        fields = ('title, content, published_at')
  • ModelForm을 상속받은 클래스 안에 Meta 라는 또다른 클래스가 들어가는 형식의 문법을 사용한다.
  • 입력 받을 정보들은 fields 변수명에 튜플 또는 리스트 형태로 선언한다.

2. 백엔드 처리

# views.py
def create_model_form(req):
    if req.method == "POST":
        form = forms.PostModelForm(req.POST)
        if form.is_valid():
            form.save()
            return redirect( "/blog/" )
  • 이미 프레임워크가 모델과 연동되어 처리를 끝낸 폼을 들고오기 때문에 그대로 저장이 가능하다.
  • 이후 HTML에 삽입하는 과정은 II. Django Form을 통한 처리와 동일하다.

번외) CSRF(Cross Site Request Forgery) 공격

  • Cross Site : 사이트가 서로 교차된다. 즉, 도메인이 서로 다른 사이트를 의미함
  • CSRF는 웹 보안 취약점의 일종이며, 사용자가 자신의 의지와 무관하게 공격자가 의도한 행위를 하게끔 하는 공격을 CSRF 공격이라 한다.

  • 공격자는 사용자가 CSRF 스크립트가 포함된 게시물 등을 읽거나 또는 스크립트가 삽입된 피싱 사이트 등을 방문하도록 유도한다.
  • 사용자가 CSRF 스크립트를 실행하는 순간 사용자의 정보를 바탕으로 서버에 CSRF 요청이 발생한다.
  • 서버가 CSRF 공격에 응답하면 공격자는 원하는 정보를 가져오거나, 서버에서 특정 동작을 수행하도록 하여 사용자 및 서버에 피해가 발생한다.
    • 웹사이트의 취약점을 바탕으로 사용자의 패스워드를 변경하게끔 하거나, 관리자 권한을 얻도록하게 하거나 하는 공격이 가능하다.

예방

  1. Referrer check (리퍼러 체크)
    • request 헤더의 리퍼러 값이 호스트와 일치하는지 확인한다.
      리퍼러에는 현재 페이지에 요청한 이전 페이지의 url 등의 정보가 담겨있어 중간에 가로챈 것이 있는지 확인이 가능하다.
    • 하지만 리퍼러 자체를 쉽게 조작할 수 있기 때문에 리퍼러 체크만으로는 보안성이 부족하다.
  2. CAPTCHA 도입
    • CAPTCHA를 통한 인증
  3. CSRF 토큰
    • 모든 요청마다 임의의 값을 발급하고 세션에 저장
    • 이 값을 요청과 함께 전송되도록 하여 저장된 값과 일치하는지 확인
    • 주로 body에 정보를 담아 보내는 대부분의 POST 요청에 사용
    • {% csrf_token %}이 바로 CSRF를 자동으로 생성해주는 django의 기능이다.

Admin Page 커스터마이징

관리자 페이지 (관리 Tool)

  • 모든 시스템에 관리툴은 반드시 필요한 페이지이다.
  • 서비스 구현보다 관리툴 구현이 더 어려울 수도 있다.
  • 하지만 Django는 우수한 성능의 관리자 페이지를 기본 제공해주고, 커스터마이징이 가능하기 떄문에 매우 쉽게 관리 툴을 구현할 수 있다.

예시 : 간단한 설문조사 웹 애플리케이션 만들기

https://docs.djangoproject.com/ko/4.0/intro/tutorial01/

설문조사 Pool을 간단히 구현하는 튜토리얼 예제이다.
질문 Question과 질문에서 선택할 수 있는 Choice의 일대다 관계로 이루어진 모델 구조를 갖고 있다.

1. 목록 출력의 개선

  • 파이썬의 매직메서드를 이용한다.
  • __str__ : 출력 시 표시될 텍스트를 설정하는 매직메서드

기존

관리자 페이지에서 어떤 데이터가 어떤 내용을 담고 있는지 확인할 수 없다.

메직메서드 사용

def __str__(self):
    return f"{self.question.question_text} : {self.choice_text}"

이제 기존 질문의 내용과 선택지의 내용을 한눈에 확인할 수 있다.

2. 필드 추가

관리자 페이지에서 보이는 필드를 추가할 수 있다.
우선 관리자페이지의 설정을 담당하는 admin.py에 클래스를 선언하여 등록해주어야 한다.

# Customizing
class QuestionAdmin(admin.ModelAdmin):
    list_display = ('question_text', 'publish_date', 'was_published_recently')

admin.site.register(models.Question, QuestionAdmin)
  • 어드민 클래스는 admin.ModelAdmin을 상속 받는다.
  • list_display 변수명에 관리자 페이지에 사용할 필드명을 리스트 또는 필터에 담아 선언한다.
  • 마지막으로 admin.site.register 메서드를 통해 클래스와 모델을 연결한다.

  • list_display에 선언한 필드가 추가되었다.

3. 필터 추가 및 커스터마이징

관리자 페이지에서는 원하는 데이터만 필터링하여 볼 수 있는 필터 기능을 제공한다.
이것 역시 원하는 페이지에 추가하거나 커스터마이징 할 수 있다.

# Customizing
class QuestionAdmin(admin.ModelAdmin):
    list_display = ('question_text', 'publish_date', 'was_published_recently')
    list_filter = ('publish_date',)

admin.site.register(models.Question, QuestionAdmin)
  • list_display와 동일한 방식으로 필터로 사용할 필드명을 리스트 또는 튜플 형태로 선언하면 된다.

4. Inline 기능

위 상황에서 하나의 질문에 있는 선택지를 모두 바꾸고 싶다고 가정하자.
현재 상황에서는 일일히 선택지 하나씩 하나씩 들어가 수정해주어야 한다.
하지만 Inline을 이용하면 Question에서 관계를 맺고있는 Choice를 모두 불러와 한꺼번에 처리할 수 있다.

class ChoiceInline(admin.TabularInline):
    model = models.Choice
    extra = 0

class QuestionAdmin(admin.ModelAdmin):
    # ... 중략 ... 
    inlines = (ChoiceInline,)
  • Inline을 위한 별도의 클래스를 만들어주어야한다.
    • admin.TabularInline 또는 admin.StackedInline 을 상속받는다. 두 클래스의 차이는 화면에 표시될 모양의 차이이며 기능은 동일하다.
    • model 변수명을 사용하여 인라인안에 들어갈 모델을 입력한다.
    • extra 옵션을 통해 생성을 원할 때 별도로 생성버튼을 누르지 않아도 양식이 미리 표시되게 끔 할 수 있다.

화면 길이상 잘렸지만, 스크롤을 내리면 다른 CHOICE들이 모두 보인다.

세션과 쿠키

세션과 쿠기는 웹을 위한 저장 공간이다.
둘의 차이점은 저장 장소인데, 클라이언트에 저장하면 쿠키, 서버에 저장하면 세션이다.

쿠키

  • 클라이언트에 저장
  • 저장 내용이 브라우저에 그대로 노출되기 때문에 보안이 취약하다.
  • 때문에 보안이 필요한 정보는 대부분 세션에 저장한다.
  • 한 도메인당 20개, 하나의 쿠키는 최대 4KB의 데이터를 저장할 수 있다.

세션

  • 서버에 저장되는 데이터로 key-value의 형태를 갖고 있다.
  • key 만을 쿠키에 저장하고 보안이 필요한 value는 서버 내에만 존재하기 때문에 노출될 위험이 적다.
  • 서버가 허락하는 한 용량에 제한이 없다.

세션과 쿠키가 필요한 이유

사용자를 식별하고, 사용자 행동에 대한 정보를 기억하기 위해 필요하다.

  • 사용자에 대한 데이터 수집
  • 장바구니 기능, 자동 로그인 등 기능 제공 등

실습

쿠키 실습

def cookie_counter(req):
    visits = int(req.COOKIES.get('visits', 0)) + 1
    res = HttpResponse(f"너를 위해 구웠지: {visits}")
    res.set_cookie('visits', visits)
    return res
  • visits = int(req.COOKIES.get('visits', 0)) + 1
    • req.COOKIES는 딕셔너리이다.
    • 딕셔너리의 .get() 메서드를 통해 key 값으로 조회가 된다면 해당 값을 가져오고, 없다면 default 값(0)을 가져온다.
  • 이제 새로고침을 할 때마다 visits 카운터가 올라가는 것을 쿠키 저장소에서 볼 수 있다.

세션 실습

def session_counter(req):
    req.session['count'] = req.session.get('count', 0) + 1
    return HttpResponse( f"이건 니꺼 아님: {req.session['count']}")
  • req.session['count'] = req.session.get('count', 0) + 1
    • req.session 역시 딕셔너리이다.
  • 쿠키와 동일하게 count가 새로고침 때마다 1씩 증가하지만, 증가하는 카운트를 어디에서도 찾을 수 없다.

Django 회원관리 기능

django는 놀랍게도 회원 페이지 기능까지 제공하기 때문에 별도로 구현할 필요가 없다.
심지어 기본 템플릿도 제공된다.
물론 실무 수준에서는 login페이지를 직접 만들어 사용하지만, 빠르게 개발하려고 할 때 이보다 유용한 것이 없다.

기능 설정

# urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("", include('django.contrib.auth.urls')),
]
  • 프로젝트 폴더에 있는 urls.py에 위와 같은 설정을 해주면 된다.
  • 예시에서는 url에 아무것도 입력하지 않았지만 /accounts/login/ 과 같은 형대가 되도록 subpath도 설정할 수 있다.

템플릿 설정

django 기본 설정으로 이들은 베이스 템플릿 경로의 registraion/ 디렉토리에서 회원 페이지에 표시할 템플릿을 찾는다.

로그인 후 이동 경로 변경

django 기본 설정으로는 로그인후 account/profile 페이지로 리다이렉션 된다.
이를 변경하고 싶다면 settings.py를 다음과 같이 수정하자.

  • LOGIN_REDIRECT_URL = '/blog/'

이제 로그인 후 이 주소로 이동하게 된다.

post-custom-banner

0개의 댓글