● 페이징

페이징을 구현하기 전에 페이징을 테스트할 수 있을 정도로 충분한 데이터 생성이 필요하다.
대량의 테스트 데이터를 만드는 가장 좋은 방법은 장고셸을 이용하면 된다.

 

- 테스트 데이터 대량 상성

* 장고 셸 실행
(mysite) c:\projects\mysite>python manage.py shell
>>> from pybo.models import Question
>>> from django.utils import timezone

- 100개 데이터 생성
>>> for i in range(100):
...     q = Question(subject='test data:[%03d]' % i, content='내용무', create_date=timezone.now())
...     q.save() #엔터필수
...
>>>  exit()

**

[%03d]

% : 문자열 포맷팅 연산자

0 : 해당 숫자를 0으로 채우라는 의미

3 : 최소한의 자리수이며, 위 내용은 세자리로 표시됨을 의미

ㄴ 숫자가 세자리 미만이면 앞에 0으로 채워진다.

d : 십진수 정수를 의미

 

** 

100개의 데이터가 한 페이지에 생성된 것을 확인할 수 있다.


● Paginator

페이징을 위해 사용하는 클래스

[파일명: projects\mysite\pybo\views.py]
(.. 생략 ..)
from
.forms import
QuestionForm, AnswerForm
from django.core.paginator import Paginator


def index(request):
    page = request.GET.get('page', '1')  #페이지
    question_list = Question.objects.order_by('-create_date')
    paginator = Paginator(question_list, 10)  # 페이지당 10개씩 보여주기
    page_obj = paginator.get_page(page)
    context = {'question_list': page_obj}
    return render(request, 'pybo/question_list.html', context)

(.. 생략 ..)

**

page = request.GET.get('page', '1')은 http://localhost:8000/pybo/?page=1 처럼

GET 방식으로 호출된 URL에서 page값을 가져올 때 사용한다.

만약 http://localhost:8000/pybo/ 처럼 page값 없이 호출된 경우에는 디폴트로 1이라는 값을 설정

 

paginator를 이용하여 요청된 페이지(page)에 해당되는 페이징 객체(page_obj) 생성했다.

이렇게 하면 장고 내부적으로는 데이터 전체를 조회하지 않고 해당 페이지의 데이터만 조회하도록 쿼리가 변경

 

※ 페이징 객체 page_obj 속성

항목 설명
paginator.count 전체 게시물 개수
paginator.per_page 페이지당 보여줄 게시물 개수
paginator.page_range 페이지 범위
number 현재 페이지 번호
previous_page_number 이전 페이지 번호
next_page_number 다음 페이지 번호
has_previous 이전 페이지 유무
has_next 다음 페이지 유무
start_index 현재 페이지 시작 인덱스(1부터 시작)
end_index 현재 페이지의 끝 인덱스(1부터 시작)
이 속성들은 템플릿에서 페이징을 처리할 때 사용된다.

- 템플릿에 페이지 적용

[파일명: projects\mysite\templates\pybo\question_list.html]
(.. 생략 ..)
    </table>
    <!-- pagin 처리 -->
    <ul class="pagination justify-content-center">
        <!-- 이전페이지 -->
        {% if question_list.has_previous %}
        <li class="page-item">
            <a class="page-link" href="?page={{ question_list.previous_page_number }}">이전</a>
        </li>
        {% else %}
        <li class="page-item disabled">
            <a class="page-link" tabindex="-1" aria-disabled="true" href="#">이전</a>
        </li>
        {% endif %}
        <!-- 페이지 리스트 -->
        {% for page_number in question_list.paginator.page_range %}
        {% if page_number == question_list.number %}
        <li class="page-item active" aria-current="page">
            <a class="page-link" href="?page={{ page_number }}">{{ page_number }}</a>
        </li>
        {% else %}
        <li class="page-item">
            <a class="page-link" href="?page={{ page_number }}">{{ page_number }}</a>
        </li>
        {% endif %}
        {% endfor %}
        <!-- 다음페이지 -->
        {% if question_list.has_next %}
        <li class="page-item">
            <a class="page-link" href="?page={{ question_list.next_page_number }}">다음</a>
        </li>
        {% else %}
        <li class="page-item disabled">
            <a class="page-link" tabindex="-1" aria-disabled="true" href="#">다음</a>
        </li>
        {% endif %}
    </ul>
    <!-- 페이징 처리 끝 -->
    <a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a>
</div>
{% endblock %}

 

※ 템플릿에 사용된 주요 페이징 기능 표

페이징 기능 코드
이전 페이지가 있는지 체크 {% if question_list.has_previous %}
이전 페이지 번호 {{ question_list.previous_page_number }}
다음 페이지가 있는지 체크 {% if question_list.has_next %}
다음 페이지 번호 {{ question_list.next_page_number }}
페이지 리스트 루프 {% for page_number in question_list.paginator.page_range %}
현재 페이지와 같은지 체크 {% if page_number == question_list.number %}

**

pagination, page-item, page-link 등이 부트스트랩 pagination 컴포넌트의 클래스
부트스트랩 pagination - https://getbootstrap.com/docs/5.1/components/pagination/

 

Pagination

Documentation and examples for showing pagination to indicate a series of related content exists across multiple pages.

getbootstrap.com


- 페이지 리스트

[파일명: projects\mysite\templates\pybo\question_list.html]
(... 생략 ...)
<!-- 페이지리스트 -->
{% for page_number in question_list.paginator.page_range %}
{% if page_number >= question_list.number|add:-5 and page_number <= question_list.number|add:5 %}
{% if page_number == question_list.number %}
<li class="page-item active" aria-current="page">
    <a class="page-link" href="?page={{ page_number }}">{{ page_number }}</a>
</li>
{% else %}
<li class="page-item">
    <a class="page-link" href="?page={{ page_number }}">{{ page_number }}</a>
</li>
{% endif %}
{% endif %}
{% endfor %}
(... 생략 ...)

**

|add:-5, |add:5 는 템플릿 필터 // |add:-5는 5만큼 빼라는 의미이고 |add:5는 5만큼 더하라는 의미

페이지 리스트가 현재 페이지 기준으로 좌우 5개씩 보이도록 만든다.

현재 페이지를 의미하는 question_list.number보다 5만큼 크거나 작은 값만 표시되도록 만들었다.

'Web > Django' 카테고리의 다른 글

Django - 내비게이션바  (0) 2024.05.27
Django - 폼  (0) 2024.05.21
Django - 템플릿 상속  (0) 2024.05.18
Django - 부트스트랩  (0) 2024.05.18
Django - 스태틱  (0) 2024.05.18

● 내비게이션바

내비게이션바는 모든 화면 위쪽에 고정되어 있는 부트스트랩 컴포넌트
부트스트랩 내비게이션바: getbootstrap.com/docs/5.1/components/navbar

