스터디 목표
마크다운을 사용해 블로그의 게시물을 쉽게 작성하고, 사이트맵을 통해 블로그를 직관적으로 확인할 수 있도록 기능을 추가한다. 더하여 블로그 게시물용 피드를 만들고 검색 기능을 추가해 장고를 마무리한다.
from django.utils.safestring import mark_safe
import markdown
@register.filter(name='markdown')
def markdown_format(text):
return mark_safe(markdown.markdown(text))
{% extends "blog/base.html" %}
{% load blog_tags %}
{% block title %}{{ post.title }}{% endblock %}
{% block content %}
<h1>{{ post.title }}</h1>
<p class = "date">
Published {{ post.publish }} by {{ post.author }}
</p>
{{ post.body|markdown }}
...
{% extends "blog/base.html" %}
{% block title %} My blog {% endblock %}
{% load blog_tags %}
{% block content %}
<h1>My blog</h1>
{% if tag %}
<h2>Posts tagged with "{{ tag.name }}"</h2>
{% endif %}
<form method="get" class="search-form">
{% csrf_token %}
<input type="text" name="q" placeholder="Search" class="search-input">
<button type="submit" class="search-button">Search</button>
</form>
{% if query %}
{% if posts %}
<h2>Posts containing "{{ query }}"</h2>
<h3>Found result</h3>
{% for post in posts %}
<h4>
<a href="{{ post.get_absolute_url }}">
{{ post.title }}
</a>
</h4>
{{ post.body|markdown|truncatewords:30|linebreaks }}
{% empty %}
<p>There are no results for your query.</p>
{% endfor %}
{% else %}
<p>No posts found for your search query.</p>
{% endif %}
{% else %}
{% for post in posts %}
<h2>
<a href="{{ post.get_absolute_url }}">
{{ post.title }}
</a>
</h2>
<p class="tags">
Tags:
{% for tag in post.tags.all %}
<a href="{% url 'blog:post_list_by_tag' tag.slug %}">
{{ tag.name }}
</a>
{% if not forloop.last %}, {% endif %}
{% endfor %}
</p>
<p class="date">
Published {{ post.publish }} by {{ post.author }}
</p>
{{ post.body | markdown | truncatewords_html:30 }}
{% endfor %}
{% include "pagination.html" with page=posts %}
{% endif %}
{% endblock %}
SITE_ID = 1
INSTALLED_APPS = [
...,
"django.contrib.sites",
"django.contrib.sitemaps",
]
from django.contrib.sitemaps import Sitemap
from .models import Post
class PostSitemap(Sitemap):
changefreq = 'weekly'
priority = 0.9
def items(self_):
return Post.published.all()
def lastmod(self, obj):
return obj.updated
from django.contrib.sitemaps.views import sitemap
from .sitemaps import PostSitemap
from .feeds import LatestPostFeed
sitemaps = {
'posts':PostSitemap,
}
urlpatterns = [
...,
path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
path('feed/', LatestPostFeed(), name='post_feed'),
]
import markdown
from django.contrib.syndication.views import Feed
from django.template.defaultfilters import truncatewords_html
from django.urls import reverse_lazy
from .models import *
from django.utils.feedgenerator import DefaultFeed
class PostTypeFeed(DefaultFeed):
content_type = 'application/xml; charset=utf-8'
class LatestPostFeed(Feed) :
title = 'My blog'
link = reverse_lazy('blog:post_list')
description = 'New posts of my blog.'
feed_type = PostTypeFeed
def items(self):
return Post.published.all()[:5]
def item_title(self, item):
return item.title
def item_description(self, item):
return truncatewords_html(markdown.markdown(item.body), 30)
def item_pubdate(self, item):
return item.publish
<p>
<a href="{% url 'blog:post_feed' %}">
Subscribe to my RSS feed
</a>
</p>
<h3>Latest posts</h3>
from django.db.models import Count, Q
def post_list(request, tag_slug=None):
post_list = Post.published.all() # 모든 발행된 게시물 목록 가져옴
tag = None # 초기에는 태그가 지정되지 않았으므로 None으로 설정
if tag_slug: # 특정 태그가 선택된 경우
tag = get_object_or_404(Tag, slug=tag_slug) # 지정된 슬러그를 가진 태그를 가져옴
post_list = post_list.filter(tags__in=[tag]) # 해당 태그에 속하는 게시물들로 필터링
search_query = request.GET.get('q')
if search_query:
post_list = post_list.filter(
Q(title__icontains=search_query) | Q(body__icontains=search_query)
)
if not tag_slug and not search_query and not post_list.exists():
messages.info(request, 'No posts found.')
paginator = Paginator(post_list, 3) # 한 페이지에 3개의 포스트가 있도록 설정
page_number = request.GET.get('page', 1) # page 매개변수가 요청의 GET 파라미터에 없을 경우 기본 값으로 1을 사용
try:
posts = paginator.page(page_number) # 현재 페이지 번호에 해당하는 포스트들을 가져오기
except PageNotAnInteger:
posts = paginator.page(1) # 정수가 아닌 경우 첫 번째 페이지를 다시 염
except EmptyPage:
posts = paginator.page(paginator.num_pages) # 요청한 페이지가 범위를 벗어나면 마지막 페이지 반환
return render(request, 'blog/post/list.html', {'posts': posts, 'tag': tag, 'query': search_query})
/* 검색 폼 스타일 */
.search-form{
margin-bottom: 20px;
}
.search-input{
width: 200px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 14px;
}
.search-button{
padding: 10px 20px;
background-color: #007bff;
border: none;
color: white;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
.search-button:hover{
background-color: #0056b3;
}
실행 결과
Terminal에 python manage.py runserver 명령을 입력하여 출력 결과를 확인한다.
오답노트
모르는 코드 부분을 정확하게 이해하고 정리한다.
문제 :
python manage.py runserver를 하였을 때 실행 오류가 발생했다.
문제의 원인 :
데이터베이스의 마이그레이션이 제대로 적용되지 않아 발생하는 오류이다.
'migrate' 명령어를 실행하지 않으면 장고가 모델을 기반으로 데이터베이스 스키마를 업데이트할 수 없어 필요한 테이블이 생성되지 않는다.
해결 방법 :
모델을 변경하거나 새로 추가하면 해당 변경 사항을 반영하는 마이그레이션 파일을 생성해야 한다. 또한 생성된 마이그레이션 파일을 데이터베이스에 적용하여 스키마를 업데이트한다.
python manage.py makemigrations
python manage.py migrate
해당 명령어를 작성하여 해결한다.
문제 :
장고 프로젝트에서 css가 적용되지 않는 문제가 발생했다.
문제의 원인 :
브라우저가 이전에 캐시한 css 파일을 사용하고 있어 새로운 css 파일이 로드되지 않아 생긴 오류이다.
해결 방법 :
브라우저의 인터넷 사용 기록(캐시)을 지우면 최신 css 파일이 로드되어 해결할 수 있다.