目录
一、项目准备
1.1 项目分析
1.2 工程创建和配置
二、注册
2.2 定义用户模型类
2.3 图形验证码
2.4 短信验证码
2.5 用户注册实现
2.6 展示首页
2.7 状态保持
三、登录
3.1 手机登录
3.2 首页用户名展示
3.3 退出登录
四、忘记密码
五、用户中心
5.2 用户中心修改
六、写博客
6.1 写博客页面展示
6.2 文章分类模型
6.3 文章分类后台管理
6.4 写博客页面展示分类
查询数据并展示
6.5 文章模型
6.6 博客保存
七、博客首页
7.1 首页分类数据展示
7.2 首页文章数据展示
八、博客详情
8.1 详情页面展示
8.2 404页面展示
修改查询不到数据返回的响应
8.3 推荐文章数据展示
8.4 评论模型
8.5 发表评论
8.6 详情评论数据展示
需求分析原因
需求分析方式
需求分析内容
1. 项目主要页面介绍
1.注册页面
2.登录页面
3.忘记密码页面
4.用户中心页面
5.写博客页面
6.博客首页
7.博客详情页面
2. 归纳项目主要模块
模块 | 功能 |
---|---|
注册 | 图形验证、短信验证 |
登录 | 状态保持、Cookie、Session |
个人中心 | 图片上传、更新数据 |
发布博客 | 数据入库 |
博客首页 | 数据分页 |
博客详情 | 博客详情数据展示、评论功能 |
3. 项目开发模式
选项 | 技术选型 |
---|---|
开发模式 | 前后端不分离 |
后端框架 | Django + Django模板引擎 |
1.2.1 工程创建和配置
1. 准备项目代码仓库
1. 源码托管网站
2. 创建源码远程仓库:itheima_blog
2. 克隆项目代码仓库
1.进入本地项目目录
cd Desktop/
2.克隆仓库
git clone https://github.com/qruihua/itheima_blog.git
3. 创建美多商城工程
1.进入本地项目仓库
cd itheima_blog/
2.创建美多商城虚拟环境,安装Django框架
$ mkvirtualenv -p python3 blog
$ pip install django==2.2
3.创建美多商城Django工程
$ django-admin startproject blog
创建工程完成后:运行程序,测试结果。
1.2.2 配置MySQL数据库
1. 新建MySQL数据库
1.新建MySQL数据库:blog
$ create database blog charset=utf8;
2.新建MySQL用户
$ create user itheima identified by '123456';
3.授权itheima用户访问blog数据库
$ grant all on blog.* to 'itheima'@'%';
4.授权结束后刷新特权
$ flush privileges;
2. 配置MySQL数据库
文档
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 数据库引擎
'HOST': '127.0.0.1', # 数据库主机
'PORT': 3306, # 数据库端口
'USER': 'itheima', # 数据库用户名
'PASSWORD': '123456', # 数据库用户密码
'NAME': 'blog' # 数据库名字
},
}
文档 可能出现的错误
出现错误的原因:
解决办法:
3. 安装PyMySQL扩展包
1.安装驱动程序
$ pip install PyMySQL
2.在工程同名子目录的__init__.py 文件中,添加如下代码:
import pymysql
pymysql.install_as_MySQLdb()
配置完成后:运行程序,测试结果。
1.2.3 配置Redis数据库
1. 安装django-redis扩展包
1.安装django-redis扩展包
$ pip install django-redis
2.django-redis使用说明文档
点击进入文档
2. 配置Redis数据库
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:
session:
SESSION_ENGINE
SESSION_CACHE_ALIAS:
配置完成后:运行程序,测试结果。
1.2.4 配置工程日志
文档
1. 配置工程日志
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', # 日志器接收的最低日志级别
},
}
}
2. 准备日志文件目录
3. 日志记录器的使用
不同的应用程序所定义的日志等级可能会有所差别,分的详细点的会包含以下几个等级:
import logging
# 创建日志记录器
logger = logging.getLogger('django')
# 输出日志
logger.debug('测试logging模块debug')
logger.info('测试logging模块info')
logger.error('测试logging模块error')
1.2.5 静态资源文件
1. 准备静态文件
2. 指定静态文件加载路径
STATIC_URL = '/static/'
# 配置静态文件加载路径
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
配置完成后:运行程序,测试结果。
2.1 界面展示
1. 创建用户模块应用
创建应用users
$ python manage.py startapp users
2. 注册用户模块应用
INSTALLED_APPS = [
...
'users.apps.UsersConfig',
]
注册完users应用后,运行测试程序。
3. 准备模板目录并设置模板路径
4. 定义用户注册视图
1.将static文件夹下在register.html拖拽到templates文件中
2.在users.views.py文件中定义视图
from django.views import View
class RegisterView(View):
"""用户注册"""
def get(self, request):
"""
提供注册界面
:param request: 请求对象
:return: 注册界面
"""
return render(request, 'register.html')
5. 定义用户注册路由
1.在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'),
]
2.在工程的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')),
]
运行测试程序。
6.修改静态文件加载方式
1.是 由于静态资源加载是相对路径,因此我们需要修改静态资源的加载方式
以下代码是html的header处修改
{% load staticfiles %}
。。。
以下代码是html的footer处修改
运行测试程序,没有问题
1. Django默认用户认证系统
2. Django默认用户模型类
父类AbstractUser介绍
3. 自定义用户模型类
思考:为什么要自定义用户模型类?
如何自定义用户模型类?
from django.db import models
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
4. 指定用户模型类
文档
思考:为什么Django默认用户模型类是User?
结论:
配置规则: AUTH_USER_MODEL = '应用名.模型类名'
# 指定本项目用户模型类
AUTH_USER_MODEL = 'users.User'
5. 迁移用户模型类
1.创建迁移文件
2.执行迁移文件
运行测试程序
1. 准备captcha包(该包用于生成图形验证码)
1.将生成图片验证码的库复制到新建的libs包中。
2.安装Python处理图片的库 报错信息如下:
解决方案:在虚拟环境中安装Pillow。执行pip install Pillow即可
2. 图形验证码后端接口设计
1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | imagecode?uuid=xxxxx-xxxx-xxxxxx |
2.请求参数:路径参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
uuid | string | 是 | 唯一编号 |
3.响应结果:image/jpeg
3. 图形验证码后端实现
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'),
]
4. 修改模板中图片验证码HTML代码
1.html中的原代码如下
2.修改如下
1. 容联云短信平台操作
1.容联云官网
2.容联云管理控制台
3 .添加容联云测试手机号
4.短信模板 免费开发测试使用的模板ID为1,形式为:【云通讯】您使用的是云通讯短信模板,您的验证码是{1},请于{2}分钟内正确输入。
2. 短信发送测试
1.集成短信SDK到库中 CCPRestSDK.py:由容联云通讯开发者编写的官方SDK文件,包括发送模板短信的方法 ccp_sms.py:调用发送模板短信的方法
2.修改相应参数进行测试
3.进行测试
3. 短信验证码后端接口设计
1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /smscode/?mobile=xxxxℑ_code=xxxx&uuid=xxxxx |
2.请求参数:路径参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
mobile | string | 是 | 手机号 |
image_code | string | 是 | 图形验证码 |
uuid | string | 是 | 唯一编号 |
3.响应结果:JSON
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
4. 短信验证码后端逻辑实现
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': '发送短信成功'})
5. 添加response_code文件
在工程中新建utils包,将response_code文件复制到utils中
1. 用户注册接口设计
1.请求方式
选项 | 方案 |
---|---|
请求方法 | POST |
请求地址 | /register/ |
2.请求参数:表单参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
mobile | string | 是 | 用户名 |
password | string | 是 | 密码 |
password2 | string | 是 | 确认密码 |
sms_code | string | 是 | 短信验证码 |
3.响应结果:HTML
响应结果 | 响应内容 |
---|---|
注册失败 | 响应错误提示 |
注册成功 | 重定向到首页 |
2. 用户注册接口实现
1.注册视图
import re
from users.models import User
from django.db import DatabaseError
class RegisterView(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.create_user(username=mobile,mobile=mobile, password=password)
except DatabaseError:
return HttpResponseBadRequest('注册失败')
# 响应注册结果
return HttpResponse('注册成功,重定向到首页')
2.在进行页面提交时报403csrf问题
3.在HTML表单中添加csrf_token
1.创建首页子应用
1.创建首页应用:home
$ python manage.py startapp home
2.定义首页视图:IndexView
from django.views import View
class IndexView(View):
"""首页广告"""
def get(self, request):
"""提供首页广告界面"""
return render(request, 'index.html')
3.配置首页路由
在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')),
]
4.测试首页是否可以正常访问
http://127.0.0.1:8000/
5.修改注册成功后,重定向到首页
from django.shortcuts import redirect
from django.urls import reverse
# 响应注册结果
return redirect(reverse('home:index'))
说明:
当前的需求是:注册成功后即表示用户认证通过
1. login()方法介绍
2. login()方法使用
# 保存注册数据
try:
user=User.objects.create_user(username=mobile,mobile=mobile, password=password)
except DatabaseError:
return HttpResponseBadRequest('注册失败')
# 实现状态保持
login(request, user)
# 响应注册结果
return redirect(reverse('home:index'))
3. 查看状态保持结果
4. 设置首页所需的用户名信息和登录状态
1.设置cookie代码
# 保存注册数据
try:
user=User.objects.create_user(username=mobile,mobile=mobile, password=password)
except DatabaseError:
return HttpResponseBadRequest('注册失败')
# 实现状态保持
from django.contrib.auth import login
login(request, user)
#跳转到首页
response = redirect(reverse('home:index'))
#设置cookie
#登录状态,会话结束后自动过期
response.set_cookie('is_login',True)
#设置用户名有效期一个月
response.set_cookie('username',user.username,max_age=30*24*3600)
return response
2.查看cookie数据
1. 登录页面展示
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中的资源加载方式
{% load staticfiles %}
...
...
还没有账号?注册新账号
2. 登录接口设计
1.请求方式
选项 | 方案 |
---|---|
请求方法 | POST |
请求地址 | /login/ |
2.请求参数:表单
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
username | string | 是 | 用户名 |
password | string | 是 | 密码 |
remember | string | 否 | 是否记住用户 |
3.响应结果:HTML
字段 | 说明 |
---|---|
登录失败 | 响应错误提示 |
登录成功 | 重定向到首页 |
3. 登录接口实现
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
注意: 默认的认证方法中是对username进行认证。我们需要修改认证的字段为mobile。所以我们需要在User的模型中修改 USERNAME_FIELD='mobile'才能实现手机号的认证
1. 用户名写入到cookie
# 响应登录结果
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
2. Vue渲染首页用户名
1.index.html
[[username]]
登录
2.index.js
mounted(){
//获取用户名信息
this.username=getCookie('username');
//获取是否登录信息
this.is_login=getCookie('is_login');
},
1. logout()方法介绍
logout()位置:
logout(request)
2. logout()方法使用
from django.contrib.auth import logout
class LogoutView(View):
def get(self,request):
# 清理session
logout(request)
# 退出登录,重定向到登录页
response = redirect(reverse('home:index'))
# 退出登录时清除cookie中的登录状态
response.delete_cookie('is_login')
return response
提示:
3.实现退出登录
1. 忘记密码页面展示
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
urlpatterns = [
# 参数1:路由
# 参数2:视图函数
# 参数3:路由名,方便通过reverse来获取路由
path('forgetpassword/', ForgetPasswordView.as_view(),name='forgetpassword'),
]
3 .修改forget_password.html中的资源加载方式
{% load staticfiles %}
...
...
4 .修改login.html中的忘记密码的跳转连接
忘记密码?
2. 忘记密码接口设计
1.请求方式
选项 | 方案 |
---|---|
请求方法 | POST |
请求地址 | /forgetpassword/ |
2.请求参数:表单
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
mobile | string | 是 | 用户名 |
password | string | 是 | 密码 |
password2 | string | 是 | 确认密码 |
sms_code | string | 是 | 短信验证码 |
3.响应结果:HTML
字段 | 说明 |
---|---|
修改失败 | 响应错误提示 |
修改成功 | 重定向到登录 |
3. 忘记密码接口实现
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
5.1 用户中心展示
1. 页面展示
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中的资源加载方式
{% load staticfiles %}
...
...
个人信息
退出登录
4 .修改index.html中的的跳转连接
个人信息
2. 判断用户是否登录
需求:
实现方案:
Django用户认证系统提供了方法
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'))
3. 获取用户信息,模板渲染数据
1.获取用户信息修改center.html中的数据显示
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中的数据显示
1. 用户中心接口设计
1.请求方式
选项 | 方案 |
---|---|
请求方法 | POST |
请求地址 | /center/ |
2.请求参数:表单
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
username | string | 否 | 用户名 |
avatar | file | 否 | 头像 |
desc | string | 否 | 个人简介 |
3.响应结果:HTML
字段 | 说明 |
---|---|
修改失败 | 响应错误提示 |
修改成功 | 刷新展示 |
2. 用户中心修改接口实现
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
3. 用户中心头像的上传和展示
1.在settings.py文件中设置图片上传的路径并新建文件夹media
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
2.重新上传之后发现无法访问
解决方案:
第一步:设置图片的统一url都以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. 页面展示
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中的资源加载方式
{% load staticfiles %}
...
...
写文章
个人信息
退出登录
4 .修改index.html中的的跳转连接
写文章
1. 定义模型类
在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
2. 迁移模型类
1.创建迁移文件
2.执行迁移文件
网站的管理员负责查看、添加、修改、删除数据
1.管理界面本地化
LANGUAGE_CODE = 'zh-Hans' #原配置信息为'en-us'
TIME_ZONE = 'Asia/Shanghai'#原配置信息为'UTC'
2.创建管理员
1.我们需要在User模型中设置 REQUIRED_FIELDS
#创建超级管理员的需要必须输入的字段
REQUIRED_FIELDS = ['username','email']
2.在终端创建超级管理员
3.注册模型类
1.站点管理能够显示类别管理是因为我们的模型的元类中设置做了设置 class Meta: verbose_name = '类别管理' verbose_name_plural = verbose_name 2. 注册模型成功后, 就可以在站点管理界面方便快速的管理数据.
4.发布内容到数据库
模型类展示我们输入的内容是因为我们在模型中实现了__ str\_方法_ 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文件中使用模板语言展示数据
1. 定义模型类
在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
2. 迁移模型类
1.创建迁移文件
2.执行迁移文件
1. 博客保存接口设计
1.请求方式
选项 | 方案 |
---|---|
请求方法 | POST |
请求地址 | /writeblog/ |
2.请求参数:表单
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
title | string | 是 | 标题 |
avatar | file | 是 | 标题图 |
category | string | 是 | 栏目分类 |
tags | string | 是 | 标签 |
sumary | string | 是 | 文章摘要 |
content | string | 是 | 文章内容 |
3.响应结果:HTML
字段 | 说明 |
---|---|
提交失败 | 响应错误提示 |
提交成功 | 跳转到详情页面 |
2. 用户中心修改接口实现
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'))
1. 首页接口设计
1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /?cat_id=xxx&page_num=xxx&page_size=xxx |
2.请求参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
cat_id | string | 否 | 分类id |
page_num | string | 否 | 文章分页页码 |
page_size | string | 否 | 文章每页条目数 |
3.响应结果:HTML
字段 | 说明 |
---|---|
失败 | 响应错误提示 |
成功 | 展示数据 |
2. 查询分类数据并展示
1.查询分类文章数据并通过context传递给HTML
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文件中使用模板语言展示分类数据
1. 查询分页数据并展示
1.查询分类文章数据并通过context传递给HTML
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文件中使用模板语言展示分类数据
{% for article in articles %}
{{ article.category.title }}
{{ article.tags }}
{{ article.title }}
{{ article.sumary }}
{{ article.total_views }}
{{ article.comments_count }}
{{ article.created | date }}
{% endfor %}
3.修改底部js分页代码
2. 插入更多测试数据
我们可以通过蠕虫复制来插入更多测试数据
insert into tb_article(avatar,tags,title,sumary,content,total_views,comments_count,created,updated,author_id,category_id)
select avatar,tags,title,sumary,content,total_views,comments_count,created,updated,author_id,category_id fro
1. 页面展示
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中的资源加载方式
{% load staticfiles %}
...
...
写文章
个人信息
退出登录
2. 首页接口设计
1.请求方式
选项 | 方案 |
---|---|
请求方法 | POST |
请求地址 | /?id=xxx&page_num=xxx&page_size=xxx |
2.请求参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
id | string | 否 | 文章id |
page_num | string | 否 | 评论页码 |
page_size | string | 否 | 评论每页条目数 |
3.响应结果:HTML
字段 | 说明 |
---|---|
失败 | 响应错误提示 |
成功 | 展示数据 |
3. 查询分类数据并展示
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文件中使用模板语言展示文章数据
#分类数据展示
#详情数据展示
{{ article.title }}
作者:{{ article.author.username }}浏览:{{ article.total_views }}
{{ article.content|safe }}
4. 修改首页跳转到详情页面的链接
{{ article.title }}
1.在home.views.py文件中定义视图
try:
article=Article.objects.get(id=id)
except Article.DoesNotExist:
return render(request,'404.html')
2 .修改404.html中的资源加载方式
{% load staticfiles %}
...
1. 添加文章浏览量数据
1.每次请求文章详情时给浏览量+1
try:
article=Article.objects.get(id=id)
except Article.DoesNotExist:
return render(request,'404.html')
else:
article.total_views+=1
article.save()
2. 查询推荐文章并展示
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文件中使用模板语言展示推荐数据
1. 定义模型类
在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
2. 迁移模型类
1.创建迁移文件
2.执行迁移文件
1. 发表评论接口设计
1.请求方式
选项 | 方案 |
---|---|
请求方法 | POST |
请求地址 | /detail/ |
2.请求参数:表单
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
user_id | string | 是 | 发表评论的用户id |
article_id | string | 是 | 评论的文字id |
content | string | 是 | 评论内容 |
3.响应结果:HTML
字段 | 说明 |
---|---|
提交失败 | 响应错误提示 |
提交成功 | 刷新页面展示 |
2. 发表评论接口实现
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修改
1. 查询评论数据并展示
1.查询评论数据并通过context传递给HTML
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文件中使用模板语言展示分类数据
共有{{ total_count }}条评论
{% for comment in comments %}
{{ comment.user.username }} {{ comment.created | date:'Y:m:d H:i:s' }}
{{ comment.content|safe }}
{% endfor %}
3.修改底部js分页代码
2. 插入更多测试数据
我们可以通过蠕虫复制来插入更多测试数据
insert into tb_comment(content,created,article_id,user_id)
select content,created,article_id,user_id from tb_comment;