Web Programming/Django

Python Django로 게시판 만들기_게시판 구현(2)

안녕하세요. 씨앤텍 시스템즈입니다.

이번 글은 admin에서 작성한 글을 화면에 띄우고url을 통해 접속하는것을 구현해보도록 하겠습니다.

 


0. 템플릿 언어

장고 코드를 작성하기에 앞서 뒤에 나올 템플릿 언어에 대해 알아보겠습니다.

템플릿 언어는 파이썬 변수˙문법을 html 안에서 쓸 수 있도록 장고에서 제공하는 언어입니다.

템플릿 언어에는 템플릿 변수, 템플릿 필터, 템플릿 태그, 템플릿 코멘트가 있습니다.

0.1 템플릿 변수

템플릿 변수는 {{ }}로 구성되어 있으며, 뷰에서 템플릿으로 컨텍스트를 전달할 수 있습니다.

일반적인 변수들과 동일하게 띄어쓰기는 사용할 수 없고 _와 대소문자를 이용합니다.

.를 사용해서 변수의 속성에 접근할 수 있습니다.

ex){{ post.title }}

0.2 템플릿 태그

템플릿 태그는 {% %}로 구성되어 있으며, 주로 반복문과 조건문에 자주 쓰입니다.

{% extends %}처럼 단독으로 사용할 수 있는 태그와 {% for %}{% endfor %} 처럼 닫아야 하는 태그가 있습니다.

ex)
{% for post in posts %}
      {{ post.title }}
{% endfor %}

0.3 템플릿 필터

템플릿 필터는 |를 이용해서 적용할 수 있으며, 필터로 변수의 값을 특정 형식으로 변환할 때 사용합니다.

템플릿 변수를 이후에 |를 적어 적용하려는 필터를 명시합니다.

ex){{ post.mod_date|date:"N d, Y"  }}

0.4 템플릿 코멘트

