본문 바로가기
Language/파이썬

장고(django)blog app(+아카이브) - 파이썬 장고를 활용한 쉽고 빠른 웹 개발 프로그래밍

by javapp 자바앱 2022. 1. 5.
728x90

 

앱 만들기 예제)

 

 

URL 설계

URL 패턴 View name template name
/blog/ PostLV post_all.html
/blog/post/ PostLV post_all.html
/blog/post/django-example/ PostDV post_detail.html
/blog/archive/ PostAV post_archive.html
/blog/archive/2019 PostYAV post_archive_year.html
/blog/archive/2019/nov PostMAV post_archive_month.html
/blog/archive/2019/nov/10 PostDAV post_archive_day.html
/blog/archive/today PostTAV post_archive_day.html
/admin/    

 

 

코딩 순서

  • 뼈대 만들기
    • startapp : 앱 생성
    • settings.py : 앱 등록
  • 모델
    • models.py : 모델(테이블)정의
    • admin.py : 사이트에 모델 등록
    • makemigrations : 변경사항 추출
    • migrate : 변경사항 반영 
  • URLconf
    • urls.py : URL 정의
    • views.py : 뷰 로직 작성
  • 템플릿 
    • template 디렉터리 : html 등 작성
  • 그외

 

 


 

 

뼈대 만들기

 

앱 생성

python manage.py startapp blog

 

앱 등록

settings.py

INSTALLED_APPS = [
    ....
    'blog.apps.BlogConfig',
]

 


 

모델 정의

models.py

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

class Post(models.Model):
    title = models.CharField(verbose_name='TITLE', max_length=50)
    # 페이지나 포스트를 설명하는 핵심 단어의 집합
    # 제목의 단어들을 하이푼으로 연결해 생성, pk 대신 사용 가능
    # allow_unicode : 한글 처리
    slug = models.SlugField('SLUG',unique=True, allow_unicode=True,help_text='one word for title alias')
    description = models.CharField('DESCRIPTION',max_length=100, blank=True,help_text='simple description text.')
    content = models.TextField('CONTENT')
    # auto_now_add : 객체가 생성될 때의 시각을 자동으로 기록
    create_dt = models.DateTimeField('CREATE DATE',auto_now_add=True)
    # auto_now : 데이터베이스에 저장될 때의 시각을 자동으로 기록 - 객체가 변경될 때의 시각 기록
    modify_dt = models.DateTimeField('MODIFY DATE',auto_now=True)


    # 필드 속성 외에 필요한 파라미터가 있을 때
    class Meta:
        verbose_name = 'post'
        verbose_name_plural = 'posts' # 별칭을 복수로 가질 때
        db_table = 'blog_posts'
        ordering = ('-modify_dt',) # 내림차순 정렬

    def __str__(self): # 뷰 출력
        return self.title

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

    def get_previous(self):
        return self.get_previous_by_modify_dt() # modify_dt 칼럼 기준으로 최신 포스트 반환

    def get_next(self):
        return self.get_next_by_modify_dt()

 

reverse() 함수 사용

URL 패턴을 만들어주는 장고 내장 함수

reverse('blog:post_list') # '/blog/'
reverse('blog:post_detail', args=[10]) # '/blog/10/' args 인자로 리스트 지정 필요
reverse('blog:post_detail', kwargs={'id':10}) # '/blog/10/'
reverse('/hello/') # NoReverseMatch 오류 발생

 

slug

slug = models.SlugField('SLUG',unique=True, allow_unicode=True,help_text='one word for title alias')

slug 칼럼은 제목의 별칭, SlugFieldunique 옵션을 추가해 특정 포스트를 검색할 때 기본키 대신 사용

 

allow_unicode=True

한글 처리 가능

 

def get_previous(self):
    return self.get_previous_by_modify_dt() # modify_dt 칼럼 기준으로 최신 포스트 반환

def get_next(self):
    return self.get_next_by_modify_dt()