[파일명: projects\mysite\templates\base.html]
홈, 로그인 등 버튼 상단에 추가
{% load static %}
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- bootstrap -->
    <link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">
    <!-- pybo css-->
    <link rel="stylesheet" type="text/css" href="{% static 'style.css' %}">
    <title>Hello, pybo!</title>
</head>
<body>
<!-- 네비게이션 바 추가-->
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
    <div class="container-fluid">
        <a class="navbar-brand" href="{% url 'pybo:index' %}">Pybo</a>
        <button class="navbar-toggle" type="button"
                data-bs-toggle="collapse"
                data-bs-target="#navbarSupportedContent"
                aria-controls="navbarSupportedContent"
                aria-expanded="false"
                aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                <li class="nav-item">
                    <a class="nav-link" href="#">로그인</a>
                </li>
            </ul>
        </div>
    </div>
</nav>
<!-- 기본 템플릿 안에 산입될 내용 start-->
{% block content %}
{% endblock %}
<!-- 기본 템플릿 안에 삽입될 내용 end -->
</body>
</html>

**

#pybo:index 페이지로 이동해 주는 'Pybo' 로고를 가장 왼쪽에 배치

#오른쪽에는 '로그인' 링크를 추가
#내비게이션바의 'Pybo' 로고를 누르면 메인페이지 이동
#페이지 크기를 줄이면 로그인 버튼이 사라지고 햄버거 메뉴 버튼이 보인다.

 

 

 

[파일명: projects\mysite\templates\base.html] 수정
(... 생략 ...)
{% endblock %}
<!-- 기본 템플릿 안에 삽입될 내용 end -->
<!-- Bootstrap Js -->
<script src="{% static 'bootstrap.min.js' %}"></script> #js 파일 추가
</body>
</html>

 

**

#숨겨진 링크가 표시된다.


● include

include 태그란?

템플릿의 특정 위치에 다른 템플릿을 삽입할 수 있는 태그

템플릿에 특정 영역이 반복적으로 사용될 경우 중복을 없애기 위해 사용한다.

 

[파일명: projects\mysite\templates\navbar.html] 신규 파일 생성
base.html에 포함시킬 navbar.html 템플릿 작성
<!-- 네비게이션바 -->
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
    <div class="container-fluid">
        <a class="navbar-brand" href="{% url 'pybo:index' %}">Pybo</a>
        <button class="navbar-toggler" type="button"
                data-bs-toggle="collapse"
                data-bs-target="#navbarSupportedContent"
                aria-controls="navbarSupportedContent"
                aria-expanded="false"
                aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                <li class="nav-item">
                    <a class="nav-link" href="#">로그인</a>
                </li>
            </ul>
        </div>
    </div>
</nav>

[파일명: projects\mysite\templates\base.html]
(.. 생략 ..)
<body>
<!-- 네비게이션바 -->
{% include "navbar.html" %} # 수정
<!-- 기본 템플릿 안에 삽입될 내용 Start -->
{% block content %}
{% endblock %}
<!-- 기본 템플릿 안에 삽입될 내용 End -->
<!-- Bootstrap JS -->
<script src="{% static 'bootstrap.min.js' %}"></script>
</body>
</html>

**

네비게이션바 HTML 코드들을 삭제하고 navbar.html 템플릿을 include 태그로 포함

'Web > Django' 카테고리의 다른 글

Django - 페이징  (0) 2024.05.27
Django - 폼  (0) 2024.05.21
Django - 템플릿 상속  (0) 2024.05.18
Django - 부트스트랩  (0) 2024.05.18
Django - 스태틱  (0) 2024.05.18

● 폼

- 질문 등록

[파일이름: projects\mysite\templates\pybo\question_list.html]

 

{% extends 'base.html' %}
{% block content %}
<div class="container my-3">
(...생략...)
    </table>
    <a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a> #추가
</div>
{% endblock %}

** <a href="..."> 과 같은 링크이지만 부트스트랩의 btn btn-primary 클래스를 적용하면 버튼 적용
** 버튼을 클릭 시 pybo:question_create 별칭에 해당되는 URL 호출


- URL 매핑

[파일명: projects\mysite\pybo\urls.py]

 

from django.urls import path
from . import views

app_name = 'pybo' #앱의 네임스페이스 부여

urlpatterns = [
    path('',views.index, name='index'), #name속성 부여
    path('<int:question_id>/', views.detail, name='detail'), #name 속성 부여
    path('answer/create/<int:question_id>/', views.answer_create, name='answer_create'),
    path('question/create/', views.question_create, name='question_create'), #질문 추가
]

** views.question_create 함수를 호출하도록 매핑


- 폼(Form)

페이지 요청시 전달되는 파라미터들을 쉽게 관리하기 위해 사용하는 클래스
필수 파라미터의 값이 누락되지 않았는지, 파라미터의 형식은 적절한지 등을 검증할 목적으로 사용
HTML을 자동으로 생성하거나 폼에 연결된 모델을 이용하여 데이터를 저장하는 기능도 있다.

[파일명: projects\mysite\pybo\forms.py] 생성
from django import forms
from pybo.models import Question

class QuestionForm(forms.ModelForm): #forms.ModelForm을 상속받아 사용
    class Meta:
        model = Question #사용할 모델
        fields = ['subject', 'content'] #QuestionForm에서 사용할 Question 모델의 속성

**

QuestionForm은 모델 폼(forms.ModelForm) 상속  
장고의 폼은 일반 폼(forms.Form)과 모델 폼(forms.ModelForm)이 있으며,
모델 폼은 모델(Model)과 연결된 폼으로 폼을 저장하면 연결된 모델의 데이터를 저장할수 있는 폼이다. 

 

모델 폼은 이너 클래스인 Meta 클래스가 반드시 필요하다. 
Meta 클래스에는 사용할 모델과 모델의 속성을 적어야 한다.

즉, QuestionForm은 Question 모델과 연결된 폼이고 

속성으로 Question 모델의 subject와 content를 사용한다고 정의한 것이다.


- 뷰 함수

[파일명: projects\mysite\pybo\views.py]
from django.shortcuts import render, get_object_or_404, redirect
from django.utils import timezone
from .models import Question
from .forms import QuestionForm

(... 생략 ...)

def question_create(request):
    form = QuestionForm()
    return render(request, 'pybo/question_form.html',{'form':form})

** question_create 함수는 위에서 작성한 QuestionForm을 사용
** render 함수에 전달한 {'form': form}은 템플릿에서 질문 등록시 사용할 폼 엘리먼트를 생성할 때 쓰인다.


- 템플릿

[파일명: projects\mysite\templates\pybo\question_form.html] 생성
{% extends 'base.html' %}
{% block content %}
<div class="container">
    <h5 class="my-3 border border-bottom pb-2">질문 등록</h5>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit" class="btn btn-primary">저장하기</button>
    </form>