템플릿 코멘트는 {# #}와 {% comment %}{% endcomment %}총 두가지가 있으며, html 문서에서 주석이 필요한 경우 사용합니다.

{##}는 한줄 주석, {% comment %}{% endcomment %}는 여러줄을 주석처리할 때 사용합니다.

ex)
{# 씨앤텍시스템즈 입니다. #}
{% comment %}
       이렇게
      코멘트태그 사이에
      여러줄
      주석처리를 위한 내용을
      적어줍니다.

{% endcomment %}

 

1. 이전 코드 수정

1.1 models.py 수정

admin에서 작성 한 글을 화면으로 가져오기에 앞서 models.py 를 수정합니다.

#blog>models.py

from django.db import models
from django.utils import timezone
from django.urls import reverse

# Create your models here.
class Post(models.Model):
    title = models.CharField(verbose_name='TITLE', max_length=100)
    content = models.TextField('CONTENT', default='')
    pub_date = models.DateTimeField('PUBLISH DATE', default = timezone.now)
    mod_date = models.DateTimeField('MODIFY DATE', auto_now=True)

    class Meta:
        verbose_name='post'
        verbose_name_plural='posts'
        db_table='blog_posts'
        ordering = ('-mod_date',)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('blog:post_detail',args=(self.id,))

    def get_previous(self):
        return self.get_previous_by_mod_date()
        
    def get_next(self):
        return self.get_next_by_mod_date()

reverse() 함수는 url 패턴을 만들어주는 장고의 내장함수 입니다.

장고는 urls.py 변경을 통해 각 뷰에 대한 url이 변경되는 유연한 시스템을 가지고 있어 url이 변경되더라도 url reverse가 변경된 url을 추적합니다.

 

1.2 admin.py 수정

admin 사이트에서 보이는 모습을 정의해줍니다.

#blog>admin.py

from django.contrib import admin
from blog.models import Post
# Register your models here.

#admin.site.register(Post)
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ('id', 'title', 'mod_date')
    list_filter = ('mod_date',)
    search_fields = ('title', 'content')

admin.site.register()을 사용해도 되지만 @(데코레이터)를 사용하면 훨씬 간단합니다.

 

1.3 데이터베이스에 반영

수정한 models.py 와 admin.py를 데이터베이스에 반영해줍니다.

데이터베이스 반영

    1.3.1 The value of 'list_filter' must be a list or tuple

    해당 에러는 오타에 의해 나는 경우가 많습니다.

    admin.py 의 list_filter에서 'mod_date'뒤에 , 가 없을 경우에 발생하는 에러 입니다.

1.4 반영된 결과 확인

장고 서버를 실행하고 admin 페이지에서 수정한 결과를 확인합니다.

admin 글 목록

admin.py에서 list_display에서 설정한것처럼 ID, 제목, 수정한 날짜가 나옵니다.

 

2. url로 장고 앱 접속하기

2.1 urls.py 수정

url로 접속하기 위해 urls.py를 수정합니다.

blog의 urls.py 뿐만 아니라 프로젝트의 urls.py도 함께 수정해주어야 합니다.

 

URLconf를 2계층으로 코딩하는 것이 확장성 측면에서 유리하기때문에 나눠서 코딩하도록 합니다.

#프로젝트>urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls')),
]
#blog>urls.py

from django.urls import path
from blog import views

app_name = 'blog'

urlpatterns =[
    path('', views.PostLV.as_view(), name = 'index'),
    path('post/', views.PostLV.as_view(), name='post_list'),
    path('post/<int:pk>/', views.PostDV.as_view(), name = 'post_detail'),
    path('archive/', views.PostAV.as_view(), name = 'post_archive'),
    path('archive/<int:year>/', views.PostYAV.as_view(), name = 'post_year_archive'),
    path('archive/<int:year>/<str:month>/', views.PostMAV.as_view(), name = 'post_month_archive'),
    path('archive/<int:year>/<str:month>/<int:day>/', views.PostDAV.as_view(), name = 'post_day_archive'),
    path('archive/today/', views.PostTAV.as_view(), name = 'post_today'),
]

* 게시물 제목을 클릭해서 들어갈때는 게시물 번호를 통해 들어가고, 게시물 번호는 자동으로 pk값으로 지정되어 있기때문에 "post/<int:pk>/" 로 적어줍니다.

 

2.2 views.py 수정

urls.py에서 지정한 클래스형 제너릭 뷰를 정의해줍니다.

#blog>views.py

from django.shortcuts import render
from django.views.generic import ListView, DetailView
from django.views.generic.dates import ArchiveIndexView, TodayArchiveView

from blog.models import Post

# Create your views here.

class PostLV(ListView):
    model = Post
    context_object_name = 'posts'
    paginate_by = 2

class PostDV(DetailView):
    model = Post

class PostAV(ArchiveIndexView):
    model = Post
    date_field = 'mod_date'

class PostYAV(YearArchiveView):
    model = Post
    date_field = 'mod_date'
    make_object_list=True

class PostMAV(MonthArchiveView):
    model = Post
    date_field = 'mod_date'

class PostDAV(DayArchiveView):
    model = Post
    date_field = 'mod_date'

class PostTAV(TodayArchiveView):
    model = Post
    date_field = 'mod_date'

 

2.3 template 수정

django admin에서 작성한 글들을 보여주기 위한 html을 수정합니다.

장고는 별도의 html 태그 지정이 없으면 body 영역으로 간주합니다.

<!-- blog>templates>blog>post_list.html -->

<head>
  <meta charset="utf-8">
  <title>CNT Blog</title>
</head>
<body>
  <h2>Post List</h2>
  <span>
    <a href="{% url 'blog:post_archive' %}">아카이브</a>
  </span>

  <p>
    {% for post in posts %}
      <h3><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h3>
      {{ post.mod_date|date:"N d, Y" }}
    {% endfor %}
  </p>
  <br>
  <div>
    {% if page_obj.has_previous %}
      <a href="?page={{ page_obj.previous_page_number }}">이전페이지</a>
    {% endif %}

    Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}

    {% if page_obj.has_next %}
      <a href="?page={{ page_obj.next_page_number }}">다음페이지</a>
    {% endif%}
  </div>
</body>
<!-- blog>templates>blog>post_detail.html -->
  <head>
    <meta charset="utf-8">
    <title>CNT Blog - {{ object.title }}</title>
  </head>
  <body>
    <p><strong>{{ object.title }}</strong></p>

    <p>
      {% if object.get_previous %}
      <a href="{{ object.get_previous.get_absolute_url }}" title="이전 글">&larr; {{ object.get_previous }}</a>
      {% endif %}

      {% if object.get_next %}
      | <a href="{{ object.get_next.get_absolute_url }}" title="다음 글">{{ object.get_next }} &rarr;</a>
      {% endif %}
    </p>
    <P>{{ object.mod_date|date:"j F Y" }}</P>
    <br>
    <br>
    <div>
      {{ object.content|linebreaks }}
    </div>
    <div>
      <a href="{% url 'blog:post_list' %}">글 목록</a>
    </div>
  </body>
