25/10/16 장고

344th·2025년 12월 11일

AWS AI

목록 보기
24/48

스크롤 초기화 문제점 해결

답변을 작성하거나 수정하면 질문 상세 화면의 브라우저 스크롤바가 항상 페이지 상단으로 고정됨

앵커 엘리먼트로 스크롤 문제 해결 가능

  • 앵커 요쇼 a
    • URL 을 호출하면 원하는 위치로 스크롤을 이동시킴
    • name 과 URL 뒤 # 뒤 문자열이 일치하면 해당 앵커 요소 위치로 스크롤이 이동
    • html 자체 기능

답변 등록, 답변 수정 시 앵커 기능 추가

질문 상세 화면에 앵커 요소 추가

templates\pybo\question_detail.html

<!-- 딥변 내용 -->
{% for answer in question.answer_set.all %}
<a name="answer_{{answer.id}}"></a>

앵커 요소로 이동할 수 있도록 redirect 수정

pybo\views\answer_views.py

from django.shortcuts import render, get_object_or_404, redirect, resolve_url
...

@login_required(login_url='common:login')
def answer_create(request, question_id):
    ...
            return redirect('{}#answer_{}'.format(resolve_url('pybo:detail', question_id=question.id), answer.id))
    ...

@login_required(login_url='common:login')
def answer_modify(request, answer_id):
    ...
            return redirect('{}#answer_{}'.format(resolve_url('pybo:detail', question_id=answer.question.id), answer.id))
    ...
  • pybo:detail 에 #answer_2 와 같은 앵커를 추가하기 위해 formatresolve_url 함수를 사용
    • resolve_url : 실제 호출되는 URL을 문자열로 반환하는 함수

댓글에 앵커 기능 추가

위 과정과 동일

마크다운 기능 적용

마크다운 문법

파이보에 마크다운을 적용하기 전에 마크다운의 문법을 간단하게 알아보자.

리스트

목록을 표시하기 위해 다음처럼 작성한다.

* 파이썬
* 장고
* 알고리즘

위 문자열을 마크다운 해석기가 HTML로 변환하면 실제 화면에서는 다음과 같이 보인다.

  • 파이썬
  • 장고
  • 알고리즘

마크다운 해석기는 조금 후에 설치하고 실습할 것이니 우선은 마크다운 문법에 집중하자.

순서가 있는 목록을 표시하려면 다음처럼 작성한다.

1. 하나
1. 둘
1. 셋

결과는 다음과 같다.

  1. 하나

강조

작성한 글자를 강조 표시하려면 강조할 텍스트 양쪽에 **를 넣어 감싼다.

장고는 **파이썬**으로 만들어진 웹 프레임워크이다.

결과는 다음과 같다.

장고는 파이썬으로 만들어진 웹 프레임워크이다.

링크

HTML 링크는 다음처럼 [링크명](링크주소) 규칙을 적용하여 생성한다.

파이썬 홈페이지는 http://www.python.org 입니다.

결과는 다음과 같다.

파이썬 홈페이지는 http://www.python.org 입니다.

소스코드

