[파이썬 웹프로그래밍] Chap 5. polls 애플리케이션 - 클래스형 뷰로 변경하기

  • 0
  • 0
0
0

1. URLconf 코딩하기

URL 패턴별 함수형 뷰로 매핑했던 사항을 아래 표처럼 클래스형 뷰로 변경해서 매핑해주면 된다.

URL 패턴기존 뷰 이름(함수형 뷰)새로운 뷰 이름(클래스형 뷰)변경사항(템플릿 파일명은 동일함)
/polls/index()IndexView뷰와 템플릿 모두 변경함(index.html)
/polls/99/detail()DetailView뷰와 템플릿 모두 변경함(detail.html)
/polls/99/results/results()ResultsView뷰와 템플릿 모두 변경함(results.html)
/polls/99/votevote()vote()뷰와 템플릿 모두 변경사항

A. polls/urls.py


from django.urls import path  
# from . import views  
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'), # /polls/ path('', views.IndexView.as_view(), name='index'),  

    # /polls/99/  
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),  

    # /polls/99/results/  
    path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),  

    # /polls/99/vote/  
    path('<int:question_id>/vote/', views.vote, name='vote'),  
]

DetailView 제네릭 뷰의 동작 방식 때문에 URL 패턴의 파라미터 이름이 <pk>로 변경되었다. 테이블의 특정 레코드를 조회하는 경우 Primary Key로 검색한다. (Primary Key를 담을 변수 명을 pk라고 함)

2. View 코딩하기

클래스형 뷰를 코딩할 때 어떤 제네릭 뷰를 사용할지 가장 먼저 고려해야 한다. 애플리케이션의 로직을 분석해보고 가장 적합한 제네릭 뷰를 찾을 수 있어야 한다.

URL 패턴기존 뷰 이름(함수형 뷰)새로운 뷰 이름(클래스형 뷰)변경사항(템플릿 파일명은 동일함)
/polls/index()IndexView질문 리스트를 보여주는 로직, ListView를 사용함 (테이블에서 복수의 레코드를 가져온다.)
/polls/99/detail()DetailView세부 정보를 보여주는 로직, DetailView를 사용함 (테이블에서 특정 한 개의 레코드를 가져온다.)
/polls/99/results/results()ResultsView각 질문에 대한 세부 정보, DetailView를 사용함 (테이블에서 특정 한 개의 레코드를 가져온다.)
/polls/99/votevote()vote()변경사항 없음

A. polls/views.py


# 클래스형 뷰  
  
from django.shortcuts import get_object_or_404, render  
from django.http import HttpResponseRedirect  
from django.urls import reverse  
from django.views import generic  
from polls.models import Choice, Question  
  
#--- Class-based GeneriicView  
class IndexView(generic.ListView):  
    template_name = 'polls/index.html'  
    context_object_name = 'latest_question_list'  
  
    def get_queryset(self):  
        """최근 생성된 질문 5개를 반환함"""  
        return Question.objects.order_by('-pub_date')[:5]  
  
class DetailView(generic.DetailView):  
    model = Question  
    template_name = 'polls/detail.html'  
  
class ResultsView(generic.DetailView):  
    model = Question  
    template_name = 'polls/results.html'  
  
#--- Fundtion-based View  
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()  
        # POST 데이터를 정상적으로 처리하였으면,  
        # 항상 HttpResponseRedirect를 반환하여 리다이렉션 처리함  
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

a. 클래스형 제네릭 뷰

from django.views import generic  

클래스형 제네릭 뷰를 사용하기 위해 generic 모듈을 임포트

b. IndexView 클래스 (ListView 제네릭 뷰)

class IndexView(generic.ListView):  
    template_name = 'polls/index.html'  
    context_object_name = 'latest_question_list'  
  
    def get_queryset(self):  
        """최근 생성된 질문 5개를 반환함"""  
        return Question.objects.order_by('-pub_date')[:5]  

  • Line 1.
    • ListView 제네릭 뷰 사용
    • ListView를 상속받는 경우 객체가 들어있는 리스트를 구성하여 컨텍스트 변수로 템플릿 시스템에 넘겨준다.
      • 리스트를 테이블에 들어있는 모든 레코드를 가져와 구성하는 경우, 테이블명(모델 클래스명)만 지정한다.
      • 그렇지 않은 경우 get_queryset() 메소드를 오버라이딩으로 정의하여 원하는 리스트를 구성한다.
    • 템플릿 파일명컨텍스트 변수명디폴트 값을 사용하거나 명시적으로 지정할 수 있다.
  • Line 3.
    • 컨텍스트 변수명을 latest_question_list로 지정
    • ListView 사용 시 디폴트 컨텍스트 변수명object_list모델명 소문자를 사용한 question_list, 둘 다 가능.
  • Line 4.
    • 처리 대상 객체 리스트를 구성하기 위해 get_queryset() 메소드를 오버라이딩
c. DetailView 클래스 (DetailView 제네릭 뷰)

