이 글은 Django 공식 문서의 Getting Started를 참고하여 정리한 글입니다. ( 링크 )

또한 이전 글과 내용이 이어집니다. ( 링크 )



1. admin 페이지에서 테스트 데이터 추가

이전 글에서 Model을 조작하는 방법에 대해 알아보았습니다.

Django Shell에서 데이터를 조작할 수 있지만, admin 페이지에서 조작하는 방법도 알아보기 위해 admin 페이지로 접속하겠습니다.


admin 페이지에 모델을 추가하기 위해서는 admin.py 파일을 수정해야 합니다.

polls/admin.py

from django.contrib import admin from polls.models import Question, Choice
admin.site.register(Question) admin.site.register(Choice)

admin.site.register() 메서드를 호출하면 admin 페이지에 테이블이 추가됩니다.


이제 Django Server를 실행 한 후, http://127.0.0.1:8000/admin 경로로 접속합니다.

$ python manage.py runserver


그러면 위와 같이 Questions와 Choices 테이블이 추가된 것을 확인할 수 있습니다.


이제 Add 버튼을 클릭하여 테스트를 위한 데이터를 추가하도록 합니다.





2. URLconf 파일 작성

다음으로 View와 Template을 매핑하기 위한 URLconf 파일을 작성할 것입니다.

이 예제에서는 총 3개의 html 파일이 필요하며, html 파일을 응답하기 위한 3개의 URL이 필요합니다.

그리고 POST 방식으로 처리되는 URL이 1개 더 필요합니다.


즉, URL 요청이 오면 알맞은 Template이 응답되어야 하는데 이를 처리한 것이 views.py 파일이며,

View와 URL을 매핑 하는 것이 urls.py 파일 입니다.

mysite/urls.py

from django.contrib import admin from django.urls import path from polls import views
urlpatterns = [     path(
'admin/', admin.site.urls),     path('polls/', views.index, name='index'),     path('polls/<int:question_id>/', views.detail, name='detail'),     path('polls/<int:question_id>/results/', views.results, name='results'),     path('polls/<int:question_id>/vote/', views.vote, name='vote') ]

예를 들어 URL에 127.0.0.1:800/polls/ 요청이 오면, 아직 작성하지 않은 views.index() 함수가 실행되고,

127.0.0.1:8000/2/results/ 요청이 오면 views.results() 함수가 실행되고, 인자로 question_id=2가 전달됩니다.

( question_id는 동적으로 바뀌는 값이며, 이 예제에서는 설문( question )의 id 값입니다. )


이렇게 URL과 컨트롤러인 View를 매핑할 수 있습니다.



그런데 1개의 urls.py 파일에 모든 URL 경로를 작성하는 것은 유지관리 측면에서 좋지 않습니다.
한 프로젝트에는 여러 애플리케이션이 있을 수 있기 때문에 애플리케이션 당 urls.py 파일을 갖는 것이 좋습니다.
따라서 아래와 같이 수정하도록 하겠습니다.
mysite/urls.py

from django.contrib import admin from django.urls import path, include
urlpatterns = [     path(
'admin/', admin.site.urls),     path('polls/', include('polls.urls')) ]


polls/urls.py 파일을 생성하여 아래와 같이 작성

from django.urls import path
from polls 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')
]

이렇게 애플리케이션 마다 urls.py 파일을 만들어서 관리하도록 하고,

프로젝트/urls.py 파일에서는 애플리케이션/urls.py 파일들을 작성하여 정리하면 URL 관리가 쉬워집니다.






3. views.index() 및 index.html 템플릿 작성

이제 첫 번째 URL 매핑에 해당하는 views.index() 메서드와 index.html 템플릿을 작성하도록 하겠습니다.


Template은 애플리케이션 내에서 별도의 디렉터리를 만들어서 관리하는 것이 좋습니다.

polls/templates/poll 디렉터리를 생성한 후, index.html 파일을 생성합니다.


이어서 index.html 파일을 작성하겠습니다.

polls/templates/polls/index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</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> 설문이 없습니다. </p>
{% endif %}
</body>
</html>

{% %}는 Template 시스템 언어로서, Python 문법과는 조금 다릅니다.

때문에 별도의 학습은 여기를 참고해주세요 !


여기서 latest_question_list 변수는 View로부터 받은 Python 객체로서, 설문 리스트가 저장되어 있습니다.



다음으로 views.index()를 작성해보겠습니다.

polls/views.py

from django.shortcuts import render
from polls.models import Question

def index(request):
latest_question_list = Question.objects.all().order_by('-pub_date')[:5]
context = {'latest_question_list': latest_question_list}
return render(request, 'polls/index.html', context)

View 함수를 작성할 때 매개변수 request는 필수적으로 작성해야 합니다.


latest_question_list는 Question 테이블에서 pub_date 칼럼을 역순으로 정렬하여 5개를 조회하여 얻은 리스트입니다.


context는 Template으로 전달 할 데이터들을 Dictionary 형태로 갖습니다.

즉, Template에서는 {% latest_question_list %}를 통해 데이터를 사용할 수 있습니다.

여기서는 latest_question_list 밖에 없으므로 한 속성만 추가되었지만, 여러 변수들을 전달해야 한다면 key:value 쌍으로 추가하면 됩니다.


render() 함수는 Template 파일인 polls/index.html에 context 변수를 적용하여, 사용자에게 보여 줄 최종 index.html 파일을 만들고, 이를 담아서 HttpResponse 객체를 반환합니다.


한 가지 주의할 점은 Django가 polls/index.html 파일을 어떻게 찾느냐는 것인데,

Django는 Template 파일을 찾을 때 settings.py 파일의 INSTALLED_APPS 리스트를 참고합니다.



 