</div>
{% endblock %}

** {{ form.as_p }}의 form은 question_create 함수에서 전달한 QuestionForm의 객체
** {{ form.as_p }}는 폼에 정의한 subject, content 속성에 해당하는 HTML 코드를 자동으로 생성


- 질문등록 저장하기

[파일명: projects\mysite\pybo\views.py] 수정
def question_create(request):
    if request.method =='POST': #form 사용자가 입력한 내용들이 담긴다.
        form = QuestionForm(request.POST) #정확한 데이터인지 유효성 검사
        if form.is_valid(): #is_valid()는 form의 데이터가 유효한지 검사
            question = form.save(commit=False)
            #form에 지정된 데이터로 Question데이터를 저장하기 위한 코드, commit=False는 임시저장
            question.create_date = timezone.now() #시간 값 저장
            question.save() #전체 데이터가 모이면 저장
            return redirect('pybo:index') #완료되면 인덱스로 이동
    else:
        form = QuestionForm()
    context = {'form':form}
    return render(request, 'pybo/question_form.html', context)

** 

더보기

기억해야할 부분은 동일한 URL 요청을 POST, GET 요청 방식에 따라 다르게 처리한 부분이다.

 

질문 목록 화면에서 "질문 등록하기" 버튼을 클릭한 경우에는 

/pybo/question/create/ 페이지가 GET 방식으로 요청되어 question_create 함수가 실행된다.

 

왜냐하면 <a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a>와 같이

링크를 통해 페이지를 요청할 경우에는 무조건 GET 방식이 사용되기 때문이다.

 

따라서 이 경우에는 request.method 값이 GET이 되어 if .. else .. 구문에서

else 구문을 타게 되어 질문을 등록하는 화면을 랜더링한다.

 

그리고 질문 등록 화면에서 subject, content 항목에 값을 기입하고 "저장하기" 버튼을 누르면

이번에는 /pybo/question/create/ 페이지를 POST 방식으로 요청한다.

 

왜냐하면 앞서 설명했듯이 form 태그에 action 속성이 지정되지 않으면

현재 페이지가 디폴트 action으로 설정되기 때문이다.

 

따라서 질문 등록 화면에서 "저장하기" 버튼을 클릭하면 question_create 함수가 실행되고 

request.method 값은 POST가 되어 다음 코드 블록이 실행될 것이다.

 

    if request.method == 'POST':
        form = QuestionForm(request.POST)
        if form.is_valid():  # 폼이 유효하다면
            question = form.save(commit=False)  # 임시 저장하여 question 객체를 리턴받는다.
            question.create_date = timezone.now()  # 실제 저장을 위해 작성일시를 설정한다.
            question.save()  # 데이터를 실제로 저장한다.
            return redirect('pybo:index')

 

GET 방식에서는 form = QuestionForm() 처럼 QuestionForm을 인수 없이 생성했지만

POST 방식에서는 form = QuestionForm(request.POST) 처럼 request.POST를 인수로 생성했다. 

request.POST를 인수로 QuestionForm을 생성할 경우에는 request.POST에 담긴

subject, content 값이 QuestionForm의 subject, content 속성에 자동으로 저장되어 객체가 생성된다.

request.POST에는 화면에서 사용자가 입력한 내용들이 담겨있다.

 

그리고 form.is_valid()는 form이 유효한지를 검사한다.

만약 form에 저장된 subject, content의 값이 올바르지 않다면 form에는 오류 메시지가 저장되고

form.is_valid()가 실패하여 다시 질문 등록 화면을 랜더링 할 것이다.

이 때 form에는 오류 메시지가 저장되므로 화면에 오류를 표시할 수 있다.

 

form이 유효하다면 if form.is_valid(): 이후의 문장이 수행되어 질문 데이터가 생성된다. 

question = form.save(commit=False)는 form에 저장된 데이터로 Question 데이터를 저장하기 위한 코드이다.

 

QuestionForm이 Question 모델과 연결된 모델 폼이기 때문에 이와 같이 사용할 수 있다.

여기서 commit=False는 임시 저장을 의미한다.

 

즉, 실제 데이터는 아직 데이터베이스에 저장되지 않은 상태를 말한다.

여기서 form.save(commit=False) 대신 form.save()를 수행하면

Question 모델의 create_date에 값이 없다는 오류가 발생할 것이다.

 

왜냐하면 QuestionForm에는 현재 subject, content 속성만 정의되어 있고 create_date 속성은 없기 때문이다.

이러한 이유로 임시 저장을 하여 question 객체를 리턴받고 create_date에 값을 설정한 후 

question.save()로 실제 데이터를 저장하는 것이다.

create_date 속성은 데이터 저장 시점에 생성해야 하는 값이므로 QuestionForm에 등록하여 사용하지 않는다

** 질문 등록하기 버튼 선택 후 글작성 및 저장이 가능하다.


- 폼 위젯

{{ form.as_p }} 태그는 HTML 코드를 자동으로 생성하기 때문에 부트스트랩 적용이 불가하다.
완벽하지는 않지만 다음처럼 QuestionForm을 조금 수정하면 어느정도 해결 가능

[파일명: projects\mysite\pybo\forms.py]
from django import forms
from pybo.models import Question

class QuestionForm(forms.ModelForm):
    class Meta:
        model = Question #사용할 모델
        fields = ['subject', 'content'] #QuestionForm에서 사용할 Question 모델의 속성
        widgets = {
            'subject': forms.TextInput(attrs={'class':'form-control'}),
            'content': forms.Textarea(attrs={'class': 'form-control','rows':10}),
        }

** widgets 속성을 지정하면 subject, content 입력 필드에 form-control과 같은 부트스트랩 클래스 추가 가능


- 폼 레이블

질문 등록 화면에 표시되는 'Subject', 'Content'를 영문이 아닌
한글 표시를 하고 싶다면 labels 속성이 필요하다.

 

[파일명: projects\mysite\pybo\forms.py]
from django import forms
from pybo.models import Question

class QuestionForm(forms.ModelForm):
    class Meta:
        model = Question #사용할 모델
        fields = ['subject', 'content'] #QuestionForm에서 사용할 Question 모델의 속성
        widgets = {
            'subject': forms.TextInput(attrs={'class':'form-control'}),
            'content': forms.Textarea(attrs={'class': 'form-control', 'rows':10}),
        }
        labels = {
            'subject' : '제목',
            'content' : '내용',
        }

 

** labels 속성 지정하여 내용 변경

** 장고 폼(form) 관련 URL : https://docs.djangoproject.com/en/4.0/topics/forms/

 


- 수동 폼 작성

{{ form.as_p }}를 사용하면 빠르게 템플릿을 만들 수 있지만 

