본문 바로가기
SK Shieldus Rookies 19th/인프라 활용을 위한 파이썬

[SK shieldus Rookies 19기][Django] - Django 2

by En_Geon 2024. 3. 23.

1. DTL(Django Template Language)

Django 템플릿 언어

 

  • 변수
    • {{ 변수 }}
  • 필터
    • 변수의 값을 특정 형식으로 변환할 때 사용
    • 변수 다음에 | (파이프)를 넣어서 필터를 명시
    • 필터는 : 문자를 통해 인자를 받을 수 있음
    • {{ text | escape | lineberaks }}
    • {{ text | truncatewords:30 }}
    • {{ text | default:"default value" }} 
    • {{ text | length }}  {{ text | upper }}
  • 태그
    • {% 태그 %}
    • if 문 또는 for 문처럼 흐름을 제어하기 위해 사용
    • {% extends %}와 같이 단독으로 사용하는 템플릿 태그
    • {% if %} {% endif %}처럼 반듯이 닫아줘야 하는 템플릿 태그 
  • 주석
    • 주석 처리할 때 사용 
    • {# 한 줄 주석 #} 
    • {% comment %} 
      여러 줄 주석 
      {% endcomment %} 

 

  • pybo\views.py
def index(request):
    question_list = Question.objects.order_by('-create_date')
    context = { 'question_list' : question_list }
    
    return render(request, 'pybo/question_list.html', context)     # context를 question으로 전달

 

 

  • templates\pybo\question_list.html
{% if question_list %}   # 뷰에서 전달받은 question_list가 있다면
    <ul>
        {% for question in question_list %}                      # 뷰의 context 변수에 정의한 키 이름 
            <li><a href="/pybo/{{ question.id }}/">{{ question.subject }}</a></li>
        {% endfor %}                                                       # 아이디와 제목을 하나씩 출력하는 반복문   
    </ul>
{% else %}
    <p>질문이 없습니다.</p>
{% endif %}

 

 

 

2. 질문 상세 기능 구현

1) pybo/에서 질문 목록 클릭

오류 발생

 

/pybo/숫자/ 형식의 URL 패턴이 정의되어 있지 않아서 질문을 클릭하면 상세 페이지로 넘어가지 못한다.

질문을 클릭했을 때 상세 페이지로 갈 수 있도록 기능을 구현한다.

 

오류 페이지

 

 

2) pybo\urls.py 파일에 패턴 추가

 

from django.urls import path
from . import views         # 현재 패키지에서 views 모듈을 가져옴

urlpatterns = [
    path('', views.index),
    path('<int:question_id>/', views.detail),	# 추가
]

 

 

게시물을 눌렀을 때 숫자마 들어오는데 이 숫자는 고정되어 있는 게 아니다. 변하는 값을 어떻게 넣는지 알 수 있다.

(<타입:받을_변수>, 모듈.함수) 이 구문은 변하는 타입의 값을 받아서 views 모델에 있는 detail 함수로 보내는 구문이다.

 

 

3) pybo\views.py 파일에 detail 메서드 추가

 

from django.shortcuts import render
from .models import Question

def index(request):
    question_list = Question.objects.order_by('-create_date')
    context = { 'question_list' : question_list }
    
    return render(request, 'pybo/question_list.html', context)

def detail(request, question_id) :					# 메서드 추가
    question = Question.objects.get(id=question_id)
    context = { 'question ' : question }

    return render(request, 'pybo/question_detail.html', context)

 

detail 함수는 path에서 받은 question_id를 받는다. 이 id에 대한 question 값을 받아올 수 있다. Question 객체에서 get을 사용하는데 이 get은 하나만 가져올 때 사용한다. 이때 id는 매개변수로 받은 question_id를 사용한다.

 

이 받은 것을 화면으로 출력을 해줘야 하는데 딕셔너리는 사용해 정의한다. 이 값을 화면을 어떻게 출력할 것인지 정해놓은 템플리에 다시 보내주어야 우리가 원하는 방식대로 출력되기 때문에 render 함수를 사용해 question_detail.html로 context 변수를 보내주는 것이다.

 

 

4) templates\pybo\question_detail.html 템플릿 정의

 

<h1>{{ question.subject }}</h1>

<div>
    {{ question.content }}
</div>

 

