'웹 공부 자료' 시리즈애플리케이션 개발하기_View 및 Template

1. 요청에서부터 응답까지의 처리 흐름에 대한 로직 설계

URL과 뷰는 1:1 관계로 (N:1도 가능) 매핑된다.

URL/뷰 매핑을 URLconf라고 하며 urls.py 파일에 작성

URL 패턴뷰 이름뷰가 처리하는 내용
/polls/index()index.html 템플릿을 보여준다.
/polls/5/detail()detail.html 템플릿을 보여준다.
/polls/5/votevote()detail.html에 있는 폼을 POST 방식으로 처리한다.
/polls/5/results/results()results.html 템플릿을 보여준다.
/admin/(장고 기능)Admin 사이트를 보여준다. (장고에서 기본적으로 제공함).

URLconf를 먼저 코딩 -> 뷰 OR 템플릿 코딩

2. URLconf 코딩

urls.py


from django.contrib import admin  
from django.urls import path
from polls import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('polls/', view.index, name='index'),
    path('polls/<int:question_id>/', views.detail, name='detail'),
    path('polls/<int:question_id>/result/', views.results, name='results'),
    path('polls/<int:question_id>/vote/', views.vote, name='vote'),

]

  • URL 패턴 매칭은 위에서 아래로 진행함.

path() 함수 는 route, view 2개의 필수 인자, kwargs, name 2개의 선택 인자를 받는다.

  • route
    • URL 패턴을 표현하는 문자열
    • URL 스트링
  • view
    • URL 스트링이 매칭되면호출되는 뷰 함수
    • HttpRequest 객체와 URL 스트링에서 추출된 항목이 뷰 함수의 인자로 전달된다.
  • kwargs
    • URL 스트링에서 추출된 항목 외에 추가적인 인자를 뷰 함수에 전달
    • 파이썬 딕셔너리 타입으로 인자를 정의
  • name
    • 각 URL 패턴별로 이름을 붙인다.
    • 이 이름은 템플릿 파일에서 많이 사용

요청 URL이 /polls/3/ 이면 path('polls/<int:question_id>/', views.detail, name='detail')이 매칭되고, views.detail(request, question_id=3)처럼 인자가 대입된다.

A. 추가

mysite/settings.py


ROOT_URLCONF = 'mysite.urls'

B. 더 나은 방식

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 . 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'),  
]

URLconf 모듈을 계층적으로 구성하면 변경도 쉽고, 확장도 용이해진다.

나중에 polls 애플리케이션을 재사용하는 경우, 하위 URLconf를 그대로 가져가서 사용할 수 있다.

polls/urls.py 파일에서 사용한 app_name 변수는 URL 패턴의 이름이 충돌나는 것을 방지하기 위한 이름 공간(namespace) 역할을 한다.

3. 뷰 함수 index() 및 템플릿 작성

뷰 함수와 템플릿은 서로에게 영향을 미치기 때문에 보통 같이 작업한다.

A. index.html UI

B. index.html 템플릿 파일

polls/templates/polls


{% 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 %}

  • latest_question_list 객체는 index() 뷰 함수에서 넘겨주는 파라미터
  • question_text를 순서 없는 리스트로 화면에 보여준다 (<ul>, <li> 태그 역할)
    • 각 텍스트에 URL 링크를 연결한다 (<a href> 속성 역할)
  • latest_question_list 객체에 내용이 없다면 "No polls ~" 문장이 화면에 출력된다.
a. 중요한 점

index.html을 작성하면서 필요한 변수가 무엇인지 찾아내야 한다.

뷰 함수에서 context 변수로 정의해서 템플릿으로 넘겨줘야 하기 때문.

index 템플릿에서는 1. 질문으로 사용될 여러 개의 question_text, 2. URL 링크를 연결하기 위해 question.id 가 필요.

이 두 가지 정보가 함께 들어있는 Question 객체를 뷰 함수로부터 넘겨받으면 된다. Question 객체들의 리스트가 들어있는 latest_question_list 변수를 뷰 함수로부터 템플릿 파일로 전달해야 한다.