HTML 코드가 자동으로 생성되므로 디자인 측면에서 제한이 발생한다.
폼을 이용하여 자동으로 HTML 코드를 생성하지 말고 직접 HTML 코드를 작성하는 방법에 대해 알아보자.

[파일명: projects\mysite\pybo\forms.py]
widget 속성 제거)
from django import forms
from pybo.models import Question

class QuestionForm(forms.ModelForm):
    class Meta:
        model = Question #사용할 모델
        fields = ['subject', 'content'] #QuestionForm에서 사용할 Question 모델의 속성
        labels = {
            'subject' : '제목',
            'content' : '내용',
        }

 


 

- 질문 템플릿 수정

[파일명: projects\mysite\templates\pybo\question_form.html]
{% extends 'base.html' %}
{% block content %}
<div class="container">
    <h5 class="my-3 border border-bottom pb-2">질문 등록</h5>
    <form method="post">
        {% csrf_token %} <!-- form의 post 방식일 때 무조건 작성 필요 -->
        <!-- 오류표시 Start -->
        {% if form.errors %}
        <div class="alert alert-danger" role="alert">
            {% for field in form %}
            {% if field.errors %}
            <div>
                <strong>{{ field.label }}</strong>
                {{ field.errors }}
            </div>
            {% endif %}
            {% endfor %}
        </div>
        {% endif %}
        <!-- 오류표시 End -->
        <div class="mb-3">
            <label for="subject" class="form-label">제목</label>
            <input type="text" class="form-control" name="subject" id="subject"
                   value="{{ form.subject.value|default_if_none:'' }}">
        </div>
        <div class="mb-3">
            <label for="content" class="form-label">내용</label>
            <textarea class="form-control" name="content"
                      id="content" rows="10">{{ form.content.value|default_if_none:'' }}</textarea>
        </div>
        <button type="submit" class="btn btn-primary">저장하기</button>
    </form>
</div>
{% endblock %}

** {{ form.as_p }}로 자동으로 생성되는 HTML 대신 제목과 내용에 해당되는 HTML코드를 직접 작성

 

**question_create 함수에서 form.is_valid() 가 실패할 경우 발생하는 오류의 내용을 표시하기 위해 오류 표시 영역 추가

 

** 제목(subject) 항목의 value에는 {{ form.subject.value|default_if_none:'' }} 처럼 값을 대입해 주었는데 

이것은 오류가 발생했을 경우 기존에 입력했던 값을 유지하기 위함이다.

 

** |default_if_none:''의 의미는 폼 데이터(form.subject.value)에 값이 없을 경우 

None 이라는 문자열이 표시되는데 None 대신 공백으로 표시하라는 의미의 템플릿 필터

 

** 장고의 템플릿 필터는 |default_if_none:'' 처럼 | 기호와 함께 사용된다.

** 내용에는 아무 내용도 입력하지 않았기 때문에 저장하기 버튼 선택 시 오류메시지 출력, 제목은 그대로 유지된다.


- 답변 등록

[파일명: projects\mysite\pybo\forms.py]
from django import forms
from pybo.models import Question, Answer

(.. 생략 ..)

class AnswerForm(forms.ModelForm):
    class Meta:
        model = Answer #사용할 모델
        fields = ['content'] #사용할 모델
        labels = {
            'content' : '내용',
        }

 


- answer_create 함수 수정

[파일명: projects\mysite\pybo\views.py]
(... 생략 ...)
from django.http import HttpResponseNotAllowed
from .forms import QuestionForm, AnswerForm
(... 생략 ...)

def answer_create(request, question_id):
    """
    pybo 답변등록
    """
    question = get_object_or_404(Question, pk=question_id)
    if request.method == "POST":
        form = AnswerForm(request.POST)
        if form.is_valid():
            answer = form.save(commit=False)
            answer.create_date = timezone.now()
            answer.question = question
            answer.save()
            return redirect('pybo:detail', question_id=question.id)
    else:
        return HttpResponseNotAllowed('Only POST is possible.')
    context = {'question': question, 'form': form}
    return render(request, 'pybo/question_detail.html', context)

** question_create와 같은 방법으로 AnswerForm을 이용하도록 변경
** 답변 등록은 POST 방식만 사용되기 때문에 GET 방식으로 요청 시 HttpResponseNotAllowed 오류 발생하도록 설정

 


- 질문 상세 템플릿도 오류를 표시하기 위한 영역 수정

[파일명: projects\mysite\templates\pybo\question_detail.html]
{% extends 'base.html' %}
{% block content %}
<div class="container my-3">
    (... 생략 ...)
    <form action="{% url 'pybo:answer_create' question.id %}" method="post" class="my-3">
        {% csrf_token %}
        <!-- 오류표시 Start -->
        {% if form.errors %}
        <div class="alert alert-danger" role="alert">
            {% for field in form %}
            {% if field.errors %}
            <div>
                <strong>{{ field.label }}</strong>
                {{ field.errors }}
            </div>
            {% endif %}
            {% endfor %}
        </div>
        {% endif %}
        <!-- 오류표시 End -->
        <div class="mb-3">
            <textarea name="content" id="content" class="form-control" rows="10"></textarea>
        </div>
        <input type="submit" value="답변등록" class="btn btn-primary">
    </form>
</div>
{% endblock %}

 

 

** 답변 내용 없이 등록 시 오류 메시지 출력

 

 

 

 

 

'Web > Django' 카테고리의 다른 글

Django - 페이징  (0) 2024.05.27
Django - 내비게이션바  (0) 2024.05.27
Django - 템플릿 상속  (0) 2024.05.18
Django - 부트스트랩  (0) 2024.05.18
Django - 스태틱  (0) 2024.05.18

● 템플릿 상속

지금까지 작성한 질문 목록, 질문 상세 템플릿은 표준 HTML 구조가 아니다.
어떤 웹 브라우저를 사용하더라도 웹 페이지가 동일하게 보이고 
정상적으로 작동하게 하려면 웹 표준을 지키는 HTML 문서를 작성해야 한다.


- 표준 HTML 구조

※ [표준 HTML 구조 예시]

<!doctype html>
<html lang="ko">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" type="text/css" href="/static/bootstrap.min.css">
    <title>Hello, pybo!</title>
</head>
<body>
(... 생략 ...)
</body>
</html>

** 표준 HTML 문서의 구조는 html, head, body 엘리먼트가 있어야 하며, 
CSS 파일 링크는 head 엘리먼트 안에 있어야 한다
head 엘리먼트 안에는 meta, title 엘리먼트 등이 포함되어야 한다.

※ 태그와 엘리먼트

<table> (... 생략 ...) </table>  <!-- table 엘리먼트 -->

