[Django] 파이썬 웹 프로그래밍 - Django 의 핵심 기능 #2 |
< 4.4. 폼 처리하기 >
* 4.4.1. HTML 에서의 폼
-
HTTP 프로토콜 중 폼에서 사용할 수 있는 HTTP 메소드는 GET 과 POST 뿐이다.
장고는 이 중에서도 폼 처리에는 POST 방식만을 사용하고 있다.
추가적으로 장고는 보안을 강화하기 위해 CSRF 방지 기능을 제공한다.
* 4.4.2. 장고의 폼 기능
-
장고는 폼 처리를 위해 다음 3가지 기능을 제공한다.
폼 생성에 필요한 데이터를 폼 클래스로 구조화하기
폼 클래스의 데이터를 랜더링하여 HTML 폼 만들기
사용자로부터 제출된 폼과 데이터를 수신하고 처리하기
-
장고의 모델 클래스가 데이터베이스 테이블의 논리적인 구조 및 동작 기능, 우리에게 보여지는 방식들을 기술하는 것과 마찬가지로 폼 클래스는 폼을 기술하고 폼이 어떻게 작동하고 어떻게 보이는지를 결정한다.
모델 클래스의 필드가 데이터베이스의 필드로 매핑되듯,
폼 클래스의 필드도 HTML 폼의 <input> 엘리먼트에 매핑된다.
-
필드는 폼 데이터를 저장하고 있으며, 폼이 제출되면 자신의 데이터에 대한 유효성 검사를 실시한다.
필드는 저장하는 데이터의 종류에 따라 자신의 타입을 가진다.
폼의 필드는 브라우저에서 HTML 위젯으로 표현되고, 필드 타입마다 디폴트 위젯 클래스를 가지고 있으며 필요 시 오버라이딩 될 수 있다.
-
폼도 결국 템플릿의 일부이므로 템플릿 코드에 포함되어 렌더링 절차를 거친다.
장고에서 객체를 랜더링 할 때는 다음의 3가지 과정이 이루어진다.
렌더링할 객체 뷰로 가져오기
그 객체를 템플릿 시스템으로 넘겨주기
템플릿 문법을 처리해서 HTML 마크업 언어로 변환하기
-
폼 데이터는 데이터가 있을수도 없을 수도 있다.
데이터가 없는 폼을 언바운드(unbound) 폼이라고 하며, 언바운드 폼은 렌더링되어 사용자에게 보여질 때 폼은 비어있거나 디폴트값이 채워진다.
바운드(bound) 폼은 제출된 데이터를 갖고 있어 데이터 유효성을 검사하는데 사용된다.
* 4.3.3. 폼 클래스로 폼 생성
-
폼 클래스는 django.forms.Form 을 상속한다.
from django import forms
class NameForm(forms.Form):
your_name = forms.CharField(label=‘Your name’, max_length=100)
# max_length 는 제약사항이면서, validation 으로도 사용된다.
# label 을 지정하지 않으면 디폴트 라벨을 사용하며, 이는 필드명의 첫글자는 대문자, 그리고 _ 를 white space 로 치환하여 적용된다.
# 변수명은 label for 와 input id 에도 매핑된다.
위 코드의 적용 결과는 다음과 같다.
<label for=“your_name”>Your name: </label>
<input id=“your_name” type=“text” name=“your_name” maxlength=“100”>
-
CharField 의 default 위젯은 TextInput 이다.
<input type=“text”> 로 매핑된다.
만약 default 위젯을 textarea 로 하려면 다음과 같이 기술해야 한다.
...
your_name = forms.CharField(label=‘Your name’, max_length=100, widget=forms.Textarea)
...
-
장고의 폼 클래스는 모든 필드에 대해 유효성 검사 루틴을 실행시키는 is_valid() 메소드를 가지고 있다.
이 메소드가 호출되어 유효성 검사를 하고, 유효하다면 is_valid() 메소드는 다음과 같은 2가지 일을 한다.
True 를 반환
폼 데이터를 cleaned_data 속성에 넣는다.
-
Form class 에 대한 랜더링 결과에 form 태그나 submit 버튼은 없는데, 이는 개발자가 직접 템플릿에 넣어줘야 한다.
<form action=“your-name/” method=“post”>
{% csrf_token %}
{{ form }}
<input type=“submit” value=“Submit” />
</form>
* 4.4.4. 뷰에서 폼 클래스 처리
-
form 을 처리하는 뷰는 2개가 필요하다.
하나는 폼을 보여주는 뷰이고, 다른 하나는 제출된 폼을 처리하는 뷰이다.
2개의 뷰는 하나의 뷰로 통합하여 처리할 수 있는데, 장고에서는 하나의 뷰로 통합하여 처리하는 것을 권장한다.
하나의 뷰에서 2가지 기능을 처리하려면, 처음 사용자에게 보여주는 폼과 사용자가 데이터를 입력한 후 제출된 폼을 구분하여 처리할 수 있어야 한다.
장고에서는 이를 HTTP 메소드로 구분한다.
뷰가 GET 방식으로 요청을 받은 경우에는 사용자에게 처음으로 폼을 보여주도록 처리하고, 뷰가 POST 방식으로 요청을 받은 경우에는 데이터가 담긴 제출된 폼으로 간주하여 처리한다.
from django.shortcuts import render
from django.http import HttpResponseRedirect
def get_name(request):
if request.method == ‘POST’:
form = NameForm(request.POST)
if form.is_valid():
new_name = form.cleaned_data[‘your_name’]
return HttpResponseRedirect(‘/thanks/‘)
else:
form = NameForm()
return render(request, ‘name.html’, {‘form’ : form })
* 4.4.5. 폼 클래스를 템플릿으로 변환
-
폼 클래스를 템플릿으로 변환하기 위해서는 폼 객체를 생성해서 이를 템플릿 시스템에 넘겨주면 된다.
템플릿 문법 및 폼 객체를 해석해서 HTML 템플릿 파일을 만들어 준다.
앞에서 본 {{ form }} 이외에도 3가지 옵션이 더 있다. 감싸는 것은 “각각의 field” 에 대해 적용된다.
{{ form.as_table }} : <tr> 태그로 감싸서 테이블 셀로 렌더링한다.
{{ form.as_p }} : <p> 태그로 감싸도록 렌더링된다.
{{ form.as_ul }} : <li> 태그로 감싸도록 렌더링된다.
<table>, <ul> 태그는 개발자가 직접 추가해야 한다.
-
cf) ContactForm
from django import forms
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField(widget=forms.Textarea)
sender = forms.EmailField()
cc_myself = forms.BooleanField(required=False)
< 4.5. 클래스형 뷰 >
-
뷰는 요청을 받아서 응답을 반환해주는 호출 가능한 객체(callable)이다.
장고에서는 뷰를 함수로 작성할 수도 있고 클래스로도 작성할 수 있다.
-
함수형 뷰보다 클래스형 뷰가 장점이 많다.
클래스형 뷰(Class-based View)를 사용하면 상속과 믹스인 기능을 사용해 코드를 재사용할 수 있고, 뷰를 체계적으로 구성할 수 있다.
제네릭 뷰 역시 클래스형 뷰로 작성되어 있다.
간단한 경우에는 함수형 뷰로 신속하게 개발하는 것도 하나의 방법이지만,
로직이 복잡해지고 프로젝트가 커질수록 클래스형 뷰가 장점을 보여준다.
* 4.5.1. 클래스형 뷰의 시작점
-
클래스형 뷰를 사용하기 위해서는 가장 먼저 URLconf 에서 함수형 뷰가 아니라 클래스형 뷰를 사용한다고 선언해야 한다.
예를 들어 MyView 라는 클래스형 뷰를 사용하면 URLconf 는 다음과 같은 모습이 된다.
# urls.py
from django.conf.urls import patterns
from myapp.views import MyView
urlpatterns = patterns(‘’,
(r’^about/‘, MyView.as_view()),
)
장고의 URL 해석기는 요청과 관련된 파라미터들을 클래스가 아니라 함수에 전달하기 때문에,
클래스형 뷰는 클래스로 진입하기 위한 as_view() 클래스 메소드를 제공한다.
이를 진입 메소드라고 부르기도 한다.
as_view() 진입 메소드의 역할은 클래스의 인스턴스를 생성하고,
그 인스턴스의 dispatch() 메소드를 호출한다.
dispatch() 메소드는 요청을 검사해서 GET, POST 등의 어떤 HTTP 메소드가 요청되었는지를 알아낸 다음,
인스턴스 내에서 해당 이름을 갖는 메소드로 요청을 중계한다.
만일 해당 메소드가 정의되어 있지 않은 경우 HttpResponseNotAllowed exception 을 발생시킨다.
-
클래스형 뷰의 위치는 함수형 뷰와 동일하게 views.py 파일에 코딩하면 된다.
# views.py
from django.http import HttpResponse
from django.views.generic import View
class MyView(view):
def get(self, request):
return HttpResponse(‘result’)
as_view() 메소드는 장고에서 기본적으로 제공해주므로, 개발자가 별도로 정의하지 않아도 된다.
dispatch() 메소드 역시 장고에서 기본적으로 제공해준다.
* 4.5.2. 클래스형 뷰의 장점 - 효율적인 메소드 구분
-
함수형 뷰와 비교되는 클래스형 뷰의 2가지 장점은..
GET, POST 등의 HTTP 메소드에 따른 처리 기능을 코딩할 때, IF 함수를 사용하지 않고 메소드명으로 구분할 수 있어 코드 구조가 깔끔해진다.
다중 상속과 같은 객체 지향 기술이 가능하므로, 클래스형 제네릭 뷰 및 믹스인 클래스 등을 사용할 수 있고, 이는 코드의 재사용성이나 개발 생산성을 획기적으로 높여준다.
함수형 뷰에서 GET 을 처리하려면 아래와 같이 코딩한다.
def my_view(request):
if request.method == ‘GET’:
return HttpResponse(‘result’)
클래스형 뷰로 작성하면 아래와 같다.
class MyView(View):
def get(self, request):
return HttpResponse(‘result’)
* 4.5.3. 클래스형 뷰의 장점 - 상속 기능 가능
-
클래스형 뷰의 대부분은 장고가 제공해주는 제네릭 뷰를 상속받아 작성한다.
제네릭 뷰는 뷰 개발 과정에서 공통적으로 사용할 수 있는 기능들을 추상화하고, 이를 장고에서 미리 만들어 기본으로 제공해주는 것을 말한다.
-
#urls.py
from django.conf.urls import patterns
from some_app.views import AboutView
urlpatterns = pattenrs(‘’,
(r’^about/‘, AboutView.as_view()),
)
#views.py
from django.views.generic import TemplateView
class AboutView(TemplateView):
template_name = “about.html"
위와 같이 하면 TemplateView 가 알아서 about.html 을 연결해준다.
urls.py 에서 다음과 같이 사용해도 된다.
...
(r’^about/‘, TemplateView.as_view(template_name=“about.html”))
...
-
TemplateView 는 뷰에 특별한 로직이 없고, URL 에 맞춰 해당 템플릿 파일의 내용만 보여줄 때 사용하는 제네릭 뷰이다.
* 4.5.4. 클래스형 제네릭 뷰
-
제네릭 뷰는 아래와 같이 4가지로 분류될 수 있다.
Base View : 뷰 클래스를 생성하고, 다른 제네릭 뷰의 부모 클래스를 제공하는 기본 제네릭 뷰.
Generic Display View : 객체의 리스트를 보여주거나, 특정 객체의 상세 정보를 보여준다.
Generic Edit View : 폼을 통해 객체를 생성, 수정, 삭제하는 기능을 제공한다.
Generic Date View : 날짜 기반 객체의 년/월/일 페이지로 구분해서 보여준다.
-
Base View
View : 가장 기본이 되는 최상위 제네릭 뷰
TemplateView : 템플릿이 주어지면 해당 템플릿을 렌더링해준다.
RedirectView : URL 이 주어지면 해당 URL 로 리다이렉트시켜준다.
Generic Display View
DetailView : 객체 하나에 대한 상세한 정보를 보여준다.
ListView : 조건에 맞는 여러 개의 객체를 보여준다.
Generic Edit View
FormView : 폼이 주어지면 해당 폼을 보여준다.
CreateView : 객체를 생성하는 폼을 보여준다.
UpdateView : 기존 객체를 수정하는 폼을 보여준다.
DeleteView : 기존 객체를 삭제하는 폼을 보여준다.
Generic Date View
YearArchiveView : 년도가 주어지면 그 년도에 해당하는 객체를 보여준다.
MonthArchiveView : 월이 주어지면 그 월에 해당하는 객체를 보여준다.
DayArchiveView : 날짜가 주어지면 그 날짜에 해당하는 객체를 보여준다.
* 4.5.5. 클래스형 뷰에서 폼 처리
-
폼 처리 과정은 아래와 같다.
최초의 GET : 폼은 비어있거나 미리 채워진 데이터를 가짐
유효한 데이터를 가진 POST : 데이터를 처리함, 주로 리다이렉트 처리됨
유효하지 않은 데이터를 가진 POST : 보통은 에러 메시지와 함께 폼이 다시 출력됨.
-
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views.generic import View
from .forms import MyForm
class MyFormView(View):
form_class = MyForm
initial = {‘key’ : ‘value’ }
template_name = ‘form_template.html’
def get(self, request, *args, **kwargs): # 최초의 GET
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {‘form’: form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
# 로직 처리
return HttpResponseRedirect(‘/success/‘)
else:
return render(request, self.template_name, {‘form’ : ‘form}) # 유효하지 않은 데이터를 가진 POST
-
FormView 를 사용하면 위의 코드가 아래의 코드로 바뀔 수 있다.
from .forms import MyForm
from django.views.generic.edit impoty FormView
class MyFormView(FormView):
from_class = MyForm
template_name = ‘form_template.html’
success_url = ‘/thanks/‘
def form_valid(self, form):
# cleaned_data 관련 로직 처리
return super(MyFormView, self).form_valid(form)
form_class : 사용자에 보여줄 폼을 정의한 forms.py 파일 내의 클래스명
template_name : 폼을 포함하여 렌더링할 템플릿 파일 이름
success_url : MyFormView 처리가 정상적으로 완료되었을 때 리다이렉트시킬 URL
form_valid() 함수 : 유효한 폼 데이터로 처리할 로직 코딩, 반드시 super() 함수를 호출해야함.
< 4.6. 로그 남기기 >
-
장고의 로깅은 파이썬의 기본 로깅 체계를 그대로 따르면서 일부만 추가되었다.
파이썬의 로깅 모듈은 로거, 핸들러, 필터, 포맷터 4가지 컴포넌트를 정의하고 있다.
-
장고의 runserver 나 웹 서버에 의해 장고가 실행될 때 장고는 settings.py 파일에 정의된 LOGGING_CONFIG, LOGGING 항목을 참고하여 로깅에 관련된 설정을 처리한다.
settings.py 파일에 관련 항목이 없으면 디폴트 로깅 설정으로 처리된다.
즉 대부분의 경우 항상 로그를 기록하는 것이 가능한 상태이다.
* 4.6.1. 로거
-
Logger 는 로깅 시스템의 시작점으로, 로그 메시지를 처리하기 위해 메시지를 담아두는 저장소이다.
모든 로거는 이름을 가지고 있다.
로거는 로그 레벨을 갖는데, 중요도에 따라 어느 레벨 이상의 메시지를 처리할지에 대한 기준이 된다.
DEBUG
INFO
WARNING
ERROR
CRITICAL
-
로거에 저장되는 메시지를 로그 레코드라고 하며, 로그 레코드 역시 로그 레벨을 갖는다.
로그 레코드는 스택 트레이스 정보나 에러 코드 등의 로그 이벤트에 대한 유용한 메타 정보도 갖고 있다.
-
메시지가 로거에 도착하면, 로그 레코드의 로그 레벨과 로거의 로그 레벨을 비교한다.
로그 레코드의 로그 레벨이 로거 자체의 로그 레벨과 같거나 그보다 높으면 메시지 처리를 계속 진행하고, 낮으면 그 메시지는 무시한다.
처리할 메시지는 로거에서 핸들러로 넘어간다.
* 4.6.2. 핸들러
-
핸들러는 로거에 있는 메시지에 무슨 작업을 할지 결정한다.
메시지를 화면, 파일, 네트워크 소켓 등 어디에 기록할 것인지를 결정하고 동작한다.
핸들러도 로거와 같이 로그레벨을 가지고 있고, 핸들러 로그레벨이 메시지 로그레벨보다 높으면 해당 메시지는 무시한다.
-
로거는 핸들러를 여러 개 가질 수 있고, 각 핸들러는 서로 다른 로그 레벨을 가질 수 있다.
이로써 메시지의 중요도에 따라 다른 방식으로 로그 처리가 가능하다.
* 4.6.3. 필터
-
로그 레코드가 로거에서 핸들러로 넘겨질 때 필터를 사용해 로그 레코드에 추가적인 제어를 할 수 있다.
기본 처리 방식은 로그 레벨을 지정하여 그 로그 레벨에 해당되면 관련 로그 메시지를 처리하는 것이다.
필터를 적용하면 로그 처리 기준을 추가할 수 있다. 예를 들어 ERROR 메시지 중 특정 소스로부터 오는 메시지만 핸들러로 넘길 수 있다.
-
필터를 사용하면 로그 레코드를 보내기 전에 수정하는 것도 가능하다.
필터는 로거 또는 핸들러 어디에나 적용이 가능하고, 여러 개의 필터를 체인 방식으로 동작시킬 수 있다.
* 4.6.4. 포맷터
-
로그 레코드는 최종적으로 텍스트로 표현되는데, 포맷터는 텍스트로 표현 시 정확한 포맷을 지정해준다.
포맷터는 보통 파이썬의 포맷 스트링을 사용하지만, custom 을 사용할 수도 있다.
* 4.6.5. 로거 사용 및 로거 이름 계층화
-
로거, 핸들러, 필터, 포맷터 등이 준비되었으면 로그 메시지를 기록하기 위해 코드 내에서 로깅 메소드를 호출하면 된다.
import logging
logger = logging.getLogger(__name__)
def my_view(request, arg1, argN):
if bad_mojo:
logger.error(‘Something went wrong!’)
관행적으로 로거 이름에 __name__ 을 사용하는데, 이는 로거를 담고 있는 파이썬 모듈의 이름을 말한다.
이렇게 하면 로딩 호출을 모듈 단위로 처리할 수 있다.
로그 메시지를 계층화하고 싶으면 로거를 구분하는 이름을 도트(.) 방식으로 명명해주면 된다.
logger = logging.getLogger(‘project.interesting.stuff’)
-
로거 객체는 각 로그 레벨별로 로깅 호출 메소드를 가지고 있다.
logger.debug()
logger.info()
logger.warning()
logger.error()
logger.critical()
logger.log() : 원하는 로그 레벨을 정해서 로그 메시지를 생성
logger.exception() : exception 스택 정보를 포함하는 ERROR 레벨의 로그 메시지 생성
* 4.6.6. 로깅 설정
-
파이썬의 로깅 라이브러리는 다양한 설정 방식을 제공하고 있는데,
장고는 그 중에서 사전형 설정(dictConfig) 방식을 디폴트로 사용한다.
이 방식은 setitng.py 파일의 LOGGING 항목에 로깅 속성을 사전 형식으로 정의하게 된다.
-
LOGGING 속성을 정의하는 경우에 디폴트 로그 설정을 대체할 수도 있고,
디폴트 로그 설정과 병합할 수도 있다.
이는 LOGGING 설정 내용 중 disable_existing_loggers 속성으로 지정할 수 있다.
디폴트는 True 로 장고의 디폴트 로그 설정을 대체하는 것이다.
이 값을 False 로 하면 장고의 디폴트 로그 설정의 전부 또는 일부를 다시 정의할 수 있다.
ex)
LOGGING = {
‘version’ : 1,
‘disable_existing_loggers’: True,
‘formatters’: {
‘simple’: {
‘format’: ‘%(levelname)s %(message)s'
},
‘verbose’: {
‘format’: ‘%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
},
},
‘filters’: {
‘special’: {
‘()’ : ‘project.logging.SpecialFilter’,
‘foo’ : ‘bar’, // Filter 생성될 때 넘기는 param
}
},
‘handler’: {
‘null’ : {
‘level’ : ‘DEBUG’,
‘class’ : ‘logging.NullHandler’, // /dev/null 장치로 보내준다.
},
‘console’ : {
‘level’ : ‘DEBUG’,
‘class’ : ‘logging.StreamHandler’, // 표준 에러로 출력해준다.
‘formatter’ : ‘simple’,
},
‘mail_admins’: {
‘level’ : ‘ERROR’,
‘class’ : ‘django.utils.log.AdminEmailHandler’, // 사이트 관리자에게 이메일로 보내준다.
‘filters’ : [‘special’]
},
},
‘loggers’: {
‘django’ : {
‘handlers’ : ['null’],
‘propagate’ : True,
‘level’ : ‘INFO’,
},
‘django.request’ : {
‘handlers’ : [‘mail_admins’],
‘level’ : ‘ERROR’,
‘propagate’ : False,
},
‘myproject.custom’ : {
‘handlers’ : [‘console’, ‘mail_admins’],
‘level’ : ‘INFO’,
‘filters’ : [‘special’]
}
}
}
* 4.6.7. 장고의 로깅 추가 사항
-
장고에서 추가한 로거들..
django 로거 : 모든 로그 레코드를 잡는 장고의 최상위 루트 로거. 이 루트 로거에 직접 로그 메시지를 보낼 수는 없음.
django.request 로거 : 요청 처리와 관련된 메시지를 기록. 5XX 응답은 ERROR 메시지로, 4XX 응답은 WARNING 메시지로 발생. 이 로거에 담기는 메시지는 2개의 추가적인 메타 항목을 갖는다.
status_code : HTTP 응답 코드
request : 로그 메시지를 생성하는 요청 객체
django.db.backends 로거 : DB 관련된 메시지를 기록한다. 앱에서 사용하는 모든 SQL 문장들이 이 로거에 DEBUG 레벨로 기록된다. 다음의 메타 항목을 갖는다. 성능상의 이유로 SQL 로깅은 settings.DEBUG 속성이 True 인 경우만 활성화된다.
duration : SQL 문장 수행 시간
sql : 실행된 SQL 문장
params : SQL 호출에 사용된 파라미터
django.security.* 로거 : 보안 측면에서 해를 끼칠 수 있는 동작을 실행한 경우에 대한 미시지 기록.
django.db.backends.schema 로거 : 데이터베이스 스키마 변경 시 사용된 SQL 쿼리를 기록한다.
-
장고에서 추가한 핸들러
AdminEmailHandler : 로그 메시지를 이메일로 사이트 관리자에게 보낸다.
-
장고에서 추가한 필터
CallBackFilter : 콜백 함수를 지정해서 필터를 통과하는 모든 메시지에 대해 콜백 함수를 호출한다. 콜백 함수의 리턴값이 False 이면 처리하지 않는다.
RequireDebugFalse : settings.DEBUG 속성이 False 인 경우에만 메시지 처리를 진행한다.
RequireDebugTrue : settings.DEBUG 속성이 True 일 경우에만 메시지 처리를 진행한다.
-
장고의 로깅 디폴트 설정은 아래 2가지가 있다.
django.request 로거에 대한 디폴트 설정 : setting.DEBUG 속성이 False 이면 ERROR 또는 CRITICAL 레벨의 모든 메시지를 AdminEmailHandler 핸들러에게 보내주도록 되어 있다.
django 루트 로거에 대한 디폴트 설정 : django 루트 로거에 오는 모든 메시지는 DEBUG 속성이 True 인 경우 콘솔로 보내지고, DEBUG 속성이 False 인 경우 NullHandler 로 보내서 무시되도록 되어 있다.
'프로그래밍 놀이터 > Script(Python)' 카테고리의 다른 글
[ruby] url 읽어오기 (0) | 2018.04.03 |
---|---|
[Django] Tutorial 목차 (0) | 2017.04.12 |
[Django] 파이썬 웹 프로그래밍 - Django 의 핵심 기능 #1 (0) | 2016.12.13 |
[Django] 파이썬 웹 프로그래밍 - Django 웹 프레임워크 #2 (0) | 2016.12.12 |
[Django] 파이썬 웹 프로그래밍 - Django 웹 프레임워크 #1 (0) | 2016.12.09 |
댓글