项目介绍
- 目的:
- 通过完整的开发过程学习Django
- 对一本的网站开发流程有全面的认识
- 主要流程:
设计网站原型 —> 具体开发 —> 功能测试 —> 部署上线
- 网站原型的具体设计:
开始实战
1. 功能分析
每个功能模块都相当于一个Django应用!
2. 使用虚拟环境
具体内容参考博主另一篇博文:https://blog.csdn.net/jy_z11121/article/details/103970715
3. 初步创建Blog应用
此应用包括:博文、博客分类
逻辑关系:一篇博客一种分类 / 一篇博客多种分类
3.1 基本构建
创建blog
应用
完善settings.py
文件,将新应用添加到INSTALLED_APPS
数组
完善blog/models.py
文件
blog
应用的数据库迁移python manage.py migrate
创建超级管理员python manage.py createsuperuser
自定义管理员界面——修改blog/admin.py
文件
python manage.py makemigrations
python manage.py migrate
启动python manage.py runserver
进入管理界面,添加一些“实例对象”
构建博客主界面、子界面——blog/views.py
、blog/templates/blog_list.html
、blog/templates/blog_detail.html
设置路由——blog/urls.py
& urls.py
优化目录结构(主要是模板文件的存放位置。以后再写时就直接写到正确的位置了,这里由于是初学,所以多了此步骤)。
进一步调整网页转移逻辑(对路由设置进行完善、调整,使得逻辑更清晰)
3.2 网页样式初步设计
网页样式设计(技巧:先在网页端利用F12查看页面代码,在这里进行更改可以直接看到效果,如果是我们想要的效果,则再把它写进代码里)
写CSS文件、JS文件、图片等静态文件
这里又需要设置
myblog/settings.py
文件里的相关内容,目的是使我们的程序能够找到我们上面写的静态文件。设置工作如下:
设置成功后,我们可以通过以下的方式访问到里面的文本文件:
3.3 使用Bootstrap框架
这里不再自己写前端的内容,将使用CSS框架。首先部署BootStrap框架。
- 选择CSS框架时应考虑的方面:易用性、兼容性、大小、效果、功能。综合以上因素,我们将使用BootStrap框架:文档齐全,使用简单;兼容较多的浏览器;非轻量级;扁平、简介;组件齐全、响应式。
3.3.1 导航栏部分
将相关文件导入到templates/base.html
中
按照标准模板的格式重写templates/base.html
中<body>
部分——公共部分——导航栏部分:
{% load staticfiles %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{% endblock %}</title>
<!-- <link rel="stylesheet" href="/static/css/base.css" > -->
<link rel="stylesheet" href="{% static 'css/base.css' %}" >
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7/css/bootstrap.min.css' %}" >
{% block header_extends %}{% endblock %}
</head>
<body>
<!-- ---------------------------Bootstrap框架---------------------------------------- -->
<div class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="{% url 'home' %}">
我的个人博客网站
</a>
</div>
<ul class="nav navbar-nav">
<li><a href="{% url 'home' %}">首页</a></li>
<li><a href="{% url 'blog_list' %}">博客列表</a></li>
</ul>
</div>
</div>
<!-- -------------------------------自己写的------------------------------------ -->
<div class="nav">
<a class="logo" href="{% url 'home' %}">
<h3>我的个人博客网站</h3>
</a>
<a href="{% url 'home' %}">首页</a>
<a href="{% url 'blog_list' %}">博客列表</a>
</div>
<!-- -------------------------------------------------------------------------- -->
{% block content %} {% endblock %}
<script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}" ></script>
<script type="text/javascript" src="{% static 'bootstrap-3.3.7/js/bootstrap.min.js' %}" ></script>
</body>
</html>
进一步完善导航栏(变为响应式效果):
{% load staticfiles %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{% endblock %}</title>
<!-- <link rel="stylesheet" href="/static/css/base.css" > -->
<link rel="stylesheet" href="{% static 'css/base.css' %}" >
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7/css/bootstrap.min.css' %}" >
<script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}" ></script>
<script type="text/javascript" src="{% static 'bootstrap-3.3.7/js/bootstrap.min.js' %}" ></script>
{% block header_extends %}{% endblock %}
</head>
<body>
<!-- 导航栏 -->
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="{% url 'home' %}">
我的个人博客网站
</a>
<!-- 满足响应式需求;此处的data-target="#navbar-collapse"对应下面id为navbar-collapse区域里的内容 -->
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div id="navbar-collapse" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="{% block nav_home_active %}{% endblock %}">
<a href="{% url 'home' %}">首页</a>
</li>
<li class="{% block nav_blog_active %}{% endblock %}">
<a href="{% url 'blog_list' %}">博客列表</a>
</li>
</ul>
</div>
</div>
</div>
{% block content %} {% endblock %}
</body>
</html>
3.3.2 完善博客界面
下面的部分是对以下三个界面进行调整,其中包括了设计相应的CSS文件:
导航栏写完后,开始写templates/blog/blog_list.html
界面——应用Bootstrap栅格、面板、响应式:
栅格的基本语法示例:
<div class="container"> <div class="row"> <div class="col-xs-12"> <!-- col-xs-等标签根据需求进行设置,具体请看官方文档 --> ... </div> </div> </div>
{% extends 'base.html' %}
{% block title %}
我的博客网站
{% endblock %}
{% block nav_blog_active %}
active
{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<!-- ---------------------前8格用来显示博客列表--------------------- -->
<div class="col-md-8">
<!-- Bootstrap面板框架 -->
<div class="panel panel-warning">
<div class="panel-heading">
博客列表({{ blogs|length }}篇)
</div>
<div class="panel-body">
{% for blog in blogs %}
<a href="{% url 'blog_detail' blog.pk %}">
<h3>{{ blog.title }}</h3>
</a>
<p>作者:{{ blog.author }}</p>
<p>博客分类:
<a href="{% url 'blogs_with_type' blog.blog_type.pk %}">
{{ blog.blog_type.type_name }}
</a>
</p>
<p>发表日期:{{ blog.created_time|date:"Y-m-d G:n:s" }}</p>
<p>内容预览:{{ blog.content|truncatechars:30 }}</p> <!-- 只显示内容部分的前30个字 -->
{% empty %}
<p>-- 暂无博客,敬请期待 --</p>
{% endfor %}
</div>
</div>
</div>
<!-- ---------------------后4格用来显示博客的所有分类--------------------- -->
<div class="col-md-4">
<!-- Bootstrap面板框架 -->
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">博客分类</h3>
</div>
<div class="panel-body">
<ul style="list-style-type:none;">
{% for blog_type in blog_types %}
<li>
<a href="{% url 'blogs_with_type' blog_type.pk %}">{{ blog_type.type_name }}</a>
</li>
{% empty %}
<li>暂无分类</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- 统计博客数量:有以下两种方式 -->
<!-- 我的博客数量:{{ blogs_count }} blogs_count是views.py文件中初始化的 -->
<!-- 我的博客数量:{{ blogs|length }} -->
{% endblock %}
进一步调整templates/blog/blog_list.html
界面:
博客列表界面完成后,开始修整templates/blog/blogs_with_type.html
界面,此界面是列出某一分类下的所有文章,与博客列表界面相同的是:都需要把指定文章列出来。所以我们使用模板继承的方式重新改写templates/blog/blogs_with_type.html
文件,从而使得代码更清晰,只需做稍微调整即可:
{% extends 'blog/blog_list.html' %}
{% block title %}
我的博客|{{ blog_type.type_name }}
{% endblock %}
{% block blog_list_title %}
{{ blog_type.type_name }}({{ blogs|length }}篇)
<a href="{% url 'blog_list' %}">查看全部博客 </a>
{% endblock %}
修整templates/blog/blogs_detail.html
界面:
未调整前:
{% extends 'base.html' %} {% block title %} {{ blog.title }} {% endblock %} {% block nav_blog_active %} active {% endblock %} {% block content %} <h3>{{ blog.title }}</h3> <p>博客分类: <a href="{% url 'blogs_with_type' blog.blog_type.pk %}"> {{ blog.blog_type.type_name }} </a> </p> <p>发表日期:{{ blog.created_time|date:"Y-m-d G:n:s" }}</p> <p>{{ blog.content }}</p> {% endblock %}
{% extends 'base.html' %}
{% block title %} {{ blog.title }} {% endblock %}
{% block nav_blog_active %} active {% endblock %}
{% load staticfiles %}
{% block header_extends %}
<link rel="stylesheet" href="{% static 'blog/blog.css' %}">
{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-xs-10 col-xs-offset-1"> <!-- 将col-xs-10向右偏移了一个栅格的宽度 -->
<h3>{{ blog.title }}</h3>
<ul class="blog-info-description">
<li>作者:{{ blog.author }}</li>
<li>
博客分类:
<a href="{% url 'blogs_with_type' blog.blog_type.pk %}">
{{ blog.blog_type.type_name }}
</a>
</li>
<li>发表日期:{{ blog.created_time|date:"Y-m-d" }}</li>
</ul>
<div class="blog-content">{{ blog.content }}</div>
</div>
</div>
</div>
{% endblock %}
3.4 分页功能和shell命令行模式
- 博客文章数过多 --> 全部一次性加载出来太过耗时 —> 分页加载 基于这一结果,我们将学习分页功能;除此之外,本节还涉及使用shell命令行模式快速新增和编辑博客内容、分页器。
- 主要涉及
myblog\blog\templates\blog\blog_list.html
、myblog\blog\templates\blog\blogs_with_type.html
界面的优化。
3.4.1 Shell命令行模式快速添加博客
# 1. 进入Python的命令行模式下
python manage.py shell
# 2. 导入需要的模型类
from blog.models import Blog
# 3. 新增模型类的实例
>>> blog.title = "shell_test1"
>>> blog.content = "shellshellshellshellshellshell"
>>> blog.blog_type = blog_type
>>> from django.contrib.auth.models import User
>>> User.objects.all()
<QuerySet [<User: admin>]>
>>> user = User.objects.all()[0]
>>> blog.author = user
># 4. 保存到数据库
blog.save()
# 其他
Blog.objects.all() # 查看Blog模型类中的所有实例
Blog.objects.all().count() # 查看Blog模型类中实例总数
dir() # 查看已存在的所有实例对象
dir(实例对象) # 查看实例对象的方法或属性
# 为进一步提高效率,我们使用for循环的方式添加博客
>>> from blog.models import Blog, BlogType
>>> blog_type = BlogType.objects.all()[0]
>>> from django.contrib.auth.models import User
>>> user = User.objects.all()[0]
>>> for i in range(1, 31):
... blog = Blog()
... blog.title = "for %s" % i
... blog.content = "forforforforfor: %s" % i
... blog.blog_type = blog_type
... blog.author = user
... blog.save()
...
>>> Blog.objects.all().count()
34
3.4.2 分页器实现分页
首先修改blog/models.py
文件,设置文章展示在页面上时的展示顺序——最新发表的文章在最前面展示:
from django.db import models
from django.contrib.auth.models import User
class BlogType(models.Model):
type_name = models.CharField(max_length=15)
def __str__(self):
return self.type_name
class Blog(models.Model):
title = models.CharField(max_length=50)
blog_type = models.ForeignKey(BlogType, on_delete=models.DO_NOTHING)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.DO_NOTHING)
created_time = models.DateTimeField(auto_now_add=True)
last_updated_time = models.DateTimeField(auto_now=True)
def __str__(self):
return "<Blog: %s>" % self.title
# 设置文章展示在页面上时的展示顺序:最新发表的文章在最前面展示
class Meta:
ordering = ['-created_time']
进入shell命令行模式:
# 1. 引用分页器
from django.core.paginator import Paginator
# 2. 实例化一个分页器
blogs = Blog.objects.all()
paginator = Paginator(blogs, 10) # 每页10项,共4页
>>> paginator.page_range # 查看页数取值范围
range(1, 5)
>>> paginator.num_pages # 查看总页数
4
>>> paginator.count
34
>>> page1 = paginator.page(1) # 取到第一页
>>> page1
<Page 1 of 4>
>>> dir(page1)
['__abstractmethods__', '__class__', '__contains__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__',
'__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__',
'__weakref__', '_abc_impl', 'count', 'end_index', 'has_next', 'has_other_pages', 'has_previous',
'index', 'next_page_number', 'number', 'object_list', 'paginator', 'previous_page_number', 'start_index']
>>> page1.object_list
<QuerySet [<Blog: <Blog: for 30>>, <Blog: <Blog: for 29>>, <Blog: <Blog: for 28>>, <Blog: <Blog: for 27>>, <Blog: <Blog: for 26>>, <Blog: <Blog: for 25>>, <Blog: <Blog: for 24>>, <Blog: <Blog: for 23>>, <Blog: <Blog: for 22>>, <Blog: <Blog: for 21>>]>
3.4.3 分页的使用
前端:发送请求——请求打开具体分页内容
后端:相应请求——返回具体分页内容
修改blog/views.py
里的blog_list(request)
函数:
- 未修改前
from django.shortcuts import render, render_to_response, get_object_or_404
from .models import BlogType, Blog
def blog_list(request):
context = {}
context['blogs'] = Blog.objects.all()
context['blogs_count'] = Blog.objects.all().count()
context['blog_types'] = BlogType.objects.all()
return render_to_response('blog/blog_list.html', context)
- 修改后
from django.shortcuts import render, render_to_response, get_object_or_404
from django.core.paginator import Paginator
from .models import BlogType, Blog
def blog_list(request):
blogs_all_list = Blog.objects.all()
paginator = Paginator(blogs_all_list, 10)
# 获取url的页面参数,如果此参数不存在,则默认为1(GET请求)
page_num = request.GET.get('page', 1)
# page_num不一定是int型的页数,所以使用get_page()方法将page_num转换成一个具体的页数
page_of_blogs = paginator.get_page(page_num)
context = {}
context['page_of_blogs'] = page_of_blogs
# context['blogs'] = page_of_blogs.object_list
# 即context['page_of_blogs'].object_list
# 所以在使用的时候,我们直接在页面里引用context['page_of_blogs'].object_list即可
context['blogs_count'] = Blog.objects.all().count()
context['blog_types'] = BlogType.objects.all()
return render_to_response('blog/blog_list.html', context)
- 效果(GET请求,request)
-
由于
blog/views.py
里的blog_list(request)
函数里相关数据的变化,所以对应的需要修改blog_list.html
里的相关数据。 -
使用Bootstrap分页框架完善博客展示界面
blog/templates/blog/blog_detail.html
:
<!-- Bootstrap分页框架 -->
<div>
<ul class="pagination">
{% if page_of_blogs.has_previous %} <!-- 如果上一页存在 -->
<li>
<a href="?page={{ page_of_blogs.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% else %}
<span aria-hidden="true">«</span>
{% endif %}
{% for page_num in page_of_blogs.paginator.page_range %}
<li><a href="?page={{ page_num }}"><span>{{ page_num }}</a></li>
{% endfor %}
{% if page_of_blogs.has_next %} <!-- 如果下一页存在 -->
<li>
<a href="?page={{ page_of_blogs.next_page_number }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% else %}
<span aria-hidden="true">«</span>
{% endif %}
</ul>
</div>
3.4.4 优化分页展示
- 将当前页的页码显示为高亮状态
{% for page_num in page_of_blogs.paginator.page_range %}
<!-- 如果是当前页,则显示为高亮状态 -->
{% if page_num == page_of_blogs.number %}
<li class='active'><a href="?page={{ page_num }}"><span>{{ page_num }}</a></li>
<!-- 否则,无'active'标签 -->
{% else %}
<li><a href="?page={{ page_num }}"><span>{{ page_num }}</a></li>
{% endif %}
{% endfor %}
- 若页码太多,则只显示部分
在blog/views.py
中的blog_list()
方法中添加如下语句:
# 获取当前页码
current_page_num = page_of_blogs.number
# 取当前页码的前后两页,共5页,保存为一个列表
page_range = [current_page_num-2, current_page_num-1, current_page_num, current_page_num+1, current_page_num+2]
context['page_range'] = page_range
效果:
上面的情况不是我们想要的结果,需进一步优化:
current_page_num = page_of_blogs.number
page_range = list(range(max(1, current_page_num-2), current_page_num)) + \
list(range(current_page_num, min(current_page_num+2, paginator.num_pages)+1))
context['page_range'] = page_range
效果:
再进一步优化,优化后的最终效果为:
对应的代码为:
current_page_num = page_of_blogs.number # 获取当前页码
page_range = list(range(max(1, current_page_num-2), current_page_num)) + \
list(range(current_page_num, min(current_page_num+2, paginator.num_pages)+1))
if page_range[0] - 1 >= 2:
page_range.insert(0, '...')
if page_range[0] != 1:
page_range.insert(0, 1)
if page_range[-1] + 2 < paginator.num_pages:
page_range.append('...')
if page_range[-1] != paginator.num_pages:
page_range.append(paginator.num_pages)
context['page_range'] = page_range
{% for page_num in page_range %}
{% if page_num == page_of_blogs.number %} <!-- 如果是当前页,则显示为高亮状态 -->
<li class='active'><span>{{ page_num }}</span></a></li>
{% else %}
{% if page_num == '...' %}
<li><span>{{ page_num }}</span></a></li>
{% else %}
<li><a href="?page={{ page_num }}">{{ page_num }}</a></li>
{% endif %}
{% endif %}
{% endfor %}
blog/blog_list.html
安排好后,再随之修改blog/blogs_with_type.html
页面,修改对应的blog/views.py
中的内容。
3.5 博客具体内容页面的优化
- 主要涉及
myblog\blog\templates\blog\blog_detail.html
界面的优化。
3.5.1 上下篇博客
fliter()
(用于筛选出符合条件的条目)常用筛选条件:
- 实例:
返回类型是
QuerySet
。
exclude()
(用于排除带有指定内容的条目)常用排除条件:
【同fliter()
的常用筛选条件】- 实例:
修改blog/views.py
中blog_detail()
函数,添加下面两条语句:
context['last_blog'] = Blog.objects.filter(created_time__gt=blog.created_time).last()
context['next_blog'] = Blog.objects.filter(created_time__lt=blog.created_time).first()
在myblog\blog\templates\blog\blog_detail.html
中添加:
<div class="blog-more">
<p>上一篇:
{% if last_blog %}
<a href="{% url 'blog_detail' last_blog.pk %}">{{last_blog.title}}</a>
{% else %}
无
{% endif %}
</p>
<p>下一篇:
{% if next_blog %}
<a href="{% url 'blog_detail' next_blog.pk%}">{{next_blog.title}}</a>
{% else %}
无
{% endif %}
</p>
</div>
效果: