一、个人主页

随笔分类

需求:查询当前站点每一个分类的名称以及对应的文章数

完成这个需求,就可以展示左侧的分类

它需要利用分组查询,那么必须要会基于双下划线的查询。

基于双下划线的查询,简单来讲,就是用join。将多个表拼接成一张表,那么就可以单表操作了!

表关系图

图中箭头开始的英文字母表示关联字段

按照箭头方向查询,表示正向查询,否则为反向查询

分解步骤:

先来查询每一个分类的名称以及对应的文章数

看上面的关系图,以Category表为基础表来查询Article表对应的文章数,需要用到反向查询。

记住一个原则,正向查询使用字段,反向查询使用表名

修改views.py,导入相关表和聚合函数

from django.shortcuts import render,HttpResponse,redirect
from django.contrib import auth
from blog.models import Article,UserInfo,Blog,Category,Tag
from django.db.models import Sum,Avg,Max,Min,Count
# Create your views here.
def login(request):if request.method=="POST":user=request.POST.get("user")pwd=request.POST.get("pwd")# 用户验证成功,返回user对象,否则返回Noneuser=auth.authenticate(username=user,password=pwd)if user:# 登录,注册session# 全局变量 request.user=当前登陆对象(session中)
            auth.login(request,user)return redirect("/index/")return render(request,"login.html")def index(request):article_list=Article.objects.all()return render(request,"index.html",{"article_list":article_list})def logout(request):  # 注销
    auth.logout(request)return redirect("/index/")def homesite(request,username):"""查询:param request::param username::return:"""# 查询当前站点的用户对象user=UserInfo.objects.filter(username=username).first()if not user:return render(request,"not_found.html")# 查询当前站点对象blog=user.blog# 查询当前用户发布的所有文章article_list=Article.objects.filter(user__username=username)ret = Category.objects.values("pk").annotate(c=Count("article__title")).values("title","c")print(ret)dict = {"blog":blog,"article_list":article_list,}return render(request,"homesite.html",dict)

View Code

解释:

pk表示主键

上面这句sql表示以Category表id来分组,得到分类名以及统计数

多添加几篇文章,给另外一个用户也添加几篇文章

访问个人站点:http://127.0.0.1:8000/xiao/

查看Pycharm控制台输出:

<QuerySet [{'c': 1, 'title': 'python'}, {'c': 1, 'title': 'ajax'}, {'c': 2, 'title': 'django'}, {'c': 2, 'title': 'linux运维'}]>

上面得到了所有文章的分类以及文章数。

再来查询当前站点每一个分类的名称以及对应的文章数

思路:只需要对Category表进行筛选,过滤中当前站点用户的分类

在homesite视图函数中,已经有一个当前站点的blog对象。

在Category模型表中,有一个blog属性,它和blog是一对多关系。那么只需要blog=blog,就可以了

修改视图函数homesite的ret变量

ret = Category.objects.filter(blog=blog).annotate(c=Count("article__title")).values("title","c")

注意:等式左边的blog表示Category模型表的blog属性,实际上就是blog_id字段

等式右边的是blog变量,它是一个model对象。那么blog=blog,就可以查询出,当前站点的分类了!

刷新网页,查看Pycharm控制台输出:

<QuerySet [{'title': 'python', 'c': 1}, {'title': 'ajax', 'c': 1}, {'title': 'django', 'c': 2}]

既然结果出来了,模板就可以渲染了

修改homesite视图函数,完整代码如下:

from django.shortcuts import render,HttpResponse,redirect
from django.contrib import auth
from blog.models import Article,UserInfo,Blog,Category,Tag
from django.db.models import Sum,Avg,Max,Min,Count
# Create your views here.
def login(request):if request.method=="POST":user=request.POST.get("user")pwd=request.POST.get("pwd")# 用户验证成功,返回user对象,否则返回Noneuser=auth.authenticate(username=user,password=pwd)if user:# 登录,注册session# 全局变量 request.user=当前登陆对象(session中)
            auth.login(request,user)return redirect("/index/")return render(request,"login.html")def index(request):article_list=Article.objects.all()return render(request,"index.html",{"article_list":article_list})def logout(request):  # 注销
    auth.logout(request)return redirect("/index/")def homesite(request,username):"""查询:param request::param username::return:"""# 查询当前站点的用户对象user=UserInfo.objects.filter(username=username).first()if not user:return render(request,"not_found.html")# 查询当前站点对象blog=user.blogprint(blog,type(blog))# 查询当前用户发布的所有文章article_list=Article.objects.filter(user__username=username)cate_list = Category.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")print(cate_list)dict = {"blog":blog,"article_list":article_list,"cate_list":cate_list}return render(request,"homesite.html",dict)

View Code