<!-- blog>templates>blog>post_archive.html -->
<head>
  <meta charset="utf-8">
  <title>CNT Blog</title>
</head>
<body>
  <p>Post Archives until {% now "N d, Y" %}</p>

  <ul>
    {% for date in date_list %}
    <li>
      <a href="{% url 'blog:post_year_archive' date|date:'Y' %}">{{ date|date:'Y' }}</a>
    </li>
    {% endfor %}
  </ul>
  <div></div>
  <div>
    <ul>
      {% for post in object_list %}
      <li>{{ post.mod_date|date:"Y-m-d" }}&nbsp;<a href="{{ post.get_absolute_url }}"><strong>{{ post.title }}</strong></a></li>
      {% endfor %}
    </ul>
  </div>

</body>
<!-- blog>templates>blog>post_archive_year.html -->
<head>
  <meta charset="utf-8">
  <title>CNT Blog</title>
</head>
<body>
  <p>Post Archives for {{ year|date:"Y" }}</p>

  <ul>
    {% for date in date_list %}
    <li>
      <a href="{% url 'blog:post_month_archive' year|date:'Y' date|date:'m' %}">{{ date|date:'F' }}</a>
    </li>
    {% endfor %}
  </ul>
  <div></div>
  <div>
    <ul>
      {% for post in object_list %}
      <li>{{ post.mod_date|date:"Y-m-d" }}&nbsp;&nbsp;&nbsp; <a href="{{ post.get_absolute_url }}"><strong>{{ post.title }}</strong></a></li>
      {% endfor %}
    </ul>
  </div>

</body>
<!-- blog>templates>blog>post_archive_month.html -->
<head>
  <meta charset="utf-8">
  <title>CNT Blog</title>
</head>
<body>
  <p>Post Archives for {{ month|date:"N, Y" }}</p>
  <div>
    <ul>
      {% for post in object_list %}
      <li>{{ post.mod_date|date:"Y-m-d" }}&nbsp;&nbsp;&nbsp; <a href="{{ post.get_absolute_url }}"><strong>{{ post.title }}</strong></a></li>
      {% endfor %}
    </ul>
  </div>
</body>
<!-- blog>templates>blog>post_archive_day.html -->
<head>
  <meta charset="utf-8">
  <title>CNT Blog</title>
</head>
<body>
  <p>Post Archives for {{ day|date:'N d, Y' }}</p>

  <div>
    <ul>
      {% for post in object_list %}
      <li>{{ post.mod_date|date:"Y-m-d" }}&nbsp;&nbsp;&nbsp; <a href="{{ post.get_absolute_url }}"><strong>{{ post.title }}</strong></a></li>
      {% endfor %}
    </ul>
  </div>
</body>

 

2.4 서버 실행

장고 서버를 실행하고 결과를 확인합니다.

http://localhost:8000/blog/

아직 template에 css를 적용하지 않았기 때문에 알아보기는 어렵지만 admin 페이지에서 작성한 글이 그대로 잘 나오는 것을 확인할 수 있습니다.

아카이브

 

 

    2.4.1 Invalid date string "2020__5월__" given format "%Y__%b__" 에러

    해당 에러는 date format 형식이 맞지 않아서 발생하는 에러입니다. 

    url을 확인해보면 2020/5월/ 로 값이 전달되는것을 확인할 수 있습니다.

    이 문제는 view.py에서 month_format 을 추가해주면 해결됩니다.

#blog>views.py
class PostMAV(MonthArchiveView):
    model = Post
    date_field = 'mod_date'
    month_format = '%m'

class PostDAV(DayArchiveView):
    model = Post
    date_field = 'mod_date'
    month_format = '%m'

아카이브

상단의 코드에는 없지만 월별로 정렬 뿐만아니라 일별로 정렬을 하고 싶은 경우에는 post_archive_month.html에 하단의 코드를 삽입해주면 됩니다.

  <ul>
    {% for date in date_list %}
    <li style="list-style:none; display:inline-block">
      <a href="{% url 'blog:post_day_archive' date|date:'Y' month|date:'m' date|date:'d' %}">{{ date|date:'d' }}</a>
    </li>
    {% endfor %}
  </ul>

 


지금까지 admin에서 작성한 게시글을 html화면을 통해 확인해보았습니다.

다음 시간에는 css를 이용해 화면을 꾸며보고, 화면에서 글을 작성하고 수정 삭제를 구현해보도록 하겠습니다.

 

감사합니다.

 

 

Written by 기술연구소 이경빈 연구원

728x90