6 - 필수적으로 알아야 하는 Django의 핵심 기능들
1) Admin 사이트 꾸미기
admin사이트에서 테이블을 보여주는 UI양식 수정 ==> admin.py 내용 수정
필드 순서 변경
방법
1. admin.py에서 기존 ModelAdmin클래스를 상속받는 새로운 클래스 정의
2. fields = []로 필드 순서 결정
3.새로운 클래스를 admin.site.register()함수의 2번째 인자로 전달

기존 Question 테이블 필드 순서

바뀐 Question 테이블 필드 순서

각 필드를 분리해서 보여주기
방법
1. admin.py에서 기존 ModelAdmin클래스를 상속받는 새로운 클래스 정의
2. fieldsets = []로 필드 분리 ('제목', {'fields': ['필드값'] })
3.새로운 클래스를 admin.site.register()함수의 2번째 인자로 전달

바뀐 Question 테이블 필드

필드 항목 접기(숨기기)
필드 분리방법과 동일 => 튜플에서 'classes': ['collapse']만 추가

바뀐 Question 테이블 필드

외래키 관계 화면
두 개의 테이블이 1:N 관계로 이루어져 있을 때 사용
현재 실습에서 Question과 Choice는 1:N의 관계 ==> 기존에는 Quesiton에 대한 Choice의 작업을 3번 해야했음
해결책 : Question과 Choice를 한 UI에서 동시에 보면 훨씬 편리
방법1 - StackedInline - 세로로 한줄로 보는 방식
1. (N 테이블) - StackedInline 클래스를 상속받는 새로운 클래스를 정의
2. model변수로 해당 테이블 연결, extra변수로 한 번에 보여주는 필드 값 설정
3. (1 테이블) - inlines = [새로운클래스]를 추가하여 연결해줌

이제 Question 테이블의 객체 한개를 클릭하면 다음과 같이 Choice까지 함께 확인 가능
extra = 2이므로, 기존에 있던 Seoul말고 2개 추가로 입력할 수 있는 칸 보여짐

방법2 - TabularInline - 테이블 형식으로 보는 방식
1. (N 테이블) - TabularInline 클래스를 상속받는 새로운 클래스를 정의
2. model변수로 해당 테이블 연결, extra변수로 한 번에 보여주는 필드 값 설정
3. (1 테이블) - inlines = [새로운클래스]를 추가하여 연결해줌

레코드 리스트 제목 컬럼 지정
admin사이트에서 테이블을 클릭하면 해당 테이블의 레코드 리스트들이 출력
해당 레코드 리스트 제목은 models.py에서 지정한 __str__()메소드 리턴값이 디폴트
방법 : list_display = ()를 통해 레코드 제목 변경 가능

변경후 Question테이블 레코드 리스트

레코드 리스트 필터 적용
방법 - list_filter = []를 이용해 admin사이트에서 UI화면 우측에 필터를 적용 가능

레코드 리스트 오른쪽 편에 필터 생성

레코드 리스트 검색 적용
방법 - search_fields = []를 이용해 admin 사이트에서 UI화면에 검색 박스 표시 가능

레코드 리스트 위쪽에 검색 바 생성