** 위에서 은 table 태그이고  ~  처럼 table 태그로 시작해서  table 태그로 닫힌 구간(Block)은 table 엘리먼트이다.


- 템플릿 상속

앞서 작성한 질문 목록, 질문 상세 템플릿을 표준 HTML 구조가 되도록 수정하면
body 엘리먼트 바깥 부분(head 엘리먼트 등)은 모두 같은 내용으로 중복 된다.
이 경우 CSS 파일 이름이 변경되거나 추가될 때마다 모든 템플릿 파일 수정이 필요하다.
장고는 중복과 불편함을 해소하기 위해 템플릿 상속(extend) 기능을 제공한다.


- base.html

[파일이름: projects/mysite/templates/base.html]
* base.html 파일 생성
{% load static %}
<!doctype html>
<html lang="ko">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">
    <!-- pybo CSS -->
    <link rel="stylesheet" type="text/css" href="{% static 'style.css' %}">
    <title>Hello, pybo!</title>
</head>
<body>
<!-- 기본 템플릿 안에 삽입될 내용 Start -->
{% block content %}
{% endblock %}
<!-- 기본 템플릿 안에 삽입될 내용 End -->
</body>
</html>

** base.html 템플릿은 모든 템플릿이 상속해야 하는 템플릿으로 표준 HTML 문서의 기본 틀
** body 엘리먼트 안의 {% block content %} 와 {% endblock %} 템플릿 태그는 
base.html을 상속한 템플릿에서 개별적으로 구현해야 하는 영역


- question_list.html

[파일이름: projects/mysite/templates/pybo/question_list.html]
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">  #삭제
{% extends 'base.html' %}
{% block content %}
<div class="container my-3">
    <table class="table">

        (... 생략 ...)

    </table>
</div>
{% endblock %}

** base.html 템플릿을 상속하기 위해 {% extends 'base.html' %} 처럼 extends 템플릿 문법 사용
** {% block content %} 와 {% endblock %} 사이에 question_list.html에서만 쓰이는 내용 작성
question_list.html은 base.html 템플릿을 상속받아 표준 HTML문서로 변경된다.


- question_detail.html

[파일이름: projects/mysite/templates/pybo/question_detail.html]
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">   #삭제
{% extends 'base.html' %}
{% block content %}
<div class="container my-3">
    <h2 class="border-bottom py-2">{{ question.subject }}</h2
    (... 생략 ...)
    </form>
</div>
{% endblock %}

** {% extends 'base.html' %} 템플릿 태그를 맨 위에 추가하고 
기존 내용 위 아래로 {% block content %}와 {% endblock %}를 작성


- style.css

[파일명: projects\mysite\static\style.css]

 

부트스트랩 적용으로 내용 삭제

'Web > Django' 카테고리의 다른 글

Django - 내비게이션바  (0) 2024.05.27
Django - 폼  (0) 2024.05.21
Django - 부트스트랩  (0) 2024.05.18
Django - 스태틱  (0) 2024.05.18
Django - 데이터 저장  (0) 2024.05.18

● 부트스트랩

부트스트랩 설치
https://getbootstrap.com/docs/5.1/getting-started/download/
* 자세한 설명 : https://wikidocs.net/70838

 

※ 부트스트랩 주의사항

부트스트랩은 3.x, 4.x, 5.x 등의 버전이 존재하고 메이저 번호(3, 4, 5)에 따라 그 사용방법이 다르다.
프로젝트 관련 버전에 맞게 사용하는 것이 중요하다.

** bootstrap.min.css 파일 static 폴더에 붙여넣기


※ 부트스트랩 저장 경로

projects\mysite\static\bootstrap.min.css


- 부트스트랩 적용

[파일명: projects\mysite\templates\pybo\question_list.html] 수정
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">
<div class="container my-3">
    <table class="table">
        <thead>
            <tr class="table-dark">
                <th>번호</th>
                <th>제목</th>
                <th>작성일자</th>
            </tr>
        </thead>
        <tbody>
            {% if question_list %} <!-- question_list가 존재하면 -->
            {%for question in question_list %} <!-- for 반복문 사용, :(콜론) 미사용 -->
            <tr>
                <td>{{ forloop.counter }}</td> <!-- 번호 출력 -->
                <td>
                    <a href="{% url 'pybo:detail' question.id %}">{{question.subject}}</a> <!-- 제목 출력 -->
                </td>
                <td>{{ question.create_date }}</td> <--! 작성일 출력 -->
            </tr>
            {% endfor %} <!-- for 반복문이 끝나니 필수 작성 -->
            {% else %} <!-- 데이터가 없을 경우 -->
            <tr>
                <td colspan="3">질문이 업습니다.</td>
            </tr>
            {% endif %} <!-- if문 종료 -->
        </tbody>
    </table>
</div>

** for 문의 현재 순서를 의미하는 {{ forloop.counter }} 사용
** class="container my-3", class="table", class="table-dark" 등은 부트스트랩 스타일에 정의되어 있는 클래스
** 부트스트랩 참조 URL : https://getbootstrap.com/docs/5.1/getting-started/introduction/


- 질문 상세 템플릿 부트스트랩 적용

[파일명: projects\mysite\templates\pybo\question_detail.html]
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">
<div class="container my-3">
    <!-- 질문 -->
    <h2 class="border-bottom py-2">{{ question.subject }}</h2>
    <div class="card my-3">
        <div class="card-body">
            <div class="card-text" style="white-space: pre-line;">{{ question.content }}</div>
            <div class="d-flex justify-content-end">
                <div class="badge bg-light text-dark p-2">
                    {{ question.create_date }}
                </div>
            </div>
        </div>
    </div>
    <!-- 답변 -->
    <h5 class="border-bottom my-3 py-2">{{question.answer_set.count}}개의 답변이 있습니다.</h5>
    {% for answer in question.answer_set.all %}
    <div class="card my-3">
        <div class="card-body">
            <div class="card-text" style="white-space: pre-line;">{{ answer.content }}</div>
            <div class="d-flex justify-content-end">
                <div class="badge bg-light text-dark p-2">
                    {{ answer.create_date }}
                </div>
            </div>
        </div>
    </div>
    {% endfor %}
    <!-- 답변 등록 -->
    <form action="{% url 'pybo:answer_create' question.id %}" method="post" class="my-3">
        {% csrf_token %}
        <div class="mb-3">
            <label for="content" class="form-label">답변내용</label>
            <textarea name="content" id="content" class="form-control" rows="10"></textarea>
        </div>
        <input type="submit" value="답변등록" class="btn btn-primary">
    </form>
</div>
부트스트랩 card 컴포넌트 : 
https://getbootstrap.com/docs/5.1/components/card/

※ 사용한 부트스트랩 클래스 표

