위 글은 점프 투 장고를 참고해 작성하였습니다.
이번 포스팅에서는 pybo 앱의 핵심기능인 질문 목록과 질문 상세 기능을 구현할 것이다.
질문 목록, 질문 상세
- 질문 목록: 등록한 질문들을 게시물 목록으로 조회하는 기능
- 질문 상세: 게시물 목록 중 한 건의 데이터를 상세하게 조회하는 기능
현재 '안녕하세요 pybo에 오신것을 환영합니다.'라는 문구가 나오는 http://localhost:8000/pybo/
페이지를 요청 시 등록한 질문들을 조회할 수 있도록 pybo/views.py
파일을 수정할 것이다.
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)
질문 목록 데이터(Question_list)는 Question.objects.order_by('-create_date')
로 얻어옴. order_by
는 조회결과를 정렬하는 함수로, order_by('-create_date')
는 create-date 속성을 기준으로 역순 정렬이라는 의미 !(-가 앞에 붙으면 역순 정렬임)
render 함수는 파이썬 데이터를 템플릿에 적용해 HTML로 반환하는 함수
- render() 함수는 request 객체를 첫번째 인수로 받고, 템플릿 이름을 두번째 인수로 받으며, context 사전형 객체를 세번째 선택적(optional) 인수로 받습니다. 인수로 지정된 context로 표현된 템플릿의 HttpResponse 객체가 반환됩니다. (장고 공식문서 참고)
- 템플릿 파일은 HTML 파일과 비슷하지만 파이썬 데이터를 읽어서 사용가능한 HTML 파일
이제 render 함수에서 사용한 pybo/question-list.html
템플릿 파일을 작성해야 함
템플릿 파일을 작성하기 전 템플릿 파일을 저장할 디렉터리를 config/settings.py
파일의 TEMPLATES 항목에 설정해야 함
...
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
...
DIRS
는 템플릿 디렉터리를 여러개 등록할 수 있도록 리스트로 되어있고, 파이보는 BASE_DIR / 'templates
디렉터리 한 개만 등록함
Base_DIR /'templates'
에서 BASE_DIR(=앱 생성 위치)은 projects/mysite
이므로 추가한 디렉터리의 전체 경로는 users/workspace/projects/mysite/templates
이다.
(mysite) gyu@DESKTOP-4OUGKIK:~/workspace/projects/mysite$ mkdir templates
(mysite) gyu@DESKTOP-4OUGKIK:~/workspace/projects/mysite$
mkdir templates
를 통해 templates
디렉터리를 생성
템플릿 = 화면을 구성하는 파일(안에 내용을 작성하면 화면에 표시됨)
template 파일 위치
- 위에서 만든 mysite의 templates 파일은 여러 앱이 사용하는 공통 템플릿
- 따라서 pybo 앱만의 templates 디렉터리를 생성해야함
projects/mysite/pybo/templates
이 위치에 생성해도 되지만, 이 경우 하나의 웹사이트에서 여러 앱을 사용할때 여러 템플릿이 여러 앱 디렉터리에 나누어져 있어 관리에 불편하다.- 따라서 템플릿 파일들은 한 디렉터리에 모아 관리하는 것이 여러 모로 좋다 !
- 여러 앱이 공통으로 사용할 템플릿 디렉터리 -
projects/mysite/templates
- pybo 앱이 사용할 템플릿 디렉터리 -
projects/mysite/templates/pybo
- common 앱이 사용할 템플릿 디렉터리 -
projects/mysite/templates/common
위에 render 함수에서 사용한 템플릿 파일명은 pybo/question_list.html
따라서 question_list.html
파일은 projects/mysite/templates/pybo/question_list.html
경로에 생성해야 함
그리고 question_list.html
파일 안 내용을 아래와 같이 작성
{% if question_list %}
<ul>
{% for question in question_list %}
<li><a href="/pybo/{{ question.id }}/">{{ question.subject }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>질문이 없습니다.</p>
{% endif %}
{% 이나 %}로 둘러쌓인 문장들: 템플릿 태그라 함
태그 | 설명 |
---|---|
{% if question_list %} | question_list가 있다면 |
{% for question in question_list %} | question_list를 순회하며 순차적으로 하나씩 question에 대입 |
{{ question.id }} | for문에 의해 대입된 question 객체의 id 번호를 출력 |
{{ question.subject }} | for문에 의해 대입된 question 객체의 제목을 출력 |
템플릿에서 사용한 question_list는 render 함수로 전달한 "질문 목록" 데이터(아래 사진 참고)
장고에서 사용하는 템플릿 태그는 다음과 같이 3가지 유형
분기문 태그의 사용법은 다음과 같다.
{% if 조건문1 %}
<p>조건문1에 해당되는 경우</p>
{% elif 조건문2 %}
<p>조건문2에 해당되는 경우</p>
{% else %}
<p>조건문1, 2에 모두 해당되지 않는 경우</p>
{% endif %}
{% else %}
태그로 닫아주어야 함반복문 태그의 사용법은 다음과 같다.
{% for item in list %}
<p>순서: {{ forloop.counter }} </p>
<p>{{ item }}</p>
{% endfor %}
파이썬의 for문과 비슷함. 다만 항상 {% endfor %}
태그로 닫아주어야 함
템플릿 for문 안에서는 다음과 같은 forloop
객체를 사용 가능
forloop 속성 | 설명 |
---|---|
forloop.counter | 루프내의 순서로 1부터 표시 |
forloop.counter0 | 루프내의 순서로 0부터 표시 |
forloop.first | 루프의 첫번째 순서인 경우 True |
forloop.last | 루프의 마지막 순서인 경우 True |
객체를 출력하기 위한 태그의 사용법은 다음과 같다.
{{ 객체 }}
ex) {{ item }}
객체에 속성이 있는 경우 파이썬과 동일한 방법으로 도트(.)문자를 이용해 표시
{{ 객체.속성 }}
ex) {{question.id}}, {{question.subject}}
템플릿 문법에 대한 자세한 내용은 다음 URL을 참고
위의 question_list.html
까지 수정하고 http://localhost:8000/pybo/
페이지를 입력한 뒤, python manage.py runserver
를 통해 장고 개발 서버를 구동해보자 !
페이지에서 뜬 질문 목록 중 한 개를 선택하여 클릭해보자
위와 같은 오류가 표시됨. 이유는 http://localhost:8000/pybo/3/
과 같은 페이지에 대한 URL매핑이 아직 없기 때문 !
질문 목록 화면에서 링크를 클릭하여 요청한 질문 상세 URL은 http://localhost:8000/pybo/3/
이는 id 값이 3인 Question을 상세 조회한다는 뜻으로, 이 URL이 동작할 수 있도록 pybo/urls.py
를 다음과 같이 수정
from django.urls import path
from . import views
urlpatterns = [
path('', views.index),
path('<int:question_id>/', views.detail),
]
맨 아래 path('<int:question_id>/', views.detail),
라는 URL 매핑을 추가함
이제 http://localhost:8000/pybo/3/
페이지가 요청되면 위에서 적용한 매핑에 의해서 http://localhost:8000/pybo/<int:question_id>/
가 적용되어 question_id 에 3가 저장되고 views.detail
함수도 실행될 것 !
<int:question_id>
에서 int는 숫자가 매핑됨을 의미함
위 URL 매핑 규칙에 의해 실행되는 views.detail
함수를 만들어야 함
pybo/views.py
를 다음과 같이 수정
...
def detail(request, question_id):
question = Question.objects.get(id=question_id)
context = {'question': question}
return render(request, 'pybo/question_detail.html', context)
전에 만든 index 함수와 크게 다른 부분은 없지만 detail 함수 호출 시 전달되는 매개변수가 request 외에 question_id가 추가됨
여기서 매개변수 question_id는 3(question 모델에 할당되는 PK값)
id=question_id(여기에서는 3)인 question을 Question.object.get
으로 가져와서 question 객체 안에 담음
그리고 render() 함수로 파이썬 데이터를 템플릿에 적용해 HTML로 반환 !
datail함수에서 사용할 'pybo/question_detail.html' 템플릿을 projects/mysite/templates/pybo
안에 생성하고 아래와 같이 작성
<h1>{{ question.subject }}</h1>
<div>
{{ question.content }}
</div>
{{ question.suject }}
과 {{ question.content }}
의 question은 detail 함수에서 템플릿에 context 변수로 전달한 Question 모델 객체question = Question.objects.get(id=question_id)
이 부분 question위의 question_detail.html
까지 수정하고 http://localhost:8000/pybo/
페이지를 입력한 뒤, python manage.py runserver
를 통해 장고 개발 서버를 구동해보자 !
질문을 누르면 질문 상세가 화면에 출력된다 !
이번에는 http://localhost:8000/pybo/30/
페이지를 요청해보자
그러면 DoesNotExist 오류가 발생! 이 오류는 전달된 question_is가 30이므로 위의 detail 함수에서 Question.object.get(id=30)
이 호출되어 발생한 오류임. 이 때 브라우저에 전달되는 오류코드는 500
오류코드 | 설명 |
---|---|
200 | 성공 (OK) |
500 | 서버오류 (Internal Server Error ) |
404 | 서버가 요청한 페이지(Resource)를 찾을 수 없음 (Not Found) |
이렇게 없는 데이터를 요청할 경우 500 오류 보다는 "Not Found (404)" 페이지를 리턴하는 것이 바람직하므로, 500 페이지 대신 404 페이지를 출력하도록 pybo/views.py
의 detail 함수를 수정해보자
from django.shortcuts import render, get_object_or_404
from .models import Question
...
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
context = {'question': question}
return render(request, 'pybo/question_detail.html', context)
Question.objects.get(id=question_id)
를 get_object_or_404(Question, pk=question_id)
로 변경
여기서 사용한 pk는 Question 모델의 기본키(question 모델에서는 question_id)에 해당하는 값을 의미함
get_object_or_404
는 객체를 가져오거나 오류가 발생하면 404 에러를 반환
이렇게 수정하고 다시 http://localhost:8000/pybo/30/
페이지를 요청해보면 500 대신 404 오류 페이지가 출력되는 것을 확인할 수 있다.