3.功能开发
3.3 设计博客首页
博客首页中最重要的就是中间的文章区域,所以我首先把文章区域设计出来,一开始并没有数据,如果用orm添加或者直接在数据库添加数据较为麻烦,这时候需要用到django提供的admin,在地址栏输入127.0.0.1:8000/admin,会看见如下的页面:
这时候输入超级用户的账号和密码,超级用户通过终端输入python manage.py createsuperuser
进行添加,登录过后是会看见如下页面:
接下来在这个页面进行数据的添加,需要注意是添加的顺序,首先应该添加User表,加下来分别是Blog表、Sort表、Tag表,最后是Article表以及Article to tag表,评论表和点赞表可以暂时不填。添加完数据以后就开始设计博客主界面了。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>博客首页</title>
<link rel="icon" href="/static/blog/image/favicon.ico">
<link rel="stylesheet" href="/static/blog/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/blog/css/index.css">
</head>
<body>
<!--顶部导航栏-->
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="/index">博客园</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="#">新闻</a></li>
<li><a href="#">博文</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if request.user.is_authenticated %}
<li><a href="#"><img src="/static/blog/image/user_log.png" id="user_log">{{ request.user.username }}
</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">用户中心 <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">修改密码</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">修改头像</a></li>
<li role="separator" class="divider"></li>
<li><a href="/logout">注销</a></li>
</ul>
</li>
{% else %}
<li><a href="/register">注册</a></li>
<li><a href="/login">登录</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<!-- 主体内容 -->
<div class="container-fluid">
<div class="row">
<!-- 左侧区域 -->
<div class="col-md-3">
<div class="panel panel-default">
<div class="panel-heading">Panel heading without title</div>
<div class="panel-body">
Panel content
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Panel title</h3>
</div>
<div class="panel-body">
Panel content
</div>
</div>
</div>
<!-- 中间文章区域 -->
<div class="col-md-6">
<div class="article_list">
{% for article in article_list %}
<div class="article_item">
<h5><a>{{ article.title }}</a></h5>
<div class="article_desc media-body">
<span class="media-left">
<a href="/{{ article.user.username }}"><img src="/media/{{ article.user.avatar }}" id="user_avatar"></a>
</span>
<span class="media-right">{{ article.abstract }}</span>
</div>
<div class="article_foot small">
<span><a href="/{{ article.user.username }}">{{ article.user.username }}</a></span>
<span>发布于 {{ article.create_time|date:"Y-m-d H:i"}} </span>
<span><a><img src="/static/blog/image/comment.png">评论({{ article.comment_count }})</a> </span>
<span><a><img src="/static/blog/image/like.png">点赞({{ article.comment_count }})</a></span>
</div>
</div>
<hr>
{% endfor %}
</div>
</div>
<!-- 右侧区域 -->
<div class="col-md-3">1</div>
</div>
</div>
</body>
<script type="text/javascript" src="/static/blog/js/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="/static/blog/js/bootstrap.min.js"></script>
</html>
index.css
a:hover {
cursor: pointer;
text-decoration: none;
}
.login-head {
margin-bottom: 10px;
border-bottom: 1px solid #ddd;
}
.login-head a {
margin-left: 287px;
color: #5cb85c;
}
#avatar_img {
width: 60px;
height: 60px;
margin-left: 20px;
}
.error {
color: red;
}
#user_log {
margin-right: 5px;
}
#user_avatar {
width: 60px;
height: 60px;
}
.article_foot {
margin-top: 10px;
}
.article_foot img {
vertical-align: -3px;
margin-right: 2px;
}
views.py
# 博客首页
def index(request):
article_list = Article.objects.all().order_by("-create_time")
return render(request, "index.html", locals())
# 注销
def logout(request):
auth.logout(request)
return redirect("/login")
urls.py
urlpatterns = [
path('index/', index),
]
展示效果:
3.4 设计个人站点首页
由于个人站点以及后面的文章详情页他们的不同知识文章区域,所以为了避免重复的代码,首先设计一个模板页面,将需要用到的数据先规划好,个人站点需要名字、圆龄、随笔分类以及对应文章数量、标签分类以及对应的文章数量、时间分类以及对应的文章数量以及文章列表。
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ user.username }} - 博客园</title>
<link rel="icon" href="/static/blog/image/favicon.ico">
<link rel="stylesheet" href="/static/blog/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="/static/blog/css/home_site.css">
<style type="text/css">
a:hover {
cursor: pointer;
text-decoration: none;
}
.panel-body a:hover {
color: red;
}
</style>
</head>
<body>
<div id="home">
<div id="header">
<div id="blogTitle">
<h1><a href="/{{ user.username }}">{{ user.username }}</a></h1>
</div>
<div id="navigator">
<ul id="navList">
<li><a href="/index">博客园</a></li>
<li><a href="/{{ username }}">首页</a></li>
<li><a>管理</a></li>
</ul>
</div>
</div>
<div id="main">
<div class="col-md-3">
{% load my_tags %}
{% get_style username %}
</div>
<div class="col-md-9">
{% block content %}
{% endblock %}
</div>
</div>
</div>
</body>
</html>
首先介绍一下{% load my_tags %} {% get_style username %}
在开发中,如果遇到视图与数据相结合作为模板的时候,可以使用inclusion_tag(xxx)
,它接受一个参数,这个参数是你需要渲染的html代码。步骤大概如下:
在项目中创建一个名为templatetags的包(名字必须是这个)
在templatetags的包中创建存放自定义标签的py文件
my_tags.py
from django import template from django.db.models import Count from django.db.models.functions import TruncMonth import datetime from blog.models import * register = template.Library() @register.inclusion_tag("get_style.html") # get_style.html 即数据和视图相结合的Html部分 def get_style(username): user = User.objects.filter(username=username).first() if user: blog = user.blog now = datetime.datetime.now() both = user.create_time day = (now - both).days age = int(day / 30) # 园龄 # 当前站点的每一个分类名称以及文章数 sort_list = Sort.objects.filter(blog=blog).values("sid").annotate( c=Count("article__aid")).values_list("title", "c") # 当前站点每个标签名称以及文章数 tag_list = Tag.objects.filter(blog=blog).values("tid").annotate( c=Count("article__aid")).values_list("name", "c") # 当前站点每一个年月的名称以及文章数 # 方式一 # 当用到较为复杂的sql查询语句时(例如日期的阶段,大小的比较),需要用到extra(select="")方法 # date_list = Article.objects.filter(user=user).extra( # select={"Y_m_date": "date_format(create_time, '%%Y年%%c月')"}).values("Y_m_date").annotate( # c=Count("aid")).values_list("Y_m_date", "c") # 方式二,使用django封装的TruncMonth()方法,它会将日期截断到月,抛弃后面的,接下来只需要在html中通过date标签来格式化日期格式就OK了 """ Article.objects.filter(user=user) .annotate(Y_m_date=TruncMonth("create_time")) 将日期截断至月份并加入到字段中 .values("Y_m_date") group by Y_m_date .annotate(c=Count("aid")) select count(aid) as c .values_list("Y_m_date", "c") select Y_m_date, c """ date_list = Article.objects.filter(user=user) \ .annotate(Y_m_date=TruncMonth("create_time")) \ .values("Y_m_date") \ .annotate(c=Count("aid")) \ .values_list("Y_m_date", "c") return {"user": user, "blog": blog, "sort_list": sort_list, "tag_list": tag_list, "date_list": date_list, "age": age} # 将所需数据存放在字典中传递给html部分
get_style.html
<div id="user_info"> 昵称:{{ user.username }}<br> 园龄:{{ age }}个月 </div> <div class="panel panel-primary"> <div class="panel-heading">随笔分类</div> {% for sort in sort_list %} <div class="panel-body"> <a href="/{{ user.username }}/sort/{{ sort.0 }}">{{ sort.0 }}({{ sort.1 }})</a> </div> {% endfor %} </div> <div class="panel panel-primary"> <div class="panel-heading">我的标签</div> {% for tag in tag_list %} <div class="panel-body"> <a href="/{{ user.username }}/tag/{{ tag.0 }}/">{{ tag.0 }}({{ tag.1 }})</a> </div> {% endfor %} </div> <div class="panel panel-primary"> <div class="panel-heading">文章归档</div> {% for date in date_list %} <div class="panel-body"> <a href="/{{ user.username }}/date/{{ date.0|date:"Y-n" }}/">{{ date.0|date:"Y年n月" }}({{ date.1 }})</a> </div> {% endfor %} </div>
home_site.html
{% extends "base.html" %} {% block content %} {% for article in article_list %} <div class="article_item"> <div class="article_date"> {{ article.create_time|date:"Y月n月d日" }} </div> <div class="article_title"> <a>{{ article.title }}</a> </div> <div class="article_desc"> <span class="media-right">摘要:{{ article.abstract }}</span> </div> <div class="article_foot"> posted @ {{ article.create_time|date:"Y-m-d H:i" }} {{ user.username }} 评论({{ article.comment_count }}) 点赞({{ article.up_count }}) </div> </div> {% endfor %} {% endblock %}
home_site.css
* { margin: 0; padding: 0; } body { background: url('/static/blog/image/web_site_bk.jpg') fixed no-repeat; background-position: 50% 5%; background-size: cover; font-family: inherit; } #home { margin: 0 auto; width: 80%; min-width: 980px; background-color: rgba(245, 245, 245, 0.7); padding: 30px; margin-top: 50px; margin-bottom: 50px; box-shadow: 0 2px 6px rgba(100, 100, 100, 0.3); overflow: hidden; } a { text-decoration: none; } a:visited, a:link { color: black; } #blogTitle { margin-bottom: 25px; } #blogTitle h1 { font-size: 36px; font-weight: bold; line-height: 1.8em; margin-top: 10px; margin-left: 35px; } #blogTitle a:hover { color: #ff4001; } #navigator { background-color: #009ACD; height: 60px; line-height: 60px; overflow: hidden; clear: both; float: left; } #navList { min-height: 30px; float: left; margin-right: 579px; } #navList li { float: left; list-style: none; } #navList li:hover { background-color: #343434; } #navList a { display: block; padding: 0 1.5em; height: 60px; float: left; font-size: 1.2em; text-align: center; transition-duration: 0.3s; color: #eee; } #navList a:hover { text-decoration: none; } #blogStats { float: right; padding-right: 10px; text-align: right; color: #eee; } #main { min-width: 950px; text-align: left; padding: 20px 0 0 10px; overflow: hidden; } #user_avatar { width: 60px; height: 60px; margin-left: 20px; } .article_item { min-height: 10px; box-shadow: 1px 1px 2px #A7A8AD; color: #666666; margin: 0 5px 60px 0; padding: 5px 20px 10px; background: rgba(255, 255, 255, 0.5) } .article_title { border-left: 8px solid rgba(33, 160, 139, 0.68); margin-left: 10px; margin-bottom: 10px; font-size: 20px; float: right; width: 100%; clear: both; font-weight: bold; border-bottom: 1px dashed #ccc; line-height: 2.5em; padding-left: 10px; } .article_title a:hover { margin-left: 20px; } .article_desc { margin-top: 70px; } .article_date { width: 100%; color: black; line-height: 2.2em; font-size: 22px; clear: both; border-bottom: 1px solid #ccc; text-align: center; } .article_foot { color: #757575; width: 100%; clear: both; text-align: left; font-size: 13px; margin-top: 20px; line-height: 1.8; padding-bottom: 20px; } #user_info { border-radius: 7px; box-shadow: 1px 1px 2px #A7A8AD; color: black; padding: 15px 15px 10px; background: rgba(255, 255, 255, 0.5); margin-bottom: 30px; } .panel { background: rgba(255, 255, 255, 0.5); margin-bottom: 30px; } .panel-heading { font-size: 20px; } .panel-body { border-bottom: 1px solid white; }
views.py
def home_site(request, username, **kwargs):
"""
个人站点
:param request:
:param username:
:return:
"""
user = User.objects.filter(username=username).first()
if user:
article_list = Article.objects.filter(user=user)
# 当前的站点下的所有文章
# 基于对象查询 article_list = user.article_set.all()
# 基于双下划线查询
if kwargs:
condition = kwargs.get("condition")
parm = kwargs.get("parm")
if condition == "sort":
article_list = article_list.filter(sort__title=parm)
elif condition == "tag":
article_list = article_list.filter(tag__name=parm)
elif condition == "date":
year = parm.split("-")[0]
month = parm.split("-")[1]
article_list = article_list.filter(create_time__year=year, create_time__month=month)
article_list = article_list.order_by("-create_time")
return render(request, "home_site.html", locals())
else:
return render(request, "404.html")
urls.py
urlpatterns = [
# 个人站点查询
re_path(r'^(?P<username>\w+)/$', home_site),
# 个人站点跳转
re_path(r'^(?P<username>\w+)/(?P<condition>sort|tag|date)/(?P<parm>.*)/$', home_site),
]
展示效果:
它的顺序是:
- 在自定义的标签中,首先会执行代码,将数据传递给
inclusion_tag(xxx)
参数中的html,即get_style.html - get_style.html根据传来的数据完成自身的渲染
- base.html中调用
inclusion_tag
标签的部分完成渲染,即添加get_style.html内容 - 个人站点页面继承模板页面,最终呈现
注意:
- 利用inclusion_tag完成数据与视图的结合
- 个人站点url的跳转(例如点击分类会出现当代当前分类的所有文章)主要通过给每个分类/标签/时间添加a标签,指向url,而在视图函数中通过增加参数来完成筛选,将筛选后的文章列表传递给html
- 当只需要用到datetime的个别元素,例如年月,可以使用django的Trunc函数,例如TruncMonth只会截断到月,剩余的日时分秒不会被取到
- 避免过多的重复代码
实现效果: