该系列文章
django搭建博客一前言
django搭建博客二之初始化工程
django搭建博客三初始化应用
django搭建博客四页面布局和自定义异常视图
django搭建博客五系统模型建立与注册
django搭建博客六邮件模板和邮件工具类
django搭建博客七用户注册
django搭建博客八用户登录
django搭建博客九用户密码重置
django搭建博客十博客首页
文章应用模型建立
编辑\myblog\article\models.py
内容如下:
import logging
import os
from django.db import models
from django.db.models import F
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.conf import settings
from django.utils.html import strip_tags
from django.contrib.auth import get_user_model
from mdeditor.fields import MDTextField
from system.models import BaseModel
logger = logging.getLogger('django')
User = get_user_model()
class Category(BaseModel):
name = models.CharField(verbose_name='类别名称', max_length=20, unique=True)
slug = models.SlugField(verbose_name="分类slug", unique=True)
level = models.IntegerField(verbose_name="分类层级", default=1)
parent = models.ForeignKey('self', verbose_name='父级导航', related_name='parent_category', null=True, blank=True,
on_delete=models.CASCADE, help_text="父级导航可以为空",
limit_choices_to={"parent__isnull": True})
show = models.BooleanField(verbose_name="是否显示", default=True)
icon = models.CharField(verbose_name="导航图标", max_length=30, help_text="目前导航图标仅支持font-awesome", null=True,
blank=True)
order = models.IntegerField(verbose_name="序号", help_text="序号越大的越靠前", default=1)
class Meta:
db_table = "article_category"
verbose_name = '文章分类'
verbose_name_plural = verbose_name
ordering = ['-order']
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if self.parent:
self.level = self.parent.level + 1
super(Category, self).save(*args, **kwargs)
class Tag(BaseModel):
name = models.CharField(verbose_name='标签名', max_length=20, unique=True)
slug = models.SlugField(verbose_name="标签slug", unique=True)
show = models.BooleanField(verbose_name="是否显示", default=True)
class Meta:
db_table = "article_tag"
verbose_name = '标签'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
def get_article_count(self):
return self.tag_article.count()
class Article(BaseModel):
slug = models.SlugField(verbose_name="文章slug", unique=True)
title = models.CharField(max_length=500, verbose_name='文章标题')
summary = models.TextField(verbose_name='文章摘要', help_text="系统自动生成摘要", null=True, blank=True)
content = MDTextField(verbose_name="内容", null=True, blank=True)
cover = models.ImageField(verbose_name="文章封面", null=True, blank=True)
views = models.IntegerField(verbose_name='浏览数', default=0, editable=False)
loves = models.IntegerField(verbose_name='点赞数', default=0, editable=False)
category = models.ForeignKey(Category, verbose_name='分类', on_delete=models.DO_NOTHING,
related_name="category_article", limit_choices_to={'level__gt': 1},
db_constraint=False, null=True, blank=True)
tags = models.ManyToManyField(Tag, verbose_name='标签', related_name='tag_article', blank=True,
db_table="sys_article_tag")
show = models.BooleanField(verbose_name="是否显示", default=True)
can_comment = models.BooleanField(verbose_name="允许评论", default=True)
auto_summary = models.BooleanField(verbose_name="自动生成摘要", help_text="如果不勾选,则不会自动生成摘要信息", default=True)
place_top = models.BooleanField(verbose_name="是否置顶", default=False)
class Meta:
db_table = "article"
verbose_name = "文章"
verbose_name_plural = verbose_name
ordering = ["-place_top", "-create_time"]
def __str__(self):
return self.title[:20]
def get_previous_article(self):
previous_article = Article.objects.only("id", "slug").filter(id__lt=self.id).last()
return previous_article
def get_next_article(self):
next_article = Article.objects.only("id", "slug").filter(id__gt=self.id).last()
return next_article
def increase_views(self):
Article.objects.filter(id=self.id).update(views=F('views') + 1)
def increase_loves(self):
Article.objects.filter(id=self.id).update(loves=F('loves') + 1)
def get_tag_list(self):
return self.tags.all()
def show_image(self):
url = os.path.join(settings.MEDIA_URL, self.cover.url)
return mark_safe("<img src='{url}' width=75></a>".format(url=url))
show_image.short_description = "封面"
def save(self, *args, **kwargs):
if self.auto_summary:
summary = strip_tags(self.content).replace(" ", "").replace("\n", "").replace(" ", " ")
self.summary = summary[:200]
super(Article, self).save(*args, **kwargs)
admin后台配置
新增\myblog\article\admin.py
内容如下
from django.contrib.admin import register
from article.models import Article, Tag, Category
from system.admin import BaseModelAdmin
@register(Category)
class CategoryModelAdmin(BaseModelAdmin):
list_display = ['id', 'name', 'slug', 'show', 'parent', 'order']
list_filter = ["show", "level"]
search_fields = ['name', 'slug']
list_editable = ['name', 'slug', 'show', 'order']
@register(Tag)
class TagModelAdmin(BaseModelAdmin):
list_display = ['id', 'name', 'slug', 'show']
search_fields = ['name', 'slug']
list_editable = ['name', 'show']
@register(Article)
class ArticleModelAdmin(BaseModelAdmin):
actions = ["batch_show_action", "batch_hide_action"]
list_display = ['title', 'category', 'views', 'loves', 'can_comment', 'show', 'place_top','show_image']
list_filter = ['title', 'category', 'tags', 'show']
search_fields = ['title', 'content']
list_editable = ['show', 'place_top', 'can_comment']
list_per_page = 10
list_order_field = ["views", "loves"]
filter_horizontal = ("tags",)
preserve_filters = ("category",)
def batch_show_action(self, queryset):
for obj in queryset:
obj.show = True
obj.save()
return None
batch_show_action.short_description = "批量显示所选的"
def batch_hide_action(self, queryset):
for obj in queryset:
obj.show = False
obj.save()
return None
batch_hide_action.short_description = "批量隐藏所选的"
def queryset(self):
"""控制用户只能查看自己的文章(除了超级管理员)"""
qs = super().queryset()
if not self.request.user.is_superuser:
qs = qs.filter(creator=self.request.user)
return qs
meditor路由配置
编辑\myblog\myblog\urls.py
由于在Article模型里面用到了富文本编辑器应用django-meditor,所以这里需要在urlpatterns列表里面添加以下内容
path(r'mdeditor/', include('mdeditor.urls')),
后台效果图
文章分类效果图
文章标签效果图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ibnEjIsX-1656230280008)(assets/1656127970807.png)]
文章效果图
文章编辑效果图
自定义文章模板标签
新增\myblog\article\templatetags模块文件夹
新增\myblog\article\templatetags\article_tags.py文件
添加如下内容
from django import template
from article.models import Article, Tag
import logging
logger = logging.getLogger('django')
register = template.Library()
@register.simple_tag
def get_article_date_list():
article_date_list = Article.objects.only("id", "slug", "title").filter(show=True).datetimes('create_time', 'month',
order='DESC')
return article_date_list
@register.simple_tag
def get_article_by_date(year, month):
article_list = Article.objects.only("id", "slug", "title").filter(show=True, create_time__year=year,
create_time__month=month)
return article_list
@register.simple_tag
def get_tag_list():
"""返回标签列表"""
from django.db.models import Count
tag_list = Tag.objects.annotate(article_nums=Count('tag_article')).filter(article_nums__gt=0).order_by('name')
return tag_list
@register.simple_tag
def get_new_article_list():
return Article.objects.only("id", "slug", "title").filter(show=True).order_by('-create_time')[:5]
@register.simple_tag
def get_hot_article_list():
return Article.objects.only("id", "slug", "title").filter(show=True).order_by('-views')[:5]
自定义模板上下文
编辑\myblog\system\template_context_processors.py
import logging
from system.models import User
logger = logging.getLogger("django")
def template_context(request):
superuser = User.objects.filter(is_superuser=True).first()
context = {"superuser": superuser}
return context
编辑\myblog\myblog\settings.py,修改TEMPLATES注册自己定义的上下文处理器
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'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',
# 把media全局注册进template模板中
'django.template.context_processors.media',
'system.template_context_processors.template_context',
],
},
},
]
首页控件
文章分页控件
编写\myblog\templates\layout\pagination.html
添加以下内容
{% load system_tags %}
{% if is_paginated %}
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center my-3 pagination-lg">
{% if page_obj.has_previous %}
<li class="page-item">
<a href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% endif %}
{% get_elided_page_range paginator page_obj.number as page_list %}
{% for page in page_list %}
{% ifequal page page_obj.number %}
<li class="page-item active"><span>{{ page }}</span></li>
{% else %}
{% ifequal page page_obj.paginator.ELLIPSIS %}
<li class="page-item"><span>{{ page }}</span></li>
{% else %}
<li class="page-item"><a href="?page={{ page }}&q={{ query }}">{{ page }}</a></li>
{% endifequal %}
{% endifequal %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a href="?page={{ page_obj.next_page_number }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
效果图
文章列表控件
编写\myblog\templates\article\article-query-list.html
添加以下内容
{% for article in articles %}
<article class="d-block bg-white hvr-float">
<div class="media d-block">
<div class="article-thumb">
<a href="#"><img src="{{ MEDIA_URL }}{{ article.cover }}" class="mr-3"></a>
</div>
<div class="media-body py-3 pl-1 overflow-hidden">
<div class="index-article-header">
<a href="#" class="label ">{{ article.category.name }}<i
class="position-absolute c-arrow"></i></a>
<h2 class="d-inline article-title"><a href="#"
class="text-dark">{{ article.title }}</a></h2>
{% if article.place_top %}<span class="new-icon">置顶</span>{% endif %}
</div>
<div class="article-summary">
<p class="text-wrap text-secondary">
{{ article.summary|truncatechars:170 }}{% if article.summary|length > 150 %}
...{% endif %}</p>
</div>
</div>
<div class="article-relative-meta">
<span class="float-left">
<a href="#"><i class="fa fa-calendar c4"></i>{{ article.create_time|date:'Y年m月d日' }}</a>
<a href="#"><i
class="fa fa-comments c5"></i>{{ 0 }}评论</a>
<a href="#"><i class="fa fa-eye c6"></i>{{ article.views|default:0 }}阅读</a>
</span>
<span class="visible-xs float-left">
<a href="#"><i class="fa fa-heart c7"></i>{{ article.loves|default:0 }}喜欢</a>
<a {% if article.creator.link %}href="{{ article.creator.link }}"{% endif %}><i
class="fa fa-user c8"></i>{% firstof article.creator.nickname article.creator.username %}</a>
</span>
<span class="float-right">
<a class="mr-0" href="#" title="阅读全文">阅读全文<i class="fa fa-chevron-circle-right"></i></a>
</span>
</div>
</div>
</article>
{% empty %}
<div class="d-block bg-white mb-3 text-center"
style="width: 100%;font-size: 18px;font-weight: bold;padding-top: 15%;height: 300px;">暂时还没有发布的文章!
</div>
{% endfor %}
效果图
首页侧边栏控件
编写\myblog\templates\layout\aside\page_aside.html
添加以下内容
<aside class="col-lg-4 col-xl-4 hidden-xs hidden-sm" id="aside">
<div class="sidebar" id="sidebar">
{% include 'layout/aside/sentence.html' %}
{% include 'layout/aside/aboutme.html' %}
{% include 'layout/aside/recommendation-article.html' %}
{% include 'layout/aside/article-tag.html' %}
</div>
</aside>
侧边栏每日一句控件
编写\myblog\templates\layout\aside\sentence.html
添加以下内容
{% load system_tags %}
<aside class="mb-2">
<div class="sentence">
<strong>随机一句</strong>
<h2>{% now "Y年m月d日" %}</h2>
<p>己所不欲,勿施于人。</p>
</div>
</aside>
效果图
侧边栏关于我控件
编写\myblog\templates\layout\aside\aboutme.html
添加以下内容
{% load static %}
<aside class="card about mb-2">
<div class="card-bg"></div>
<div class="card-body text-center p-0">
{% if request.user.is_authenticated %}
<img class="xwcms" src="{{ MEDIA_URL }}{{ request.user.avatar }}"/>
<h5 class="card-title text-dark mt-2">{% firstof request.user.nickname request.user.username %}</h5></a>
<p class="card-text mb-2"
style="font-size: 18px;font-family: STXingkai;">{{ request.user.introduction|default:'' }}</p>
<p>
<a href="#" class="text-reset "><i class="fa fa-user-circle"></i>修改资料</a>
<a href="#" class="text-reset "><i class="fa fa-key"></i>修改密码</a>
<a href="{% url 'sys:logout' %}" class="text-reset "><i class="fa fa-sign-out"></i>退出登录</a>
</p>
{% else %}
<a href="#"><img class="xwcms"
src="{{ MEDIA_URL }}{{ superuser.avatar }}"></a>
<a href="#" class=""><h5
class="card-title text-dark mt-2 superuser">{% firstof superuser.nickname superuser.username %}</h5>
</a>
<p class="card-text mb-2"
style="font-size: 18px;font-family: STXingkai;">{{ superuser.introduction|default:'' }}</p>
<div class="contact-me text-center">
<a id="link_weixin" href="javascript:;" rel="nofollow" data-toggle="popover" data-trigger="hover click"
data-html="true" data-title="公众号"
data-content="<img src='{% static '/images/weixin_link.jpg' %}' width='150px' height='150px'>"><i
class="fa fa-weixin" aria-hidden="true"></i></a>
<a target="_blank" href="https://github.com/tongyuebin" rel="nofollow"><i class="fa fa-github"
aria-hidden="true"></i></a>
<a target="_blank" href="http://mail.qq.com/cgi-bin/qm_share?t=qm_mailme&email={{ superuser.email }}"
rel="nofollow"><i class="fa fa-envelope-o" aria-hidden="true"></i></a>
<a id="link_qq" href="javascript:;" rel="nofollow" data-toggle="popover" data-title="博主QQ"
data-trigger="hover click" data-html="true"
data-content="<img src='{% static '/images/qq_link.jpg' %}' width='150px' height='150px'>"><i
class="fa fa-qq" aria-hidden="true"></i></a>
</div>
</div>
<div class="text-center" style="border-top: 1px solid #ddd;">
<a id="bookmark" class="bookmark button--primary button--animated" href="javascript:;" title="加入书签"
target="_self"><i class="fa fa-bookmark" aria-hidden="true"></i><span class="pl-2">加入书签</span></a>
</div>
{% endif %}
</aside>
未登录效果图
登陆后效果图
侧边栏文章推荐控件
编写\myblog\templates\layout\aside\recommendation-article.html
添加以下内容
{% load article_tags %}
{% get_new_article_list as newArticles %}
{% get_hot_article_list as hotArticles %}
{% if newArticles or hotArticles %}
<aside class="mb-2 py-2 pl-3 recommendation tab-6">
<ul class="nav nav-tabs" role="tablist">
{% if newArticles %}
<li class="nav-item">
<a class="nav-link border-0 text-center text-dark active" data-toggle="tab" href="#new" role="tab"
aria-controls="new">最新文章</a>
</li>
{% endif %}
{% if hotArticles %}
<li class="nav-item">
<a class="nav-link border-0 text-center text-dark" data-toggle="tab" href="#hot" role="tab"
aria-controls="hot">最热文章</a>
</li>
{% endif %}
</ul>
<div class="tab-content">
<div class="tab-pane active" id="new" role="tabpanel">
<ul class="list-group list-group-flush">
{% for newArticle in newArticles %}
<a class="pl-0 list-group-item text-truncate text-dark " title="{{ newArticle.title }}"
href="#">
<i class="fa fa-book"></i>
{{ newArticle.title }}
</a>
{% endfor %}
</ul>
</div>
<div class="tab-pane" id="hot" role="tabpanel">
<ul class="list-group list-group-flush">
{% for hotArticle in hotArticles %}
<a class="pl-0 list-group-item text-truncate text-dark " title="{{ hotArticle.title }}"
href="#">
<i class="fa fa-book"></i>
{{ hotArticle.title }}
</a>
{% endfor %}
</ul>
</div>
</div>
</aside>
{% endif %}
效果图
侧边栏文章标签控件
新增\myblog\templates\layout\aside\article-tag.html
内容如下
{% load system_tags %}
{% load article_tags %}
{% get_tag_list as tags %}
{% if tags %}
<aside class="mb-2 py-2 pl-3 aside-article-tag tab-6">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link border-0 text-center text-dark active" data-toggle="tab" href="#aside-article-tag"
role="tab" aria-controls="aside-article-tag">标签云</a>
</li>
</ul>
<div class="tab-content pt-2">
<div class="tab-pane active" id="aside-article-tag" role="tabpanel">
<!--标签云-->
<div class="tags btn-group-sm d-inline-block">
{% for tag in tags %}
<a class="btn btn-sm border rounded-pill" href="{% url 'article:tag' tag.slug %}"
data-toggle="tooltip" title="该标签下有{{ tag.article_nums }}篇文章">{{ tag.name }}</a>
{% endfor %}
</div>
</div>
</div>
</aside>
{% endif %}
效果图
首页模板
新增\myblog\templates\index.html
{% extends 'base.html' %}
{% load static %}
{% block title %}
{{ title|default:'首页' }}
{% endblock %}
{% block main %}
<div class="container">
<div class="row">
<section class="col-sm-12 col-md-12 col-lg-8 index" id="main">
{% include 'article/article-query-list.html' %}
{% include 'layout/pagination.html' %}
</section>
{% include 'layout/page_aside.html' %}
</div>
</div>
{% endblock %}
首页视图
编写\myblog\article\views.py
添加以下内容
from django.http import Http404
import logging
from django.views.generic import ListView
from article.models import Article, Tag, Category
logger = logging.getLogger('django')
class ArticleListView(ListView):
template_name = 'index.html'
context_object_name = 'articles'
paginate_by = 10
page_kwarg = 'page'
http_method_names = ['get']
model = Article
queryset = Article.objects.only("id",
"slug",
"title",
"cover",
"summary",
"category__name",
"category_id",
"place_top",
"create_time",
"views",
"loves",
"creator__username")
def get_queryset(self):
qs = super(ArticleListView, self).get_queryset()
qs = qs.filter(**self.kwargs)
if not self.request.user.is_superuser:
qs = qs.filter(show=True)
return qs
def get_context_data(self, **kwargs):
title = "首页"
try:
if 'tags__slug' in self.kwargs.keys(): # 按标签分类文章
slug = self.kwargs.get('tags__slug')
tag = Tag.objects.get(slug=slug)
title = "文章标签归档:" + tag.name
elif 'category__slug' in self.kwargs.keys():
slug = self.kwargs.get('category__slug')
category = Category.objects.get(slug=slug)
title = "文章分类归档:" + category.name
elif 'create_time__year' in self.kwargs.keys() and 'create_time__month' in self.kwargs.keys(): # 日期分类文章
title = "文章日期归档:" + str(self.kwargs.get("create_time__year")) + "-" + str(
self.kwargs.get("create_time__month"))
kwargs.update({"title": title})
except Exception as e:
logger.info(e)
raise Http404()
return super(ArticleListView, self).get_context_data(**kwargs)
路由绑定
编辑\myblog\article\urls.py
添加以下内容
from django.urls import path
from article import views
urlpatterns = [
path('category/<slug:category__slug>', views.ArticleListView.as_view(), name='category'),
path('tag/<slug:tags__slug>', views.ArticleListView.as_view(), name='tag'),
path('date/<int:create_time__year>/<int:create_time__month>', views.ArticleListView.as_view(), name='date'),
]
编辑\myblog\myblog\urls.py
添加以下内容
from article.views import ArticleListView
path('', ArticleListView.as_view(), name="index"),
首页整体效果图
未登录效果图
登陆后效果图