class DetailView(generic.DetailView):  
    model = Question  
    template_name = 'polls/detail.html'  

  • Line 1.
    • DetailView를 상속받는 경우 특정 객체 하나컨텍스트 변수에 담아서 템플릿 시스템에 넘겨준다.
      • 특정 객체를 테이블에서 Primary Key로 조회해서 가져오는 경우, 테이블명(모델 클래스명)만 지정. ^58d0cd
    • Primary Key 값URLconf에서 pk라는 파라미터 이름으로 넘겨 받는다.
      • DetailView 제네릭 뷰에서 알아서 처리함.
    • 컨텍스트 변수명템플릿 파일명디폴트 값을 사용하거나 명시적으로 지정할 수 있음.
  • Line 2.
    • Question 테이블로부터 특정 레코드를 가져와 컨텍스트 변수를 구성([[Chap 5. polls 애플리케이션 - 클래스형 뷰로 변경하기#^58d0cd|참고]])
    • 컨텍스트 변수명은 디폴트 값을 사용한다.
      • object와 모델명 소문자인 question, 둘 다 가능.
d. ResultsView 클래스 (DetailView 제네릭 뷰)

class ResultsView(generic.DetailView):  
    model = Question  
    template_name = 'polls/results.html'  

  • Line 2.
    • 템플릿 시스템에 넘겨주는 객체는 Choice 객체가 아니라 Question 객체이다.
    • 특정 Question 객체를 구해서 해당 객체와 ForeignKey로 연결된 Choice 객체들을 구하는 로직
      • 템플릿 파일에서 question.choice_set.all() 구문으로 구현

3. Template 코딩하기

상속 기능을 추가한다. 부모 템플릿이 되는 base.html을 코딩하였기 때문에 이를 상속받은 base_polls.html 템플릿 파일을 만들고, 기존 각 템플릿 파일에서 base_polls.html 템플릿을 상속받는다.

A. templates/base_polls.html


{% extends "base.html" %}  
  
<title>{% block title %}Polls Application Site{% endblock %}</title>  
  
{% block sidebar %}  
{{ block.super }}  
<ul>  
    <li><a href="/polls/">Polls_Home</a></li>  
</ul>  
{% endblock %}

B. polls/templates/polls/index.html


<!--클래스형 뷰와 연결되는 템플릿-->  
  
{% extends "base_polls.html" %}  
  
{% block content %}  
  
<h2>Polls Question List</h2>  
{% if latest_question_list %}  
    <ul>  
 {% for question in latest_question_list %}  
            <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>  
 {% endfor %}  
    </ul>  
{% else %}  
    <p>No polls are available.</p>  
{% endif %}  
  
{% endblock content %}  
  
<!--함수형 뷰와 연결되는 템플릿-->  
  
<!--{% 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 %}-->

C. polls/templates/polls/detail.html


<!--클래스형 뷰와 연결되는 템플릿-->  
  
{% extends "base_polls.html" %}  
  
{% block content %}  
  
<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>  
  
{% endblock content %}  
  
<!--함수형 뷰와 연결되는 템플릿-->  
  
<!--<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>-->

D. polls/templates/polls/results.html


<!--클래스형 뷰와 연결되는 템플릿-->  
  
{% extends "base_polls.html" %}  
  
{% block content %}  
  
<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>  
  
{% endblock content %}  
  
<!--함수형 뷰와 연결되는 템플릿-->  
  
<!--<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>-->

4. 로그 추가하기

settings.py 파일에 로깅 설정을 추가해야 함.

A. mysite/settings.py


(...)

STATIC_URL = '/static/'  
  
# Default primary key field type  
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field  
  
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'  
    
LOGGING = {  
    'version': 1,  
    'disable_existing_loggers': False,  
    'formatters': {  
        'verbose': {  
            'format' : "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s",  
            'datefmt' : "%d/%b/%Y %H:%M:%S"  
        },  
    },  
    'handlers': {  
        'file': {  
            'level': 'DEBUG',  
            'class': 'logging.FileHandler',  
            'filename': os.path.join(BASE_DIR, 'logs', 'mysite.log'),  
            'formatter': 'verbose'  
        },  
    },  
    'loggers': {  
        'polls':{  
            'handlers': ['file'],  
            'level': 'DEBUG',  
        },  
    },  
}

django 로거를 오버라이딩하지 않고(장고의 디폴트 설정 그대로) django 로거를 사용하고 있다.

view.py 파일에서 __name__ 변수로 로거를 취득하기 위해 로거 이름을 polls라고 바꿨다.

B. polls/views.py


# 클래스형 뷰  
  
(...)

import logging  
  
logger = logging.getLogger(__name__)  
  
(...)

def vote(request, question_id):  
    logger.debug("vote().question_id: %s" % question_id)  
    question = get_object_or_404(Question, pk=question_id)  

    (...)

  • Line 4.
    • getLogger(__name__) 메소드를 호출하여 polls.views 로거 객체를 취득한다.
    • __name__은 모듈 경로를 담고 있는 파이썬 내장 변수
    • views.py 파일의 모듈 경로는 polls.views (이것이 우리가 사용하고자 하는 로거 객체의 이름)
    • 이 로거에서 생산한 로그 레코드는 상위 polls 로거에게 전파되고, polls 로거에서 메시지를 기록한다.
    • 위 동작을 위해 settings.py 파일에 polls 로거를 설정한 것이다.
  • Line 7.
    • 로거 객체의 debug() 메소드를 호출하여 로거에게 DEBUG 수준으로 로그 레코드를 생성하도록 요청한다.
    • 로거는 로깅 설정에 따라 file 핸들러를 사용하여 로그 메시지를 기록한다.

참고문헌

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

#Django #애플리케이션 #class #URLconf #View #Template #log

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