修改homesite.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><style>* {margin: 0;padding: 0;}.header {width: 100%;height: 59px;background-color: #369;
        }.header .title {line-height: 59px;color: white;font-weight: lighter;margin-left: 20px;font-size: 18px;}.left_region {margin-top: 10px;}.info {margin-top: 10px;color: darkgray;}h5 a {color: #105cb6;font-size: 14px;font-weight: bold;text-decoration: underline;}</style><link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"><script src="/static/js/jquery.js"></script><script src="/static/bootstrap/js/bootstrap.js"></script>
</head>
<body>
<div class="header"><p class="title">{{ blog.title }}</p>
</div><div class="container-fluid"><div class="row"><div class="col-md-3"><div class="left_region"><div class="panel panel-success"><div class="panel-heading"><h3 class="panel-title">随笔分类</h3></div><div class="panel-body">{% for cate in cate_list %}<p><a href="">{{ cate.0 }}({{ cate.1 }})</a></p>{% endfor %}</div></div><div class="panel panel-warning"><div class="panel-heading"><h3 class="panel-title">我的标签</h3></div><div class="panel-body">Panel content</div></div><div class="panel panel-info"><div class="panel-heading"><h3 class="panel-title">日期归档</h3></div><div class="panel-body">Panel content</div></div></div></div><div class="col-md-9"><div class="article_list">{% for article in article_list %}<div class="article_item clearfix"><h5><a href="">{{ article.title }}</a></h5><div><span class="small desc ">{{ article.desc }}</span></div><div class="info small pull-right">发布于 <span>{{ article.create_time|date:'Y-m-d H:i' }}</span>&nbsp;&nbsp;<img src="/static/img/icon_comment.gif" alt=""><ahref="">评论({{ article.comment_count }})</a>&nbsp;&nbsp;<span class="glyphicon glyphicon-thumbs-up"></span><a href="">点赞({{ article.up_count }})</a></div></div><hr>{% endfor %}</div></div></div>
</div></body>
</html>

View Code

values_list返回的是一个元组,所以模板中,直接用cate.0就可以取到分类名

刷新网页,效果如下:

我的标签

我的标签和随笔的查询语句是类似的,换一个表名,就可以了!

先在admin后台为不同的用户,添加标签

由于admin后台无法直接将博客表和标签表做对应关系,所以只能手动绑定关系。

使用navicat打开blog_article2tag表

注意:以实际情况为准

修改homesite视图函数,查询我的标签

def homesite(request,username):"""查询:param request::param username::return:"""# 查询当前站点的用户对象user=UserInfo.objects.filter(username=username).first()if not user:return render(request,"not_found.html")# 查询当前站点对象blog=user.blogprint(blog,type(blog))# 查询当前用户发布的所有文章article_list=Article.objects.filter(user__username=username)#随笔分类cate_list = Category.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")print(cate_list)#我的标签tag_list = Tag.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")print(tag_list)dict = {"blog":blog,"article_list":article_list,"cate_list":cate_list,"tag_list":tag_list,}return render(request,"homesite.html",dict)

View Code

刷新网页,查看Pycharm控制台

<QuerySet [('python全栈', 2)]>

修改homesite.html,开始渲染网页

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><style>* {margin: 0;padding: 0;}.header {width: 100%;height: 59px;background-color: #369;
        }.header .title {line-height: 59px;color: white;font-weight: lighter;margin-left: 20px;font-size: 18px;}.left_region {margin-top: 10px;}.info {margin-top: 10px;color: darkgray;}h5 a {color: #105cb6;font-size: 14px;font-weight: bold;text-decoration: underline;}</style><link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"><script src="/static/js/jquery.js"></script><script src="/static/bootstrap/js/bootstrap.js"></script>
</head>
<body>
<div class="header"><p class="title">{{ blog.title }}</p>
</div><div class="container-fluid"><div class="row"><div class="col-md-3"><div class="left_region"><div class="panel panel-success"><div class="panel-heading"><h3 class="panel-title">随笔分类</h3></div><div class="panel-body">{% for cate in cate_list %}<p><a href="">{{ cate.0 }}({{ cate.1 }})</a></p>{% endfor %}</div></div><div class="panel panel-warning"><div class="panel-heading"><h3 class="panel-title">我的标签</h3></div><div class="panel-body">{% for tag in tag_list %}<p><a href="">{{ tag.0 }}({{ tag.1 }})</a></p>{% endfor %}</div></div><div class="panel panel-info"><div class="panel-heading"><h3 class="panel-title">日期归档</h3></div><div class="panel-body">Panel content</div></div></div></div><div class="col-md-9"><div class="article_list">{% for article in article_list %}<div class="article_item clearfix"><h5><a href="">{{ article.title }}</a></h5><div><span class="small desc ">{{ article.desc }}</span></div><div class="info small pull-right">发布于 <span>{{ article.create_time|date:'Y-m-d H:i' }}</span>&nbsp;&nbsp;<img src="/static/img/icon_comment.gif" alt=""><ahref="">评论({{ article.comment_count }})</a>&nbsp;&nbsp;<span class="glyphicon glyphicon-thumbs-up"></span><a href="">点赞({{ article.up_count }})</a></div></div><hr>{% endfor %}</div></div></div>
</div></body>
</html>

View Code

刷新网页,效果如下:

注意:如果网页数据没有展示,请一定要查看数据库是否有对应的记录!

日期归档

查看Article表的create_time字段

注意:它的时间后面,有很多小数点。每一条是不一样的,所以不能直接分组,否则没有意义!

要实现这个功能,有3个小知识点:

  • 1.dateformat
  • 2.extra
  • 3.单表分组查询

dateformat

DATE_FORMAT() 函数用于以不同的格式显示日期/时间数据。

每个数据库都有日期/时间 处理的函数,在MySQL中,叫dateformat。SQLite中,叫strftime

举例:

#截取年月日
mysql> select date_format("2018-07-11 06:39:07",'%Y-%m-%d') as date;
+------------+
| date       |
+------------+
| 2018-07-11 |
+------------+
1 row in set (0.00 sec)#截取年月
mysql> select date_format("2018-07-11 06:39:07",'%Y-%m') as date;
+---------+
| date    |
+---------+
| 2018-07 |
+---------+
1 row in set (0.00 sec)

View Code

extra

有些情况下,Django的查询语法难以表达复杂的where子句,对于这种情况, Django 提供了extra()QuerySet修改机制 。它能在QuerySet生成的SQL从句中注入新子句,extra可以指定一个或多个参数,例如select、where或tables。 这些参数都不是必须的,但是至少要使用一个。

语法:

extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)

select参数

select参数可以在select从句中添加其他字段信息,它应该是一个字典,存放着属性名到 SQL 从句的映射。

举例:

修改homesite视图函数,增加几行代码

def homesite(request,username):"""查询:param request::param username::return:"""# 查询当前站点的用户对象user=UserInfo.objects.filter(username=username).first()if not user:return render(request,"not_found.html")# 查询当前站点对象blog=user.blogprint(blog,type(blog))# 查询当前用户发布的所有文章article_list=Article.objects.filter(user__username=username)#随笔分类cate_list = Category.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")print(cate_list)#我的标签tag_list = Tag.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")print(tag_list)#测试日期test_date = Article.objects.filter(comment_count=0).extra(select={'y_m_date': "create_time > '2017-09-05'"})print(test_date)for i in test_date:print(i.y_m_date)dict = {"blog":blog,"article_list":article_list,"cate_list":cate_list,"tag_list":tag_list,}return render(request,"homesite.html",dict)

View Code

大概意思就是,查询创建时间大于2017-09-05的记录

刷新网页,查看Pycharm控制台输出:

1
1
1

如果条件成立,返回1。否则返回0

需要注意的是:此时已经给Article表增加一个临时字段y_m_date。它在内存中,每次使用extra查询才会存在!

单表分组查询

查询当前用户的所有文章,根据日期归档

def homesite(request,username):"""查询:param request::param username::return:"""# 查询当前站点的用户对象user=UserInfo.objects.filter(username=username).first()if not user:return render(request,"not_found.html")# 查询当前站点对象blog=user.blogprint(blog,type(blog))# 查询当前用户发布的所有文章article_list=Article.objects.filter(user__username=username)#随笔分类cate_list = Category.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")print(cate_list)#我的标签tag_list = Tag.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")print(tag_list)#日期归档date_list = Article.objects.filter(user=user).extra(select={"y_m_date": "strftime('%%Y/%%m',create_time)"}).values("y_m_date").annotate(c=Count("title")).values_list("y_m_date", "c")print(date_list)dict = {"blog":blog,"article_list":article_list,"cate_list":cate_list,"tag_list":tag_list,"date_list":date_list,}return render(request,"homesite.html",dict)

View Code

解释:

SQLite的日期格式化使用strftime,它使用2个%号来区分。

user=user  等式左边的user是Article模型表的user属性,也就是user_id。等式右边的user是UserInfo表的model对象!

刷新页面,查看Pychram控制台输出:

<QuerySet [('2018/07', 4)]>

修改homesite.html,开始渲染网页

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><style>* {margin: 0;padding: 0;}.header {width: 100%;height: 59px;background-color: #369;
        }.header .title {line-height: 59px;color: white;font-weight: lighter;margin-left: 20px;font-size: 18px;}.left_region {margin-top: 10px;}.info {margin-top: 10px;color: darkgray;}h5 a {color: #105cb6;font-size: 14px;font-weight: bold;text-decoration: underline;}</style><link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"><script src="/static/js/jquery.js"></script><script src="/static/bootstrap/js/bootstrap.js"></script>
</head>
<body>
<div class="header"><p class="title">{{ blog.title }}</p>
</div><div class="container-fluid"><div class="row"><div class="col-md-3"><div class="left_region"><div class="panel panel-success"><div class="panel-heading"><h3 class="panel-title">随笔分类</h3></div><div class="panel-body">{% for cate in cate_list %}<p><a href="">{{ cate.0 }}({{ cate.1 }})</a></p>{% endfor %}</div></div><div class="panel panel-warning"><div class="panel-heading"><h3 class="panel-title">我的标签</h3></div><div class="panel-body">{% for tag in tag_list %}<p><a href="">{{ tag.0 }}({{ tag.1 }})</a></p>{% endfor %}</div></div><div class="panel panel-info"><div class="panel-heading"><h3 class="panel-title">日期归档</h3></div><div class="panel-body">{% for date in date_list %}<p><a href="">{{ date.0 }}({{ date.1 }})</a></p>{% endfor %}</div></div></div></div><div class="col-md-9"><div class="article_list">{% for article in article_list %}<div class="article_item clearfix"><h5><a href="">{{ article.title }}</a></h5><div><span class="small desc ">{{ article.desc }}</span></div><div class="info small pull-right">发布于 <span>{{ article.create_time|date:'Y-m-d H:i' }}</span>&nbsp;&nbsp;<img src="/static/img/icon_comment.gif" alt=""><ahref="">评论({{ article.comment_count }})</a>&nbsp;&nbsp;<span class="glyphicon glyphicon-thumbs-up"></span><a href="">点赞({{ article.up_count }})</a></div></div><hr>{% endfor %}</div></div></div>
</div></body>
</html>

View Code

刷新网页,效果如下:

左侧面板添加链接

接下来,需要点击左边的分类、标签、归档,显示相关的文章

访问博客园左侧的分类、标签、归档,方法它有一个规律

标签:

http://www.cnblogs.com/用户名/tag/标签名/

分类:

https://www.cnblogs.com/用户名/category/分类id.html

归档:

https://www.cnblogs.com/用户名/archive/年/月.html

修改urls.py,增加3个路径。注意要导入re_path模块

re_path('(?P<username>\w+)/category/(?P<params>.*)', views.homesite),
re_path('(?P<username>\w+)/tag/(?P<params>.*)', views.homesite),
re_path('(?P<username>\w+)/achrive/(?P<params>.*)', views.homesite),

仔细观察个人站点网页的布局

发现,点击不同的分类、标签、归档。红色区域和绿色区域始终不变,只有紫色区域在变动。变动区域取决于article_list变量!

那么个人站点首页、分类、标签、归档这4种url可以共用一个视图函数homesite模板以及视图函数。

重新修改urls.py,完整代码如下:

from django.contrib import admin
from django.urls import path,re_pathfrom blog import  views
urlpatterns = [path('admin/', admin.site.urls),path('login/', views.login),path('index/', views.index),path('logout/', views.logout),path('', views.index),# 跳转re_path('(?P<username>\w+)/(?P<condition>category|tag|achrive)/(?P<params>.*)/$', views.homesite),# 个人站点re_path('(?P<username>\w+)/$', views.homesite),
]

View Code

那么问题来了,访问个人站点时,不需要额外的参数。

访问分类/标签/归档 这2个类别是,必须要2个额外的变量。分别是类别、类别参数。

homesite视图函数,如果分别接收呢?答案是,使用**kwargs,它可以接收可变的关键字参数,至少1个或者多个参数!

修改homesite.html,增加一个网页图标,否则待会测试时,会有2次请求。

如果网页没有图标,每次会请求一次网络请求,请求favicon.ico

在title标签下面,增加一行

<link rel="shortcut icon" href="https://common.cnblogs.com/favicon.ico" type="image/x-icon" />

修改homesite视图函数

def homesite(request,username,**kwargs):"""查询:param request::param username::return:"""print("kwargs", kwargs)# 查询当前站点的用户对象user=UserInfo.objects.filter(username=username).first()if not user:return render(request,"not_found.html")# 查询当前站点对象blog=user.blog# 查询当前用户发布的所有文章article_list=Article.objects.filter(user__username=username)#随笔分类cate_list = Category.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")# print(cate_list)#我的标签tag_list = Tag.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")# print(tag_list)#日期归档date_list = Article.objects.filter(user=user).extra(select={"y_m_date": "strftime('%%Y/%%m',create_time)"}).values("y_m_date").annotate(c=Count("title")).values_list("y_m_date", "c")# print(date_list)
dict = {"blog":blog,"article_list":article_list,"cate_list":cate_list,"tag_list":tag_list,"date_list":date_list,}return render(request,"homesite.html",dict)

View Code

访问个人站点: http://127.0.0.1:8000/xiao/

Pycharm控制台输出:kwargs {}

访问个人分类python: http://127.0.0.1:8000/xiao/category/python/

Pycharm控制台输出:kwargs {'params': 'python/', 'condition': 'category'}

访问个人标签: http://127.0.0.1:8000/xiao/tag/python全栈/

Pycharm控制台输出:kwargs {'params': 'python全栈/', 'condition': 'tag'}

访问个人归档: http://127.0.0.1:8000/xiao/achrive/2018/07

Pycharm控制台输出:kwargs {'params': '2018/07', 'condition': 'achrive'}

注意:要带上用户名,否则出现404错误

那么,只需要判断kwargs变量,就可以区分了!

修改homesite视图函数

def homesite(request,username,**kwargs):"""查询:param request::param username::return:"""print("kwargs", kwargs)# 查询当前站点的用户对象user=UserInfo.objects.filter(username=username).first()if not user:return render(request,"not_found.html")# 查询当前站点对象blog=user.blog# 查询当前用户发布的所有文章if not kwargs:article_list = Article.objects.filter(user__username=username)else:condition = kwargs.get("condition")params = kwargs.get("params")#判断分类、随笔、归档if condition == "category":article_list = Article.objects.filter(user__username=username).filter(category__title=params)elif condition == "tag":article_list = Article.objects.filter(user__username=username).filter(tags__title=params)else:year, month = params.split("/")article_list = Article.objects.filter(user__username=username).filter(create_time__year=year,create_time__month=month)#随笔分类cate_list = Category.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")# print(cate_list)#我的标签tag_list = Tag.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")# print(tag_list)#日期归档date_list = Article.objects.filter(user=user).extra(select={"y_m_date": "strftime('%%Y/%%m',create_time)"}).values("y_m_date").annotate(c=Count("title")).values_list("y_m_date", "c")# print(date_list)
dict = {"blog":blog,"article_list":article_list,"cate_list":cate_list,"tag_list":tag_list,"date_list":date_list,"username":username,}return render(request,"homesite.html",dict)

View Code

修改homesite.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><link rel="shortcut icon" href="https://common.cnblogs.com/favicon.ico" type="image/x-icon"/><style>* {margin: 0;padding: 0;}.header {width: 100%;height: 59px;background-color: #369;
        }.header .title {line-height: 59px;color: white;font-weight: lighter;margin-left: 20px;font-size: 18px;}.left_region {margin-top: 10px;}.info {margin-top: 10px;color: darkgray;}h5 a {color: #105cb6;font-size: 14px;font-weight: bold;text-decoration: underline;}</style><link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"><script src="/static/js/jquery.js"></script><script src="/static/bootstrap/js/bootstrap.js"></script>
</head>
<body>
<div class="header"><p class="title">{{ blog.title }}</p>
</div><div class="container-fluid"><div class="row"><div class="col-md-3"><div class="left_region"><div class="panel panel-success"><div class="panel-heading"><h3 class="panel-title">随笔分类</h3></div><div class="panel-body">{% for cate in cate_list %}<p><a href="/{{ username }}/category/{{ cate.0 }}">{{ cate.0 }}({{ cate.1 }})</a></p>{% endfor %}</div></div><div class="panel panel-warning"><div class="panel-heading"><h3 class="panel-title">我的标签</h3></div><div class="panel-body">{% for tag in tag_list %}<p><a href="/{{ username }}/tag/{{ tag.0 }}">{{ tag.0 }}({{ tag.1 }})</a></p>{% endfor %}</div></div><div class="panel panel-info"><div class="panel-heading"><h3 class="panel-title">日期归档</h3></div><div class="panel-body">{% for date in date_list %}<p><a href="/{{ username }}/achrive/{{ date.0 }}">{{ date.0 }}({{ date.1 }})</a></p>{% endfor %}</div></div></div></div><div class="col-md-9"><div class="article_list">{% for article in article_list %}<div class="article_item clearfix"><h5><a href="">{{ article.title }}</a></h5><div><span class="small desc ">{{ article.desc }}</span></div><div class="info small pull-right">发布于 <span>{{ article.create_time|date:'Y-m-d H:i' }}</span>&nbsp;&nbsp;<img src="/static/img/icon_comment.gif" alt=""><ahref="">评论({{ article.comment_count }})</a>&nbsp;&nbsp;<span class="glyphicon glyphicon-thumbs-up"></span><a href="">点赞({{ article.up_count }})</a></div></div><hr>{% endfor %}</div></div></div>
</div></body>
</html>

View Code

访问个人站点:http://127.0.0.1:8000/xiao/

效果如下:

主题切换

查看blog_blog表,有2条记录

theme对应主题的css文件

在homesite.html中的style标签定义了一些样式。现在需要分离出来!

每一个用户有自己的标题颜色,比如xiao用默认的蓝色,zhang用绿色

在static中新建css目录,在css中新建文件夹theme,新建3个css文件,其中common.css是公共样式!

common.css

* {margin: 0;padding: 0;
}.header {width: 100%;height: 59px;background-color: #369;
}.header .title {line-height: 59px;color: white;font-weight: lighter;margin-left: 20px;font-size: 18px;
}.left_region {margin-top: 10px;
}.info {margin-top: 10px;color: darkgray;}h5 a {color: #105cb6;font-size: 14px;font-weight: bold;text-decoration: underline;
}

View Code

xiao.css

.header {width: 100%;height: 59px;background-color: #369;
}

View Code

zhang.css

.header {width: 100%;height: 59px;background-color: green;
}

View Code

修改homesite.html,修改head部分,完整代码如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><link rel="shortcut icon" href="https://common.cnblogs.com/favicon.ico" type="image/x-icon"/>{#公共样式#}<link rel="stylesheet" href="/static/css/theme/common.css">{#个人站点主题样式#}<link rel="stylesheet" href="/static/css/theme/{{ blog.theme }}"><link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"><script src="/static/js/jquery.js"></script><script src="/static/bootstrap/js/bootstrap.js"></script>
</head>
<body>
<div class="header"><p class="title">{{ blog.title }}</p>
</div><div class="container-fluid"><div class="row"><div class="col-md-3"><div class="left_region"><div class="panel panel-success"><div class="panel-heading"><h3 class="panel-title">随笔分类</h3></div><div class="panel-body">{% for cate in cate_list %}<p><a href="/{{ username }}/category/{{ cate.0 }}">{{ cate.0 }}({{ cate.1 }})</a></p>{% endfor %}</div></div><div class="panel panel-warning"><div class="panel-heading"><h3 class="panel-title">我的标签</h3></div><div class="panel-body">{% for tag in tag_list %}<p><a href="/{{ username }}/tag/{{ tag.0 }}">{{ tag.0 }}({{ tag.1 }})</a></p>{% endfor %}</div></div><div class="panel panel-info"><div class="panel-heading"><h3 class="panel-title">日期归档</h3></div><div class="panel-body">{% for date in date_list %}<p><a href="/{{ username }}/achrive/{{ date.0 }}">{{ date.0 }}({{ date.1 }})</a></p>{% endfor %}</div></div></div></div><div class="col-md-9"><div class="article_list">{% for article in article_list %}<div class="article_item clearfix"><h5><a href="">{{ article.title }}</a></h5><div><span class="small desc ">{{ article.desc }}</span></div><div class="info small pull-right">发布于 <span>{{ article.create_time|date:'Y-m-d H:i' }}</span>&nbsp;&nbsp;<img src="/static/img/icon_comment.gif" alt=""><ahref="">评论({{ article.comment_count }})</a>&nbsp;&nbsp;<span class="glyphicon glyphicon-thumbs-up"></span><a href="">点赞({{ article.up_count }})</a></div></div><hr>{% endfor %}</div></div></div>
</div></body>
</html>

View Code

使用谷歌浏览器登录xiao的用户,进入个人主页

使用火狐浏览器登录zhang的用户

进入个人主页,发现标题颜色没有换过来

进入admin后台,点击users表,找到zhang用户,发现它没有绑定个人站点。

因为使用命令创建用户时,blog_id字段,默认为空!

手动绑定一下

再次刷新页面,效果如下:

文章详情

由于文章详情页,功能繁多,必须专门做一个视图才行。

修改urls.py,增加路径article_detail

urlpatterns = [path('admin/', admin.site.urls),path('login/', views.login),path('index/', views.index),path('logout/', views.logout),path('', views.index),#文章详情re_path('(?P<username>\w+)/articles/(?P<article_id>\d+)/$', views.article_detail),# 跳转re_path('(?P<username>\w+)/(?P<condition>category|tag|achrive)/(?P<params>.*)/$', views.homesite),# 个人站点re_path('(?P<username>\w+)/$', views.homesite),
]

View Code

由于文章详情页的左测和标题部分是通用的,需要用到模板继承

模板继承

新建base.html,将homesite.html的代码复制过来,删除多余的部分。增加block

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>{#公共样式#}<link rel="stylesheet" href="/static/css/theme/common.css">{#个人站点主题样式#}<link rel="stylesheet" href="/static/css/theme/{{ blog.theme }}">{#bootstrap#}<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"><script src="/static/js/jquery.js"></script><script src="/static/bootstrap/js/bootstrap.js"></script><link rel="shortcut icon" href="https://common.cnblogs.com/favicon.ico" type="image/x-icon" />
</head>
<body>
<div class="header"><p class="title">{{ blog.title }}</p>
</div><div class="container-fluid"><div class="row"><div class="col-md-3"><div class="left_region"><div class="panel panel-success"><div class="panel-heading"><h3 class="panel-title">随笔分类</h3></div><div class="panel-body">{% for cate in cate_list %}<p><a href="/{{ username }}/category/{{ cate.0 }}">{{ cate.0 }}({{ cate.1 }})</a></p>{% endfor %}</div></div><div class="panel panel-warning"><div class="panel-heading"><h3 class="panel-title">我的标签</h3></div><div class="panel-body">{% for tag in tag_list %}<p><a href="/{{ username }}/tag/{{ tag.0 }}">{{ tag.0 }}({{ tag.1 }})</a></p>{% endfor %}</div></div><div class="panel panel-info"><div class="panel-heading"><h3 class="panel-title">日期归档</h3></div><div class="panel-body">{% for date in date_list %}<p><a href="/{{ username }}/achrive/{{ date.0 }}">{{ date.0 }}({{ date.1 }})</a></p>{% endfor %}</div></div></div></div><div class="col-md-9">{% block content %}{% endblock %}</div></div>
</div></body>
</html>

View Code

修改homesite.html

{% extends "base.html" %}{% block content %}<div class="article_list">{% for article in article_list %}<div class="article_item clearfix"><h5><a href="/{{ username }}/articles/{{ article.pk }}">{{ article.title }}</a></h5><div><span class="small desc ">{{ article.desc }}</span></div><div class="info small pull-right">发布于 <span>{{ article.create_time|date:'Y-m-d H:i' }}</span>&nbsp;&nbsp;<span class="glyphicon glyphicon-comment"></span><a href="">评论({{ article.comment_count }})</a>&nbsp;&nbsp;<span class="glyphicon glyphicon-thumbs-up"></span><a href="">点赞({{ article.up_count }})</a></div></div><hr>{% endfor %}</div>
{% endblock %}

View Code

增加article_detail.html

{% extends "base.html" %}{% block content %}<div class="article_info"><h4 class="text-center">{{ article_obj.title }}</h4><div class="content">{{ article_obj.content }}</div></div>{% endblock %}

View Code

修改article_detail视图函数

def article_detail(request,username,article_id):user = UserInfo.objects.filter(username=username).first()# 查询当前站点对象blog = user.blog# 查询指定id的文章article_obj=Article.objects.filter(pk=article_id).first()# 随笔分类cate_list = Category.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")# print(cate_list)# 我的标签tag_list = Tag.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")# print(tag_list)# 日期归档date_list = Article.objects.filter(user=user).extra(select={"y_m_date": "strftime('%%Y/%%m',create_time)"}).values("y_m_date").annotate(c=Count("title")).values_list("y_m_date", "c")# print(date_list)
dict = {"blog": blog,"article_obj": article_obj,"cate_list": cate_list,"tag_list": tag_list,"date_list": date_list,"username": username,}return render(request,'article_detail.html',dict)

View Code

刷新网页,点击左侧的一个分类,效果如下:

点击右边的一篇文章

关于内容部分,为什么是html标签。这些暂时不处理,后面会讲到如何处理!

查看article_detail和homesite 这2个视图函数,有重复的代码。在编程的过程中,最好不要出现重复代码,怎么办呢?使用函数封装!

函数封装

修改views.py,增加函数get_query_data。删掉article_detail和homesite 这2个视图函数中的重复代码,完整代码如下:

from django.shortcuts import render,HttpResponse,redirect
from django.contrib import auth
from blog.models import Article,UserInfo,Blog,Category,Tag
from django.db.models import Sum,Avg,Max,Min,Count
# Create your views here.
def login(request):if request.method=="POST":user=request.POST.get("user")pwd=request.POST.get("pwd")# 用户验证成功,返回user对象,否则返回Noneuser=auth.authenticate(username=user,password=pwd)if user:# 登录,注册session# 全局变量 request.user=当前登陆对象(session中)
            auth.login(request,user)return redirect("/index/")return render(request,"login.html")def index(request):article_list=Article.objects.all()return render(request,"index.html",{"article_list":article_list})def logout(request):  # 注销
    auth.logout(request)return redirect("/index/")def get_query_data(request,username):user = UserInfo.objects.filter(username=username).first()# 查询当前站点对象blog = user.blog# 随笔分类cate_list = Category.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")# print(cate_list)# 我的标签tag_list = Tag.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")# print(tag_list)# 日期归档date_list = Article.objects.filter(user=user).extra(select={"y_m_date": "strftime('%%Y/%%m',create_time)"}).values("y_m_date").annotate(c=Count("title")).values_list("y_m_date", "c")# print(date_list)
dict = {"blog": blog,"cate_list": cate_list,"tag_list": tag_list,"date_list": date_list,"username": username,}return dict #返回字典def homesite(request,username,**kwargs):"""查询:param request::param username::return:"""print("kwargs", kwargs)# 查询当前站点的用户对象user=UserInfo.objects.filter(username=username).first()if not user:return render(request,"not_found.html")# 查询当前站点对象blog=user.blog# 查询当前用户发布的所有文章if not kwargs:article_list = Article.objects.filter(user__username=username)else:condition = kwargs.get("condition")params = kwargs.get("params")#判断分类、随笔、归档if condition == "category":article_list = Article.objects.filter(user__username=username).filter(category__title=params)elif condition == "tag":article_list = Article.objects.filter(user__username=username).filter(tags__title=params)else:year, month = params.split("/")article_list = Article.objects.filter(user__username=username).filter(create_time__year=year,create_time__month=month)dict = get_query_data(request,username)  #调用函数dict['article_list'] = article_list  # 增加一个keyreturn render(request,"homesite.html",dict)def article_detail(request,username,article_id):content_text = get_query_data(request,username)  #调用函数#查询指定id的文章article_obj = Article.objects.filter(pk=article_id).first()content_text['article_obj'] = article_obj  # 增加一个keyreturn render(request,'article_detail.html',content_text)

View Code

注意:get_query_data必须要在2个视图函数的上面,否则无法调用!

重新访问网页,效果如下:

封装函数,有一个局限性,如果新增变量,需要增加字典key-value。由于继承模板时,变量是不会继承的。所以引用的视图函数,必须重新传值才可以渲染。那么可不可以,将模板和数据包装成一个模板,作为一个整体。其他模板继承时,就是一个已经渲染过的模板呢?

答案是有的,那就是inclusion_tag

包含标签(Inclusion tags)

Django过滤器和标签功能很强大,而且支持自定义标签,很是方便;其中一种标签是Inclusion tags,即包含标签
包含标签(Inclusion tags)通过渲染其他的模板来展示内容,这类标签的用途在于一些相似的内容的展示,并且返回的内容是渲染其他模板得到的内容。

自定义标签必须在应用名目录下创建templatetags目录。注意:此目录名必须叫这个名字,不可改变。

在templatetags目录下,创建my_tags.py,这个文件名,是可以随便的

先来增加一个乘法的标签

from django import templateregister=template.Library()@register.simple_tag
def mul_tag(x,y):return x*y

View Code

修改article_detail.html,调用这个自定义标签

{% extends "base.html" %}{% block content %}{% load my_tags %}<p>{% mul_tag 2 7 %}</p><div class="article_info"><h4 class="text-center">{{ article_obj.title }}</h4><div class="content">{{ article_obj.content }}</div></div>{% endblock %}

View Code

必须重启django项目,否则模板无法引用自定义标签!

必须重启django项目,否则模板无法引用自定义标签!

必须重启django项目,否则模板无法引用自定义标签!

随便访问一篇文章,出现一个14,说明调用成功了

那么这个my_tags,如何渲染左侧的分类,标签,归档呢?

新建标签get_query_data,必须返回一个字典

将视图函数中的相关代码,复制过来即可。

from django import templateregister=template.Library()@register.simple_tag
def mul_tag(x,y):return x*yfrom blog.models import Category,Tag,Article,UserInfo
from django.db.models import Count,Avg,Max@register.inclusion_tag("left_region.html")
def get_query_data(username):user = UserInfo.objects.filter(username=username).first()# 查询当前站点对象blog = user.blog# 查询当前站点每一个分类的名称以及对应的文章数cate_list = Category.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")# print(cate_list)# 查询当前站点每一个标签的名称以及对应的文章数tag_list = Tag.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list("title", "c")# 日期归档date_list = Article.objects.filter(user=user).extra(select={"y_m_date": "strftime('%%Y/%%m',create_time)"}).values("y_m_date").annotate(c=Count("title")).values_list("y_m_date", "c")# print(date_list)return {"blog":blog,"username":username,"cate_list":cate_list,"tag_list":tag_list,"date_list":date_list}

View Code

@register.inclusion_tag("left_region.html") 表示将返回结果渲染给left_region.html

如果在模板中有调用left_redig.html,那么这个文件,就会渲染,是渲染后的html文件!

在templates中新建文件left_region.html

<div class="left_region"><div class="panel panel-success"><div class="panel-heading"><h3 class="panel-title">随笔分类</h3></div><div class="panel-body">{% for cate in cate_list %}<p><a href="/{{ username }}/category/{{ cate.0 }}">{{ cate.0 }}({{ cate.1 }})</a></p>{% endfor %}</div></div><div class="panel panel-warning"><div class="panel-heading"><h3 class="panel-title">我的标签</h3></div><div class="panel-body">{% for tag in tag_list %}<p><a href="/{{ username }}/tag/{{ tag.0 }}">{{ tag.0 }}({{ tag.1 }})</a></p>{% endfor %}</div></div><div class="panel panel-info"><div class="panel-heading"><h3 class="panel-title">日期归档</h3></div><div class="panel-body">{% for date in date_list %}<p><a href="/{{ username }}/achrive/{{ date.0 }}">{{ date.0 }}({{ date.1 }})</a></p>{% endfor %}</div></div>
</div>

View Code

修改base.html,将<div class="col-md-3"></div> 中的内容部分,改为引用get_query_data标签

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>{#公共样式#}<link rel="stylesheet" href="/static/css/theme/common.css">{#个人站点主题样式#}<link rel="stylesheet" href="/static/css/theme/{{ blog.theme }}">{#bootstrap#}<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"><script src="/static/js/jquery.js"></script><script src="/static/bootstrap/js/bootstrap.js"></script><link rel="shortcut icon" href="https://common.cnblogs.com/favicon.ico" type="image/x-icon"/>
</head>
<body>
<div class="header"><p class="title">{{ blog.title }}</p>
</div><div class="container-fluid"><div class="row"><div class="col-md-3">{#加载自定义标签模块#}{% load my_tags %}{#调用get_query_data标签,它返回left_region.html,是已经被渲染过的文件#}{% get_query_data username %}</div><div class="col-md-9">{% block content %}{% endblock %}</div></div>
</div></body>
</html>

View Code

此时刷新网页,效果同上!

修改views.py中的视图函数,删除get_query_data!

删除homesite和article_detail两个视图函数多余的代码

from django.shortcuts import render,HttpResponse,redirect
from django.contrib import auth
from blog.models import Article,UserInfo,Blog,Category,Tag
from django.db.models import Sum,Avg,Max,Min,Count
# Create your views here.
def login(request):if request.method=="POST":user=request.POST.get("user")pwd=request.POST.get("pwd")# 用户验证成功,返回user对象,否则返回Noneuser=auth.authenticate(username=user,password=pwd)if user:# 登录,注册session# 全局变量 request.user=当前登陆对象(session中)
            auth.login(request,user)return redirect("/index/")return render(request,"login.html")def index(request):article_list=Article.objects.all()return render(request,"index.html",{"article_list":article_list})def logout(request):  # 注销
    auth.logout(request)return redirect("/index/")def query_current_site(request,username):  # 查询当前站点的博客标题# 查询当前站点的用户对象user = UserInfo.objects.filter(username=username).first()if not user:return render(request, "not_found.html")# 查询当前站点对象blog = user.blogreturn blogdef homesite(request,username,**kwargs):"""查询:param request::param username::return:"""print("kwargs", kwargs)blog = query_current_site(request,username)# 查询当前用户发布的所有文章if not kwargs:article_list = Article.objects.filter(user__username=username)else:condition = kwargs.get("condition")params = kwargs.get("params")#判断分类、随笔、归档if condition == "category":article_list = Article.objects.filter(user__username=username).filter(category__title=params)elif condition == "tag":article_list = Article.objects.filter(user__username=username).filter(tags__title=params)else:year, month = params.split("/")article_list = Article.objects.filter(user__username=username).filter(create_time__year=year,create_time__month=month)return render(request,"homesite.html",{"blog":blog,"username":username,"article_list":article_list})def article_detail(request,username,article_id):blog = query_current_site(request,username)#查询指定id的文章article_obj = Article.objects.filter(pk=article_id).first()return render(request,'article_detail.html',{"blog":blog,"username":username,'article_obj':article_obj})

View Code

注意:get_query_data标签只是定义了左侧的标签!那么homesite和article_detail两个视图函数,需要知道当前站点的博客标题。

所以需要专门定义个函数,来获取博客标题!

修改article_detail.html,删除测试的自定义标签

{% extends "base.html" %}{% block content %}<div class="article_info"><h4 class="text-center">{{ article_obj.title }}</h4><div class="content">{{ article_obj.content }}</div></div>{% endblock %}

View Code

重新访问个人站点,随便乱点,效果同上!

Inclusion tags的优点:

1.不用在视图函数中return字典

2.数据和样式结合在一起,返回一个渲染后的html

详情页显示html代码问题

访问一篇博客详情,比如:

http://127.0.0.1:8000/xiao/articles/5/

效果如下:

那么django响应给浏览器的,真的是原来的html代码吗?

修改id为5的文章记录,修改content字段的数据,先备份一下,改成一个h1标签。

刷新网页,效果如下:

打开浏览器控制台-->network,查看这次请求的响应体

发现代码被转义了!那么是谁转义了呢?当然是....

注意:这不是浏览器的锅,是django转义的,这是它的安全策略做的。遇到html或者js代码,会自动转义!

那么我们不要django转义呢?使用safe过滤器即可!

修改article_detail.html中的代码

{{ article_obj.content|safe }}

重新刷新页面,效果如下:

注意:这样有一个安全隐患!

举例:将内容修改为一段js代码

重新刷新页面,它会有一个提示框

注意:它只会弹一次。如果是下面这段代码呢?

<script>for (var i = 0; i < 99999; i++) {alert("hello");}</script>

数据库记录如下:

刷新页面试试?不用想了,你今天啥事不用干,疯狂的点击吧!

那么既要网页安全,又需要网站展示真实的内容,怎么办呢?

答案就是:数据库不存储html代码即可!存html和js代码时,需要做特殊处理。

后面讲到富文本编辑器,会讲到。完美解决这个问题!


参考资料:

Django 基础教程

转载声明:
作者:肖祥
出处: https://www.cnblogs.com/xiao987334176/

转载于:https://www.cnblogs.com/bqwzx/p/10198769.html

python 全栈开发,Day81(博客系统个人主页,文章详情页)相关推荐

  1. python个人网站开发_python 全栈开发,Day81(博客系统个人主页,文章详情页)

    一.个人主页 随笔分类 需求:查询当前站点每一个分类的名称以及对应的文章数 完成这个需求,就可以展示左侧的分类 它需要利用分组查询,那么必须要会基于双下划线的查询. 基于双下划线的查询,简单来讲,就是 ...

  2. Spring boot实训开发个人博客(二)详情页

    Spring boot实训开发个人博客(二)详情页 1.在index页面添加归档: 2.开始写详情页: 1.头部文件: 2.添加文章内容 <h2 class="ui center al ...

  3. python 全栈开发,Day112(内容回顾,单例模式,路由系统,stark组件)

    python 全栈开发,Day112(内容回顾,单例模式,路由系统,stark组件) 一.内容回顾 类可否作为字典的key 初级 举例: class Foo(object):pass_registry ...

  4. python全栈开发工程师_【2018重磅巨献】老男孩Python高级全栈开发工程师第三期...

    2018年老男孩python全栈第三期老男孩python全栈3期 老男孩教育Python全栈开发为国内首家专注python全方面开发的教育机构 老男孩Python全栈第三期课程详细目录,高清不加密! ...

  5. python 全栈开发,Day45(html介绍和head标签,body标签中相关标签)

    python 全栈开发,Day45(html介绍和head标签,body标签中相关标签) 一.html介绍 1.web标准 web准备介绍: w3c:万维网联盟组织,用来制定web标准的机构(组织) ...

  6. python全栈开发实践入门_Python全栈开发实践入门

    Python全栈开发实践入门 编辑 锁定 讨论 上传视频 <Python全栈开发实践入门>是2017年10月电子工业出版社出版的图书,作者是谢瑛俊. 书 名 Python全栈开发实践入门 ...

  7. 收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!

    Python Web全栈开发入门实战教程教程    大家好,我叫亓官劼(qí guān jié ),这个<Python Web全栈开发入门实战教程教程>是一个零基础的实战教程,手把手带你开 ...

  8. python 全栈开发,Day32(知识回顾,网络编程基础)

    python 全栈开发,Day32(知识回顾,网络编程基础) 一.知识回顾 正则模块 正则表达式 元字符 :. 匹配除了回车以外的所有字符\w 数字字母下划线\d 数字\n \s \t 回车 空格 和 ...

  9. python利器手机版-将安卓手机打造成你的python全栈开发利器

    超神利器- 相信多数安卓用户都使用过Qpython这款移动端的Python编辑器吧?之前我也研究过一阵子这个工具,但因为一次简单的爬虫让我对它失望之极.Qpython不支持lxml这个模块,然而pyt ...

最新文章

  1. OpenCV支持向量机SVM用于非线性可分离数据
  2. 编写登录成功和失败的处理器
  3. ui原型设计工具_UI设计师的工具包,用于专业模型,原型和产品插图
  4. ALM 中查看某个 test 的更改 history 历史
  5. CSS3的线性渐变(linear-gradient)
  6. java几种远程服务调用协议的比较
  7. 计算机桌面文件自动备份取消,电脑如何取消自动备份_电脑怎么取消自动备份...
  8. 解决雷神笔记本风扇声音太响太吵问题
  9. p2p终结者在交换机上的机器用P2P终结者
  10. Ansible#Ansible-Jinja2模板介绍
  11. 腾讯、淘宝的架构大数据你有了解么?大数据技术及算法为你解析
  12. Kamailio nats模块编译
  13. Win10使用Ctrl+Space切换输入法
  14. QR分解的三种实现方法
  15. matlab 正交多项式,常用正交多项式
  16. Fama-French 三因子模型介绍、修改与框架搭建
  17. 分享商品生成海报的需求技术点总结
  18. 跳板机配置,镜像创建,网站云平台部署
  19. 网络设备上常用的安全技术
  20. QML一个漂亮的仪表盘

热门文章

  1. python 调用父类classmethod_Python__子类调用父类的方法
  2. 文件创建_LAMMPS data文件创建工具--moltemplate
  3. android电子书控件,Android控件大全.pdf
  4. linux下载tar.gz文件夹,手把手给你细说linux-Ubuntu如何安装tar.gz文件
  5. matlab矩阵元素绝对值,如何解决矩阵内部元素绝对值化
  6. 7 php 内存泄漏_APP内存优化之内存泄漏
  7. jquery遍历节点
  8. Java集合框架:ArrayList扩容机制解释
  9. python动态变量名_python实现可变变量名方法详解
  10. 牛客练习赛31: D. 神器大师泰兹瑞与威穆(链表)