부트스트랩 클래스 설명
card, card-body, card-text 부트스트랩 Card 컴포넌트
badge 부트스트랩 Badge 컴포넌트
form-control, form-label 부트스트랩 Form 컴포넌트
border-bottom 아래방향 테두리 선
my-3 상하 마진값 3
py-2 상하 패딩값 2
p-2 상하좌우 패딩값 2
d-flex justify-content-end 컴포넌트의 우측 정렬
bg-light 연회색 배경
text-dark 검은색 글씨
text-start 좌측 정렬
btn btn-primary 부트스트랩 버튼 컴포넌트

** 질문 내용과 답변 내용에는 style="white-space: pre-line;" 타일을 지정 // 글 내용의 줄 바꿈 정상 표시

'Web > Django' 카테고리의 다른 글

Django - 폼  (0) 2024.05.21
Django - 템플릿 상속  (0) 2024.05.18
Django - 스태틱  (0) 2024.05.18
Django - 데이터 저장  (0) 2024.05.18
Django - URL 별칭  (0) 2024.05.18

● 스태틱

- 스태틱(static) 디렉토리

스타일시트 파일은 장고의 스태틱 디렉토리에 저장해야 한다.
즉 css파일 저장을 위한 디렉토리
스태틱 디렉토리도 템플릿 디렉토리와 마찬가지로 config/settings.py 파일에 등록하여 사용

 

[파일명: projects\mysite\config\settings.py]
STATIC_URL = 'static/'
STATICFILES_DIRS = [
    BASE_DIR / 'static',
]

** STATICFILES_DIRS이라는 리스트 변수를 추가 및 BASE_DIR / 'static' 디렉터리를 추가
** 'static'은 C:\projects\mysite\static 디렉토리 의미

 

※ static 디렉토리 생성

(mysite) c:\projects\mysite> mkdir static


- 스타일시트

[파일명: projects\mysite\static\style.css] 생성
textarea{
    width : 100%;
}

input[type=submit] {
    margin-top : 10px;
}

** 답변 등록시 사용하는 텍스트 창의 넓이를 100%로 하고 "답변등록" 버튼 상단에 10 픽셀의 마진을 설정


- 템플릿에 스타일 적용

[파일명: projects\mysite\templates\pybo\question_detail.html]
{% load static %}
<link rel = 'stylesheet' type="text/css" href="{% static 'style.css' %}">

<h1>{{ question.subject }}</h1>
<div>
(... 생략 ...)

** 템플릿에 스타일시트와 같은 스태틱 파일을 사용하기 위해서는 템플릿 최상단에 {% load static %} 추가 필요

** 텍스트 창의 넓이가 넓어지고 "답변등록" 버튼 위에 여유공간이 생겼다.

'Web > Django' 카테고리의 다른 글

Django - 템플릿 상속  (0) 2024.05.18
Django - 부트스트랩  (0) 2024.05.18
Django - 데이터 저장  (0) 2024.05.18
Django - URL 별칭  (0) 2024.05.18
Django - 조회와 템플릿  (0) 2024.05.16

● 데이터 저장

- 답변 등록 폼

[파일명: projects\mysite\templates\pybo\question_detail.html]
<h1>{{ question.content }}</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>

** {% csrf_token %}은 보안에 관련된 항목으로 form으로 전송한 데이터가 

실제 웹 페이지에서 작성한 데이터인지를 판단하는 가늠자 역할
** form 태그 바로 밑에 {% csrf_token %} 태그를 항상 위치 // 없을 시 오류 발생


※ CSRF란?
CSRF(cross site request forgery)는 웹 사이트 취약점 공격을 방지를 위해 사용하는 기술이다. 
장고가 CSRF 토큰 값을 세션을 통해 발행하고 웹 페이지에서는 폼 전송시에 
해당 토큰을 함께 전송하여 실제 웹 페이지에서 작성된 데이터가 전달되는지를 검증하는 기술이다.

 

csrf_token 사용을 위해서는 CsrfViewMiddleware 미들웨어가 필요한데 
이 미들웨어는 settings.py의 MIDDLEWARE 항목에 디폴트로 추가되어 있으므로 별도의 설정은 필요 없다.

 

[파일명: projects\mysite\config\settings.py]
(... 생략 ...)
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
(... 생략 ...)
** csrf_token 기능을 사용하고 싶지 않다면 이 부분을 주석처리 필요

- URL 매핑

** 페이지 새로고침 시 answer_create 별칭을 찾을 수 없다는 오류 발생
질문 상세 템플릿에 {% url 'pybo:answer_create' question.id %}처럼 pybo:answer_create 별칭을 사용했기 때문

 

 

[파일명: projects\mysite\pybo\urls.py] 매핑 등록 필요
from django.urls import path
from . import views

app_name = 'pybo' #앱의 네임스페이스 부여

urlpatterns = [
    path('',views.index, name='index'), #name속성 부여
    path('<int:question_id>/', views.detail, name='detail'), #name 속성 부여
    path('answer/create/<int:question_id>/', views.answer_create, name='answer_create'),
]

** answer_create 별칭에 해당하는 URL 매핑 규칙을 등록
** http://locahost:8000/pybo/answer/create/2/ 와 같은 페이지를 요청하면 

URL 매핑 규칙에 의해 views.answer_create 함수가 호출


-  뷰 함수

[파일명: projects\mysite\pybo\views.py]
from django.shortcuts import render, get_object_or_404, redirect
from django.utils import timezone
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)
    #pybo/question_list.html의 html을 템플릿(Template)이라 한다.
    #템플릿은 HTML 파일과 비슷하지만, 파이썬 데이터를 읽어 사용할 수 있는 HTML

def detail(request, question_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)
    #create row 생성(내용은 POST 방식으로 넘어온 content, 현재시간)
    question.answer_set.create(content=request.POST.get('content'), create_date = timezone.now())
    return redirect('pybo:detail', question_id=question.id) # 자신의 페이지에 다시 던져라

** answer_create 함수의 매개변수 question_id는 URL 매핑에 의해 그 값이 전달된다.
http://locahost:8000/pybo/answer/create/2/ 라는 페이지를 요청하면 매개변수 question_id에는 2라는 값이 전달
request.POST.get('content')로 텍스트창에 입력한 내용을 읽을 수 있다.
request.POST.get('content')는 POST로 전송된 폼(form) 데이터 항목 중 content 값을 의미
question.answer_set.create 를 사용하였으며,question.answer_set은 질문의 답변을 의미한다.
Question과 Answer 모델은 서로 ForeignKey 로 연결되어 있기때문 사용 가능


- 답변 조회

[파일명: projects\mysite\templates\pybo\question_detail.html]
<h1>{{ question.subject }}</h1>
<div>
    {{ question.content }}