간단하게 질문의 제목과 내용을 받아서 출력한다.

 

 

5) 브라우저 확인

 

상세 질문

 

위에서 오류 페이지가 나왔지만 다시 잘 나오는 것을 확인할 수 있다.

 

 

 

3. get_object_or_404 메서드 활용

존재하지 않는 데이터를 조회하는 오류 발생

 

존재하지 않는 데이터를 조회하면 오류가 발생하는데 오류 메시지에 시스템(프로그램) 내부 구조 및 로직이 포함되어 출력된다. 

 

공격자는 오류 메시지를 통해 정보를 수집해 추가 공격 계획을 쉽게 할 수 있다.

오류 메시지를 내부 정보가 포함되지 않는 단순한 메시지로 출력하기 위해서는 get_object_or_404 메서드를 활용한다.

 

없는 데이터 오류

 

 

 

1) get 메서드를 get_objcet_or_404 메서드로 대체

모델의 기본키를 이용해서 모델 객체 한 것을 반환

 

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

def index(request):
    question_list = Question.objects.order_by('-create_date')
    context = { 'question_list' : question_list }
    
    return render(request, 'pybo/question_list.html', context)

def detail(request, question_id) :
    # question = Question.objects.get(id=question_id)			# 주석
    question = get_object_or_404(Question, pk=question_id)		# 추가
    context = { 'question' : question }

    return render(request, 'pybo/question_detail.html', context)

 

 

get_object_or_404()는 첫 번째 인자로 모델을 넣어주고, 몇 개의 키워드 인수를 모델 관리자의 get() 함수에 넘긴다.

여기서는 pk=question_id를 넘겼는데 Question 모델에서 question_id를 가져오는 것이다.

 

 

(1) 존재하는 데이터를 조회한 경우

 

상세 페이지

 

 

 

(2) 존재하지 않는 데이터를 조회한 경우

 

오류 페이지

 

 

이전에 존재하지 않은 데이터를 조회했을 때는 자세한 오류 메시지가 출력 됐지만, get_object_or_404를 사용해 내부 정보가 포함되지 않는 단순한 오류 메시지가 출력되도록 수정했다.

 

 

 

4. URL 맵핑 정보에 별칭 사용

1) question_list.html 템플릿

 

<li><a href="/pybo/{{ question.id }}/">{{ question.subject }}</a></li>

 

  • 질문 목록에서 제목(링크)을 클릭했을 때 이동하는 주소

 

주소가 하드 코딩 되어 있어 변경을 일관되고 쉽게 반영하기 어렵다. 이 패턴은 여러 군데 사용될 수 있는데 id가 아니고 name, subject 또는 어떤 무언가가 와서 패턴이 바뀌면 여러 군데 사용한 모든 것을 다 찾아서 수정을 해주어야 한다.

 

URL의 주소 패턴 바뀌더라도 이를 쉽게 반영하기 위해서는 별칭을 사용한다.

 

 

2) pybo\urls.py 파일에 URL 별칭 추가

 

from django.urls import path
from . import views         # 현재 패키지에서 views 모듈을 가져옴

urlpatterns = [
    path('', views.index, name = 'index'),
    path('<int:question_id>/', views.detail, name = 'detail'),
]

 

  • /pybo/ 형식의 주소에 index라는 이름을 부여
  • /pybo/숫자/ 형식의 주소에 detail라는 이름을 부여

 

 

3) templates\pybo\question_list.html 템플릿 파일을 URL 별칭을 사용하도록 수정

 

{% if question_list %}
    <ul>
        {% for question in question_list %}
            {# <li><a href="/pybo/{{ question.id }}/">{{ question.subject }}</a></li> #}
            <li><a href="{% url 'detail' question.id %}">{{ question.subject }}</a></li>
        {% endfor %}
    </ul>
{% else %}
    <p>질문이 없습니다.</p>
{% endif %}

 

'detail'은 별칭이다. 이 detail은 방금 위에서 설정한 형식을 가진다. detail에서 설정한 형식은 pybo/숫자가 나오는 형식인데 이때 받는 숫자는 question_id가 된다. 

 

직접적으로 정의하는 게 아니고 정의된 형식을 urls에서 가져와서 사용하는 것이다.

 

 

4) URL 네임스페스

