准备阶段
首先熟悉项目的根本流程,下面是对项目的每个模块流程阶段具体分析
模块 | 功能 |
---|---|
注册 | 图形验证、短信验证 |
登陆 | 状态保存、cookie、session |
个人中心 | 图片上传、更新数据 |
发布博客 | 数据入库 |
博客首页 | 数据分页 |
博客详情 | 博客详情数据展示、评论工程 |
总体流程介绍完毕开始做准备工作需要数据库来存储说用到的数据,根据数据字典创建对象的表格
文件所使用的静态资源和一些配置以提供,方便大家使用
创建py项目
使用pycharm软件创建一个django项目
项目结构分析:
manange.py
:以后和项目交互基本上都是基于这个文件。一般都是在终端输入python manage.py [子命令]。可以输入python manage.py help看下能做什么事情。除非你知道你自己在做什么,一般情况下不应该编辑这个文件。settings.py
:保存项目所有的配置信息。urls.py
:用来做url与视图函数映射的。以后来了一个请求,就会从这个文件中找到匹配的视图函数。wsig.py
:专门用来做部署的。不需要修改。
项目创建完之后 可以运行 启动项目
可能会出现的坑
该问题就是把‘/’识别为除号了,两个str无法进行除号。代码实际意思是将两个str进行拼接,进入settings.py进行如下修改:
后面有具体配置文件
- ‘DIRS‘: [BASE_DIR / ‘templates‘] TypeError: unsupported operand type(s) for /: ‘str‘ and ‘str‘
更改地方
2.可能会有此问题query = query.decode(errors=‘replace‘)
项目运行成功显示界面
准备阶段
配置数据库MySQL
打开本地数据库–> 用户自行选择,可以创建一个新的用户也可以使用root
新建数据库
create database blog charset=utf8;
创建新用户或者使用root用户
create user diangen identified by '123456';
授权新用户的权限
grant all on blog.* to 'diangen';
授权结束后刷新特权
flush privileges;
数据库创建完毕
创建一个django项目找到配置文件里面的settings.py 连接数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 数据库引擎
'HOST': '127.0.0.1', # 数据库主机
'PORT': 3306, # 数据库端口
'USER': 'itheima', # 数据库用户名
'PASSWORD': '123456', # 数据库用户密码
'NAME': 'blog' # 数据库名字
},
}
可能出现的错误
Error loading MySQLdb module: No module named ‘MySQLdb’.
出现错误的原因:
Django中操作MySQL数据库需要驱动程序MySQLdb
目前项目虚拟环境中没有驱动程序MySQLdb
配置并使用数据库
配置Redis数据库
检查并安装django-redis拓展包
pip install django-redis
settings.py文件夹中添加
CACHES = {
"default": {
# 默认
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/0",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
"session": {
# session
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
}
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "session"
default:
默认的Redis配置项,采用0号Redis库。
session:
状态保持的Redis配置项,采用1号Redis库。
SESSION_ENGINE
修改session存储机制使用Redis保存。
SESSION_CACHE_ALIAS:
使用名为"session"的Redis配置项存储session数据。 配置完成后:运行程序,测试结果
电脑安装redis并进入redis文件目录
在命令行中运行
redis-server
redis运行成功
如果运行报错 看看是不是redis的问题
例如
问题原因是Redis 快照关闭了导致不能存储,可以通过关闭stop-writes-on-bgsave-error配置来解决。
(1)Windows系统中找到了redis.windows.conf文件,可以看到如下
# By default Redis will stop accepting writes if RDB snapshots are enabled
# (at least one save point) and the latest background save failed.
# This will make the user aware (in a hard way) that data is not persisting
# on disk properly, otherwise chances are that no one will notice and some
# disaster will happen.
#
# If the background saving process will start working again Redis will
# automatically allow writes again.
#
# However if you have setup your proper monitoring of the Redis server
# and persistence, you may want to disable this feature so that Redis will
# continue to work as usual even if there are problems with disk,
# permissions, and so forth.
stop-writes-on-bgsave-error yes
默认该设置是打开的,可以直接在此处修改为no
stop-writes-on-bgsave-error no
配置日志
settings.py文件夹中添加(后期报错方便在日志查看)
LOGGING = {
'version': 1,
'disable_existing_loggers': False, # 是否禁用已经存在的日志器
'formatters': {
# 日志信息显示的格式
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
},
'simple': {
'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
},
},
'filters': {
# 对日志进行过滤
'require_debug_true': {
# django在debug模式下才输出日志
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': {
# 日志处理方法
'console': {
# 向终端中输出日志
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'file': {
# 向文件中输出日志
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(BASE_DIR, 'logs/blog.log'), # 日志文件的位置
'maxBytes': 300 * 1024 * 1024,
'backupCount': 10,
'formatter': 'verbose'
},
},
'loggers': {
# 日志器
'django': {
# 定义了一个名为django的日志器
'handlers': ['console', 'file'], # 可以同时向终端与文件中输出日志
'propagate': True, # 是否继续传递日志信息
'level': 'INFO', # 日志器接收的最低日志级别
},
}
}
创建文件夹
不同的应用程序所定义的日志等级可能会有所差别,分的详细点的会包含以下几个等级:
FATAL/CRITICAL = 重大的,危险的
ERROR = 错误
WARNING = 警告
INFO = 信息
DEBUG = 调试
NOTSET = 没有设置
urls.py文件中
创建日志记录器
import logging
logger = logging.getLogger('django')
输出日志
logger.debug('测试logging模块debug')
logger.info('测试logging模块info')
logger.error('测试logging模块error')
配置静态资源
项目文件夹下创建目录static文件
settings.py 文件中添加
指定静态文件加载路径
STATIC_URL = '/static/'
配置静态文件加载路径
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
配置完成后:运行程序,测试结果。
项目启动成功
下面开始我们的具体的模块与应用
创建用户模块应用—注册
创建应用users
python manage.py startapp users
创建完之后目录结构会出现一个文件夹
在工程的setting.py中 注册用户模块应用
定义用户注册视图
创建一个子目录 把注册的html放进去 并更改配置
将static文件夹下在register.html拖拽到templates文件中
设置模板路径
在users.views.py文件中定义视图
from django.views import View
class RegisterView(View):
"""用户注册"""
def get(self, request):
"""
提供注册界面
:param request: 请求对象
:return: 注册界面
"""
return render(request, 'register.html')
定义用户注册路由
在users子应用中创建urls.py文件,并定义子路由
from django.urls import path
from users.views import RegisterView
urlpatterns = [
# 参数1:路由
# 参数2:视图函数
# 参数3:路由名,方便通过reverse来获取路由
path('register/',RegisterView.as_view(),name='register'),
]
在工程的urls.py总路由中添加子应用路由引导 dblog中的urls
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
# include 参数1要设置为元组(urlconf_module, app_name)
# namespace 设置命名空间
path('', include(('users.urls', 'users'), namespace='users')),
]
需要把static中的界面拖拽到templates文件夹中 因为我们配置的路径是template存放的界面
运行测试程序。
以上步骤是为了完成 第一个注册见面并且配置路由
首先配置user里面的视频viwes 和路由,然后在设置工程的urls.py总路由中添加子应用路由引导
修改静态文件加载方式
是由于静态资源加载是相对路径,因此我们需要修改静态资源的加载方式
使用static标签来加载静态文件。要使用static标签,首先需要{% load static %}
# 以下代码是html的header处修改
{
% load staticfiles %}
<!-- 引入bootstrap的css文件 -->
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
<!-- 引入vuejs -->
<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
<script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
# 以下代码是html的footer处修改
<!-- 引入js -->
<script type="text/javascript" src="{% static 'js/host.js' %}"></script>
<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
<script type="text/javascript" src="{% static 'js/register.js' %}"></script>
# 运行测试程序,没有问题
定义用户模型类
自定义用户模型类
思考:为什么要自定义用户模型类?
观察注册界面会发现,个人博客注册页面中必须有手机号,而且在登录页面中也使用手机号进行认证。此外个人中心页面中有个人头像和个人简介字段。
说白了就是用户需要自己增加字段to
如何自定义用户模型类?
继承自AbstractUser(可通过阅读Django默认用户模型类的源码得知) 。
新增手机号字段,头像字段和简介字段这个基类仅有少部分由于Django机制,而设定的函数和常量。
如果连这个都不想继承的话,直接用自定义,这将有可能导致其余的Django组件无法正常工作。
当然无论是继承还是自定义,都必须在settings中设置AUTH_USER_MODEL
用户信息
from django.db import models
# Create your models here.
from django.contrib.auth.models import AbstractUser
# 用户信息
class User(AbstractUser):
# 电话号码字段
# unique 为唯一性字段
mobile = models.CharField(max_length=20, unique=True,blank=True)
# 头像
# upload_to为保存到响应的子目录中
avatar = models.ImageField(upload_to='avatar/%Y%m%d/', blank=True)
# 个人简介
user_desc = models.TextField(max_length=500, blank=True)
# 修改认证的字段
USERNAME_FIELD = 'mobile'
#创建超级管理员的需要必须输入的字段
REQUIRED_FIELDS = ['username','email']
# 内部类 class Meta 用于给 model 定义元数据
class Meta:
db_table='tb_user' #修改默认的表名
verbose_name='用户信息' # Admin后台显示
verbose_name_plural=verbose_name # Admin后台显示
def __str__(self):
return self.mobile
指定本项目用户模型类
AUTH_USER_MODEL = 'users.User'
迁移用户模型类
执行迁移文件 python manage.py migrate
运行测试程序
图形验证码接口设计和定义
准备captcha包(该包用于生成图形验证码)
将生成图片验证码的库复制到新建的libs包中。
安装Python处理图片的库
图形验证码后端接口设计
1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | imagecode?uuid=xxxxx-xxxx-xxxxxx |
2.请求参数:路径参数
参数名 类型 是否必传 说明
uuid string 是 唯一编号
3.响应结果:image/jpeg
图形验证码后端实现
1.图形验证码视图
from django.http import HttpResponseBadRequest,HttpResponse
from libs.captcha.captcha import captcha
from django_redis import get_redis_connection
class ImageCodeView(View):
def get(self,request):
#获取前端传递过来的参数
uuid=request.GET.get('uuid')
#判断参数是否为None
if uuid is None:
return HttpResponseBadRequest('请求参数错误')
# 获取验证码内容和验证码图片二进制数据
text, image = captcha.generate_captcha()
# 将图片验内容保存到redis中,并设置过期时间
redis_conn = get_redis_connection('default')
redis_conn.setex('img:%s' % uuid, 300, text)
# 返回响应,将生成的图片以content_type为image/jpeg的形式返回给请求
return HttpResponse(image, content_type='image/jpeg')
2.总路由
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
# include 参数1要设置为元组(urlconf_module, app_name)
# namespace 设置命名空间
path('', include(('users.urls', 'users'), namespace='users')),
]
3.子路由
from django.urls import path
from users.views import ImageCodeView
urlpatterns = [
# 参数1:路由
# 参数2:视图函数
# 参数3:路由名,方便通过reverse来获取路由
path('imagecode/', ImageCodeView.as_view(),name='imagecode'),
]
修改模板中图片验证码HTML代码
1.html中的原代码如下
<img src="{% static 'img/image_code.png' %}" @click="generate_image_code" alt="" style="width: 110px;height: 40px;">
2.修改如下
<img :src="image_code_url" @click="generate_image_code" alt="" style="width: 110px;height: 40px;">
路由配置成功在注册界面可以正常显示验证码并切换
短信验证码
短信验证码 使用的是容联云官网 注册 获取
1.集成短信SDK到库中
CCPRestSDK.py:由容联云通讯开发者编写的官方SDK文件,包括发送模板短信的方法
ccp_sms.py:调用发送模板短信的方法
短信验证码后端逻辑实现
from django.http import JsonResponse
from utils.response_code import RETCODE
from random import randint
from libs.yuntongxun.sms import CCP
import logging
logger=logging.getLogger('django')
class SmsCodeView(View):
def get(self,request):
# 接收参数
image_code_client = request.GET.get('image_code')
uuid = request.GET.get('uuid')
mobile=request.GET.get('mobile')
# 校验参数
if not all([image_code_client, uuid,mobile]):
return JsonResponse({
'code': RETCODE.NECESSARYPARAMERR, 'errmsg': '缺少必传参数'})
# 创建连接到redis的对象
redis_conn = get_redis_connection('default')
# 提取图形验证码
image_code_server = redis_conn.get('img:%s' % uuid)
if image_code_server is None:
# 图形验证码过期或者不存在
return JsonResponse({
'code': RETCODE.IMAGECODEERR, 'errmsg': '图形验证码失效'})
# 删除图形验证码,避免恶意测试图形验证码
try:
redis_conn.delete('img:%s' % uuid)
except Exception as e:
logger.error(e)
# 对比图形验证码
image_code_server = image_code_server.decode() # bytes转字符串
if image_code_client.lower() != image_code_server.lower(): # 转小写后比较
return JsonResponse({
'code': RETCODE.IMAGECODEERR, 'errmsg': '输入图形验证码有误'})
# 生成短信验证码:生成6位数验证码
sms_code = '%06d' % randint(0, 999999)
#将验证码输出在控制台,以方便调试
logger.info(sms_code)
# 保存短信验证码到redis中,并设置有效期
redis_conn.setex('sms:%s' % mobile, 300, sms_code)
# 发送短信验证码
CCP().send_template_sms(mobile, [sms_code, 5],1)
# 响应结果
return JsonResponse({
'code': RETCODE.OK, 'errmsg': '发送短信成功'})
当点击验证码的时候 pycharm控制台中也会发送一个
添加response_code文件
在工程中新建utils包,将response_code文件复制到utils中
用户注册实现
from users.models import User
from django.db import DatabaseError
#注册视图
class RegisterView(View):
def get(self,request):
return render(request,'register.html')
def post(self,request):
"""
1.接收数据
2.验证数据
2.1 参数是否齐全
2.2 手机号的格式是否正确
2.3 密码是否符合格式
2.4 密码和确认密码要一致
2.5 短信验证码是否和redis中的一致
3.保存注册信息
4.返回响应跳转到指定页面
:param request:
:return:
"""
# 1.接收数据
mobile=request.POST.get('mobile')
password=request.POST.get('password')
password2=request.POST.get('password2')
smscode=request.POST.get('sms_code')
# 2.验证数据
# 2.1 参数是否齐全
if not all([mobile,password,password2,smscode]):
return HttpResponseBadRequest('缺少必要的参数')
# 2.2 手机号的格式是否正确
if not re.match(r'^1[3-9]\d{9}$',mobile):
return HttpResponseBadRequest('手机号不符合规则')
# 2.3 密码是否符合格式
if not re.match(r'^[0-9A-Za-z]{8,20}$',password):
return HttpResponseBadRequest('请输入8-20位密码,密码是数字,字母')
# 2.4 密码和确认密码要一致
if password != password2:
return HttpResponseBadRequest('两次密码不一致')
# 2.5 短信验证码是否和redis中的一致
redis_conn = get_redis_connection('default')
redis_sms_code=redis_conn.get('sms:%s'%mobile)
if redis_sms_code is None:
return HttpResponseBadRequest('短信验证码已过期')
if smscode != redis_sms_code.decode():
return HttpResponseBadRequest('短信验证码不一致')
# 3.保存注册信息
# create_user 可以使用系统的方法来对密码进行加密
try:
user=User.objects.create_user(username=mobile,
mobile=mobile,
password=password)
except DatabaseError as e:
logger.error(e)
return HttpResponseBadRequest('注册失败')
from django.contrib.auth import login
login(request,user)
# 4.返回响应跳转到指定页面
# 暂时返回一个注册成功的信息,后期再实现跳转到指定页面
# redirect 是进行重定向
# reverse 是可以通过 namespace:name 来获取到视图所对应的路由
response = redirect(reverse('home:index'))
# return HttpResponse('注册成功,重定向到首页')
#设置cookie信息,以方便首页中 用户信息展示的判断和用户信息的展示
response.set_cookie('is_login',True)
response.set_cookie('username',user.username,max_age=7*24*3600)
return response
在HTML表单中添加csrf_token
首页展示
1.创建首页应用:home
python manage.py startapp home
2.定义首页视图:IndexView—查询分类数据并展示
2.1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /?cat_id=xxx&page_num=xxx&page_size=xxx |
2.2.请求参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
cat_id | string | 否 | 分类id |
page_num | string | 否 | 文章分页页码 |
page_size | string | 否 | 文章每页条目数 |
2.3.响应结果:HTML
字段 | 说明 |
---|---|
失败 | 响应错误提示 |
成功 | 展示数据 |
3.查询分类文章数据并通过context传递给HTML
from django.urls import reverse
from django.views import View
# Create your views here.
class IndexView(View):
"""首页广告"""
def get(self, request):
"""提供首页广告界面"""
return render(request, 'index.html')
4.配置首页路由
在home子应用中创建urls.py文件,并定义子路由
from django.urls import path
from home.views import IndexView
urlpatterns = [
path('', IndexView.as_view(),name='index'),
]
在工程的urls.py总路由中添加子应用路由引导
from django.urls import path, include
urlpatterns = [
path('', include(('home.urls','home'),namespace='home')),
]
5.重定注册界面的跳转到首页
# 响应注册结果
return redirect(reverse('home:index'))
用户登陆
- 登录页面展示
1.在users.views.py文件中定义视图
from django.views import View
class LoginView(View):
def get(self,request):
return render(request,'login.html')
2.在users.urls.py文件中定义路由
from users.views import LoginView
urlpatterns = [
# 参数1:路由
# 参数2:视图函数
# 参数3:路由名,方便通过reverse来获取路由
path('login/', LoginView.as_view(),name='login'),
]
3.修改login.html中的资源加载方式
<!-- Header部分 -->
{% load staticfiles %}
<!-- 引入bootstrap的css文件 -->
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
<!-- 引入vuejs -->
<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
<script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
<!-- Footer部分 -->
<script type="text/javascript" src="{% static 'js/host.js' %}"></script>
<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
<script type="text/javascript" src="{% static 'js/login.js' %}"></script>
<!-- 点击注册部分 -->
<small class="form-text text-muted ml-1">还没有账号?<a href="{% url 'users:register' %}" style="color: cornflowerblue; ">注册新账号</a>
登录接口实现
from django.contrib.auth import login
from django.contrib.auth import authenticate
class LoginView(View):
def post(self,request):
# 接受参数
mobile = request.POST.get('mobile')
password = request.POST.get('password')
remember = request.POST.get('remember')
# 校验参数
# 判断参数是否齐全
if not all([mobile, password]):
return HttpResponseBadRequest('缺少必传参数')
# 判断手机号是否正确
if not re.match(r'^1[3-9]\d{9}$', mobile):
return HttpResponseBadRequest('请输入正确的手机号')
# 判断密码是否是8-20个数字
if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
return HttpResponseBadRequest('密码最少8位,最长20位')
# 认证登录用户
# 认证字段已经在User模型中的USERNAME_FIELD = 'mobile'修改
user=authenticate(mobile=mobile, password=password)
if user is None:
return HttpResponseBadRequest('用户名或密码错误')
# 实现状态保持
login(request, user)
# 响应登录结果
response = redirect(reverse('home:index'))
# 设置状态保持的周期
if remember != 'on':
# 没有记住用户:浏览器会话结束就过期
request.session.set_expiry(0)
# 设置cookie
response.set_cookie('is_login', True)
response.set_cookie('username', user.username, max_age=30 * 24 * 3600)
else:
# 记住用户:None表示两周后过期
request.session.set_expiry(None)
# 设置cookie
response.set_cookie('is_login', True, max_age=14*24 * 3600)
response.set_cookie('username', user.username, max_age=30 * 24 * 3600)
#返回响应
return response
注册登陆设置成功之后现在开始对首页进行设置用户名显示到首页
首页用户名展示
用户名写入到cookie
Vue渲染首页用户名
1.index.html
<!-- 如果用户已经登录,则显示用户名下拉框 -->
<li class="nav-item dropdown" v-if="is_login">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" @click="show_menu_click">[[username]]</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown" style="display: block" v-show="show_menu">
<a class="dropdown-item" href="../static/write_blog.html">写文章</a>
<a class="dropdown-item" href='../static/center.html'>个人信息</a>
<a class="dropdown-item" href='#'>退出登录</a>
</div>
</li>
<!-- 如果用户未登录,则显示登录按钮 -->
<li class="nav-item" v-else>
<a class="nav-link" href="login.html">登录</a>
</li>
2.index.js
mounted(){
//获取用户名信息
this.username=getCookie('username');
//获取是否登录信息
this.is_login=getCookie('is_login');
},
退出登录
logout()方法介绍
退出登录:
对session操作的,也就是操作redis,所以返回的要么是空,要么成功,不会出现异常
logout()方法:
只需要传入一个request对象就行,就会把当前用户的session清除
logout()位置:
django.contrib.auth.init.py文件中
from django.contrib.auth import logout
class LogoutView(View):
def get(self,request):
# 1.session数据清除
logout(request)
# 2.删除部分cookie数据
response=redirect(reverse('home:index'))
response.delete_cookie('is_login')
#3.跳转到首页
return response
提示:
由于首页中登录状态是从cookie中读取的。
所以退出登录时,需要将cookie中登录状态清除。
实现退出登录
<div class="dropdown-menu" aria-labelledby="navbarDropdown" style="display: block" v-show="show_menu">
<a class="dropdown-item" href="../static/write_blog.html">写文章</a>
<a class="dropdown-item" href='../static/center.html'>个人信息</a>
<a class="dropdown-item" href='{% url 'users:logout' %}'>退出登录</a>
</div>
路由配置 users.urls.py
urlpatterns = [
# 参数1:路由
# 参数2:视图函数
# 参数3:路由名,方便通过reverse来获取路由
path('register/',RegisterView.as_view(),name='register'),
path('imagecode/', ImageCodeView.as_view(),name='imagecode'),
# 短信发送
path('smscode/', SmsCodeView.as_view(), name='smscode'),
# 登录路由
path('login/', LoginView.as_view(), name='login'),
# 退出登录
path('logout/', LogoutView.as_view(), name='logout'),
]
忘记密码
1.在users.views.py文件中定义视图
from django.views import View
class ForgetPasswordView(View):
def get(self, request):
return render(request, 'forget_password.html')
2.在users.urls.py文件中定义路由
from users.views import ForgetPasswordView
path('forgetpassword/', ForgetPasswordView.as_view(),name='forgetpassword'),
3.修改forget_password.html中的资源加载方式
<!-- Header部分 -->
{% load staticfiles %}
<!-- 引入bootstrap的css文件 -->
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
<!-- 引入vuejs -->
<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
<script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
...
<!-- Footer部分 -->
<script type="text/javascript" src="{% static 'js/host.js' %}"></script>
<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
<script type="text/javascript" src="{% static 'js/forget_password.js' %}"></script>
...
<!-- 图片验证码部分 -->
<img :src="image_code_url" @click="generate_image_code" alt="" style="width: 110px;height: 40px;">
4.修改login.html中的忘记密码的跳转连接
<small class="form-text text-muted ml-1"><a class="secondaryAction layui-text" href="{% url 'users:forgetpassword' %}">忘记密码?</a>
忘记密码接口实现
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
mobile | string | 是 | 用户名 |
password | string | 是 | 密码 |
password2 | string | 是 | 确认密码 |
sms_code | string | 是 | 短信验证码 |
代码
class ForgetPasswordView(View):
def post(self, request):
# 接收参数
mobile = request.POST.get('mobile')
password = request.POST.get('password')
password2 = request.POST.get('password2')
smscode = request.POST.get('sms_code')
# 判断参数是否齐全
if not all([mobile, password, password2, smscode]):
return HttpResponseBadRequest('缺少必传参数')
# 判断手机号是否合法
if not re.match(r'^1[3-9]\d{9}$', mobile):
return HttpResponseBadRequest('请输入正确的手机号码')
# 判断密码是否是8-20个数字
if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
return HttpResponseBadRequest('请输入8-20位的密码')
# 判断两次密码是否一致
if password != password2:
return HttpResponseBadRequest('两次输入的密码不一致')
# 验证短信验证码
redis_conn = get_redis_connection('default')
sms_code_server = redis_conn.get('sms:%s' % mobile)
if sms_code_server is None:
return HttpResponseBadRequest('短信验证码已过期')
if smscode != sms_code_server.decode():
return HttpResponseBadRequest('短信验证码错误')
# 根据手机号查询数据
try:
user = User.objects.get(mobile=mobile)
except User.DoesNotExist:
# 如果该手机号不存在,则注册个新用户
try:
User.objects.create_user(username=mobile, mobile=mobile, password=password)
except Exception:
return HttpResponseBadRequest('修改失败,请稍后再试')
else:
# 修改用户密码
user.set_password(password)
user.save()
# 跳转到登录页面
response = redirect(reverse('users:login'))
return response
路由配置 urts.py
# 忘记密码
path('forgetpassword/', ForgetPasswordView.as_view(),name='forgetpassword'),
用户中心展示
- 页面展示
1.在users.views.py文件中定义视图
from django.views import View
class UserCenterView(View):
def get(self,request):
return render(request,'center.html')
2.在users.urls.py文件中定义路由
from users.views import UserCenterView
urlpatterns = [
# 参数1:路由
# 参数2:视图函数
# 参数3:路由名,方便通过reverse来获取路由
path('center/', UserCenterView.as_view(),name='center'),
]
3.修改center.html中的资源加载方式
<!-- Header部分 -->
{% load staticfiles %}
<!-- 引入bootstrap的css文件 -->
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
<!-- 引入vuejs -->
<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
<script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
...
<!-- Footer部分 -->
<script type="text/javascript" src="{% static 'js/host.js' %}"></script>
<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
<script type="text/javascript" src="{% static 'js/center.js' %}"></script>
...
<!-- 页面跳转部分 -->
<a class="dropdown-item" href='{% url 'users:center' %}'>个人信息</a>
<a class="dropdown-item" href='{% url 'users:logout' %}'>退出登录</a>
4.修改index.html中的的跳转连接
<a class="dropdown-item" href='{% url 'users:center' %}'>个人信息</a>
判断用户是否登录
根据是否登录的结果,决定用户是否可以访问用户中心。
Django用户认证系统提供了方法
request.user.is_authenticated()来判断用户是否登录。如果通过登录验证则返回True。反之,返回False。
LoginRequiredMixin封装了判断用户是否登录的操作。
1.用户中心使用LoginRequiredMixin
from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin
class UserCenterView(LoginRequiredMixin,View):
def get(self,request):
return render(request,'center.html')
2.设置未登录用户跳转的路由
#在工程的settings.py文件中,添加以下配置。
LOGIN_URL = '/login/'
3.根据登录的next参数设置登录跳转路由
实现状态保持
login(request, user)
响应登录结果
next = request.GET.get('next')
if next:
response= redirect(next)
else:
response = redirect(reverse('home:index'))
1.获取用户信息,模板渲染数据users.views.py 中添加功能
from django.contrib.auth.mixins import LoginRequiredMixin
class UserCenterView(LoginRequiredMixin,View):
def get(self,request):
# 获取用户信息
user = request.user
#组织模板渲染数据
context = {
'username': user.username,
'mobile': user.mobile,
'avatar': user.avatar.url if user.avatar else None,
'user_desc': user.user_desc
}
return render(request,'center.html',context=context)
2.修改center.html中的数据显示
<form method="post" enctype="multipart/form-data">
<!-- username -->
<div class="form-group col-md-4">
<label for="username">用户名</label>
<input type="text" class="form-control" id="username" name="username" value="{
{ username }}" >
</div>
{% if avatar %}
<br> <div class="col-md-4">头像</div>
<img src="{
{ avatar }}" style="max-width: 20%; border-radius: 15%;" class="col-md-4"><br>
{% else %}
<br><h5 class="col-md-4">暂无头像</h5><br>
{% endif %}
<!-- avatar -->
<div class="form-group col-md-4">
<label for="avatar">上传头像</label>
<input type="file" class="form-control-file" name="avatar" id="avatar">
</div>
<!-- phone -->
<div class="form-group col-md-4">
<label for="phone">电话</label>
<input type="text" class="form-control" disabled="disabled" id="phone" name="phone" value="{
{ mobile }}">
</div>
<!-- desc -->
<div class="form-group col-md-4">
<label for="desc">简介</label>
<!-- 文本区域 -->
<textarea type="text" class="form-control" id="desc" name="desc" rows="12" >{
{ user_desc }}</textarea>
</div>
<!-- 提交按钮 -->
<button type="submit" class="btn btn-primary" style="margin-left: 12px" >修改</button>
</form>
用户中心修改
- 用户中心接口设计
1.请求方式
选项 | 方案 |
---|---|
请求方法 | POST |
请求地址 | /center/ |
2.请求参数:表单
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
username string | 否 | 用户名 | |
avatar | file | 否 | 头像 |
desc | string | 否 | 个人简介 |
3.响应结果:HTML
字段 | 说明 |
---|---|
修改失败 | 响应错误提示 |
修改成功 | 刷新展示 |
- 用户中心修改接口实现
from django.contrib.auth.mixins import LoginRequiredMixin
class UserCenterView(LoginRequiredMixin,View):
def post(self,request):
# 接收数据
user = request.user
avatar = request.FILES.get('avatar')
username = request.POST.get('username',user.username)
user_desc = request.POST.get('desc',user.user_desc)
# 修改数据库数据
try:
user.username=username
user.user_desc=user_desc
if avatar:
user.avatar=avatar
user.save()
except Exception as e:
logger.error(e)
return HttpResponseBadRequest('更新失败,请稍后再试')
# 返回响应,刷新页面
response = redirect(reverse('users:center'))
#更新cookie信息
response.set_cookie('username',user.username,max_age=30*24*3600)
return response
- 用户中心头像的上传和展示
1.在settings.py文件中设置图片上传的路径并新建文件夹media
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
在settings.py文件中设置
图片的统一路由
MEDIA_URL = '/media/'
设置路由匹配规则。在工程的urls.py文件中设置
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
# include 参数1要设置为元组(urlconf_module, app_name)
# namespace 设置命名空间
path('', include(('users.urls', 'users'), namespace='users')),
path('', include(('home.urls','home'),namespace='home')),
]
#以下代码为设置图片访问路由规则
from django.conf import settings
from django.conf.urls.static import static
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
写博客页面展示
- 页面展示
1.在users.views.py文件中定义视图
from django.views import View
class WriteBlogView(LoginRequiredMixin,View):
def get(self,request):
return render(request,'write_blog.html')
2.在users.urls.py文件中定义路由
from users.views import WriteBlogView
urlpatterns = [
# 参数1:路由
# 参数2:视图函数
# 参数3:路由名,方便通过reverse来获取路由
path('writeblog/', WriteBlogView.as_view(),name='writeblog'),
]
3.修改center.html中的资源加载方式
<!-- Header部分 -->
{% load staticfiles %}
<!-- 引入bootstrap的css文件 -->
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
<!-- 引入vuejs -->
<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
<script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
<!-- Footer部分 -->
<!--ckeditor-->
<script type="text/javascript" src="{% static 'ckeditor/ckeditor-init.js' %}" data-ckeditor-basepath="{% static 'ckeditor/ckeditor/' %}" id="ckeditor-init-script"></script>
<script type="text/javascript" src="{% static 'ckeditor/ckeditor/ckeditor.js' %}"></script>
<!-- 引入js -->
<script type="text/javascript" src="{% static 'js/host.js' %}"></script>
<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
<script type="text/javascript" src="{% static 'js/write_blog.js' %}"></script>
<!-- 页面跳转部分 -->
<a class="dropdown-item" href="{% url 'users:writeblog' %}">写文章</a>
<a class="dropdown-item" href='{% url 'users:center'%}'>个人信息</a>
<a class="dropdown-item" href='{% url 'users:center' %}'>退出登录</a>
4.修改index.html中的的跳转连接
<a class="dropdown-item" href="{% url 'users:writeblog' %}">写文章</a>
文章分类模型
- 定义模型类
在home子应用的models.py模型中定义文章分类模型
from django.db import models
from django.utils import timezone
class ArticleCategory(models.Model):
"""
文章分类
"""
# 栏目标题
title = models.CharField(max_length=100, blank=True)
# 创建时间
created = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.title
class Meta:
db_table='tb_category'
verbose_name = '类别管理'
verbose_name_plural = verbose_name
- 迁移模型类
1.创建迁移文件
python manage.py makemigrations
2.执行迁移文件
python manage.py migrate
文章分类后台管理
当刚创建的时候,首页是没有数据,我们需要从后台管理界面进行文章分类,从而展示到主界面
网站的管理员负责查看、添加、修改、删除数据
Django能够根据定义的模型类自动地生成管理模块
登陆站点:http://127.0.0.1:8000/admin
在setting中设置中文信息
LANGUAGE_CODE = 'zh-Hans' #原配置信息为'en-us'
TIME_ZONE = 'Asia/Shanghai'#原配置信息为'UTC'
创建管理员
1.我们需要在User模型中设置 REQUIRED_FIELDS
在 users.modle.py中设置配置信息
#创建超级管理员的需要必须输入的字段
REQUIRED_FIELDS = ['username','email']
2.在终端创建超级管理员
创建管理员的命令 :
python manage.py createsuperuser
然后重新登陆进入 站点服务
注册模型类
在应用的admin.py文件中注册模型类
需要导入模型模块 :from home.models import ArticleCategory
模型注册完之后我们即可在站点管理进行对分类操作
模型类展示我们输入的内容是因为我们在模型中实现了__str_方法_
home.model.py
class ArticleCategory(models.Model):
"""
文章分类
"""
# 栏目标题
title = models.CharField(max_length=100, blank=True)
# 创建时间
created = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.title
写博客页面展示分类
- 查询数据并展示
1.查询分类文章数据并通过context传递给HTML
from home.models import ArticleCategory
class WriteBlogView(LoginRequiredMixin,View):
def get(self,request):
# 获取博客分类信息
categories = ArticleCategory.objects.all()
context = {
'categories': categories
}
return render(request,'write_blog.html',context=context)
2.在write_blog.html文件中使用模板语言展示数据
<!-- 文章栏目 -->
<div class="form-group">
<label for="column">栏目</label>
<select class="form-control col-3" id="category" name="category">
{% for category in categories %}
<option value="{
{ category.id }}">{
{ category.title }}</option>
{% endfor %}
</select>
</div>
文章模型
在home子应用的models.py模型中定义文章模型
from users.models import User
class Article(models.Model):
"""
文章
"""
# 定义文章作者。 author 通过 models.ForeignKey 外键与内建的 User 模型关联在一起
# 参数 on_delete 用于指定数据删除的方式,避免两个关联表的数据不一致。
author = models.ForeignKey(User, on_delete=models.CASCADE)
# 文章标题图
avatar = models.ImageField(upload_to='article/%Y%m%d/', blank=True)
# 文章栏目的 “一对多” 外键
category = models.ForeignKey(
ArticleCategory,
null=True,
blank=True,
on_delete=models.CASCADE,
related_name='article'
)
# 文章标签
tags = models.CharField(max_length=20,blank=True)
# 文章标题。
title = models.CharField(max_length=100,null=False,blank=False)
# 概要
sumary = models.CharField(max_length=200,null=False,blank=False)
# 文章正文。
content = models.TextField()
# 浏览量
total_views = models.PositiveIntegerField(default=0)
# 文章评论数
comments_count = models.PositiveIntegerField(default=0)
# 文章创建时间。
# 参数 default=timezone.now 指定其在创建数据时将默认写入当前的时间
created = models.DateTimeField(default=timezone.now)
# 文章更新时间。
# 参数 auto_now=True 指定每次数据更新时自动写入当前时间
updated = models.DateTimeField(auto_now=True)
# 内部类 class Meta 用于给 model 定义元数据
class Meta:
# ordering 指定模型返回的数据的排列顺序
# '-created' 表明数据应该以倒序排列
ordering = ('-created',)
db_table='tb_article'
verbose_name='文章管理'
verbose_name_plural=verbose_name
# 函数 __str__ 定义当调用对象的 str() 方法时的返回值内容
# 它最常见的就是在Django管理后台中做为对象的显示值。因此应该总是为 __str__ 返回一个友好易读的字符串
def __str__(self):
# 将文章标题返回
return self.title
迁移模型类
1.创建迁移文件
python manage.py makemigrations
2.执行迁移文件
python manage.py migrate
博客保存
博客保存接口设计
1.请求方式
选项 | 方案 |
---|---|
请求方法 | POST |
请求地址 | /writeblog/ |
2.请求参数:表单
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
title string | 是 | 标题 | |
avatar file | 是 | 标题图 | |
category | string | 是 | 栏目分类 |
tags | string | 是 | 标签 |
sumary | string | 是 | 文章摘要 |
content | string | 是 | 文章内容 |
3.响应结果:HTML
字段 | 说明 |
---|---|
提交失败 | 响应错误提示 |
提交成功 | 跳转到详情页面 |
- 用户中心修改接口实现
from home.models import ArticleCategory,Article
class WriteBlogView(LoginRequiredMixin,View):
def post(self,request):
#接收数据
avatar=request.FILES.get('avatar')
title=request.POST.get('title')
category_id=request.POST.get('category')
tags=request.POST.get('tags')
sumary=request.POST.get('sumary')
content=request.POST.get('content')
user=request.user
#验证数据是否齐全
if not all([avatar,title,category_id,sumary,content]):
return HttpResponseBadRequest('参数不全')
#判断文章分类id数据是否正确
try:
article_category=ArticleCategory.objects.get(id=category_id)
except ArticleCategory.DoesNotExist:
return HttpResponseBadRequest('没有此分类信息')
#保存到数据库
try:
article=Article.objects.create(
author=user,
avatar=avatar,
category=article_category,
tags=tags,
title=title,
sumary=sumary,
content=content
)
except Exception as e:
logger.error(e)
return HttpResponseBadRequest('发布失败,请稍后再试')
#返回响应,跳转到文章详情页面
#暂时先跳转到首页
return redirect(reverse('home:index'))
首页分类数据展示
2.定义首页视图:IndexView—查询分类数据并展示
2.1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /?cat_id=xxx&page_num=xxx&page_size=xxx |
2.2.请求参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
cat_id | string | 否 | 分类id |
page_num | string | 否 | 文章分页页码 |
page_size | string | 否 | 文章每页条目数 |
2.3.响应结果:HTML
字段 | 说明 |
---|---|
失败 | 响应错误提示 |
成功 | 展示数据 |
1.查询分类文章数据并通过context传递给HTML
home.views.py
from home.models import ArticleCategory
from django.http import HttpResponseNotFound
class IndexView(View):
"""首页广告"""
def get(self, request):
"""提供首页广告界面"""
#?cat_id=xxx&page_num=xxx&page_size=xxx
cat_id=request.GET.get('cat_id',1)
#判断分类id
try:
category = ArticleCategory.objects.get(id=cat_id)
except ArticleCategory.DoesNotExist:
return HttpResponseNotFound('没有此分类')
# 获取博客分类信息
categories = ArticleCategory.objects.all()
context = {
'categories':categories,
'category':category
}
return render(request, 'index.html',context=context)
2.在index.html文件中使用模板语言展示分类数据
<ul class="nav navbar-nav">
{% for cat in categories %}
{% if cat.id == category.id %}
<li class="nav-item active">
<a class="nav-link mr-2" href="/?cat_id={
{ cat.id }}">{
{ cat.title }}</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link mr-2" href="/?cat_id={
{ cat.id }}">{
{ cat.title }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
查询分页数据并展示
from home.models import ArticleCategory,Article
from django.http import HttpResponseNotFound
from django.core.paginator import Paginator,EmptyPage
class IndexView(View):
"""首页广告"""
def get(self, request):
"""提供首页广告界面"""
#?cat_id=xxx&page_num=xxx&page_size=xxx
cat_id=request.GET.get('cat_id',1)
page_num = request.GET.get('page_num', 1)
page_size = request.GET.get('page_size', 10)
#判断分类id
try:
category = ArticleCategory.objects.get(id=cat_id)
except ArticleCategory.DoesNotExist:
return HttpResponseNotFound('没有此分类')
# 获取博客分类信息
categories = ArticleCategory.objects.all()
#分页数据
articles = Article.objects.filter(
category=category
)
# 创建分页器:每页N条记录
paginator = Paginator(articles, page_size)
# 获取每页商品数据
try:
page_articles = paginator.page(page_num)
except EmptyPage:
# 如果没有分页数据,默认给用户404
return HttpResponseNotFound('empty page')
# 获取列表页总页数
total_page = paginator.num_pages
context = {
'categories':categories,
'category':category,
'articles': page_articles,
'page_size': page_size,
'total_page': total_page,
'page_num': page_num,
}
return render(request, 'index.html',context=context)
2.在index.html文件中使用模板语言展示分类数据
<div class="container">
<!-- 列表循环 -->
{% for article in articles %}
<div class="row mt-2">
<!-- 文章内容 -->
<!-- 标题图 -->
<div class="col-3">
<img src="{
{ article.avatar.url }}" alt="avatar" style="max-width:100%; border-radius: 20px">
</div>
<div class="col">
<!-- 栏目 -->
<a role="button" class="btn btn-sm mb-2 btn-warning">{
{ article.category.title }}</a>
<!-- 标签 -->
<span>
<a class="badge badge-secondary">{
{ article.tags }}</a>
</span>
<!-- 标题 -->
<h4>
<b><a href="./detail.html" style="color: black;">{
{ article.title }}</a></b>
</h4>
<!-- 摘要 -->
<div>
<p style="color: gray;">
{
{ article.sumary }}
</p>
</div>
<!-- 注脚 -->
<p>
<!-- 查看、评论、时间 -->
<span><i class="fas fa-eye" style="color: lightskyblue;"></i>{
{ article.total_views }} </span>
<span><i class="fas fa-comments" style="color: yellowgreen;"></i>{
{ article.comments_count }} </span>
<span><i class="fas fa-clock" style="color: pink;"></i>{
{ article.created | date }}</span>
</p>
</div>
<hr style="width: 100%;"/>
</div>
{% endfor %}
<!-- 页码导航 -->
<div class="pagenation" style="text-align: center">
<div id="pagination" class="page"></div>
</div>
</div>
3.修改底部js分页代码
<script type="text/javascript">
$(function () {
$('#pagination').pagination({
currentPage: {
{
page_num }},
totalPage: {
{
total_page }},
callback:function (current) {
location.href = '/?cat_id={
{ category.id }}&page_size={
{ page_size }}&page_num='+current;
}
})
});
</script>
博客详情
详情页面展示
- 页面展示
1.在home.views.py文件中定义视图
from django.views import View
class DetailView(View):
def get(self,request):
return render(request,'detail.html')
2.在home.urls.py文件中定义路由
from users.views import DetailView
urlpatterns = [
# 参数1:路由
# 参数2:视图函数
# 参数3:路由名,方便通过reverse来获取路由
path('detail/', DetailView.as_view(),name='detail'),
]
3.修改detail.html中的资源加载方式
<!-- Header部分 -->
{% load staticfiles %}
<!-- 引入bootstrap的css文件 -->
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
<!--详情页面导入-->
<script src="{% static 'ckeditor/ckeditor/plugins/prism/lib/prism/prism_patched.min.js' %}"></script>
<link rel="stylesheet" href="{% static 'prism/prism.css' %}">
<!--导入css-->
<link rel="stylesheet" href="{% static 'common/common.css' %}">
<link rel="stylesheet" href="{% static 'common/jquery.pagination.css' %}">
<!-- 引入vuejs -->
<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
<script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script>
...
<!-- Footer部分 -->
<!--ckeditor-->
<script type="text/javascript" src="{% static 'ckeditor/ckeditor-init.js' %}" data-ckeditor-basepath="{% static 'ckeditor/ckeditor/' %}" id="ckeditor-init-script"></script>
<script type="text/javascript" src="{% static 'ckeditor/ckeditor/ckeditor.js' %}"></script>
<!-- 引入js -->
<script type="text/javascript" src="{% static 'js/host.js' %}"></script>
<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
<script type="text/javascript" src="{% static 'js/detail.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jquery.pagination.min.js' %}"></script>
...
<!-- 页面跳转部分 -->
<a class="dropdown-item" href="{% url 'users:writeblog' %}">写文章</a>
<a class="dropdown-item" href='{% url 'users:center'%}'>个人信息</a>
<a class="dropdown-item" href='{% url 'users:center' %}'>退出登录</a>
- 查询分类数据并展示
1.查询文章数据并通过context传递给HTML
class DetailView(View):
def get(self,request):
# detail/?id=xxx&page_num=xxx&page_size=xxx
#获取文档id
id=request.GET.get('id')
# 获取博客分类信息
categories = ArticleCategory.objects.all()
try:
article=Article.objects.get(id=id)
except Article.DoesNotExist:
return render(request,'404.html')
context = {
'categories':categories,
'category':article.category,
'article':article,
}
return render(request,'detail.html',context=context)
2.在detail.html文件中使用模板语言展示文章数据
#分类数据展示
<div>
<ul class="nav navbar-nav">
{% for cat in categories %}
{% if cat.id == category.id %}
<li class="nav-item active">
<a class="nav-link mr-2" href="/?cat_id={
{ cat.id }}">{
{ cat.title }}</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link mr-2" href="/?cat_id={
{ cat.id }}">{
{ cat.title }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
#详情数据展示
<!-- 标题及作者 -->
<h1 class="mt-4 mb-4">{
{ article.title }}</h1>
<div class="alert alert-success"><div>作者:<span>{
{ article.author.username }}</span></div><div>浏览:{
{ article.total_views }}</div></div>
<!-- 文章正文 -->
<div class="col-12" style="word-break: break-all;word-wrap: break-word;">
{
{ article.content|safe }}
</div>
<br>
- 修改首页跳转到详情页面的链接
<!-- 标题 -->
<h4>
<b><a href="{% url 'home:detail' %}?id={
{ article.id }}" style="color: black;">{
{ article.title }}</a></b>
</h4>
推荐文章数据展示
- 添加文章浏览量数据
1.每次请求文章详情时给浏览量+1
try:
article=Article.objects.get(id=id)
except Article.DoesNotExist:
return render(request,'404.html')
else:
article.total_views+=1
article.save()
- 查询推荐文章并展示
1.查询推荐文章数据并通过context传递给HTML
class DetailView(View):
def get(self,request):
# detail/?id=xxx&page_num=xxx&page_size=xxx
#获取文档id
id=request.GET.get('id')
# 获取博客分类信息
categories = ArticleCategory.objects.all()
try:
article=Article.objects.get(id=id)
except Article.DoesNotExist:
return render(request,'404.html')
else:
article.total_views+=1
article.save()
# 获取热点数据
hot_articles = Article.objects.order_by('-total_views')[:9]
context = {
'categories':categories,
'category':article.category,
'article':article,
'hot_articles':hot_articles
}
return render(request,'detail.html',context=context)
2.在detail.html文件中使用模板语言展示推荐数据
<div class="sidebar__inner">
<h4><strong>推荐</strong></h4>
<hr>
{% for hot_article in hot_articles %}
<a href="{% url 'home:detail' %}?id={
{ hot_article.id }}" style="color: black">{
{ hot_article.title }}</a><br>
{% endfor %}
</div>
</div>
评论模型
- 定义模型类
在home子应用的models.py模型中定义评论模型
class Comment(models.Model):
#评论内容
content=models.TextField()
#评论的文章
article=models.ForeignKey(Article,
on_delete=models.SET_NULL,
null=True)
#发表评论的用户
user=models.ForeignKey('users.User',
on_delete=models.SET_NULL,
null=True)
#评论发布时间
created=models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.article.title
class Meta:
db_table='tb_comment'
verbose_name = '评论管理'
verbose_name_plural = verbose_name
- 迁移模型类
1.创建迁移文件
python manage.py makemigrations
2.执行迁移文件
python manage.py migrate
发表评论
- 发表评论接口设计
1.请求方式
选项 | 方案 |
---|---|
请求方法 | POST |
请求地址 | /detail/ |
2.请求参数:表单
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
user_id | string | 是 | 发表评论的用户id |
article_id | string | 是 | 评论的文字id |
content | string | 是 | 评论内容 |
3.响应结果:HTML
字段 | 说明 |
---|---|
提交失败 | 响应错误提示 |
提交成功 | 刷新页面展示 |
- 发表评论接口实现
1.发表评论实现
from home.models import Comment,Article
class DetailView(View):
def post(self,request):
#获取用户信息
user=request.user
#判断用户是否登录
if user and user.is_authenticated:
#接收数据
id=request.POST.get('id')
content=request.POST.get('content')
#判断文章是否存在
try:
article = Article.objects.get(id=id)
except Article.DoesNotExist:
return HttpResponseNotFound('没有此文章')
#保存到数据
Comment.objects.create(
content=content,
article=article,
user=user
)
#修改文章评论数量
article.comments_count+=1
article.save()
#拼接跳转路由
path=reverse('home:detail')+'?id={}'.format(article.id)
return redirect(path)
else:
#没有登录则跳转到登录页面
return redirect(reverse('users:login'))
2.detail.html修改
<form method="POST">
{% csrf_token %}
<input type="hidden" name="id" value="{
{ article.id }}">
<div class="form-group"><label for="body"><strong>我也要发言:</strong></label>
<div>
<div class="django-ckeditor-widget" data-field-id="id_body" style="display: inline-block;">
<textarea cols="40" id="id_body" name="content" rows="10" required data-processed="0" :data-config="data_config" data-external-plugin-resources="[]" data-id="id_body" data-type="ckeditortype">
</textarea>
</div>
</div>
</div>
<!-- 提交按钮 -->
<button type="submit" class="btn btn-primary ">发送</button>
</form>
详情评论数据展示
- 查询评论数据并展示
1.查询评论数据并通过context传递给HTML
home.views.py
from home.models import Comment
from django.shortcuts import redirect,reverse
class DetailView(View):
def get(self,request):
# detail/?id=xxx&page_num=xxx&page_size=xxx
#获取文档id
id=request.GET.get('id')
page_num=request.GET.get('page_num',1)
page_size=request.GET.get('page_size',5)
# 获取博客分类信息
categories = ArticleCategory.objects.all()
try:
article=Article.objects.get(id=id)
except Article.DoesNotExist:
return render(request,'404.html')
else:
article.total_views+=1
article.save()
# 获取热点数据
hot_articles = Article.objects.order_by('-total_views')[:9]
# 获取当前文章的评论数据
comments = Comment.objects.filter(
article=article
).order_by('-created')
#获取评论总数
total_count = comments.count()
# 创建分页器:每页N条记录
paginator = Paginator(comments, page_size)
# 获取每页商品数据
try:
page_comments = paginator.page(page_num)
except EmptyPage:
# 如果page_num不正确,默认给用户404
return HttpResponseNotFound('empty page')
# 获取列表页总页数
total_page = paginator.num_pages
context = {
'categories':categories,
'category':article.category,
'article':article,
'hot_articles':hot_articles,
'total_count': total_count,
'comments': page_comments,
'page_size': page_size,
'total_page': total_page,
'page_num': page_num,
}
return render(request,'detail.html',context=context)
2.在index.html文件中使用模板语言展示分类数据
<!-- 显示评论 -->
<h4>共有{
{ total_count }}条评论</h4>
<div class="row">
{% for comment in comments %}
<div class="col-12" >
<hr><p><strong style="color: pink"></strong></p>
<div>
<div><span><strong>{
{ comment.user.username }}</strong></span> <span style="color: gray">{
{ comment.created | date:'Y:m:d H:i:s' }}</span></div>
<br>
<p>{
{ comment.content|safe }}</p>
</div>
</div>
{% endfor %}
<div class="pagenation" style="text-align: center">
<div id="pagination" class="page"></div>
</div>
</div>
3.修改底部js分页代码
<script type="text/javascript">
$(function () {
$('#pagination').pagination({
currentPage: {
{
page_num }},
totalPage: {
{
total_page }},
callback:function (current) {
location.href = '/detail/?id={
{ article.id }}&page_size={
{ page_size }}&page_num='+current;
}
})
});
</script>
现在就是完成基本使用功能一个完整的**登录 注册 退出 发表文章 评论文章 **
后期待完善
转载:https://blog.csdn.net/qq_41961239/article/details/115736805