</div>
<!-- 답변 내용 추가 -->
<h5>{{ question.answer_set.count }}개의 답변이 있습니다.</h5>
<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 %} <!-- 보안 관련 항목, form으로 전송된 데이터가 실제 웹 페이지의 작성 데인터인지 가늠 -->
    <!-- form태그 밑에 무조건 작성해야 한다. 장고에서 post 방식일 때 {% csrf_token %}이 없으면 에러 발생 -->
    <textarea name="content" id="content" rows="15"></textarea>
    <input type="submit" value="답변 등록">
</form>

** question.answer_set.count는 답변의 총 개수를 의미
** question.answer_set는 질문과 연결된 답변들

'Web > Django' 카테고리의 다른 글

Django - 부트스트랩  (0) 2024.05.18
Django - 스태틱  (0) 2024.05.18
Django - URL 별칭  (0) 2024.05.18
Django - 조회와 템플릿  (0) 2024.05.16
Django - 장고 관리자  (0) 2024.05.16

● URL 별칭

- URL 하드코딩

question_list.html 템플릿에 사용된 링크
<li><a href="/pybo/{{ question.id}}/">{{question.subject}}</a></li>

 

위 주소는 질문 상세보기를 위한 URL 링크이나 이러한 URL 링크는 수정될 가능성이 있다.
http://localhost:8000/pybo/question/2 또는 http://localhost:8000/pybo/2/question

 

** 실제 프로젝트에서 URL 리펙토링은 빈번하게 발생한다.
** URL 링크의 구조가 자주 변경된다면 템플릿에서 사용한 모든 URL들을 일일이 찾아 수정해야 하는 리스크가 발생한다.

ㄴ 이러한 문제점을 해결하기 위해서는 해당 URL에 대한 실제 링크 대신 링크의 주소가 1:1 매핑되어 있는 별칭을 사용


- URL 별칭

[파일명: projects\mysite\pybo\urls.py]
from django.urls import path
from . import views

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

** http://localhost:8000/pybo/ URL은 index, http://localhost:8000/pybo/2와 같은 URL에는 detail 이라는 별칭을 부여


- 템플릿에서 URL 별칭 사용하기

[파일명: C:\projects\mysite\templates\pybo\question_list.html]
{% if question_list %} <!-- question_list가 존재하면 -->
      <ul>
         {%for question in question_list %} <!-- for 반복문 사용, :(콜론) 미사용 -->
            <li><a href="{% url 'detail' question.id %}">{{question.subject}}</a></li>
         {% endfor %} <!-- for 반복문이 끝나니 필수 작성 -->
      </ul>
{% else %} <!-- 거짓일 경우 -->
      <p> 질문이 없습니다. </p>
{% endif %} <!-- if문 종료 -->

** 하드코딩 되어 있던 /pybo/{{ question.id }} 링크를 {% url 'detail' question.id %}로 변경
** question.id는 URL 매핑에 정의된 <int:question_id>에 전달해야 하는 값을 의미
** 2개 이상의 파라미터를 사용해야 한다면 다음과 같이 공백 문자 이후에 덧 붙여주면 된다.
ㄴ 예시) {% url 'detail' question_id=question.id page=2 %}


- URL 네임스페이스

현재는 pybo 앱 하나만 사용중이지만 pybo 앱 이외의 다른 앱이 추가된다면 중복이 발생할 수 있다.
이를 해결하기 위해 pybo/urls.py 파일에 네임스페이스를 의미하는 app_name 변수를 지정

[파일명: projects\mysite\pybo\urls.py]

 

from django.urls import path
from . import views

app_name = 'pybo' #앱의 네임스페이스 부여

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

 

 

** 오류는 네임스페이스를 추가했기 때문에 발생한 오류로 해결하기 위해
템플릿에서 사용한 URL 별칭(question_list.html파일)에 네임스페이스 수정이 필요하다.


[파일명: C:\projects\mysite\templates\pybo\question_list.html]

 

{% if question_list %} <!-- question_list가 존재하면 -->
    <ul>
        {%for question in question_list %} <!-- for 반복문 사용, :(콜론) 미사용 -->
            <li><a href="{% url 'pybo:detail' question.id %}">{{question.subject}}</a></li>
        {% endfor %} <!-- for 반복문이 끝나니 필수 작성 -->
    </ul>
{% else %} <!-- 거짓일 경우 -->
    <p> 질문이 없습니다. </p>
{% endif %} <!-- if문 종료 -->

** detail 앞에 pybo 네임스페이스 추가

 

※ redirect 함수와 URL 별칭
URL별칭은 템플릿 외에 redirect 함수에서도 사용된다. 
redirect는 특정 페이지로 이동시키는 함수이다. 

 

'Web > Django' 카테고리의 다른 글

Django - 스태틱  (0) 2024.05.18
Django - 데이터 저장  (0) 2024.05.18
Django - 조회와 템플릿  (0) 2024.05.16
Django - 장고 관리자  (0) 2024.05.16
Django - 모델  (1) 2024.05.15

● 조회와 템플릿

- 질문 목록 : 등록한 질문들을 게시물 목록으로 조회하는 기능
- 질문 상세 : 게시물 목록 중 한 건의 데이터를 상세하게 조회하는 기능

 

- 질문 목록

http://localhost:8000/pybo/
[파일명: projects/mysite/pybo/views.py]
from django.http import HttpResponse # 삭제
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)

** order_by('-create_date') 작성일시 역순으로 정렬하라는 의미로 - 기호가 있으면 역방향/없으면 순방향 정렬 의미

** pybo/question_list.html의 html템플릿(Template)이라 한다.

** 템플릿은 HTML 파일과 비슷하지만, 파이썬 데이터를 읽어 사용할 수 있는 HTML


※ render() 함수

내가 가진 templates에 data를 넣어 보내고 싶을 때 이용
render(request, template_name, context=None, content_type=None, status=None, using=None)

** request와 template_name 작성 필수


- 템플릿 디렉토리

render 함수에서 사용한 pybo/question_list.html 템플릿 파일 작성 필요
템플릿을 저장할 디렉토리는 config/settings.py 파일의 TEMPLATES 항목에 설정 필요
[파일명: projects/mysite/config/settings.py]

 

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는 템플릿 디렉토리를 여러개 등록할 수 있도록 리스트로 되어 있다
** c:\projects\mysite\templates >> 추가한 디렉토리의 전체 경로


 

※ 상기 주소의 디렉토리는 현재 없기 때문에 (mysite) c:\projects\mysite> mkdir templates 생성 필요

 

 

■ 모든 앱이 공통으로 사용할 템플릿 디렉터리 - projects/mysite/templates

■ pybo 앱이 사용할 템플릿 디렉터리 - projects/mysite/templates/pybo

■ common 앱이 사용할 템플릿 디렉터리 - projects/mysite/templates/common


- 템플릿 파일