-modify_dt 칼럼을 기준으로 포스트를 반환

 

 

사이트에 모델 등록

admin.py

from django.contrib import admin
from blog.models import Post


# admin 사이트에 보이도록 작성
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ('id','title','modify_dt')
    list_filter = ('modify_dt',)
    search_fields = ('title','content') # 검색 박스 표시, title, content 칼럼에서 검색
    prepopulated_fields = {'slug': ('title',)} # title 필드를 사용해 미리 채워지도록

 

 

데이터베이스 반영

 테이블의 신규생성, 테이블의 정의 변경 등

데이터 변경이 필요한 사항이 있으면, 데이터베이스에 실제로 반영하는 작업

python manage.py makemigrations blog
python manage.py migrate

 

 


 

 

URLconf

 

Root urlconf 정의

 

include 사용

from django.contrib import admin
from django.urls import path , include # include 추가

from bookmark.views import BookmarkLV, BookmarkDV

urlpatterns = [
    path('admin/', admin.site.urls),

    # path('bookmark/',BookmarkLV.as_view(), name='index'),
    # path('bookmark/<int:pk>',BookmarkDV.as_view(), name='detail'),
    path('bookmark/', include('bookmark.urls')),

    # include 를 통해 APP 단위의 URLconf 로 위임
    path('blog/',include('blog.urls')),
]

    path('blog/',include('blog.urls')),

    include() 함수를 사용하여 APP 단위로 위임

 

 

 

bookmark/urls.py

from django.urls import path
from bookmark.views import BookmarkLV, BookmarkDV

app_name = 'bookmark'

urlpatterns = [
    # 추가
    path('',BookmarkLV.as_view(), name='index'),
    path('<int:pk>/',BookmarkDV.as_view(), name='detail'),
]

이렇게 되면 템플릿에 출력하는 방식도 바뀌게 된다.

 

기존 코드에서

            {% for bookmark in object_list %}
                <li>
<!--                {{bookmark}} 는 테이블의 특정 레코드 하나를 의미, __str__ 를 호출-->
<!--                detail 의 url 패턴 : /bookmark/1/형식 으로 웹 요청 보낸다. url패턴 기능     -->
		    <a href ="{% url 'detail' bookmark.id %}">{{bookmark}}</a>
                </li>
            {% endfor%}

 

bookmark 의 urls.py 에 정의된 urlpatterns를 참고하여

app_name:name 형식으로 url 정의 , admin.py 에 정의한 id

<a href ="{% url 'bookmark:detail' bookmark.id %}">{{bookmark}}</a>

 

admin.py

@admin.register(Bookmark) # 어드민 사이트에 등록
class BookmarkAdmin(admin.ModelAdmin):
    list_display = ('id','title','url')

 

 

 

blog/urls.py

from django.urls import path, re_path
from blog import views

app_name= 'blog'

urlpatterns = [
    # /blog/
    path('',views.PostLV.as_view(), name='index'),

    # /blog/post/ (same as /blog/)
    path('post/', views.PostLV.as_view(),name = 'post_list'),

    # /blog/post/django-example/   : slus, 기본키
    # /blog/post/슬러그/
    re_path(r'^post/(?P<slug>[-\w]+)/$', views.PostDV.as_view(), name='post_detail'),
    # ^post/로 시작 , /$ : /로 끝나는 문자열
    # [-\w]+ : 문자열(숫자, 영문, 언더바)

    # /blog/archive/
    path('archive/',views.PostAV.as_view(),name='post_archive'),

    # /blog/archive/2019/
    path('archive/<int:year>/',views.PostYAV.as_view(), name='post_year_archive'),

    # /blog/archive/2019/nov/
    path('archive/<int:year>/<str:month>/',views.PostMAV.as_view(),name='post_month_archive'),

    # /blog/archive/2019/nov/10/
    path('archive/<int:year>/<str:month>/<int:day>',views.PostDAV.as_view(),name='post_day_archive'),

    # /blog/archive/today/
    path('archive/today/', views.PostTAV.as_view(), name='post_today_archive'),
]

 

 

 


 

 

