django는 대표적으로 MVC(MVT) 디자인 패턴을 가지고 있다. model과 database는 보앗으니 controller(Routing - path)와 View(template)를 살펴보자.
뷰는 Django 어플리케이션이 일반적으로 특정 기능과 템플릿을 제공하는 웹페이지의 한 종류입니다. 예를 들어, 블로그 어플리케이션의 경우 다음과 같은 뷰를 가질 수 있습니다.
장고에서 web pages and other content 들은 views에 의해 전달된다. Each view is represented by a Python function (or method, in the case of class-based views). Django will choose a view by examining the URL that’s requested (to be precise, the part of the URL after the domain name).
url pattern은 /newsarchive/<year>/<month>/
와 같은 형식을 가질 수 있다. URL로부터 뷰를 얻기 위해, Django는 〈URLconfs’라는 것을 사용합니다. URLconf는 URL 패턴을 뷰에 연결합니다. 정확하게는 url dispatcher의 역할을 알아야한다. 해당 내용은 1장에서도 다루고 있다.
# /mysite/polls/views.py
from django.shortcuts import render
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
# ============================================================ #
# /mysite/polls/urls.py
from django.urls import path
from . import views
urlpatterns = [
# ex: /polls/
path('', views.index, name='index'),
# ex: /polls/5/
path('<int:question_id>/', views.detail, name='detail'),
# ex: /polls/5/results/
path('<int:question_id>/results/', views.results, name='results'),
# ex: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
]
node - express에서 routes를 비유하면 가장 적절할 듯 싶다. java servlet에서는 서블릿의 @WebServlet("/URL") 어노테이션을 사용한 매핑, web.xml을 통한 매핑 / spring 진영에서는 dispatcher가 인식할 수 있도록 주로 controller 단에서 url 매핑해준다. 이런 다른 방식에 비해 node - express 진영과 같이 정말 관리하기 쉽다! 매핑하기도 쉽다! 고 느껴진다.
server를 러닝해보고 매핑한 url 접속해보면 아래와 같이 뜨면 성공
브라우저에 《/polls/34/》 를 입력해 보세요. 이 주소에 접속하면 detail() 함수를 호출하여 URL 에 입력한 ID 를 출력할 것입니다. 《/polls/34/results/》 와 《/polls/34/vote/》 도 실행해 보세요. 투표 결과와 투표 페이지의 뼈대가 되는 페이지가 출력될 것입니다.
사용자가 웹사이트의 페이지를 요청할 때, 예로 《/polls/34/》를 요청했다고 하면, Django는 mysite.urls 파이썬 모듈을 불러오게 됩니다. ROOT_URLCONF 설정에 의해 해당 모듈을 바라보도록 지정되어 있기 때문입니다. mysite.urls에서 urlpatterns라는 변수를 찾고, 순서대로 패턴을 따라갑니다. 'polls/'를 찾은 후엔, 일치하는 텍스트("polls/")를 버리고, 남은 텍스트인 "34/"를 〈polls.urls〉 URLconf로 전달하여 남은 처리를 진행합니다. 거기에 '<int:question_id>/'와 일치하여, 결과적으로 detail() 뷰 함수가 호출됩니다.
detail(request=<HttpRequest object>, question_id=34)
: 문자열의 question_id 부분은 일치하는 패턴을 식별하는 데 사용할 이름을 정의하며 int 부분은 URL 경로의 이 부분과 일치해야 하는 패턴을 결정하는 변환기입니다. 콜론(:)은 변환기와 패턴 이름을 구분합니다.
각 뷰는 기본적으로 두 가지 중 하나를 하도록 되어 있습니다. 요청된 페이지의 내용이 담긴 HttpResponse 객체를 반환하거나, 혹은 Http404 같은 예외를 발생하게 해야합니다. 나머지는 당신에게 달렸습니다.
당신이 작성한 뷰는 데이터베이스의 레코드를 읽을 수도 있습니다. 또한 뷰는 Django나 Python에서 서드파티로 제공되는 템플릿 시스템을 사용할 수도 있습니다. 뷰는 PDF를 생성하거나, XML을 출력하거나, 실시간으로 ZIP 파일을 만들 수 있습니다. 뷰는 당신이 원하는 무엇이든, Python의 어떤 라이브러리라도 사용할 수 있습니다. Django에 필요한 것은 HttpResponse 객체 혹은 예외입니다. => restapi 형태라면 HttpsResponse Data 형태가 DOC(html등의)이 아니라 그저 json type이 되는 것일 뿐, return object는 HttpsResponse기반이라는 점 잊지말기.
왜냐면, 그렇게 다루는게 편리하기 때문입니다. 튜토리얼 2장의 예제에서 다룬 Django 자체 데이터베이스 API를 사용해봅시다. 새로운 index() 뷰 하나를 호출했을 때, 시스템에 저장된 최소한 5 개의 투표 질문이 콤마로 분리되어, 발행일에 따라 출력됩니다.
# /mysite/polls/views.py
from django.shortcuts import render
from django.http import HttpResponse
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
output = ', '.join([q.question_text for q in latest_question_list])
return HttpResponse(output)
def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
여기 몇가지 문제가 있습니다. 뷰에서 페이지의 디자인이 하드코딩 되어 있다고 합시다. 만약 페이지가 보여지는 방식을 바꾸고 싶다면, 이 Python 코드를 편집해야만 할 겁니다. 그럼, 뷰에서 사용할 수 있는 템플릿을 작성하여, Python 코드로부터 디자인을 분리하도록 Django의 템플릿 시스템을 사용해 봅시다. => 이 소리는 HttpResponse가 그냥 str덩어리로 <html><body>HttpResponse로 html, DOC파일 드립니다.</body></html>
로 되어 있을 가능성을 말한다. 물론 이게 불가능하고 무조건 틀렸다는 소리가 아니다. 하지만 우리는 '지성인'이다.
우선, polls 디렉토리에 templates라는 디렉토리를 만듭니다. Django는 여기서 템플릿을 찾게 될 것입니다. 그리고 index.html을 추가해줍시다.
<!-- polls/templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
</body>
</html>
프로젝트의 TEMPLATES 설정은 Django가 어떻게 템플릿을 불러오고 렌더링 할 것인지 기술합니다. 기본 설정 파일은 APP_DIRS 옵션이 True로 설정된 DjangoTemplates 백엔드를 구성합니다. 관례에 따라, DjangoTemplates은 각 INSTALLED_APPS 디렉토리의 《templates》 하위 디렉토리를 탐색합니다. => mysite project를 생성하고 해당 디렉토리 (즉 polls APP이 속한 mysite 프로젝트)에서 settings에서는 TEMPLATES = [..] 를 볼 수 있다. 해당 값에서 APP_DIRS = True 값인 것을 명시하고 있는 것이다.
그리고 템플릿 파일에 {% %}는 python이 data를 SSR(server-side-rendering)할 수 있도록 하는 python django template 구문이다.
사실 공식문서에서는 polls/templates에 바로 index를 만드는 것이 아닌, 이름 혼동 및 충돌을 방지하기 위해 polls/templates/polls/index.html을 추천하고 있다. 즉 App폴더/templates/App명/템플릿파일 형태를 추천한다. => 그래서 그냥 offical하게 추천하는 방식으로 하자! 그리고 views를 아래와 같이 다시 바꿔준다.
# mysite/polls/views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.template import loader
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = {
'latest_question_list': latest_question_list,
}
return HttpResponse(template.render(context, request))
# return HttpResponse(template.render(context, request)) 를
# 더 짧고 쉽게 쓰기 위해 우린 render함수만 따로 import해서 사용가능하다.
# 아래와 같이 index 함수를 바꿀 수 있다.
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
context = {'latest_question_list': latest_question_list}
return render(request, 'polls/index.html', context)
def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
모든 뷰에 적용한다면, 더 이상 loader와 HttpResponse를 임포트하지 않아도 됩니다. (만약 detail, results, vote에서 stub 메소드를 가지고 있다면, HttpResponse를 유지해야 할 것입니다.)
render() 함수는 request 객체를 첫번째 인수로 받고, 템플릿 이름을 두번째 인수로 받으며, context 사전형 객체를 세전째 선택적(optional) 인수로 받습니다. 인수로 지정된 context로 표현된 템플릿의 HttpResponse 객체가 반환됩니다.
# mysite/polls/views.py
from django.http import HttpResponse, Http404
... # 중략
def detail(request, question_id):
try:
question = Question.objects.get(pk=question_id)
except Question.DoesNotExist:
raise Http404("Question does not exist")
return render(request, 'polls/detail.html', {'question': question})
... # 중략
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
{{ question }}
</body>
</html>
# mysite/polls/views.py
from django.shortcuts import get_object_or_404, render
... # 중략
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
... # 중략
get_object_or_404() 함수는 Django 모델을 첫번째 인자로 받고, 몇개의 키워드 인수를 모델 관리자의 get() 함수에 넘깁니다. 만약 객체가 존재하지 않을 경우, Http404 예외가 발생합니다.
상위 계층에서 ObjectDoesNotExist 예외를 자동으로 잡아 내는 대신 get_object_or_404() 도움 함수(helper functoin)를 사용하거나, ObjectDoesNotExist 예외를 사용하는 대신 Http404 를 사용하는 이유는 무엇일까요?
왜냐하면, 모델 계층을 뷰 계층에 연결하는 방법이기 때문입니다. Django의 중요한 설계 목표는, 약결합(loose coupling) 을 관리하는 데에 있습니다. 일부 제어된 결합이 django.shortcuts 모듈에서 도입되었습니다.
또한, get_object_or_404() 함수처럼 동작하는 get_list_or_404() 함수가 있습니다. get() 대신 filter() 를 쓴다는 것이 다릅니다. 리스트가 비어있을 경우, Http404 예외를 발생시킵니다. 차이점 예제 보기
<h4>
Name : {{ name }}
Type : {{ vip.key }}
</h4>
{% if count > 0 %}
Data Count = {{ count }}
{% else %}
No Data
{% endif %}
{% for item in dataList %}
<li>{{ item.name }}</li>
{% endfor %}
{% csrf_token %}
템플릿 필터는 변수의 값을 특정한 포맷으로 변형하는 기능을 한다. 예를 들어, 날짜를 특정 날짜 포맷으로 변경하거나 문자열을 대소문자로 변경하는 일등을 할 수 있다.
날짜 포맷 지정
{{ createDate|date:"Y-m-d" }}
소문자로 변경
{{ lastName|lower }}
{# 1 라인 코멘트 #}
{% comment %}
<div>
<p>
불필요한 블럭
</p>
<span></span>
</div>
{% endcomment %}
HTML 내용 중에 <, >, ', ", & 등과 같은 문자들이 있을 경우 이를 그 문자에 상응하는 HTML Entity로 변환해 주어야 하는데, Django 템플릿에서 이러한 작업을 자동으로 처리해 주기 위해 {% autoescape on %} 템플릿 태그나 escape 라는 필터를 사용한다.
예를 들어, 아래 예제에서 content 라는 변수에 인용부호가 들어 있다고 했을 때, 아래와 같이 autoescape 태그나 escape 필터를 사용해서 자동으로 변환하게 할 수 있다. 만약 이러한 변환을 하지 않으면 HTML이 중간에 깨지게 된다.
{% autoescape on %} # autoescape 태그
{{ content }}
{% endautoescape %}
{{ content|escape }} # escape 필터
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
</body>
</html>
템플릿 시스템은 변수의 속성에 접근하기 위해 점-탐색(dot-lookup) 문법을 사용합니다. 예제의 {{ question.question_text }} 구문을 보면, Django는 먼저 question 객체에 대해 사전형으로 탐색합니다. 탐색에 실패하게 되면 속성값으로 탐색합니다. (이 예에서는 속성값에서 탐색이 완료됩니다만) 만약 속성 탐색에도 실패한다면 리스트의 인덱스 탐색을 시도하게 됩니다.
{% for %} 반복 구문에서 메소드 호출이 일어납니다. question.choice_set.all은 Python에서 question.choice_set.all() 코드로 해석되는데, 이때 반환된 Choice 객체의 반복자는 {% for %}에서 사용하기 적당합니다.
polls/index.html 템플릿에 링크를 적으면, 이 링크는 다음과 같이 부분적으로 하드코딩된다는 것을 기억하세요. => 앵커태그의 하이퍼링크에 static한 문자를 때려박으면, 수정할때 다 바꿔야함
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
이러한 강력하게 결합되고 하드코딩된 접근방식의 문제는 수 많은 템플릿을 가진 프로젝트들의 URL을 바꾸는 게 어려운 일이 된다는 점입니다. 그러나, polls.urls 모듈의 path() 함수에서 인수의 이름을 정의했으므로, {% url %} template 태그를 사용하여 url 설정에 정의된 특정한 URL 경로들의 의존성을 제거할 수 있습니다.
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
이것이 polls.urls 모듈에 서술된 URL 의 정의를 탐색하는 식으로 동작합니다. 다음과 같이 〈detail〉 이라는 이름의 URL 이 어떻게 정의되어 있는지 확인할 수 있습니다. => 아래 코드에서 차이를 보자!
...
# the 'name' value as called by the {% url %} template tag
path('<int:question_id>/', views.detail, name='detail'),
...
# 만약 상세 뷰의 URL을 polls/specifics/12/로 바꾸고 싶다면,
# 템플릿에서 바꾸는 것이 아니라 polls/urls.py에서 바꿔야 합니다.
...
# added the word 'specifics'
path('specifics/<int:question_id>/', views.detail, name='detail'),
...
튜토리얼의 프로젝트는 polls라는 앱 하나만 가지고 진행했습니다. 실제 Django 프로젝트는 앱이 몇개라도 올 수 있습니다. Django는 이 앱들의 URL을 어떻게 구별해 낼까요? 예를 들어, polls 앱은 detail이라는 뷰를 가지고 있고, 동일한 프로젝트에 블로그를 위한 앱이 있을 수도 있습니다. Django가 {% url %} 템플릿태그를 사용할 때, 어떤 앱의 뷰에서 URL을 생성할지 알 수 있을까요?
정답은 URLconf에 이름공간(namespace)을 추가하는 것입니다. polls/urls.py 파일에 app_name을 추가하여 어플리케이션의 이름공간을 설정할 수 있습니다.
# polls/urls.py
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/results/', views.results, name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>