2) 파이썬 쉘로 데이터 CRUD
앞에서는 Admin사이트에서 UI를 통해 데이터를 관리했음
쉘을 이용한 데이터 처리는 더 다양하고 복잡한 데이터 관리 가능
테이블 = 레코드의 모음 ==> Django의 ORM은 테이블의 구조를 클래스로 표현 ==> 레코드는 클래스의 객체를 의미
Create - 데이터 생성/입력
1. 테이블의 필드값을 지정 후 객체 생성
2. save() 메소드를 통해 DB에 반영 ==> 이 메소드가 SQL의 INSERT 문장에 해당
ex)
q = Question( question_text="what's new?", pub_date=timezone.now() )
q.save()
Read - 데이터 조회
테이블(클래스) 변수 objects를 이용하여 조회 ( SQL의 SELECT + WHERE 문장에 해당 )
여러개의 레코드(QuerySet) 조회
QuerySet 객체를 얻기위해 objects객체 사용 - 테이블 정보 보유
- 테이블이름.objects.all() = 모든 객체(레코드)를 QuerySet으로 반환
- +) 슬라이싱 문법 사용 가능 = 테이블이름.objects.all()[:5]
- 테이블이름.objects.filter() = 주어진 조건에 맞는 객체(레코드)를 QuerySet으로 반환
- +) 조건_ _startswith="시작" => 조건 뒤에 startswith를 붙이면 해당 문자로 시작하는 모든 레코드 반환
- 테이블이름.objects.exclude() = 주어진 조건을 제외한 나머지 객체(레코드)를 QuerySet으로 반환ex) Question.objects.filter( question_text_startswith = 'what' ).exclude( pub_date_gte=datetime.date.today() ).filter( ... )
- 특징 : 위 3가지 메소드들은 QuerySet을 반환하기에 체인식 호출이 가능
한개의 레코드(객체) 조회
- 테이블이름.objects.get() = 한 개의 요소만 있는 것이 확실할 때 사용 ==> 한 개의 객체만 반환
- 주의! 두 개 이상의 요소가 나오면 에러
update - 데이터 수정
1. 1개 수정 = 객체의 필드값을 수정 후 save()
ex> q.question_text = '수정된값' => q.save()
2. 여러개(QuerySet) 수정 = update() 메소드 이용
ex> Question.objects.filter(pub_date=2007).update(question_text="수정된값")
Delete - 데이터 삭제
delete() 메소드 사용 - QuerySet과
일반객체
에 적용 가능
ex> Question.objects.all().delete()
외래키(Foreign key)로 연결된 1:N관계
테이블(클래스) 변수 choice_set을 이용
ex) Question과 Choice 테이블은 1:N관계
q = Question.objects.get(pk=1)
q.choice\_set.all() #Question 객체 한개에 연결된 모든 Choice객체 조회
p.158쪽 더 참고
3) 템플릿 시스템
Django에서 UI에 해당하는 템플릿 코드는 HTML + 파이썬 코드의 조합
렌더링 과정 : 템플릿 코드를 해석하여 HTML, XML, CSV등의 템플릿 파일로 결과물을 도출해내는 과정
템플릿 변수
형태 : {{ 변수명 }}
특징 : 정의가 되지 않은 변수 사용시 빈 문자열('')로 채워줌 => setiings.py에서 변경 가능
변수 속성에 접근하는 도트(.) 해석 순서 ( ex> foo.bar )
- 사전 타입인지 체크 => 맞다면, foo['bar']로 해석
- foo의 속성 체크 => 맞다면, foo.bar로 해석
- 리스트인지 체크 => 맞다면, foo[bar]로 해석
템플릿 필터
어떤 객체의 처리 결과에 추가적으로 명령을 하는 것
형태 : 파이프(|) 문자를 사용
ex) {{ name|lower }}
종류 : p.161쪽
개인적으로 이 처리들은 백에서 해주는게 맞다고 생각
템플릿 태그
로직을 넣기위해 사용하는 태그
형태 : {% tag %}
종류 :
for 태그 = 리스트 항목들 반복 순회할 때 사용
ex) 시작 태그와 종료 태그가 필요
<ul>
{% for item in itemList %}
<li>{{ item }}</li>
{% endfor %}
</ul>
+) for 태그안에서 사용할 수 있는 여러 유용한 변수들 제공
forloop.counter | 루프 카운트(1부터 시작) |
---|---|
forloop.counter0 | 루프 카운트(0부터 시작) |
forloop.revcounter | 끝에서 부터 루프 카운트(1부터 시작) |
forloop.revcounter0 | 끝에서 부터 루프 카운트(0부터 시작) |
forloop.first | 첫번째 실행이면 True값 |
forloop.last | 마지막 실행이면 True값 |
forloop.parentloop | 중첩 루프에서 바로 상위의 루프를 의미 |
if태그 = 조건이 참이면 문장 표시
{% if test1 %}
test1 : 1
{% elif test2 %}
test2 : 2
{% else %}
test3 : 3
{% endif %}
csrf_token태그 = form태그 안에서 CSRF 공격 방지를 위한 태그
<form action="/" method="post">
{% csrf_token %}
</form>
url태그 = URL 하드 코딩을 방지하기 위한 태그
namespace : urls.py 파일의 include() 함수에서 정의한 이름공간
viewName : urls.py 파일에서 정한 패턴 이름
argN : 뷰함수에서 사용하는 인자들 ( 없어도 됨 )
{% url 'namespace:viewName' arg1 arg2 %}
#/polls/3/vote/
<form action={% url 'polls:vote' question.id %} method="post">
</form>
with태그 = 특정 값이나 객체를 저장해 두는 기능
why? : DB조회처럼 시스템 부하가 큰 동작의 결과를 저장하여 반복 탐색을 줄이기 위하여
{% with 변수명=저장할값 %}
thit is {{ 변수명 }}
{% endwith %}
load태그 = 사용자가 정의한 태그 및 필터를 로딩
아래 문장은 somelibrary.py 파일 및 package/otherlibrary.py 파일에 정의된 사용자 정의 태그 및 필터 로딩
{% load somelibrary package.otherlibrary %}
템플릿 주석
한줄 주석 : {# 주석입니다. #}
여러줄 주석 : 주석블록이름은 생략 가능
{% comment 주석블록이름 %}
주석
{% endcomment %}
HTML 이스케이프
템플릿 코드를 렌더링할때 템플릿 변수에 HTML 태그가 포함되어 있는 경우
XSS(Cross Site Scripting) 공격 : 커뮤니티 같은 곳에서 악성 스크립트가 담긴 글을 올리는 형태의 공격
즉, 사용자로부터 받은 입력값을 제대로 검사하지않고 그대로 사용할 경우 발생
Django는 디폴트로 위와 같은 공격을 방지하기 위한 자동 이스케이프 기능을 제공
< 문자를 | <로 변경 |
---|---|
> 문자를 | >로 변경 |
' 문자를 | '로 변경 |
" 문자를 | "로 변경 |
& 문자를 | &로 변경 |
but, HTML 이스케이프 기능을 비활성화해야하는 경우 필요
ex) HTML 태그를 반영하여 출력하고 싶은 경우 or 이스케이프 문자를 그대로 출력하고 싶은 경우
방법
1. safe 필터 사용
{{ data|safe }}
2. autoescape 태그 사용
{% autoescape off %}
내용
{% endautoescape %}
템플릿 상속
목적 : 템플릿 코드 재사용 => 사이트의 일관성 유지
방법 :
- 부모 템플릿 - block 태그를 사용하여 상속해줄 부분을 지정
- 자식 템플릿 - 가장 상단에 extends 태그를 사용해 상속을 받고, block 태그를 사용하여 상속즉, 채운부분은 오버라이딩이 되고, 안채운부분은 부모 템플릿 코드 사용
- 단, 모든 block을 채울필요는 없음
부모템플릿 - title, sidebar, content 총 3개의 블록을 지정
<!DOCTPYE html>
<html lang="en">
<head>
<link rel="stylesheet href="style.css />
<title>{% block title %}부모 타이틀{% endblock %}</title>
</head>
<body>
<div id="sidebar">
{% block sidebar %}
<ul>
<li>부모 내용1</li>
</ul>
{% endblock %}
</div>
<div id="content">
{% block content %} 부모내용2 {% endblock %}
</div>
</body>
자식 템플릿 - title과 content내용 오버라이딩
{% extends "base.html" %}
{% block title %}자식 타이틀{% endblock %}
{% block content %}
<p>자식 내용2</p>
{% endblock %}
자식 템플릿 코드 렌더링 결과 - 오버라이딩 된 부분 제외한 나머지 부모 템플릿 그대로 사용
<!DOCTPYE html>
<html lang="en">
<head>
<link rel="stylesheet href="style.css />
<title>자식 타이틀</title>
</head>
<body>
<div id="sidebar">
<ul>
<li>부모 내용1</li>
</ul>
</div>
<div id="content">
<p>자식 내용2</p>
</div>
</body>
특징 :
- {{ block.super }} 변수 : 부모 템플릿에 있는 내용 가져오기 => 부모내용을 덧붙여서 확장하는 경우 사용
- {% endblock 블록명 %} : 가독성을 높이기 위해 블록명 기입도 가능
템플릿 상속의 일반적인 사용법 :
- 사이트 전체의 룩앤필을 담고 있는 base.html 생성
- base.html을 상속받는 사이트 하위의 섹션별 스타일 생성
- 2단계 템플릿을 상속받는 개별 페이지 생성
4) 폼 처리하기
HTML 폼
GET : 서버 시스템 상태를 바꾸지 않음 & 전송할 데이터가 많지 않음일 때 사용
보통 검색 창에서 사용
POST : 위의 경우가 아니경우 사용
뷰에서 폼 처리
폼을 처리하는 뷰는 기본적으로 2개가 필요 = 처음으로 폼을 보여주는 뷰 + 폼 데이터를 처리하는 뷰
하지만, Django에서는 하나의 뷰로 통합하여 폼을 처리하는 것을 권장
방법 : request.method의 값으로 구분하여 처리
def test(request):
#POST방식이면 제출된 폼으로 간주
if request.method == 'POST':
#폼 데이터 처리하는 코드
#POST 방식이 아니면 (보통은 GET)
else:
#빈 폼을 보여주는 코드
Django의 폼 클래스
Django에서는 폼 처리를 위해 다음과 같은 2가지 기능 제공
- 폼 생성에 필요한 데이터를 폼 클래스로 구조화하고, 이것을 렌더링하여 HTML 폼 생성
폼 클래스 구조화
from django import forms
class NameForm(forms.Form):
name = forms.CharField(label='your name', max_length=100, widget=forms.Textarea)
#label인자로 label지정 가능
#max_length인자로 필드 최대 길이 지정
#widget인자는 input type과 매핑되며 CharField의 디폴트 위젯은 TextInput
폼 클래스 렌더링 결과
<label for="your_name">Your name: </label>
<input id="your_name" type="text" name="your_name" maxlength="100">
주의! 렌더링 결과에 form 태그나 submit 버튼이 없어서, 개발자가 직접 템플릿에 넣어줘야함
예시
<form action="/test" method="post">
{% csrf_token %}
{{ form }} #뷰에서 폼 클래스 객체를 전달
<input type="submit" />
</form>
+) label과 input 쌍으로 변환시 렌더링할 때 3가지 옵션을 넣을 수 있음
- {{ form.as_table }} : <tr>태그로 감싸서 테이블 셀로 렌더링
- {{ form.as_p }} : <p>태그로 감싸도록 렌더링
- {{ form.as_ul }} : <li>태그로 감싸도록 렌더링
특징 : 여기서도 label과 input 태그 쌍을 감싸는 table과 ul 태그는 개발자가 직접 추가해야함
- 제출된 폼 데이터를 수신하고 처리하기
순서
- request에 담긴 정보로 클래스 폼 생성
- 폼에 담긴 데이터가 유효한지 체크 = form.isvalid()
- 폼 데이터가 유효하면, 데이터는 cleaned_data로 복사됨
def test(request):
#POST 방식이면 데이터가 담긴 제출된 폼
if request.method == 'POST':
form = NameForm(requset.POST) #request에 담긴 정보로 클래스 폼 생성
if form.is_valid(): #폼에 담긴 데이터가 유효한지 체크 = form.isvalid()
new_name = form.cleaned_data['name'] #폼 데이터가 유효하면, 데이터는 cleaned_data로 복사됨
return HTTPResposeRedirect('/')
#POST 방식이 아닌 경우(보통 GET방식)
else:
form = NameForm()
return render(request, 'name.html', {'form': form})
5) 클래스형 뷰
간단한 경우에는 함수형 뷰로 개발하는 것이 좋지만, 로직이 복잡할 수록 클래스형 뷰를 사용하는 것이 좋음
클래스형 뷰의 시작 - URLconf 코딩
from django.conf.urls import patterns
from myapp.views import MyView #MyView라는 클래스형 뷰 사용
urlpatterns = patterns('',
(r'^about/', MyView.as_view(),
)
핵심 :
- .as_view() 클래스(진입) 메소드 = 객체를 생성 -> 객체의 dispatch() 호출, 어떤 HTTP 메소드로 요청되었는지 파악 -> 객체내 해당 이름을 갖는 메소드로 요청 중계 ( 만약 없으면 HttpResponseNotAllowed 익셉션 발생 )
클래스형 뷰 정의 - views.py 코딩
함수형 뷰와 동일하게 views.py 파일에 코딩
from django.views.generic import View
class MyView(View):
def get(self, request):
pass
핵심 :
- View 클래스 = as_view()와 dispatch()같은 메소드가 정의되어있는 클래스 상속
클래스형 뷰의 장점
- HTTP 요청(GET, POST 등..)에 따른 처리기능을 할 때, if를 사용하지 않고 메소드명으로 구분
함수형 뷰에서는 request.method 속성을 체크하는 로직이 항상 필요
클래스형 뷰에서는 HTTP 요청 이름으로 메소드를 정의하면 됨 ( dispatch() 메소드가 매핑해줌 )
단, get(), post(), head() 같이 소문자로 지정해야함
- 객체 지향 기술 사용 가능 ( 상속 ) - Django에서 제공해주는 지네릭 뷰 사용 가능
Django에서 제공하는 지네릭 뷰는 다음과 같이 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 | 일 |
지네릭 뷰 종류
클래스형 뷰에서 폼 처리 - FormView
폼 처리 과정에는 총 3가지가 있다.
- 최초의 GET : 비어있는 폼
- 유효한 데이터를 가진 POST : 데이터를 처리 -> 주로 리다이렉트 처리
- 유효하지 않은 데이터를 가진 POST : 에러 메세지와 함께 폼 다시 출력
함수형 뷰
def myview(request):
if request.method == "POST":
form = MyForm(request.POST)
if form.is_valid(): #유효한 데이터를 가진 POST
#cleaned_data로 관련 로직 처리
return HttpResponseRedirect('/success')
else:
form = MyForm(initial={'key':'value'}
#유효하지 않은 데이터를 가진 POST, 최조의 GET
return render(request, 'form_template.html', {'form': form})
클래스형 뷰
from django.views.generic.edit import FormView
class MyFormView(FormView):
form_class = MyForm
template_name = 'form_template.html'
success_url = '/thanks/'
def form_valid(self, form):
#cleaned_data로 관련 로직 처리
return super(MyFormView, self).form_valid(form)
MyFormView를 사용하면 최초의 GET과 유효하지 않은 데이터를 가진 POST가 이미 정의되어있기 때문에 get(), post() 메소드 정의가 필요 없음
핵심 :
- form_class = 사용자에게 보여줄 폼 클래스명
- template_name = 폼을 포함하고 있는 템플릿 파일 이름
- success_url = 폼 처리가 정상적으로 종료후 리다이렉트할 url
- form_valid() 함수 = 유효한 폼 데이터로 처리할 로직, 반드시 super함수 호출해야함
6) 로그 남기기
p.191