C. index() 뷰 함수


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)

  • 장고의 단축함수인 render() 함수를 임포트
  • Question 테이블에 액세스하기 위해 polls.models.Question 클래스를 임포트
  • 뷰 함수 정의
    • request 객체뷰 함수의 필수 인자
  • 템플릿에게 넘겨줄 객체의 이름은 latest_question_list
    • latest_question_list 객체는 Question 테이블 객체에서 pub_date 컬럼의역순으로 정렬하여 5개의 최근 Question 객체를 가져와서 만든다.
  • 템플릿에 넘겨주는 방식은 파이썬 딕셔너리 타입
    • 템플릿에 사용될 변수명과 객체를 매핑하는 사전으로 context 변수를 만들어서 render() 함수에 보낸다
  • render() 함수는 템플릿 파일인 polls/index.html에 context 변수를 적용하여 최종 HTML 텍스트를 만들고 이를 담아서 HttpResponse 객체를 반환한다.
  • 클라이언트에게 응답할 데이터인 HttpResponse 객체를 반환한다.
a. 단축함수
  • 웹 프로그램 개발 시 자주 사용되는 기능들
  • 공통적으로 사용되는 기능들을 장고에서는 내장 함수로 제공한다.
  • 이런 함수를 단축함수(shortcut)라고 한다.

render() 함수

  • 템플릿 코드를 로딩한 후에 컨텍스트 변수를 적용하고, 그 결과를 HTTPResponse 객체에 담하 반환하는 작업 수행

4. 뷰 함수 detail() 및 폼 템플릿 작성

A. detail.html UI

B. detail.html 템플릿


<h1>{{ question.question_text }}</h1>  
  
{% if error_message %}<p><strong>{{ error_message }}</strong></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>

  • 폼에 입력된 데이터는 POST 방식으로 보낸다
    • POST는 서버 측의 데이터를 변경하는 경우 사용
    • <form action> 속성에** {% url %} 템플릿 태그** 를 사용하여 받을 곳의 URL을 polls:vote로 지정
      • polls:vote는 URLconf에서 정의한 URL 패턴 이름
    • URL에 대한 이름 공간polls/urls.py 파일의 app_name에 정의됨.
  • CSRF 공격을 대비하기 위해 {% csrf_token %} 템플릿 태그를 사용
  • detail() 뷰 함수에서도 Question 객체를 템플릿으로 넘겨준다.
    • question.choice_set.all
      • Question 객체의 choice_set 속성에 들어있는 항목 모두를 뜻함
  • 라디오 버튼을 선택하면 POST 데이터가 'choice'='3' (choice.id) 형태로 구성되도록 name과 value 속성을 정의
  • forloop.counter 변수
    • for 루프를 실행한 횟수를 담고 있는 템플릿 변수
    • <label for> 속성<input> 속성값이 같아야 서로 바인딩 된다.
  • 전송된 데이터는 vote() 뷰 함수에서 request.POST['choice'] 구문으로 액세스한다.
    • <input>태그의 name과 value 속성값들이, request.POST 사전에 key, value로 사용된다.
a. choice_set

Question과 Choice 테이블의 관계는 1:N 관계이고, 외래키로 연결되어 있다.

1:N 관계에서는 1 테이블에 연결된 N 테이블의 항목들이라는 의미로 XXX_set 속성을 디폴트로 제공한다.

question.choice_set.all()은 Question 테이블의 question 레코드에 연결된 Choice 테이블의 레코드 모두. detail.html은 템플릿 문법상 메소드 호출을 표시하는 ()를 사용하지 않으므로, question.choice_set.all이라 표현

b. detail() 뷰 함수에서 정의해야 할 context 변수

question.text, question.id, question.choice_set 변수들은 question 변수만 정의하면 속성으로 액세스할 수 있다. 따라서 question 컨텍스트 변수 하나만 정의하는 것이 효율적이다.

choice.id, choice.choice_text 변수들도 question.choice_set 변수가 정의되면 액세스할 수 있다.

detail() 뷰 함수에서 정의해야 할 컨텍스트 변수는 question 변수 하나로 충분하다.