View

blog/views.py

from django.shortcuts import render

from django.views.generic import ListView, DetailView
from django.views.generic.dates import ArchiveIndexView, YearArchiveView, MonthArchiveView
from django.views.generic.dates import DayArchiveView, TodayArchiveView

from blog.models import Post

# List View
class PostLV(ListView): # ListView 를 상속
    model = Post
    template_name = 'blog/post_all.html'
    context_object_name = 'posts' # 템플릿 파일로 넘겨주는 객체 리스트에 대한 컨텍스트 변수 명
    paginate_by =2 # 한페이지에 보여주는 객체 리스트 갯수 - 리스트 하단에 페이지를 이동할 수 있는 버튼 만들 수 있다.

# DetailView
class PostDV(DetailView):
    model = Post

# Archive View
class PostAV(ArchiveIndexView):
    model = Post
    date_field = 'modify_dt' # 변경 날짜가 최근인 포스트를 먼저 출력

class PostYAV(YearArchiveView):
    model = Post
    date_field = 'modify_dt'
    make_object_list = True # 해당 연도에 해당하는 객체 리스트를 만들어 템플릿에 넘겨준다. object_list

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

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

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

 

 

 


 

 

 

템플릿 정의

blog/templstes/blog/post_all.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>POST ALL</title>
</head>
<body>
    <h1>Blog List</h1>
    <br>
<!--    View 에서 context_object_name = 'posts' 라고 정의 했다., object_list 로 호출 가능 -->
    {% for post in posts%}
        <h3> <a href="{{post.get_absolute_url }}">{{ post.title }} </a></h3>
        {{ post.modify_dt | date:"N d, Y"}}
        <p>{{post.description}}</p>
    {% endfor %}

    <br>

    <div>
        <span>
<!--        page_obj 는 장고의 Page 객체가 들어 있는 컨텍스트 변수 : 현재 페이지를 기준으로 이전 페이지가 있는지 확인-->

            {% if page_obj.has_previous %}
<!--            쿼리 스트링, previous_page_number : 이전 페이지 번호 -->
                <a href="?page={{page_obj.previous_page_number}}">PreviousPage</a>
            {% endif %}

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

            {% if page_obj.has_next %}
<!--            ?page=5, next_page_number : 다음 페이지 번호-->
                <a href="?page={{page_obj.next_page_number}}">NextPage</a>
            {% endif %}
        </span>
    </div>
</body>
</html>

 

 

 

제목 하이퍼링크 부분

<!--    View 에서 context_object_name = 'posts' 라고 정의 했다., object_list 로 호출 가능 -->
    {% for post in posts%}
        <h3> <a href="{{post.get_absolute_url }}">{{ post.title }} </a></h3>
        {{ post.modify_dt | date:"N d, Y"}}
        <p>{{post.description}}</p>
    {% endfor %}

 

 

 

views.py 에 정의된 PostLV 클래스

# List View
class PostLV(ListView): # ListView 를 상속
    model = Post
    template_name = 'blog/post_all.html'
    context_object_name = 'posts' # 템플릿 파일로 넘겨주는 객체 리스트에 대한 컨텍스트 변수 명
    paginate_by =2 # 한페이지에 보여주는 객체 리스트 갯수 - 리스트 하단에 페이지를 이동할 수 있는 버튼 만들 수 있다.

 

 

내용과 다음 페이지

 

 

 

    <div>
        <span>
<!--        page_obj 는 장고의 Page 객체가 들어 있는 컨텍스트 변수 : 현재 페이지를 기준으로 이전 페이지가 있는지 확인-->

            {% if page_obj.has_previous %}
<!--            쿼리 스트링, previous_page_number : 이전 페이지 번호 -->
                <a href="?page={{page_obj.previous_page_number}}">PreviousPage</a>
            {% endif %}

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

 

 

 

 


            {% if page_obj.has_next %}