서로 다른 앱세서 같은 URL 별칭을 사용하면 중복 문제가 발생
네임스페이스(namespace : 각각의 앱이 관리하는 독립된 이름 공간)를 적용해서 해결 가능

 

(1) pybo\urls.py 파일에 네임스페이스 이름을 값으로 가지는 app_name 변수 추가

 

from django.urls import path
from . import views		# 현재 패키지에서 views 모듈을 가져옴

app_name = 'pybo'		# 추가

urlpatterns = [
    path('', views.index, name = 'index'),
    path('<int:question_id>/', views.detail, name = 'detail'),
]

 

여기서 index와 detail은 pybo의 index, detail이다. 그런데 test라는 애플리케이션이 있을 때, test 애플리케이션 안에서 별칭을 위 코드와 똑같이 사용하면 question_list.html 템플릿 url 'detail'에서 pybo의 detail인지 test의 detail인지 알 수가 없다.

 

그래서 이것을 구분하기 위해서 네임스페이스를 사용한다.

 

위 코드만 적용하고 실행하면 오류가 발생한다. 그 이유는 템플릿에 사용된 별칭의 네임스페이스를 정의하지 않았기 때문이다. 

 

오류 페이지

 

 

 

(2) templats\pybo\question_list.html 파일에서 사용하고 있는 별칭의 네임스페이를 지정

 

{% if question_list %}
    <ul>
        {% for question in question_list %}
            {# <li><a href="/pybo/{{ question.id }}/">{{ question.subject }}</a></li> #}
            <li><a href="{% url 'pybo:detail' question.id %}">{{ question.subject }}</a></li>
        {% endfor %}
    </ul>
{% else %}
    <p>질문이 없습니다.</p>
{% endif %}

 

  • pybo:detail로 수정

위 detail은 pybo 네임스페이스 것이다라고 알려주는 것이다.

 

 

5. 답변 저장 및 출력 기능을 추가

1) 질문 상세 페이지(templates\pybo\question_detail.html)에 답변 등록 버튼 추가

 

<h1>{{ question.subject }}</h1>

<div>
    {{ question.content }}
</div>

<form action="{% url 'pybo:answer_create' question_id %}" method="post">  {# 추가 #}
    {% csrf_token %}
    <textarea name="content" id="content" rows="15"></textarea>
    <input type="submit" value="답변 등록"/>
</form>

 

  • form이라는 것은 form 태그 안에 있는 입력창, 선택창 등의 값을 서버로 전달하는 역할을 한다.
  • pybo : answer_create    -  pybo에 새로운 별칭 answer_create 추가

 

사용자의 입력을 서버에 전달하기 위해서는 반드시 fomr 태그를 사용해야 한다. 여기서 "서버에"라는 말은 'action'이 된다.

'action'에는 사용자의 입력이 어느 서버에 어느 페이지에 전달할 것인가를 지정하는 것이다. 이는 바로 URL이라고 한다.

 

지정된 URL 주소로 입력값을 전달하라고 지정한다. 이때 어떤 방식으로 전달할 것인가는 'method'가 된다.

방식에는 전달하는 요청을 서버에서 어떻게 처리할 것인가를 명시하는 것을 말한다. 생성, 조회, 수정, 삭제 등이 될 수 있다.

 

조회할 때는 get 또는 post를 사용한다. get은 주소 뒤에 입력 돼 값이 붙어서 가는 방식이고, post  방식은 주소가 아닌 요청 본문에 그 데이터들이 들어가는 방식이다.

 

사용자가 textarea에 입력한 내용을 서버에 전달한다. 서버로 전달할 때 id, class는 아무런 역할을 하지 않는다.

id와 class는 브라우저 안에서 어떤 태그가 있다면 그 태그를 관리하기 위한 용도로 사용한다. 관리한다는 것은 그것들을 식별해서 조회하고 수정하기 위한 것이다. 또한 JS로 제어를 하거나 CSS 등으로 스타일을 적용할 때 id나 class를 사용한다. 

간단하게 말해서 id와 class는 브라우저 안에서 그 값들을 식별하기 위해서 사용한다.

 

name은  입력되는 값을 서버로 전달할 때 서버에서 그 값을 추출하기 위해 용도로 사용한다. name은 서버로 전달했을 때 서버에서 식별하기 위해서 사용한다. 

 

name과 id의 차이점이 있다는 것을 알아야 한다.

 

'submit'은 submit이 클릭이 되면 form에서 입력된 값을 action에 기술된 주소로 서버로 보내라는 것이다.

 

 csrf_token은 csrf 취약점을 방어하기 위한 기법 중 하나인데, 정상적인 절차에 의해서 요청이 이루어졌다는 것을 보장하는 기능이다.

 

새로운 별칭을 추가했고 URL 맵핑이 정의되지 않았기 때문에 이대로 실행하면 오류가 발생한다. 

 

오류 발생

 

 

2) pybo\urls.py 파일에 URL 맵핑 등록

 