이전 글에서 settings.py 파일을 수정할 때 INSATLLED_APPS에 polls를 추가했었습니다.

위의 사진과 같이 INSTALLED_APPS를 작성할 경우, Django는 아래와 같은 순서로 templates 파일을 찾습니다.

    usr/lib/python3.6/site-pakages/django/contrib/admin/templates

-> usr/lib/python3.6/site-pakages/django/contrib/auth/templates

-> …

-> 프로젝트_real_path/polls/templates


따라서 예제의 프로젝트 구조를 보면 index.html 파일의 위치는 mysite/polls/templates/polls/index.html 이므로,

View의 index() 함수에서 렌더링을 할 때 polls/index.html으로 작성해야 렌더링이 잘 이루어질 것입니다.





3. views.detail() 및 detail.html 템플릿 작성

이번에는 설문의 목록을 보여주는 Template과 View를 구현해보겠습니다.


polls/templates/polls/detail.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>{{ question.question_text }}</h1>

{% if error_message %}
<p>{{ error_message }</p>
{% endif %}

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
<label for="choice{{ forloop.counter }}">
{{ choice.choice_text }}
</label><br>
{% endfor %}
<input type="submit" value="Vote">
</form>
</body>
</html>

form의 method는 POST이며, action은 {% url %} 태그로 지정한 경로입니다.


{% url %} 태그에서 polls:vote는 polls/urls.py 파일에서 정의한 namespace이며, question_id에는 question.id 값이 전달 됩니다.

즉 form을 전송하면 polls/2/vote와 같은 형식의 URL이 전송될 것입니다.

urls.py 파일에서 그렇게 정의를 했기 때문이죠.

 

 

 

Django에서는 CSRF 공격을 대비하기 위한 {% csrf_token %} 태그를 제공합니다.


또한 {% for %}에서 question.choice_set.all 라는 변수가 있는데, 이는 이전 글에서 Django Shell을 다룰 때 언급됐던 내용입니다.

choice_set은 Question 테이블과 Choice 테이블이 1 : N 관계를 맺고 있기 때문에, question 모델에 choice_set 속성을 제공한 것입니다.

즉 question.choice_set.all 은 question 모델의 모든 choice 레코드를 반환하는 리스트입니다.



다음으로 views.detail() 메서드를 작성하겠습니다.

polls/views.py

from django.shortcuts import render, get_object_or_404

def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})

urls.py에서 question_id를 받도록 설계를 했기 때문에 detail() 함수에서는 question_id를 인자로 받아야 합니다.


get_object_or_404()는 Question 모델 클래스로부터 pk=question_id과 같은 검색 조건에 따라 객체를 조회하는데, 조건에 맞는 객체가 없을 경우 Http404 익셉션을 발생시키는 함수입니다.





4. views.vote() 및 리다이렉션 작성

detail.html 에서 form으로 전송된 URL을 처리하는 View를 작성해야 합니다.

즉 views.vote() 메서드는 렌더링 할 페이지가 없으므로 redirect를 해야 합니다.

polls/views.py

from polls.models import Question, Choice
from django.http import HttpResponseRedirect
from django.urls import reverse

def vote(request, question_id):
p = get_object_or_404(Question, pk=question_id)
try:
selected_choice = p.choice_set.get(pk=request.POST['choice'])
except(KeyError, Choice.DoesNotExist):
return render(request, 'polls/detail.html',{
'question': p,
'error_message': "답변을 선택하지 않았습니다."
})
else:
selected_choice.votes += 1
selected_choice.save()

return HttpResponseRedirect(reverse('polls:results', args=(p.id,)))

try 안에 있는 request.POST는 제출된 form의 데이터를 담고 있는 객체로서, Dictionary 처럼 값을 가져올 수 있으며, 그 값은 String입니다.

만약, form에 choice라는 데이터가 없으면 KeyError 익셉션이 발생합니다.


익센셥이 발생하면 detail.html 페이지를 render하도록 작성했고, error_message 변수에 에러메시지를 저장합니다.


else에 있는 selected_choice.save()에서 save()는 변경 내용을 DB에 저장하는 함수입니다.


vote() 메서드는 redirect에서 URL을 처리할 때 reverse()를 사용하며, HttpResponse()가 아닌 HttpResponseRedirect()를 호출합니다.

reverse()를 사용하는 이유는, URLconf 모듈은 일반적으로 URL 스트링을 분석하여 뷰 함수를 매핑할 때 사용하는데, reverse() 함수를 사용하면 URL 패턴으로부터 URL 스트링을 구할 수 있습니다.

예를 들어, reverse('polls:results', args=(3,)) 을 호출하면 /polls/3/results를 얻을 수 있습니다.





5. views.results() 및 results.html 템플릿 작성

result는 특별한 것이 바로 작성해보겠습니다.

polls/templates/polls/results.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>{{ question.question_text }}</h1>

<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.hoice_text }} - {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>

<a href="{% url 'polls:detail' question.id %}">Vote again</a>
</body>
</html>

vote{{ choice.votes|pluralize }}는 choice.votes의 개수에 따라 vote를 복수로 나타낼 것인지, 단수로 나타낼 것인지 나타냅니다.



이어서 View를 작성합니다.

polls/views.py

def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})




6. 테스트

이제 테스트를 위해 서버를 실행합니다.

$ python manage.py runserver




이상으로 설문조사 애플리케이션을 통해 Django의 기본을 살펴보았습니다.

공식 문서에 더 많은 세부 사항들이 있으므로 참고하시면 많은 도움이 될 것 같습니다.


[ 참고 ]

https://docs.djangoproject.com/ko/2.0/intro/