<!--            ?page=5, next_page_number : 다음 페이지 번호-->
                <a href="?page={{page_obj.next_page_number}}">NextPage</a>
            {% endif %}
        </span>
    </div>

 

 

 

blog/templstes/blog/post_detail.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Post Detail</title>
</head>
<body>
  <div>
    <h2>{{object.title}}</h2>
    <br>
    <p>
        {% if object.get_previous %}
        <!-- get_previous.get_absolute_url 함수를 지징하는 URL 패턴을 반환한다. -->
        <!-- &laquo; 는 HTML 특수문자 '<<<' 를 의미한다.  -->
        <a href="{object.get_previous.get_absolute_url}" title="View previous post">&laquo;- {{object.get_previous}}</a>
        {% endif %}

        {% if object.get_next %}
        <!-- get_next.get_absolute_url 함수를 지칭하는 URL 패넡을 반환한다. -->
        <!-- &raquo; 는 HTML 특수문자 '>>>'를 의미한다.  -->
        | <a href="{{object.get_next.get_absolute_url}}" title="View next post">{{object.get_next}} -&raquo;</a>
        {% endif %}

    </p>
      <!-- 장고 date 템플릿 필터에 설명 참고 -->
      <p>{{object.modify_dt|date:"j F Y"}}</p>

      <br>
  </div>
  <div>
      {{object.content | linebreaks}} <!-- linebreaks : \n -->
  </div>
</body>
</html>

Post Detail

 

 

 

/blog/archive/

blog/templates/blog/post_archive.html

더보기
더보기
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Post Archive</title>
</head>
<body>
  <div>
      <div>

          <h1>Post Archives until {% now "N d, Y" %}</h1>

          <ul>
    <!--          date_list : DateQuerySet 객체 리스트를 담고 있다. , datetime.date 타입 객체-->
              {% for date in date_list %}
              <li style="display: inline;">
                  <a href="{% url 'blog:post_year_archive' date|date:'Y' %}">
                      Year {{date | date:"Y"}}</a>
              </li>
              {% endfor %}
          </ul>

      </div>

      <br/>

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

Post Archive

 

 

blog/templates/blog/post_year.html

더보기
더보기
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Archive Year</title>
</head>
<body>
    <h1> Post Archives for {{ year|date:"Y"}}</h1>

    <ul>
        {% for date in date_list %}
        <li style="display: inline;">
            <a href="{% url 'blog:post_month_archive' year|date:'Y' date|date:'b' %}">
                {{date|date:"F"}}
            </a>
        </li>
        {% endfor %}
    </ul>

    <br>

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

Archive Year

 

 

blog/templates/blog/post_month.html

더보기
더보기
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Post Archive Month</title>
</head>
<body>
    <div>
        <h1>Post Archives for {{month|date:"N, Y"}}</h1>

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

Archive Month

 

blog/templates/blog/post_day.html

더보기
더보기
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Post Archive Day</title>
</head>
<body>
    <div>
        <h1>Post Archives for {{day|date:"N d ,Y"}}</h1>
        <div>
            <ul>
                {% for post in object_list %}
                <li>{{post.modify_dt|date:"Y-m-d"}} &nbsp;&nbsp;&nbsp;</li>
                <a href="{{post.get_absolute_url}}"><strong>{{post.title}}</strong></a>
                {% endfor %}
            </ul>

        </div>
    </div>
</body>
</html>

Archive Day

 

 

이로써 장고의 MVT 패턴을 토대로 블로그, 아카이브 페이지를 만들어 보았다.

모델, 뷰 , 템플릿에 해당하는 각 파일들의 역할 및 URL 에서 시작해 뷰를 거쳐 템플릿으로 전개되는 로직의 흐름은

모든 애플리케이션의 공통된 사항이다.

 

 

 

책 참고 : 파이썬 웹 프로그래밍 실전편(개정판) - 김석훈

댓글