从0开发一个Django博客系统

Posted 黑马程序员官方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从0开发一个Django博客系统相关的知识,希望对你有一定的参考价值。

目录

一、项目准备

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 项目分析

需求分析原因
  • 项目中,需求驱动开发。即开发人员需要以需求为目标来实现业务逻辑。

需求分析方式

  • 企业中,借助产品原型图分析需求。
  • 需求分析完后,前端按照产品原型图开发前端页面,后端开发对应的业务及响应处理。
  • 提示:我们现在假借示例网站作为原型图来分析需求。

需求分析内容

  • 页面及页面上的业务逻辑
  • 归纳业务逻辑并划分模块

1. 项目主要页面介绍

1.注册页面

2.登录页面

3.忘记密码页面

4.用户中心页面

5.写博客页面

6.博客首页

7.博客详情页面

2. 归纳项目主要模块

模块功能
注册图形验证、短信验证
登录状态保持、Cookie、Session
个人中心图片上传、更新数据
发布博客数据入库
博客首页数据分页
博客详情博客详情数据展示、评论功能

3. 项目开发模式

选项技术选型
开发模式前后端不分离
后端框架Django + Django模板引擎

1.2 工程创建和配置

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' # 数据库名字
    ,
文档 可能出现的错误
  • Error loading MySQLdb module: No module named 'MySQLdb'.

出现错误的原因:

  • Django中操作MySQL数据库需要驱动程序MySQLdb
  • 目前项目虚拟环境中没有驱动程序MySQLdb

解决办法:

  • 安装PyMySQL扩展包
  • 因为MySQLdb只适用于Python2.x的版本,Python3.x的版本中使用PyMySQL替代MySQLdb

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:
  • 默认的Redis配置项,采用0号Redis库。

session:

  • 状态保持的Redis配置项,采用1号Redis库。

SESSION_ENGINE

  • 修改session存储机制使用Redis保存。

SESSION_CACHE_ALIAS:

  • 使用名为"session"的Redis配置项存储session数据。

配置完成后:运行程序,测试结果。

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. 日志记录器的使用

不同的应用程序所定义的日志等级可能会有所差别,分的详细点的会包含以下几个等级:

  • FATAL/CRITICAL = 重大的,危险的
  • ERROR = 错误
  • WARNING = 警告
  • INFO = 信息
  • DEBUG = 调试
  • NOTSET = 没有设置
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 %
    <!-- 引入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>
运行测试程序,没有问题

2.2 定义用户模型类

1. Django默认用户认证系统

  • Django自带用户认证系统
  • 它处理用户账号、组、权限以及基于cookie的用户会话。
  • Django认证系统位置
  • django.contrib.auth包含认证框架的核心和默认的模型。
  • Django认证系统同时处理认证和授权
  • 认证:验证一个用户是否它声称的那个人,可用于账号登录。
  • 授权:授权决定一个通过了认证的用户被允许做什么。
  • Django认证系统包含的内容
  • 用户:用户模型类、用户认证。
  • 权限:标识一个用户是否可以做一个特定的任务,MIS系统常用到。
  • 组:对多个具有相同权限的用户进行统一管理,MIS系统常用到。
  • 密码:一个可配置的密码哈希系统,设置密码、密码校验。

2. Django默认用户模型类

  • Django认证系统中提供了用户模型类User保存用户的数据。
  • User对象是认证系统的核心。
  • Django认证系统用户模型类位置
  • django.contrib.auth.models.User

父类AbstractUser介绍

  • User对象基本属性
  • 创建用户必选:username、password
  • 创建用户可选:email、first_name、last_name、last_login、date_joined、is_active 、is_staff、is_superuse
  • 判断用户是否通过认证:is_authenticated
  • USERNAME_FIELD:可以修改用户名认证字段
  • 创建用户的方法 user = User.objects.create_user(username, password, **extra_fields)
  • 用户认证的方法 from django.contrib.auth import authenticate user = authenticate(username=username, password=password, **kwargs)
  • 处理密码的方法
  • 设置密码:set_password(raw_password)
  • 校验密码:check_password(raw_password)

3. 自定义用户模型类

思考:为什么要自定义用户模型类?
  • 观察注册界面会发现,个人博客注册页面中必须有手机号,而且在登录页面中也使用手机号进行认证。此外个人中心页面中有个人头像和个人简介字段。
  • 但是Django默认用户模型类中没有这些字段,所以要自定义用户模型类。