소스코드는 백쿼트 ` 3개를 연이어 붙여 위아래로 감싸면 생성할 수 있다.

백쿼트는 백틱이라고도 한다.

결과는 다음과 같다.

def add(a, b):
     return a+b

인용

인용을 표시하려면 다음처럼 >를 문장 맨 앞에 입력하고 1칸 띄어쓰기를 한 다음 인용구를 입력한다.

> 마크다운은 Github에서 사용하는 글쓰기 도구이다.

결과는 다음과 같다.

마크다운은 Github에서 사용하는 글쓰기 도구이다.

마크다운의 보다 자세한 사용방법은 다음 문서를 참고하자.

마크다운 문법 공식 문서: www.markdownguide.org/getting-started

파이보에 마크다운 기능 추가

markdown 설치

pip install markdown

마크다운 필터 등록

pybo\templatetags\pybo_filter.py

import markdown
from django import template
from django.utils.safestring import mark_safe

register = template.Library()

@register.filter
def mark(value):
    extensions = ['nl2br', 'fenced_code']
    return mark_safe(markdown.markdown(value, extensions=extensions))
  • mark 함수는 markdown 모듈과 mark_safe 함수를 이용하여 문자열을 HTML 코드로 변환하여 반환함
  • markdown : 일반 텍스트를 HTML로 변환하는 파이썬 패키지 : 마크다운 → html 태그로 변환
  • template : Django 템플릿 시스템에서 커스텀 태그나 필터를 만들 때 필요
  • mark_safe : 변환된 문자열을 “안전한 HTML”로 표시해, 템플릿에서 자동 이스케이프(escape)를 방지
    • 변환된 HTML 문자열을 Django가 “안전한 HTML”로 인식하게 하여, <h1> 등의 태그가 실제로 렌더링되도록 함
    • 그렇지 않으면 HTML이 이스케이프되어 화면에 <h1>이 문자 그대로 표시됨
  • register = template.Library() : 필터 or 함수를 등록하기 위한 객체화
    • .

      template.Library()의 역할

      template.Library()Django 템플릿 시스템에 새로운 필터나 태그를 등록할 수 있게 해주는 “등록소(Registry)” 객체를 생성합니다. 즉,

      “이 파일 안에 정의된 함수들을 Django 템플릿에서 쓸 수 있도록 등록하겠다”

      라는 뜻이에요.


      왜 필요한가?

      Django 템플릿에서는 기본적으로 제공되는 필터(upper, lower, date 등) 외에도, 개발자가 직접 만든 커스텀 필터(custom filter)커스텀 태그(custom tag) 를 쓸 수 있습니다. 하지만 Django는 “아무 함수나 자동으로 템플릿에서 쓰게 해주지 않아요”. 그래서 template.Library()를 통해 “이 함수는 내가 템플릿용으로 등록하겠다” 하고 알려줘야 합니다.
      • template.Library() → 등록기 생성

      • @register.filter → 필터로 등록

      • @register.simple_tag → 태그로 등록


        template.Library()는 “Django에게 이 파일 안의 함수를 템플릿 필터/태그로 쓸 거야” 라고 알려주는 등록기 객체

  • @register.filter : 실제 등록
  • value : 마크다운 언어로 입력받을 값
  • 'nl2br' : 확장 도구 : 줄바꿈 문자를
    태그로 바꿔 주므로 Enter 를 한 번만 눌러도 줄바꿈으로 인식함
  • 'fenced_code' : 확장 도구 : 마크다운의 소스 코드 표현
  • 템플릿, 필터

    💡 1️⃣ 템플릿(Template)이란?

    Django의 템플릿(Template) 은 사용자에게 보여질 화면(HTML)을 생성하는 도구입니다. 즉, 백엔드의 데이터(Python 코드)프론트엔드의 표현(HTML) 을 연결하는 표현 계층 (Presentation Layer) 입니다.

    ✅ 예시

    <!-- templates/blog/detail.html -->
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
    
    여기서 {{ post.title }} 은 뷰(view) 함수에서 전달한 파이썬 객체의 값을 출력하는 부분입니다. 뷰에서 이렇게 연결합니다:
    def detail(request, post_id):
        post = Post.objects.get(id=post_id)
        return render(request, 'blog/detail.html', {'post': post})
    
    → Django는 post 데이터를 템플릿에 넣고, {{ post.title }} 을 실제 값으로 바꿔서 HTML로 만들어 브라우저에 보냅니다.

    💡 2️⃣ 템플릿의 문법 요소

    Django 템플릿 언어(DTL: Django Template Language)에는 크게 두 가지 주요 문법 요소가 있습니다:
    구분예시설명
    변수 (Variable){{ post.title }}뷰에서 전달된 context 데이터 출력
    태그 (Tag){% for post in posts %} ... {% endfor %}반복, 조건문, include 등 로직 제어

    💡 3️⃣ 템플릿의 필터(Filter)

    필터(Filter){{ 변수|필터명 }} 형태로 변수의 출력 값을 가공하는 도구입니다. 즉, 데이터 변환을 담당하는 미니 함수라고 생각하면 됩니다.
    예시설명출력 결과
    `{{ nameupper }}`모두 대문자로 변환
    `{{ priceadd:1000 }}`1000 더함
    `{{ datedate:"Y-m-d" }}`날짜 포맷 변경

    ✨ 커스텀 필터 예시

    직접 만든 필터를 쓰려면 templatetags 폴더 안에 다음처럼 작성:
    from django import template
    register = template.Library()
    
    @register.filter
    def sub(value, arg):
        return value - arg
    
    템플릿:
    {% load custom_filters %}
    {{ 10|sub:3 }}   <!-- 결과: 7 -->
    

    💡 4️⃣ 템플릿의 태그(Tag)

    태그(Tag){% ... %} 형태로, 조건문, 반복문, include, URL reverse 등 제어 로직을 수행합니다.
    예시설명
    {% if user.is_authenticated %}{% endif %}조건문
    {% for item in items %}{% endfor %}반복문
    {% include "header.html" %}다른 템플릿 삽입
    {% url 'post_detail' post.id %}URL 이름을 실제 링크로 변환

    ✨ 커스텀 태그 예시

    from django import template
    register = template.Library()
    
    @register.simple_tag
    def greeting(name):
        return f"Hello, {name}!"
    
    템플릿:
    {% load custom_tags %}
    {% greeting "수민" %}
    
    → 결과: Hello, 수민!

    💡 5️⃣ 정리

    구분역할예시결과
    템플릿 (Template)데이터 + HTML 결합{{ post.title }}향수 추천 플랫폼
    필터 (Filter)값 변환`{{ nameupper }}`
    태그 (Tag)로직 제어{% for post in posts %}반복 구조 처리

    👉 한 줄 요약

    Django의 템플릿은 데이터를 시각적으로 표현하는 HTML 생성 도구이며,

    필터는 데이터를 가공하고, 태그는 템플릿 내 로직 흐름을 제어하는 명령어입니다.

  • django 디자인 패턴 맞아요 ✅ Django는 MTV(Model–Template–View) 아키텍처 구조를 따르는 대표적인 웹 프레임워크입니다. 하지만 여기서 이름만 다를 뿐, 기본 개념은 MVC(Model–View–Controller) 와 거의 동일해요. 하나씩 정리해볼게요 👇

    💡 1️⃣ Django의 MTV 구조란?

    구성요소역할대응되는 MVC 구성요소
    Model데이터와 비즈니스 로직 담당Model
    Template사용자에게 보여지는 화면(UI) 담당View
    View요청을 받아 Model과 Template을 연결하는 중간 제어 로직Controller
    즉, 이름은 다르지만 역할은 거의 같습니다.

    💡 2️⃣ 각 구성요소 설명

    🧱 Model (모델)

    • 데이터 구조 정의데이터베이스 조작을 담당합니다.

    • Django의 ORM(Object Relational Mapper)을 통해 Python 코드로 DB를 다룰 수 있습니다.

      예시:

      class Post(models.Model):
          title = models.CharField(max_length=200)
          content = models.TextField()
          create_date = models.DateTimeField(auto_now_add=True)
      

      → SQL 없이 Python으로 DB 테이블 정의 가능


      🎨 Template (템플릿)

    • 사용자에게 보여질 화면(HTML) 을 담당합니다.

    • Django Template Language(DTL)를 사용해 데이터를 표시합니다.

      예시:

      <h1>{{ post.title }}</h1>
      <p>{{ post.content }}</p>
      

      ⚙️ View (뷰)

    • 사용자의 요청을 받고, Model에서 데이터를 가져와, Template으로 전달하는 중간 제어 로직입니다.

    • Controller 역할을 합니다.

      예시:

      from django.shortcuts import render
      from .models import Post
      
      def post_detail(request, post_id):
          post = Post.objects.get(id=post_id)
          return render(request, 'blog/post_detail.html', {'post': post})
      
    • request → 사용자 요청 수신

    • Post.objects.get(...) → Model 사용

    • render(...) → Template에 데이터 전달


      💡 3️⃣ MVC와 MTV의 차이 요약

      구분Django (MTV)전통 MVC
      ModelModelModel
      TemplateView (화면 표시)View
      ViewController (요청 처리)Controller
      ControllerDjango 프레임워크 내부에 내장됨 (URLconf + Dispatcher)명시적으로 구현

      ✅ 결론

      Django는 MTV(Model–Template–View) 구조를 따르며,

      본질적으로 MVC 패턴을 Django 방식으로 재해석한 형태입니다.

      즉, “역할은 같지만 이름이 다르다”고 이해하면 됩니다.

질문 상세 템플릿에 마크 다운 적용

templates\pybo\question_detail.html

{% extends 'base.html' %}
{% load pybo_filter %}
{% block content %}
...
<div class="card-text" style="white-space: pre-line;">
    {{ question.content|mark }}
</div>
...
...
<div class="card-text">
    {{ answer.content|mark }}
</div>
...

검색, 정렬 기능 추가

검색 기능 자세히 알아보기

질문 목록 화면에 검색창 추가

templates\pybo\question_list.html

...
<div class="container my-3">
    <div class="row justify-content-end my-3">
        <div class="col-4 input-group">
            <input type="text" class="form-control kw"
                value="{{ kw|default_if_none:'' }}">
            <div class="input-group-append">
                <button class="btn btn-outline-secondary"
                    type="button" id="btn_search">찾기</button>
            </div>
        </div>
    </div>
    ...

질문 목록 템플릿에 form 요소 추가

templates\pybo\question_list.html

...
<form id="searchForm" method="get" action="{% url 'index' %}">
    <input type="hidden" id="kw" name="kw" value="{{ kw|default_if_none:'' }}">
    <input type="hidden" id="page" name="page" value="{{ page }}">
</form>
{% endblock %}

질문 목록의 페이징 수정

templates\pybo\question_list.html

<a class="page-link" data-page="{{ question_list.previous_page_number }}" href="#">이전</a>

4개 추가

질문 목록 템플릿에 페이징과 검색을 위한 자바스크립트 코드 추가

templates\pybo\question_list.html

{% block script %}
<script type="text/javascript">
    $(document).ready(function(){
        $(".page-link").on('click', function() {
            $("#page").val($(this).data("page"));
            $("#searchForm").submit();
        });

        $("#btn_search").on('click', function() {
            $("#kw").val($(".kw").val());
            $("#page").val(1);
            $("#searchForm").submit();
        });
    });
</script>
{% endblock %}

index 함수 수정

pybo\views\base_views.py

...
from django.db.models import Q
...

def index(request):
    """
    pybo 목록 출력
    """
    # 입력 인자
    page = request.GET.get('page', '1') # 페이지
    kw = request.GET.get('kw', '') # 검색어
    
    # 조회
    question_list = Question.objects.order_by("-create_date")
    if kw:
        question_list = question_list.filter(
            Q(subject__icontains=kw) |
            Q(content__icontains=kw) |
            Q(author__username__icontains=kw) |
            Q(answer__author__username__icontains=kw)
        ).distinct()

    # 페이징 처리
    paginator = Paginator(question_list, 10)
    page_obj = paginator.get_page(page)
    
    context = {"question_list" : page_obj, 'page': page, 'kw': kw}
    return render(request, 'pybo/question_list.html', context)

정렬 기능 추가

정렬 기준

  • 최신순: 최근에 등록된 질문 우선
  • 추천순: 추천을 많이 받은 질문 우선
  • 인기순: 등록된 답변이 많은 질문 우선

이러한 정렬 기준에 해당하는 파라미터 역시 GET 방식으로 요청해야 페이징, 검색, 정렬 기능이 잘 작동함

질문 목록 화면에 정렬 조건 추가

templates\pybo\question_list.html

...
<div class="row justify-content-between my-3">
        <div class="col-2">
            <select class="form-control so">
                <option value="recent" {% if so == 'recent' %}selected{% endif %}>
                    최신순
                </option>
                <option value="recommend" {% if so == 'recommend' %}selected{% endif %}>
                    추천순
                </option>
                <option value="popular" {% if so == 'popular' %}selected{% endif %}>
                    인기순
                </option>
            </select>
        </div>
        ...

질문 목록 템플릿의 searchForm 수정

templates\pybo\question_list.html

<form id="searchForm" method="get" action="{% url 'index' %}">
    <input type="hidden" id="kw" name="kw" value="{{ kw|default_if_none:'' }}">
    <input type="hidden" id="page" name="page" value="{{ page }}">
    <input type="hidden" id="so" name="so" value="{{ so }}">
</form>

질문 목록 템플릿의 자바스크립트 코드 수정

templates\pybo\question_list.html

$(".so").on('change', function() {
    $("#so").val($(this).val());
    $("#page").val(1);
    $("#searchForm").submit();
});

index 함수 수정

pybo\views\base_views.py

...
so = request.GET.get('so', 'recent') # 정렬 기준
    
    # 정렬
    if so == 'recommend':
        question_list = Question.objects.annotate(
            num_voter=Count('voter')).order_by('-num_voter', '-create_date')
    elif so == 'popular':
        question_list = Question.objects.annotate(
            num_answer=Count('answer')).order_by('-num_answer', '-create_date')
    else:   # recent
        question_list = Question.objects.order_by("-create_date")
    
    # 조회
    # question_list = Question.objects.order_by("-create_date")
profile
새싹 개발자

0개의 댓글