C. detail() 뷰 함수


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

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() 함수
    • 첫 번째 인자는 모델 클래스, 두 번쨰 인자부터는 **검색 조건을 여러 개 **사용.
    • Question 모델 클래스로부터 pk=question_id 검색 조건에 맞는 객체를 조회한다.
    • 조건에 맞는 객체가 없으면 Http404 익셉션을 발생시킨다.
  • 뷰 함수 정의
    • request 객체는 필수 인자. 추가적으로 question_id 인자를 받는다.
    • URL 패턴에서 정규표현식으로 추출한 question_id 파라미터가 뷰 함수의 인자로 넘어온다.
  • render() 함수로 HttpResponse 객체를 반환한다.
a. get_~_or_404
  • get_list_or_404() 단축함수
    • filter() 함수를 사용
    • 리스트가 비어 있으면 Http404 익셉션을 발생
  • get_object_or_404()
    • get() 함수 사용

5. 뷰 함수 vote() 및 리다이렉션 작성

A. vote() 뷰 함수


def vote(request, question_id):  
    question = get_object_or_404(Question, pk=question_id)  
    try:  
        selected_choice = question.choice_set.get(pk=request.POST['choice'])  
    except (KeyError, Choice.DoesNotExist):  
        return render(request, 'polls/detail.html',{  
            'question': question,  
 'error_message': "You didn't select a choice.",  
 })  
    else:  
        selected_choice.votes += 1  
 selected_choice.save()  
  
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
        
  • 리다이렉트 기능이 필요하다.
    • HttpResponseRedirect 클래스를 임포트
  • url 처리를 위해 reverse() 함수를 임포트
  • get_object_or_404() 단축함수
    • Choice 테이블 검색
      • 검색 조건은 pk=request.POST['choice']
        • request.POST는 제출된 폼의 데이터를 담고 있는 객체
        • 파이썬의 딕셔너리처럼 키로 값을 구할 수 있다.
        • request.POST['choice']는 폼 데이터\에서 키가 choice 에 해당하는 값인 choice.id를 스트링으로 리턴한다.
  • 폼의 post 데이터에 choice라는 키가 없으면 KeyError 익셉션을 발생시킨다.
    • 검색 조건에 맞는 객체가 없으면 Choice.DoesNotExist 익셉션이 발생한다.
  • 익셉션이 발생하면 render() 함수에 의해 question, error_message 컨텍스트 변수를 detail.html 템플릿을 전달한다.
  • 변경사항을 해당 Choice 테이블에 저장한다.
    • selected_choice.save()
  • 이번 뷰 함수가 반환하는 객체는 HttpResponseRedirect 객체이다.
  • HttpResponseRedirect 클래스의 생성자는 리다이렉트할 타겟 URL을 인자로 받는다.
    • 타겟 URL은 reverse() 함수로 만든다
  • 리다이렉트할 타겟 URL을 담은 HttpResponseRedirect 객체를 반환한다.
a. reverse() 함수

URL 패턴명으로부터 URL 스트링을 구할 수도 있다.

인자로는 URL 패턴의 이름, URL 스트링에 사용될 파라미터. 2개의 인자를 받는다.


reverse('polls:results', args=(question.id,))

6. 뷰 함수 results() 및 템플릿 작성

results() 뷰 함수의 호출과 연계된 URL은 votes() 뷰 함수의 리다이렉트 결과로 받는다.

A. result() 뷰 함수


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

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

B. result.html UI

C. result.html 템플릿


<h1>{{ question.question_text }}</h1>  
  
<ul>  
 {% for choice in question.choice_set.all %}  
        <li>{{ choice.choice_text }} - {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>  
 {% endfor %}  
</ul>  
  
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>

  • vote{{ choice.votes|pluralize }}
    • choice.votes 값에 따라 복수 접미사(s)를 붙여주는 것
    • pluralize는 템플릿 필터

뷰 함수는 reverse() 함수, 템플릿은 {% url %} 태그를 사용하여 URL 스트링을 추출할 수 있다.

참고문헌

김석훈, "Django로 배우는 쉽고 빠른 웹개발 파이썬 웹프로그래밍", 개정판, 4쇄, 한빛미디어, 2020년

#Django #MVT #View #Template

0분전
작성된 댓글이 없습니다. 첫 댓글을 달아보세요!
    댓글을 작성하려면 로그인이 필요합니다.