render 함수에서 사용한 템플릿 파일명은 pybo/question_list.html로 다음과 같은 경로에 파일 생성이 필요하다.

[파일명: projects/mysite/templates/pybo/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 %}

 

※ question_list.html에 사용된 템플릿 태그

태그 설명
{% if question_list %} question_list가 있다면
{% for question in question_list %} question_list를 순회하며 순차적으로 하나씩 question에 대입
{{ question.id }} for문에 의해 대입된 question 객체의 id 번호를 출력
{{ question.subject }} for문에 의해 대입된 question 객체의 제목을 출력

 


- 템플릿 태그

 

1) 분기

{% if 조건문1 %}
      <p>조건문1에 해당되는 경우</p>
{% elif 조건문2 %}
      <p>조건문2에 해당되는 경우</p>
{% else %}
      <p>조건문1, 2에 모두 해당되지 않는 경우</p>
{% endif %}

** 파이썬의 if문과 동일하지만, 항상 끝에는 {% endif %} 태그로 닫아야한다.


2) 반복

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

3) 객체 출력

{{ 객체 }}
예시) {{ item }}
** 객체에 속성이 있는 경우 파이썬과 동일한 방법으로 도트( . ) 문자를 이용하여 표시

{{ 객체.속성 }}
예시) {{question.id}}, {{question.subject}}
** 템플릿 디렉토리 추가 후 로컬 서버 재시작 필수
** 템플릿 참고 URL : https://docs.djangoproject.com/en/4.0/topics/templates/

● 질문 상세

- url.py

[파일명: projects/mysite/pybo/urls.py]
from django.urls import path
from . import views

urlpatterns = [
      path('', views.index),
      path('<int:question_id>/', views.detail),  #<int:question_id>숫자 매핑
]

** path('<int:question_id>/', views.detail)  URL 매핑 추가

http://localhost:8000/pybo/3/ 페이지가 요청되면 url.py에 등록한 매핑 룰에 의해 http://localhost:8000/pybo/<int:question_id>/ 가 적용되어 question_id 에 3이 저장되며 views.detail 함수 실행


- views.py

detail 함수 추가
[파일명: projects/mysite/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에는 URL 매핑시 저장된 question_id가 전달된다. 
즉, http://localhost:8000/pybo/3/ 페이지가 요청되면 매개변수 question_id에 3가 세팅되어 detail 함수가 실행된다.


- question_detail.html

[파일명: projects/mysite/templates/pybo/question_detail.html]

 

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

** {{ question.subject }}과 {{ question.content }}의 question은 
detail 함수에서 템플릿에 context 변수로 전달한 Question 모델 객체이다.


** http://127.0.0.1:8000/pybo/4/ 페이지 요청 시 질문 상세 정상 출력


- 오류페이지

http://localhost:8000/pybo/30/ 요청

** Question.object.get(id=30)이 호출되어 발생한 오류 // 데이터가 없는 내용 호출 시 오류가 발생한다


[HTTP 주요 응답 코드 종류]

오류코드 설명
200 성공 (OK)
500 서버오류 (Internal Server Error )
404 서버가 요청한 페이지(Resource)를 찾을 수 없음 (Not Found)

없는 데이터를 요청할 경우 404 페이지 출력하는 방법
[파일명: projects\mysite\pybo\views.py]
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 모델의 기본키(Primary Key)에 해당하는 값을 의미


** Question.object.get(id=30) 입력 시 404 오류 페이지 출력

'Web > Django' 카테고리의 다른 글

Django - 데이터 저장  (0) 2024.05.18
Django - URL 별칭  (0) 2024.05.18
Django - 장고 관리자  (0) 2024.05.16
Django - 모델  (1) 2024.05.15
Django - URL과 뷰  (0) 2024.05.14

● 장고 관리자

- 슈퍼유저

장고 관리자를 사용하기 위해서는 장고 관리자 화면에 접속할 수 있는 슈퍼유저(superuser) 생성이 필요하다.

 

※ 명령 프롬포트 작성

(mysite) PS C:\projects\mysite> python manage.py createsuperuser

(mysite) PS C:\projects\mysite> python manage.py createsuperuser
사용자 이름 (leave blank to use 'user'): admin
이메일 주소: admin@mysite.com
Password:
Password (again):
비밀번호가 너무 짧습니다. 최소 8 문자를 포함해야 합니다.
비밀번호가 너무 일상적인 단어입니다. 비밀번호가 전부 숫자로 되어 있습니다.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.

※ 슈퍼유저 정보

항목
사용자 이름 admin
이메일 주소 admin@mysite.com
password 1234

** 비밀번호를 "1234"로 입력하면 관련 경고가 발생하나 y를 입력하면 그대로 생성할 수 있다.


- 장고 관리자 화면

실행 명령어 : python manage.py runserver
실행 URL : http://127.0.0.1:8000/admin/

 

** 생성한 계정정보를 입력하면 관리자 페이지 로그인이 가능하다.


● 모델 관리

[파일명: projects/mysite/pybo/admin.py]
from django.contrib import admin
from .models import Question

admin.site.register(Question)

 

** admin.site.register로 Question 모델 등록

 

** 장고 관리자화면에서 Question 모델 관리가 가능하며, 신규 질문 생성/조회/수정/삭제도 가능하다.

 

** 날짜는 오늘/현재를 자동으로 입력할 수 있다.


 

※ Answer 모델도 동일한 방법으로 등록하면 Question 모델과 마찬가지로 장고 관리자에서 사용 가능하다.

[파일명: projects/mysite/pybo/admin.py]
from django.contrib import admin
from .models import Question
from .models import Answer
admin.site.register(Question)
admin.site.register(Answer)

 


- 모델 검색

화면에서 제목(subject)으로 질문 데이터를 검색할수 있는 검색창 생성
[파일명: projects/mysite/pybo/admin.py]
from django.contrib import admin
from .models import Question
from .models import Answer

class QuestionAdmin(admin.ModelAdmin):
   search_fields = ['subject']

class AnswerAdmin(admin.ModelAdmin):
   search_fields = ['subject']

admin.site.register(Question, QuestionAdmin)
admin.site.register(Answer, AnswerAdmin)

** Question 모델에 세부 기능을 추가할 수 있는 Question 클래스 생성 후 검색을 위해

search_fields 속성에 'subject' 추가

 

** 검색창 생성 및 생성한 질문 검색이 가능하다.

** 장고 관리자의 기능은 https://docs.djangoproject.com/en/4.0/ref/contrib/admin/ 에서 참고

'Web > Django' 카테고리의 다른 글

Django - URL 별칭  (0) 2024.05.18
Django - 조회와 템플릿  (0) 2024.05.16
Django - 모델  (1) 2024.05.15
Django - URL과 뷰  (0) 2024.05.14
Django - 파이참  (0) 2024.05.13

+ Recent posts