New Blog project - 1

[ New Blog project ]

  • 새로운 블로그 웹 사이트를 만들어본다.

    1. 프로젝트를 생성하고 django를 설치한다.

      1
      $ pip install django
    2. django project를 만든다.

      1
      $ django-admin startproject config .
    3. 다음 항목을 설치하고 해당 관련 항목들의 코드를 추가하도록 한다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      # debug tool bar 설치
      $ pip install django-debug-toolbar

      # 모델 간의 관계도를 그리기 위해 extentions 설치
      $ pip install django-extensions

      # HTML 편집기를 사용하기 위해 ckeditor 설치
      $ pip install django-ckeditor

      # tag 기능을 사용하기 위해 tagging 설치
      $ pip install django-tagging

      # static 파일을 서빙하기 위해 whitenoise 설치
      $ pip install whitenoise

      # 의존성 리스트 생성
      $ pip freeze > requirements.txt
    4. post라는 이름을 가진 앱을 만든다.

      1
      python manage.py startapp post
    5. models.py를 작성한다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      # post/models.py

      from django.db import models
      from ckeditor_uploader.fields import RichTextUploadingField
      from tagging.fields import TagField
      from django.shortcuts import resolve_url
      # Create your models here.
      class Category(models.Model):
      name = models.CharField(max_length=100)
      slug = models.SlugField(max_length=120, unique=True, allow_unicode=True, db_index=True)
      # 상위 카테고리를 만들기 위해
      parent_category = models.ForeignKey("self", on_delete=models.SET_NULL, blank=True, null=True)

      def __str__(self):
      return self.name

      def get_absolute_url(self):
      return resolve_url('post:post_list_with_category', self.slug)

      class Post(models.Model):
      category = models.ForeignKey(Category, on_delete=models.SET_NULL, blank=True, null=True)
      title = models.CharField(max_length=100)
      slug = models.SlugField(max_length=120, unique=True, allow_unicode=True, db_index=True)
      text = RichTextUploadingField()
      material = models.FileField(upload_to='material/%Y/%m/%d',blank=True)
      tag = TagField(blank=True)
      created = models.DateTimeField(auto_now_add=True)
      updated = models.DateTimeField(auto_now=True)

      def __str__(self):
      return self.title + " at " + self.created.strftime("%Y-%m-%d")

      def get_absolute_url(self):
      return resolve_url('post:post_detail', self.slug)

      # 날짜.월.일만 나오는 포맷을 사용하고 싶어서 추가
      def formatcreated(self):
      return self.created.strftime('%Y.%m.%d')

      def formatupdated(self):
      return self.updated.strftime('%Y.%m.%d')

      # 모델 설계가 끝났다면 반드시 makemigrations와 migrate를 진행한다.
    6. admin.py를 작성한다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      # post/admin.py

      from django.contrib import admin
      from .models import *

      class CategoryAdmin(admin.ModelAdmin):
      list_display = ['id','name','slug']
      prepopulated_fields = {'slug':('name',)}
      admin.site.register(Category, CategoryAdmin)

      class PostAdmin(admin.ModelAdmin):
      list_display = ['id','title','slug','created','updated']
      ordering = ['-updated','-created']
      prepopulated_fields = {'slug': ('title',)}
      admin.site.register(Post, PostAdmin)
    7. views.py를 작성한다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      # post/views.py

      from django.shortcuts import render
      from django.views.generic.list import ListView
      from django.views.generic.edit import CreateView, UpdateView, DeleteView
      from django.views.generic.detail import DetailView

      from .models import *

      class PostList(ListView):
      model = Post
      # paginate 테스트를 위해 1로 설정
      paginate_by = 1
      template_name = 'post/post_list.html'

      # 해당 category에 속한 리스트만을 불러오기 위해
      def get_queryset(self):
      queryset = super().get_queryset()
      if 'category_slug' in self.kwargs:
      try:
      category = Category.objects.get(slug=self.kwargs['category_slug'])
      queryset = queryset.filter(category=category)
      except:
      pass

      return queryset

      class PostDetail(DetailView):
      model = Post
      template_name = 'post/post_detail.html'
    8. context_processors.py를 생성하고 작성한 후, settings.py도 수정한다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      # context 항목을 만드는 데 있어서 중복 작업이 있을 경우,
      # context_processors를 만들어서 사용하면 중복 작업을 제거할 수 있다.

      # post/contest_processors.py
      from .models import Category

      # 상위 카테고리만 구별하기 위해
      def category(request):
      categories = Category.objects.filter(parent_category=None)
      return {'categories': categories}


      # config/settings.py
      TEMPLATES = [
      {
      #...
      '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',
      # 추가
      'post.context_processors.category',
      ],
      },
      },
      ]
    9. urls.py를 작성한다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      # config/urls.py

      from django.contrib import admin
      from django.views.static import serve
      from django.urls import path,re_path, include
      from django.conf import settings

      urlpatterns = [
      path('admin/', admin.site.urls),
      path('ckeditor/', include('ckeditor_uploader.urls')),
      re_path(r'^media/(?P<path>.*)$', serve, {'document_root':settings.MEDIA_ROOT}),
      # post.urls 추가
      path('', include('post.urls'))
      ]

      if settings.DEBUG:
      import debug_toolbar
      urlpatterns = [
      path('__debug__/', include(debug_toolbar.urls)),
      ] + urlpatterns


      # post/urls.py
      from django.urls import path
      from .views import *

      app_name = 'post'

      urlpatterns = [
      path('detail/<slug>/', PostDetail.as_view(), name='post_detail'),
      path('<category_slug>/', PostList.as_view(), name='post_list_with_category'),
      path('', PostList.as_view(), name='post_list')
      ]
    10. template 파일을 생성하고 작성한다.

    • layout/base.html

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <title>{% block title %}Blog{% endblock %}</title>
      <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
      integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
      </head>
      <body>
      <div class="container">
      <nav class="navbar navbar-expand-lg navbar-light bg-light sticky-top">
      <a class="navbar-brand" href="/">Blog</a>
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-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 mr-auto">
      <li class="nav-item active">
      <a class="nav-link" href="/">Home</a>
      </li>
      <li class="nav-item">
      <a class="nav-link" href="#">Blog</a>
      </li>
      <li class="nav-item">
      <a class="nav-link" href="#">Guest Book</a>
      </li>
      </ul>
      <form class="form-inline my-2 my-lg-0">
      <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
      <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
      </form>
      </div>
      </nav>
      {% block content %}

      {% endblock %}
      </div>

      <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
      integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
      crossorigin="anonymous"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
      integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
      crossorigin="anonymous"></script>
      <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
      integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
      crossorigin="anonymous"></script>
      </body>
      </html>
    • post/templates/post/post_list.html

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      {% extends 'base.html' %}

      {% block content %}
      <div class="row mt-3">
      <div class="col col-md-2">
      <!-- category -->
      {% include 'post/category_list.html' %}
      </div>
      <div class="col-10 col-md-8">
      <!-- list -->
      <table class="table table-striped">
      <thead>
      <tr>
      <th>#</th>
      <th>title</th>
      <th>created</th>
      <th>updated</th>
      {% if user.is_authenticated %}
      <th>update</th>
      <th>delete</th>
      {% endif %}
      </tr>
      </thead>
      <tbody>
      {% for object in object_list %}
      <tr>
      <td>{{object.id}}</td>
      <td><a href="{{object.get_absolute_url}}">{{object.title}}</a></td>
      <td>{{object.formatcreated}}</td>
      <td>{{object.formatupdated}}</td>
      {% if user.is_authenticated %}
      <th><a href="{% url 'post:post_update' object.id %}">update</a></th>
      <th><a href="{% url 'post:post_delete' object.id %}">delete</a></th>
      {% endif %}
      </tr>
      {% endfor %}
      </tbody>
      </table>

      {% if is_paginated %}
      <nav aria-label="Page navigation">
      <ul class="pagination justify-content-center">
      {% if page_obj.has_previous %}
      <li class="page-item"><a class="page-link" href="?page={{page_obj.previous_page_number}}">Previous</a>
      </li>
      {% else %}
      <li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
      {% endif %}
      {% for page in paginator.page_range %}
      <li class="page-item"><a class="page-link" href="?page={{page}}">{{page}}</a></li>
      {% endfor %}
      {% if page_obj.has_next %}
      <li class="page-item"><a class="page-link" href="?page={{page_obj.next_page_number}}">Next</a></li>
      {% else %}
      <li class="page-item disabled"><a class="page-link" href="#">Next</a></li>
      {% endif %}
      </ul>
      </nav>
      {% endif %}
      </div>
      <div class="col"></div>
      </div>
      {% endblock %}
    • post/templates/post/category_list.html

      1
      2
      3
      4
      5
      <div class="accordion" id="category_list">
      {% for category in categories %}
      {% include 'post/part_category_list.html' %}
      {% endfor %}
      </div>
    • post/templates/post/part_category_list.html

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      <div {% if parent_id %} class="collapse card category_sub_{{parent_id}}" {% else %} class="card" {% endif %}>
      <div class="card-header" style="padding:0;">
      <h2 class="mb-0">
      {% if parent_id %}
      <a class="btn btn-link" href="{{category.get_absolute_url}}">
      {{category}}
      </a>
      {% else %}
      <button class="btn btn-link" type="button" data-toggle="collapse"
      data-target=".category_sub_{{category.id}}" aria-expanded="false">
      {{category}}
      </button>
      {% endif %}
      </h2>
      </div>
      </div>
      <!-- related_name을 쓰지 않았기 때문에 [default_name]_set으로 불러온다. -->
      {% if category.category_set %}
      <!-- category.id를 parent_id로 사용 -->
      {% with category.id as parent_id %}
      {% for category in category.category_set.all %}
      {% include 'post/part_category_list.html' %}
      {% endfor %}
      {% endwith %}
      {% endif %}
    • post/templates/post/post_detail.html

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      {% extends 'base.html' %}

      {% block title %}
      <!-- 커스텀 템플릿 태그 -->
      <!-- truncatechars : 문자열을 지정 글자 개수까지 제한 -->
      {{object.title|truncatechars:15}}
      {% endblock %}

      {% block content %}
      <div class="row mt-3">
      <div class="col">
      {% include 'post/category_list.html' %}
      </div>
      <div class="col-8">
      {{object.text|safe}}

      Tags :
      </div>
      <div class="col"></div>
      </div>

      {% endblock %}
  • 커스텀 템플릿 태그 만들기

    • 커스텀 탬플릿 태그를 만들렸면 templatetags 폴더를 만들어야 한다.
    • templatetags에 작성한 파일 이름으로 템플릿 파일에서 불러올 수 있다.
    • 템플릿 라이브러리 변수를 만든다.

      1
      2
      3
      4
      5
      # templatetags/exam.py

      from django import template

      register = template.Library()
    • 필터를 등록할 때는 다음과 같이 사용한다.

      1
      2
      3
      4
      5
      # templatetags/exam.py

      @ register.filter
      def add_two(value):
      return value + 2
    • 등록한 필터는 다음과 같이 사용한다.

      1
      2
      {% load exam %}
      {{변수|add_two}}
    • 추가 인자가 있는 필터는 다음과 같이 사용한다.

      1
      2
      3
      4
      5
      # templatetags/exam.py

      @register.filter
      def string_append(left, right):
      return left+"-"+right
    • 등록한 필터는 다음과 같이 사용한다.

      1
      2
      {% load exam %}
      {{'string1'|string_append:'string2'}}
    • 태그를 등록할 때는 다음과 같이 사용한다.

      1
      2
      3
      @register.simple_tag
      def print_template():
      return render_to_string('exam/test.html')
    • 등록한 태그는 다음과 같이 사용한다.

      1
      2
      3
      4
      5
      6
      7
      {% load exam %}
      {% print_template %}

      <!-- 태그 결과를 변수로 지정할 경우 -->
      {% load exam %}
      {% print_template as test %}
      {{test}}
Share