如何自定义用户模型类?

  • 继承自AbstractUser(可通过阅读Django默认用户模型类的源码得知) 。
  • 新增手机号字段,头像字段和简介字段。
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?
  • 阅读源代码:'django.conf.global_settings’ AUTH_USER_MODEL = 'auth.User'

结论:

  • Django用户模型类是通过全局配置项AUTH_USER_MODEL决定的

配置规则: AUTH_USER_MODEL = '应用名.模型类名'

# 指定本项目用户模型类

AUTH_USER_MODEL = 'users.User'

5. 迁移用户模型类

1.创建迁移文件
  • python manage.py makemigrations

2.执行迁移文件

  • python manage.py migrate

运行测试程序

2.3 图形验证码

1. 准备captcha包(该包用于生成图形验证码)

1.将生成图片验证码的库复制到新建的libs包中。

2.安装Python处理图片的库 报错信息如下:

解决方案:在虚拟环境中安装Pillow。执行pip install Pillow即可

2. 图形验证码后端接口设计

1.请求方式

选项方案
请求方法GET
请求地址imagecode?uuid=xxxxx-xxxx-xxxxxx

2.请求参数:路径参数

参数名类型是否必传说明
uuidstring唯一编号

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中的原代码如下
<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;">

2.4 短信验证码

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.请求参数:路径参数

参数名类型是否必传说明
mobilestring手机号
image_codestring图形验证码
uuidstring唯一编号

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中

2.5 用户注册实现

1. 用户注册接口设计

1.请求方式

选项方案
请求方法POST
请求地址/register/

2.请求参数:表单参数

参数名类型是否必传说明
mobilestring用户名
passwordstring密码
password2string确认密码
sms_codestring短信验证码

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]\\d9$', 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

2.6 展示首页

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'))

2.7 状态保持

说明:
  • 如果需求是注册成功后即表示用户认证通过,那么此时可以在注册成功后实现状态保持
  • 如果需求是注册成功后不表示用户认证通过,那么此时不用在注册成功后实现状态保持

当前的需求是:注册成功后即表示用户认证通过

1. login()方法介绍

  1. 状态保持:
  • 将通过认证的用户的唯一标识信息(比如:用户ID)写入到当前session会话中
  1. login()方法:
  • Django用户认证系统提供了login()方法
  • 封装了写入session的操作,帮助我们快速实现状态保持
  1. login()位置:
  • django.contrib.auth.__init__.py文件中 login(request, user)

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数据

三、登录

3.1 手机登录

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中的资源加载方式
<!-- 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>

2. 登录接口设计

1.请求方式

选项方案
请求方法POST
请求地址/login/

2.请求参数:表单

参数名类型是否必传说明
usernamestring用户名
passwordstring密码
rememberstring是否记住用户

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]\\d9$', 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'才能实现手机号的认证

3.2 首页用户名展示

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
<!-- 如果用户已经登录,则显示用户名下拉框 -->
<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');
,

3.3 退出登录

1. logout()方法介绍

  1. 退出登录:
  • 回顾登录:将通过认证的用户的唯一标识信息,写入到当前session会话中
  • 退出登录:正好和登录相反(清理session会话信息)
  1. logout()方法:
  • Django用户认证系统提供了logout()方法
  • 封装了清理session的操作,帮助我们快速实现登出一个用户

logout()位置:

  • django.contrib.auth.__init__.py文件中
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
提示:
  • 由于首页中登录状态是从cookie中读取的。
  • 所以退出登录时,需要将cookie中登录状态清除。

3.实现退出登录

<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>

四、忘记密码

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中的资源加载方式
<!-- 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>

2. 忘记密码接口设计

1.请求方式

选项方案
请求方法POST
请求地址/forgetpassword/

2.请求参数:表单

参数名类型是否必传说明
mobilestring用户名
passwordstring密码
password2string确认密码
sms_codestring短信验证码

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]\\d9$', 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中的资源加载方式
<!-- 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>

2. 判断用户是否登录

需求:
  • 当用户登录后,才能访问用户中心。
  • 如果用户未登录,就不允许访问用户中心,将用户引导到登录界面。

实现方案:

  • 需要判断用户是否登录。
  • 根据是否登录的结果,决定用户是否可以访问用户中心。

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'))

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中的数据显示
<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">
  

以上是关于从0开发一个Django博客系统的主要内容,如果未能解决你的问题,请参考以下文章

web开发-Django博客系统

网站开发学习Python实现-Django项目部署-同步之前写的博客(6.2.2)

纯django开发博客系统

Django——博客项目

Django 博客开发教程 10 - 页面侧边栏:使用自定义模板标签

Django 系列博客