from django.urls import path
from . import views

app_name = 'pybo'

urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name="detail"),
    path('answer/create/<int:question_id>', views.answer_create, name='answer_create'),
]

 

맵핑을 했으면 주소만 있는 것이기 때문에 화면으로 보일 views를 정의해야 한다.

 

 

3) pybo\views.py 파일에 answer_create 메서드 추가

 

from django.shortcuts import render, get_object_or_404, redirect	#추가
from .models import Question
from django.utils import timezone	#추가

def index(request):
    question_list = Question.objects.order_by('-create_date')
    context = { 'question_list' : question_list }
    
    return render(request, 'pybo/question_list.html', context)

def detail(request, question_id) :
    # question = Question.objects.get(id=question_id)
    question = get_object_or_404(Question, pk=question_id)
    context = { 'question' : question }

    return render(request, 'pybo/question_detail.html', context)

def answer_create(request, question_id) :	# 추가
    question = get_object_or_404(Question, pk=question_id)
    question.answer_set.create(content=request.POST.get('content'), create_date=timezone.now())

    return redirect('pybo:detail', question_id=question_id)

 

  • 답변을 추가할 질문을 조회
  • 답변 추가 후 상세 페이지로 리다이렉트

 

POST 방식으로 전달된 요청 객체에서 content라는 이름의 값을 가져오는 것이다. 이 content는 사용자가 입력한 값 중에서 이름이 content인 값을 가져오는 것이다. 

 

content는 위 question_detail.html에 textarea 태그 안에 name = 'content'에 연결되어 있다.

 

redirect는 요청을 다른 요청으로 바꾸는 것이다.

저장 버튼을 누르면 answer_create를 호출하면 DB에 저장을 하고 결과를 내려준다. 이때 결과는 redirect인데 detail 페이지로 다시 요청하라는 뜻으로  detail 페이지로 다시 요청하고 그 결과에 맞춰 화면에 출력해 준다.

 

그런데 이때 DB에 저장 후 detail로 다시 요청해 달라는 리턴을 받고 detail로 다시 요청을 할 때는 사용자가 관여하는 게 아니고 브라우저가 알아서 처리해 준다. 응답 헤더에 있는 로케이션 헤더의 값을 이용해서 자동으로 이루어지는 이 과정을 redirect라고 한다.

 

저장, 수정, 삭제가 되면 서버 사이드의 변경을 클라이언트가 다시 요청을 해야만 볼 수 있다. 웹은 클라이언트가 요청을 하지 않으면 응답을 받을 수 없는 구조이다.

 

 

4)  templates\pybo\queston_detail.html 파일에 답변을 출력하도록 수정

 

<h1>{{ question.subject }}</h1>

<div>
    {{ question.content }}
</div>

{# 답변 내용 출력 추가 #}	
<5>{{ question.answer_set.count }}개의 답변이 있습니다.</5>
<div>
    <ul>
        {% for answer in question.answer_set.all %}
            <li>{{ answer.content }}</li>
        {% endfor %}
    </ul>
</div>

<form action="{% url 'pybo:answer_create' question_id %}" method="post">
    {% csrf_token %}
    <textarea name="content" id="content" rows="15"></textarea>
    <input type="submit" value="답변 등록"/>
</form>

 

 

위에서 함수 set.all을 입력했는데 이전에는 set.all() 괄호를 사용해 주었다. 이것은 장고 템플릿 문법에서는 괄호를 빼고 사용한다. 마치 속성을 그대로 쓰듯이 사용한다.

 

6. 위 5번 기능을 브라우저를통해 확인

 

답변 등록

 

 

 

답변 등록

 

댓글