Django 튜토리얼 3장~4장(404에러,템플릿,제너릭뷰)

김병욱·2020년 4월 27일
1

Django

목록 보기
4/15

url로 데이터 받기

* polls/urls.py

from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
    # url뒤에 int형으로 question_id가 오면 views의 detail함수를 실행한다.
    path('<int:question_id>/results/', views.results, name='results'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
]
* polls/views.py

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))
	# latest_question_list 라는 변수에 Question모델 db중 pub_date가 가장낮은 db를 내림차순 정렬한다. 
    # template 라는 변수에 polls/index.html 을 넣어준다
    # context 라는 변수에 polls/index.html 로 넘길 latest_question_list를 사전형으로 넣어준다.
    # HttpResponse로 template변수를 render(내장함수)로 context와 request를 던져준다

def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)
    # polls/urls.py에 매핑되있는대로 url뒤에 int형 question_id가 오면 해당 HttpResponse를 반환한다
    
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)

템플릿 파일

먼저, polls 앱 아래에 templates 라는 디렉터리를 생성 후, 그아래에 polls 디렉터리를 만들고 html 파일들을 넣어주자. templates 라는 폴더는 앱별로 html파일이 들어가는 디렉터리이다.

* polls/templates/polls/index.html

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

장고 템플릿 파일에서 파이썬 코드 사용법

{% %}는 if조건문이나, while문, for문 등을 사용할때 사용된다.
{{}}는 변수이다.
기본적으로 for문이나 if조건문을 사용시엔 무조건 파이썬문장이 끝났다는 태그, 즉, {%endfor%}{%endif%}로 닫아줘야한다.
장고 템플릿 파일에서 {%if 조건%} 문 사용시, {%else%}{%elif%} 가 같은 조건문 내에 있다면 파이썬문장이 끝났다는 닫는 태그는 {%endif%} 하나면된다.

render()를 사용해 템플릿 렌더링

방금 템플릿에 context 를 채워넣어 표현한 결과는 HttpResponse 객체와 함께 돌려줬다. 하지만 이방법보다 더 쉽게 템플릿을 렌더링할 수 있다. django.shortcuts에 존재하는 render 함수를 사용한다.

* polls/views.py 

from django.shortcuts import render
from .models import Question


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)
    
    # render를 사용할땐 첫번째 인자값은 무조건 request가 되어야한다.context데이터를 polls/index.html로 던져주며 렌더링한다.

404 에러 일으키기

raise Http404() 방식
raise 는 일부러 에러를 발생시킨다.

* polls/views.py

from django.http import Http404
from django.shortcuts import render

from .models import Question
# ...
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
        # Question 모델 db에서 pk값이 url에서 넘어온 question_id와 같은 db를 찾는다
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

get_object_or404() 방식

from django.shortcuts import get_object_or_404, render

from .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})

템플릿에서 polls의 app_name을 이용하기

장고에서 url을 파이썬틱하게 쓰고싶다면. url에 파이썬코드 {% %}를 열어주고, {%url 'app_name:url_name' 넘기고싶은db 나 값%}를 해주면 된다.

app _named은 polls/urls.py에서 정의했었고 , url_name도 polls/urls.py에서 각각의 path마다 name='index'식으로 이미 우리는 작성을 했다. 따라서 다음과 같이 템플릿파일에서 url을 파이썬틱하게 써보자.

* polls/templates/polls/index.html


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

템플릿에서 form을 전송하는 법

기본적으로 장고 템플릿에서 어떠한 데이터가 담긴 form을 method="post" 방식으로 전송할땐, 반드시 <form>태그안에 {%csrf_token%}을 정의해준다. 이는 해당 form을 post방식으로 전송할때 csrf 공격을 방어하는 장고에서 이미 정의된 공격 방어법이며 form을 post방식으로 전송할때는 무조건 써주어야한다.

* polls/templates/polls/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>

위의 코드에서 forloop.counter()는 for문이 얼마나 돌았는지 숫자값을 나타낸다.

csrf 공격이란?

CSRF 공격(Cross Site Request Forgery)은 웹 어플리케이션 취약점 중 하나로, 인터넷 사용자(희생자)가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 만드는 공격

투표 로직 구현하기

* polls/views.py

HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse

from .models import Choice, Question
# ...
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,)))
        
def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/results.html', {'question': question})
        

request.POST[넘어온파라미터명]

request.POST[넘어온파라미터명]은 템플릿파일의 <form>태그에서 post방식으로 넘어온 데이터의 값을 찾는다.

HttpResponseRedirect? Reverse?

return HttpResponseRedirect(reverse('polls:results',args=(question_id,)))

reverse() 함수는 우아한url패턴(urls.py에 <int:pk>와 같은것들)에 redirect(html을거치지않고 바로 로직으로 넘어가는함수)를 걸 수 있다.
reverse('패키지앱이름:넘길함수명',args=(넘길파라메터명,)) 으로 지정한다
마지막의 args = "넘길파라메터" 는 튜플형태라 하나만 보내게 되면 무조건 뒤에 , (콘마) 를 붙혀줘야 한다

results 템플릿 파일에서 템플릿 필터 사용하기

* polls/templates/polls/results.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>

템플릿 필터란?

장고의 템플릿에서 어떠한 파이썬 데이터{{데이터}}를 받을때는 그 데이터를 꾸며주는 템플릿 필터란게 존재한다. | (파이프라인)으로 데이터의 이름과 템플릿 필터를 구분해주며 위의 템플릿파일에서는 {{choice.votes|pluralize}}와 같이 pluralize라는 템플릿 필터를 썼다.
pluralize라는 템플릿 필터의 기능은 데이터가 2개 이상일 시에 복수접미어를 사용하는 기능인데, 쉽게 말해 예를 들어, votes(투표 수)가 1개면 vote, votes(투표 수)가 2개 이상이면 votes 라고 표현한다. (데이터가 한개 밖에 존재하지 않으면 votes의 s가 사라진다)

장고의 템플릿 내장 필터 목록

https://himanmengit.github.io/django/2018/02/23/Built-In-Template-Filter.html

장고의 Generic View(제너릭 뷰)

여태까지 polls/views.py에 투표 로직을 views.py에만 정의해왔다. 하지만 장고에서 기본적으로 제공하는 GenericView(제너릭 뷰)라는 것을 사용하여 views.py에 class를 정의하여 템플릿파일을 렌더링하거나 db를 가지고 놀 수 있다.

django.views 모듈에서 generic을 제공한다.

from django.views import generic

제너릭뷰를 사용하려면 polls/urls.py에서 url로 데이터를 받는 형식을 pk로 바꾸고, 실행할 함수를 재정의 해줘야한다. 기본적으로 제너릭 뷰를 사용할때 urls.py에 호출함수 정의시 해당 views.해당제너릭뷰.as_view()의 형태로 작성해야한다.
예를 들어, IndexView 함수 정의시 urls.py에서 views.IndexView.as_view() 와 같이 작성한다.

* polls/urls.py

from django.urls import path

from . import views

app_name = 'polls'
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
    path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
]
* polls/views.py

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic

from .models import Choice, Question


class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        """Return the last five published questions."""
        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'


def vote(request, question_id):
    #vote는 이전 로직과 같으므로 ...생략
profile
개발스터디

0개의 댓글