django个人博客

总体的架构

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

创建py项目

使用pycharm软件创建一个django项目

在这里插入图片描述
项目创建完之后 可以直接在manage.py中运行 启动项目

可能会出现的坑(错误)

在这里插入图片描述

该问题就是把‘/’识别为除号了,两个str无法进行除号。代码实际意思是将两个str进行拼接,进入settings.py进行如下修改:

后面有具体配置文件

  1. TypeError: unsupported operand type(s) for /: ‘str’ and 'str

解决办法

‘DIRS‘: [BASE_DIR / ‘templates‘] TypeError: unsupported operand type(s) for /: ‘str‘ and ‘str‘
更改地方

在这里插入图片描述

  1. query = query.decode(errors=‘replace‘)
    解决办法:在这里插入图片描述

  2. Type 'manage.py help ’ for help on a specific subcommand
    在这里插入图片描述
    添加 runserver
    在这里插入图片描述
    运行项目
    在这里插入图片描述
    在这里插入图片描述
    准备阶段

配置MySQL数据库

首先要启动mysql服务器

  1. 通过系统服务器启动、停止mysql

如果mysql设置为windows服务,可以通过选择 开始>控制面板>系统与安全>管理工具>服务 打开windows服务管理器,在服务的列表找到MySQL服务器并右键选择启动
在这里插入图片描述

  1. 在命令提示符下启动、停止mysql

打开dos窗口(建议用管理员身份打开,否则权限不够拒绝访问
输入

net start mysql57

mysql57是在安装配置mysql环境中设置的服务器名称,当时我设置的是MySQL57

配置MySQL服务器

问题

如果在使用mysql命令连接MySQL服务器时提示 ‘mysql’ 不是内部或外部命令,也不是可运行的程序或批处理文件,说明未配置环境变量,需要将MySQL的bin文件夹位置添加到windows 环境变量>系统变量>path中
在这里插入图片描述

右键计算机选择管理>高级系统设置>环境变量,在系统变量中选择path,点击编辑>新建>将MySQL服务器的bin文件夹位置添加到变量值文本框中,点击确定

在这里插入图片描述
在这里插入图片描述
此时再使用MySQL命令即可连接到服务器

mysql -u登录名 -h服务器地址 -p密码

-h可省略不写

在这里插入图片描述
退出有两条命令

quit
exit

配置数据库MySQ

打开本地数据库–> 用户自行选择,可以创建一个新的用户也可以使用root
连接到数据库之,新建数据库

create database blog charset=utf8;

创建新用户或者使用root用户

create user thirteen identified by '123456';

授权新用户的权限

grant all on blog.* to 'thirteen';

授权结束后刷新特权

flush privileges;

数据库创建完毕
在这里插入图片描述
在创建好的django项目中找到配置文件里面的settings.py,连接数据库
在这里插入图片描述

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # 数据库引擎
        'HOST': '127.0.0.1',    # 数据库主机
        'NAME': 'blog',         # 数据库名字
        'PORT': 3306,           # 数据库端口
        'USER': 'thirteen',     # 数据库用户名
        'PASSWORD': "123456"    # 数据库用户密码
    }
}

可能出现的错误

Error loading MySQLdb module: No module named ‘MySQLdb’.
出现错误的原因:

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

安装pyMySQL扩展

安装驱动程序

pip install PyMySQL

配置并使用数据库
在这里插入图片描述

配置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菜鸟教程
redis安装

在这里插入图片描述

启动redis

redis-server

在这里插入图片描述
redis运行成功

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

在这里插入图片描述
配置完成后:运行程序,测试结果。
在这里插入图片描述
项目启动成功

也可以直接导入static资源

下面开始我们的具体的模块与应用

创建用户模块应用—注册

创建应用users
借助虚拟环境

python manage.py startapp users

可以在cmd命令行中完成,也可以在pycharm自带的Terminal中完成
在这里插入图片描述
创建完之后目录结构会出现users文件夹

在工程的setting.py中 注册用户模块
在这里插入图片描述

在工程下创建templates目录,一般来说pycharm在新建工程的时候就会帮我们自动创建好,如果没有的话可以自己手动创建一个

将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文件夹发生了变化,资源就无法正常加载,因此我们需要修改静态资源的加载方式
使用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>
# 运行测试程序,没有问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
运行测试程序。
以上步骤是为了完成 第一个注册见面并且配置路由
首先配置user里面的视频viwes 和路由,然后在设置工程的urls.py总路由中添加子应用路由引导
在这里插入图片描述
界面成功加载。

定义用户模型类

Django默认用户认证系统

●Django自 带用户认证系统

  • 它处理用户账号、组、权限以及基于cookie的用户会话。

●Django认证系统位置

  • django contrib .auth包含认证框架的核心和默认的模型。

●Django认证系统同时处理认证和授权

  • 认证:验证一个用户是否它声称的那个人,可用于账号登录。
  • 授权:授权决定一个通过了认证的用户被允许做什么。

●Django认证系统包含的内容 。

  • 用户:用户模型类、用户认证。

  • 权限:标识一个用户是否可以做一个特定的任务,MIS系统常用到。

  • 组:对多个具有相同权限的用户进行统一管理,MIS系统常用到。

  • 密码:一个可配置的密码哈希系统,设置密码、密码校验。 Django默认用户模型类

自定义用户模型类
●Django认证系统中提供了用户模型类User保存用户的数据。

  • User对象是认证系统的核心。

●Django认证 系统用户模型类位置

  • django.contrib.auth.models.User
class|User (AbstractUser) :
	"""
	Users within the Django authentication system are represented by this modeI.
	Username and password are required. Other fields are optiona1
	"""
	class Meta (AbstractUser. Meta) :
		swappable =’AUTH_ USER_ MODEL'

●父类AbstractUser介绍

User对象基本属性

  • 创建用户必选:username、 password
  • 创建用户可选: email、first_ naife、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, pas sword=password, **kwargs)

处理密码的方法

  • 校验密码: check password(raw_ password)
  • 校验密码: check password(raw_ password)

思考:为什么要自定义用户模型类?
观察注册界面会发现,个人博客注册页面中必须有手机号,而且在登录页面中也使用手机号进行认证。此外个人中心页面中有个人头像和个人简介字段。
说白了就是用户需要自己增加字段to

定义用户模型

在users下的models.py中添加

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 makemigrations

在这里插入图片描述

执行迁移文件

连接MySQL
在这里插入图片描述
发现所使用的数据库为空,执行迁移

python manage.py migrate

在这里插入图片描述

图形验证码接口设计和定义

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

在工程下新建一个libs包,将生成图片验证码的库复制到新建的libs包中
在这里插入图片描述
错误:
在这里插入图片描述
解决办法:在虚拟环境中安装pillow

pip install Pillow

图形验证码后端接口设计

当我们点击图片验证码时,向后台服务器发送了一个get请求,由前端生成一个uuid传给服务器我们要将生成的图片验证码保存在redis中
redis保存的时key-value形式的值,用uuid做key,图片验证码里的数值做value
前端返回uuid之后,后端返回一个图片数据请求方式

1. 请求方式

选项方案
请求方法GET
请求地址/smscode/?mobile=xxxx_code&uuid=xxxxx

2.请求参数:路径参数

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

3.响应结果JSON

字段说明
code状态码
errmsg错误信息

图形验证码后端实现

1. 在users.views.py中定义视图

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.子路由
在users.urls.py中定义

from django.urls import path
from users.views import ImageCodeView

urlpatterns = [
    # 参数1:路由
    # 参数2:视图函数
    # 参数3:路由名,方便通过reverse来获取路由
    path('imagecode/', ImageCodeView.as_view(),name='imagecode'),
]

在这里插入图片描述

修改模板中图片验证码HTML代码

查看变量名并复制
在这里插入图片描述
修改static>templates>register.html中图片验证码src的内容
html中的原代码如下

<img src="{% static 'img/image_code.png' %}" @click="generate_image_code" alt="" style="width: 110px;height: 40px;">

修改如下

<img :src="image_code_url" @click="generate_image_code" alt="" style="width: 110px;height: 40px;">

在这里插入图片描述

路由配置成功在注册界面可以正常显示验证码并切换

短信验证码

短信验证码 使用的是容联云官网容联云官网 注册 获取,并添加
在这里插入图片描述

在这里插入图片描述

1.集成短信SDK到库中

CCPRestSDK.py:由容联云通讯开发者编写的官方SDK文件,包括发送模板短信的方法

ccp_sms.py:调用发送模板短信的方法

在这里插入图片描述

在yuntongxun>sms.py文件中修改配置信息

在这里插入图片描述

在这里插入图片描述

短信验证码的后端接口设计

1.请求方式

选项方案
请求方法GET
请求方法/?cat_id=xxx&page_num=xxx&page_size=xxx

2.请求参数

参数名类型是否必传说明
cat_idstring分类id
page_numstring文章分页页码
page_sizestring文章每页条目数

3.响应结果:HTML

字段说明
失败响应错误提示
成功展示数据

短信验证码后端逻辑实现

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': '发送短信成功'})

以上代码写入users下的views下

配置response_code,因为其他地方也会用到,所以可以在工程下创建utils包,将response_code文件复制到该包下
在这里插入图片描述
在这里插入图片描述

添加路由

在users下的urls文件中添加路由

path('smscode/', SmsCodeView.as_view(),name='smscode'),

在这里插入图片描述

当点击验证码的时候 pycharm控制台中也会发送一个短信验证码,直接在run中查看就可以了
在这里插入图片描述

用户注册实现

用户注册接口设计

1.请求方式

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

2.请求参数:表单参数

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

3.响应结果:HTML

响应结果响应内容
注册失败响应错误提示
注册成功重定向到首页

用户注册实现

在users>views.py中添加

from django.db import DatabaseError
import re
from users.models import User
from django.shortcuts import redirect,reverse
#注册视图
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

在这里插入图片描述
创建子应用home

2.定义首页视图:IndexView—查询分类数据并展示
2.1.请求方式

选项方案
请求方法GET
请求方法/?cat_id=xxx&page_num=xxx&page_size=xxx

2.2.请求参数

参数名类型是否必传说明
cat_idstring分类id
page_numstring文章分页页码
page_sizestring文章每页条目数

2.3.响应结果:HTML

字段说明
失败响应错误提示
成功展示数据

3.查询分类文章数据并通过context传递给HTML

将static中的index.html文件拖拽到templates文件夹中

home>views.py中

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

在这里插入图片描述
做到这里,我们可以运行程序,查看结果
在这里插入图片描述
在这里插入图片描述
点击run中的网址,可以看到首页已经可以正常的显示出来了

注册页面重定向到首页

在users>views.py文件中的RegisterView(View)类的末尾添加代码

import re
from django.shortcuts import redirect,reverse

# 响应注册结果
return redirect(reverse('home:index'))

在这里插入图片描述

状态保持

login()方法介绍

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

调用login方法应该在页面跳转之前,注册成功之后

在users.views.py文件中定义视图

from django.contrib.auth import login
login(request,user)

在这里插入图片描述

显示注册的用户信息

设置cooki信息

用户登陆

登录页面展示

  1. 在users.views.py文件中定义视图
from django.views import View

class LoginView(View):

    def get(self,request):
        return render(request,'login.html')

在这里插入图片描述
将static下的login.html拖拽到templates文件夹下
在这里插入图片描述

  1. 在users.urls.py文件中定义路由
from users.views import LoginView
urlpatterns = [
    # 参数1:路由
    # 参数2:视图函数
    # 参数3:路由名,方便通过reverse来获取路由
    path('login/', LoginView.as_view(),name='login'),
]

在这里插入图片描述

  1. 修改login.html中的资源加载方式
<!-- Header部分 -->
<head>
    <!-- 网站采用的字符编码 -->
    <meta charset="utf-8">
    <!-- 网站标题 -->
    <title>登录</title>
    {% 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>
</head>

<!-- 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/login.js' %}"></script>

<!-- 点击注册部分 -->
<small class="form-text text-muted ml-1">还没有账号?<a href="{% url 'users:register' %}" style="color: cornflowerblue; ">注册新账号</a>
</small>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 修改index.html中的资源加载方式
<!-- body.nav标签下导航栏 -->
<!-- 定义导航栏 -->
<div>
	<a class="navbar-brand" href="{% url 'home:index' %}">个人博客</a>
</div>

<!--登录/个人中心-->
<!-- 如果用户未登录,则显示登录按钮 -->
<li class="nav-item" v-else>
	<a class="nav-link" href="{% url 'users:login' %}">登录</a>
</li>

在这里插入图片描述

在这里插入图片描述

登录接口实现

  1. 请求方式
选项方案
请求方案POST
请求地址/login/
  1. 请求参数:表单
参数名类型是否必传说明
usernamestring用户名
passwordstring密码
rememberstring是否记住用户
  1. 响应结果:HTML
字段说明
登陆成功响应错误提示
登陆失败重定向到首页

实现

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

遇到的问题

  1. 点击登录遇到CSRF问题
    在这里插入图片描述
    解决办法
    在login.html中的from表单中添加csrf_token
{% csrf_token %}

在这里插入图片描述

  1. 提示用户名或密码错误
    解决办法
    修改users.models.py中默认的字段为手机号
USERNAME_FIELD = 'mobile'

在这里插入图片描述

首页的用户名展示

用户名写入到cookie
Vue渲染首页用户名,修改静态资源的加载方式

<!--head部分-->
<head>
    <!-- 网站采用的字符编码 -->
    <meta charset="utf-8">
    <!-- 网站标题 -->
    <title>首页</title>
    {% load staticfiles %}
    <!-- 引入bootstrap的css文件 -->
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
    <!-- 引入monikai.css -->
    <link rel="stylesheet" href="{% static '/md_css/monokai.css' %}">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
    <!--导入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>
</head>

<!-- 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/index.js'%}"></script>
<script type="text/javascript" src="{% static 'js/jquery.pagination.min.js'%}"></script>

<!-- 部分图片 可以ctrl+f按照class搜索 -->
<div class="col-3">
      <img src="{% static 'img/1.jpg' %}">
</div>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
cooki中的数据是通过js去获取的,在static.js.index.js中修改

可能遇到的问题
js文件一打开就报错,这是由于编辑器的配置未修改造成的

在这里插入图片描述
打开设置>languages>javascript,将javascript language version版本修改为ECMAScript 6,点击应用确定

在index.js中我们看到vue变量的读取语法是以 “ [[ ]] ” 为格式读取的,所以要修改index.html中的用户名读取格式
在这里插入图片描述

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

在这里插入图片描述

回到index.js文件中,修改mounted()方法

mounted(){
    //获取用户名信息
    this.username=getCookie('username');
    //获取是否登录信息
    this.is_login=getCookie('is_login');
    //this.is_login=true
},

在这里插入图片描述
运行程序,注册账号,登录
在这里插入图片描述

退出登录

logout()方法介绍

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

在users.views.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

在这里插入图片描述
在路由中添加退出登录
users.urls.py

urlpatterns = [
    # 退出登录
    path('logout/', LogoutView.as_view(), name='logout'),
]

在这里插入图片描述
修改首页中退出按钮的点击
templates.index.html

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

在这里插入图片描述
最后可以修改index.html中静态资源的加载方式(避免项目结构改变导致资源无法加载)这里就不演示了

忘记密码

忘记密码的页面展示

  1. 在users.views.py文件中定义视图
from django.views import View

class ForgetPasswordView(View):

    def get(self, request):

        return render(request, 'forget_password.html')

在这里插入图片描述

  1. 将static下的forget_password.html文件拖拽到templates文件夹下
    在这里插入图片描述
  2. 在users.urls.py文件中定义路由
from users.views import ForgetPasswordView
path('forgetpassword/', ForgetPasswordView.as_view(),name='forgetpassword'),

在这里插入图片描述
配置完成后运行项目,查看忘记密码页面是否能正常加载
在这里插入图片描述

  1. 修改forget_password.html中的资源加载方式
<!-- Header部分 -->
<head>
    <!-- 网站采用的字符编码 -->
    <meta charset="utf-8">
    {% load staticfiles %}
    <!-- 网站标题 -->
    <title>重设密码</title>
    <!-- 引入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>
</head>

<!-- 导航栏部分,nav标签下 -->
<!-- 导航栏商标 -->
<div>
	<a class="navbar-brand" href="{% url 'home:index' %}">个人博客</a>
</div>

<!--登录/个人中心-->
<li class="nav-item" >
	<a class="nav-link" href="{% url 'users:login' %}">登录</a>
</li>

<!--图片验证码-->
<img :src="image_code_url" @click="generate_image_code" alt="" style="width: 110px;height: 40px;">

<!--js部分-->
<!-- 引入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/forget_password.js' %}"></script>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 修改login.html中的 “忘记密码” 的跳转连接
<a class="secondaryAction layui-text" href="{% url 'users:forgetpassword' %}">忘记密码?</a>

在这里插入图片描述
修改完成后,运行项目查看界面是否能正常显示
在这里插入图片描述

忘记密码的接口设计

  1. 请求方式
选项方案
请求方式POST
请求地址/forgetpassword/
  1. 请求参数:表单
参数名类型是否必传说明
mobilestring用户名
passwordstring密码
password2string确认密码
sms_codestring短信验证码
  1. 响应结果:HTML
字段说明
登陆成功响应错误提示
登陆失败重定向到首页

功能实现

users.views.py

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

在这里插入图片描述

  1. 在from表单中添加csrf_token
    在这里插入图片描述

用户中心的展示

页面展示

  1. 将static中的center.html文件拖拽到templates下
    在这里插入图片描述

  2. user.views.py 下定义用户中心视图

from django.views import View

class UserCenterView(View):

    def get(self,request):

        return render(request,'center.html')

在这里插入图片描述

  1. 在users.urls.py文件中定义路由
from users.views import UserCenterView
urlpatterns = [
    # 参数1:路由
    # 参数2:视图函数
    # 参数3:路由名,方便通过reverse来获取路由
    path('center/', UserCenterView.as_view(),name='center'),
]

在这里插入图片描述

  1. 修改center.html中的资源加载方式
<!-- Header部分 -->
<head>
    <!-- 网站采用的字符编码 -->
    <meta charset="utf-8">
    {% load staticfiles %}
    <!-- 网站标题 -->
    <title> 用户信息 </title>
    <!-- 引入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>
</head>

 <!-- 导航栏 -->
 <!-- 导航栏商标 -->
<div>
	<a class="navbar-brand" href="{% url 'home:index' %}">个人博客</a>
</div>

<!-- 登录用户的信息显示 -->
<!-- 如果用户已经登录,则显示用户名下拉框 -->
<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='{% url 'users:center' %}'>个人信息</a>
		<a class="dropdown-item" href='{% url 'users:logout' %}'>退出登录</a>
	</div>
</li>
<!-- 如果用户未登录,则显示登录按钮 -->
<li class="nav-item" v-else>
	<a class="nav-link" href="{% url 'users:login' %}">登录</a>
</li>

<!-- 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/center.js' %}"></script>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 修改index.html中的的跳转连接
<a class="dropdown-item" href='{% url 'users:center' %}'>个人信息</a>

在这里插入图片描述
运行项目检查界面是否正常跳转
登录博客后,点击右上角的账号,在弹出的下拉框中选择个人信息
在这里插入图片描述

判断用户是否登录

需求:

  1. 当用户登录后,才能访问用户中心。
  2. 如果用户未登录,就不允许访问用户中心,将用户引导到登录界面。

实现方案

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

Django用户认证系统提供了方法

  • request.user. is_authenticated() 来判断用户是否登录,如果通过登录验证则返回True反之,返回False
  • LoginRequiredMixin封装了判断用户是否登录的操作。

users.views.py
让UserCenterView类去继承LoginRequiredMixin

from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin
# 如果用户未登录,会进行默认的跳转,
# 默认的跳转连接是accounts/login/?next=/center/
class UserCenterView(LoginRequiredMixin,View):

    def get(self,request):

        return render(request,'center.html')

在这里插入图片描述
由于系统的自动跳转和我们的当前登录页面不匹配,需要我们手动修改系统的跳转路由

工程的settings.py,添加以下配置。

LOGIN_URL = '/login/'

在这里插入图片描述

实现next路由
users.views.py
在登录视图中LoginView(View)进行修改

# 响应登录结果
next_page=request.GET.get('next')
if next_page:
	response = redirect(next_page)
else:
	response = redirect(reverse('home:index'))  # 跳转到首页

在这里插入图片描述

获取用户信息,模板渲染数据

  1. users.views.py类UserCenterView()中
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)

在这里插入图片描述

  1. 修改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>
<!--<br><h5 class="col-md-4">暂无头像</h5><br>-->
<br> <div class="col-md-4">头像</div>
{% if avatar %}
	<img src="{{ avatar }}" style="max-width: 20%;" class="col-md-4"><br>
{% else %}
	<br><h5 class="col-md-4">暂无头像</h5><br>
{% endif %}


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

</form>

在这里插入图片描述
在这里插入图片描述
设置完成后可以使用debug查看表单数据,设置完断点,点击debug后,刷新页面
在这里插入图片描述
可以查看context的内容

用户中心修改

用户中心接口设计

  1. 请求方式
选项方案
请求方法POST
请求地址/center/
  1. 请求参数:表单
参数名类型是否必传说明
usernamestring用户名
avatarfile头像
descstring个人简介
  1. 响应结果:HTML
字段说明
修改失败响应错误提示
修改成功刷新展示

用户中心修改接口实现

  1. 设置用户中心头像上传保存的路径
    在工程下新建文件夹media
    在这里插入图片描述
    在setting中设置保存的路径
# 设置上传的头像保存到media目录下
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

在这里插入图片描述

users.views,py类UserCenterView()

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. 在center.html中添加csrf_token
<!--content-->
<div class="container" style="margin-bottom: 20px">
        <div class="row">
            <div class="col-12">
                <br>
                <form method="post" enctype="multipart/form-data">
                {% csrf_token %}

在这里插入图片描述

  1. 设置头像的图片路径
    (头像保存正常但是在页面无法显示)
    工程的setting.py
# 设置图片访问的统一路由
MEDIA_URL = '/media/'

在这里插入图片描述
设置图片访问的路由,引导到指定的文件目录
工程下的url.py

# 图片访问的路由
from django.conf import settings
from django.conf.urls.static import static
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

在这里插入图片描述
运行调试程序,登陆账号进入个人中心,填写完信息后点击修改,查看页面的显示信息

在这里插入图片描述
在这里插入图片描述
页面正常显示。
这里未对修改按钮做重定向的设置,所以点击修改后需要手动刷新浏览器才能正常显示

出现的错误

错误1

修改完简介后重新登录突然发现登录不了,点击注册也不行,控制台报错如下
在这里插入图片描述
点击注册:
在这里插入图片描述出现错误的原因

mysql数据库默认使用拉丁编码,需要将我们所使用的数据库编码修改为utf8
最后发现是我再配置数据库的时候忘记修改数据库的引擎导致
错误的引擎

解决办法

在工程下的setting.py中修改数据库引擎如下
在这里插入图片描述

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # 数据库引擎
        'HOST': '127.0.0.1',    # 数据库主机
        'NAME': 'blog',         # 数据库名字
        'PORT': 3306,           # 数据库端口
        'USER': 'thirteen',     # 数据库用户名
        'PASSWORD': "123456"    # 数据库用户密码
    }
}

修改完成后出现错误2

错误2

在这里插入图片描述

此处出现的错误同标题 “配置mysql数据库”>“可能出现的错误”

出现错误的原因

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

解决办法:

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

安装驱动程序

$ pip install PyMySQL

在这里插入图片描述

在工程同名子目录的__init__.py文件中,添加如下代码

import pymysql
pymysql.install_as_MySQLdb()
在这里插入图片描述
运行项目,登录时发生错误3

错误3

在这里插入图片描述
出现错误的原因
此错误同标题 “创建py项目”>“可能出现的坑”

解决办法
点击最下方的链接,进入operations.py文件中
在这里插入图片描述
点击运行,报错如下:
在这里插入图片描述
在这里插入图片描述

需要重新迁移文件 同 “定义用户模型类”>“迁移用户模型类”

python manage.py migrate

在这里插入图片描述
迁移完成后重新运行项目,重新注册账号,登录

完成后打开数据库,可以看到数据已经迁移过来了
在这里插入图片描述

最后如果上面的方法都不起作用的话,在setting下查看,添加注册home
在这里插入图片描述
删除users下以及home下的migrate文件夹,登录数据库,删除数据库,并重新建立一个新的数据库,然后再命令行中输入

python manage.py makemigrations
python manage.py migrate

如果执行失败的话,执行如下语句

python manage.py makemigrations appname
python manage.py migrate appname

参考自关于django中出现的No changes detected的各解决方法汇总

写博客页面展示

页面展示

将static下的write_blog.html拖拽到templates
在这里插入图片描述

在users.views.py文件中定义视图

from django.views import View

class WriteBlogView(LoginRequiredMixin,View):

    def get(self,request):

        return render(request,'write_blog.html')

在这里插入图片描述
在users.urls.py文件中定义路由

from users.views import WriteBlogView
urlpatterns = [
	# 写博客
    path('writeblog/', WriteBlogView.as_view(),name='writeblog'),
]

在这里插入图片描述
修改write_blog.html中的资源加载方式

<!-- Header部分 -->
<head>
    <!-- 网站采用的字符编码 -->
    <meta charset="utf-8">
    <!-- 网站标题 -->
    <title> 写文章 </title>
    {% load staticfiles %}
    <!-- 引入bootstrap的css文件 -->
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
    <!-- 引入monikai.css -->
    <link rel="stylesheet" href="{% static 'md_css/monokai.css' %}">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
    <!-- 引入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>
</head>

<!--导航栏 -->
<!-- 定义导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container">
        <!-- 导航栏商标 -->
        <div>
            <a class="navbar-brand" href="{% url 'home:index' %}">个人博客</a>
        </div>
    </div>
    <!--登录/个人中心-->
    <div class="navbar-collapse">
        <ul class="nav navbar-nav">
            <!-- 如果用户已经登录,则显示用户名下拉框 -->
            <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="{% url 'users:writeblog' %}">写文章</a>
                    <a class="dropdown-item" href='{% url 'users:center' %}'>个人信息</a>
                    <a class="dropdown-item" href='{% url 'users:login' %}'>退出登录</a>
                </div>
            </li>
            <!-- 如果用户未登录,则显示登录按钮 -->
            <li class="nav-item" v-else>
                <a class="nav-link" href="{% url 'users:login' %}">登录</a>
            </li>
        </ul>
    </div>
</nav>

<!--ckeditor-->
<script type="text/javascript" src="{% static 'ckeditor/ckeditor-init.js' %}" data-ckeditor-basepath="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>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在index.html中修改写文章的入口

在这里插入图片描述

文章分类

定义模型类
在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

    # admin站点显示,调试查看对象方便
    class Meta:
        db_table='tb_category'      # 修改表名
        verbose_name = '类别管理'       # admin站点显示
        verbose_name_plural = verbose_name

在这里插入图片描述
在setting中注册home子应用

注册了子应用以后才能生成迁移文件在这里插入图片描述
生成迁移文件

# 生成迁移文件
python manage.py makemigrations home
# python manage.py makemigrations appname

# 执行迁移文件
python manage.py migrate

在这里插入图片描述

文章分类后台管理

  • 网站的管理员负责查看、添加、修改、删除数据 Django能够根据定义的模型类自动地生成管理模块
  • 登陆站点:http://127.0.0.1:8000/admin
    在这里插入图片描述
  • 使用Django的管理模块, 需要按照如下步骤操作 :
  1. 管理界面本地化
  2. 创建管理员
  3. 注册模型类
  4. 发布内容到数据库

修改登陆界面的语言和时区
setting中找到LANGUAGE_CODE

# 修改语言显示
LANGUAGE_CODE = 'zh-Hans'
# 修改时区
TIME_ZONE = 'Asia/Shanghai'

在这里插入图片描述

创建管理员

users.medols.py

#创建超级管理员的需要必须输入的字段
REQUIRED_FIELDS = ['username','email']

在这里插入图片描述
终端输入创建超级管理员的指令

python manage.py createsuperuser

在这里插入图片描述
重置密码

python manager.py changepassword 用户名

登陆站点成功
站点界面中没有模型管理入口,因为没有注册模型类
在这里插入图片描述
注册模型类
home.admin.py

from home.models import ArticleCategory
# Register your models here.
# 注册模型
admin.site.register(ArticleCategory)

在这里插入图片描述
在这里插入图片描述
模型注册完之后我们即可在站点管理进行对分类操作

发布内容到数据库
在这里插入图片描述
![在这里插入图在这里插入图片描述
添加完成之后再去写文章里选择栏目
在这里插入图片描述

写博客页面展示分类

查询数据并展示

查询分类文章数据并通过context传递给HTML
users.views.py

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)

在这里插入图片描述

在write_blog.html文件中使用模板语言展示数据

<!-- 文章栏目 --
<div class="form-group"
	<label for="category">栏目</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

在这里插入图片描述
迁移模型类
先确认home子应用是否已注册

创建迁移文件

python manage.py makemigrations

执行迁移文件

python manage.py migrate

在这里插入图片描述

博客保存

博客保存接口设计

  1. 请求方式
选项方案
请求方法POST
请求地址/writeblog/
  1. 请求参数:表单
参数名类型是否必传说明
titlestring标题
avatarfile标题图
categorystring栏目分类
tagsstring标签
sumarystring文章摘要
contentstring文章内容
  1. 响应结果:HTML
字段说明
提交失败响应错误提示
提交成功跳转到详情页面

用户中心修改接口实现

users.views.py类WriteBlogView()中

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

在这里插入图片描述

在write_blog.html的from表单中添加csrf_token
在这里插入图片描述
在写博客页面中填写一点内容点击发布,跳转到首页,且数据已经入库

在这里插入图片描述

博客首页

首页分类数据展示

首页接口设计

  1. 请求方式
选项方案
请求方法GET
请求地址/?cat_id=xxx&page_num=xxx&page_size=xxx
  1. 请求参数
参数名类型是否必传说明
cat_idstring分类id
page_numstring文章分页页码
page_sizestring文章每页条目数
  1. 响应结果:HTML
字段说明
失败响应错误提示
成功展示数据

查询分类数据并展示

查询分类文章数据并通过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)

在这里插入图片描述
在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>

在这里插入图片描述

首页文章数据展示

查询分页数据并展示
查询分类文章数据并通过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)

在这里插入图片描述
在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" href="#" class="btn btn-sm mb-2 btn-warning">{{ article.category.title }}</a>
            <!-- 标签 -->
                <span>
                        <a href="#" class="badge badge-secondary">{{ article.tags }}</a>
                </span>
                <!-- 标题 -->
                <h4>
                    <b><a href="../static/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 }}&nbsp;&nbsp;&nbsp;</span>
                    <span><i class="fas fa-comments" style="color: yellowgreen;"></i>{{ article.comments_count }}&nbsp;&nbsp;&nbsp;</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>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
对分页的处理,修改底部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>

在这里插入图片描述

插入更多测试数据

我们可以通过蠕虫复制来插入更多测试数据

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 from tb_article;

用法:
命令行登录数据库后,use指定数据库,将代码复制黏贴并回车执行,回到首页刷新

博客详情

详情页面展示

页面展示
在home.views.py文件中定义视图

from django.views import View

class DetailView(View):

    def get(self,request):


        return render(request,'detail.html')

在这里插入图片描述
将static文件夹中的detial.html拖拽到templates中

在这里插入图片描述

在home.urls.py文件中定义路由

from home.views import DetailView
urlpatterns = [
    # 参数1:路由
    # 参数2:视图函数
    # 参数3:路由名,方便通过reverse来获取路由
    path('detail/', DetailView.as_view(),name='detail'),
]

在这里插入图片描述
修改detail.html中的资源加载方式

<!-- Header部分 -->
<head>
    <!-- 网站采用的字符编码 -->
    <meta charset="utf-8">
    <!-- 网站标题 -->
    <title>文章详情</title>
    {% 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>
</head>


<!-- 定义导航栏 -->

<!-- 导航栏商标 -->
<div>
	<a class="navbar-brand" href="{% url 'home:index' %}">个人博客</a>
</div>

 <!-- 页面跳转部分 -->
<!--登录/个人中心-->
<div class="navbar-collapse">
    <ul class="nav navbar-nav">

        <!-- 如果用户已经登录,则显示用户名下拉框 -->
        <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="{% url 'users:writeblog' %}">写文章</a>
                <a class="dropdown-item" href='{% url 'users:center'%}'>个人信息</a>
                <a class="dropdown-item" href='{% url 'users:center' %}'>退出登录</a>
            </div>
        </li>
        <li class="nav-item" v-else>
            <a class="nav-link" href="{% url 'users:login' %}">登录</a>
        </li>

    </ul>
</div>


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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
运行程序,输入路由

在这里插入图片描述
首页接口设计

  1. 请求方式
选项方案
请求方法POST
请求地址/?id=xxx&page_num=xxx&page_size=xxx
  1. 请求参数
参数名类型是否必传说明
idstring文章id
page_numstring评论页码
page_sizestring评论每页条目数
  1. 响应结果:HTML
字段说明
失败响应错误提示
成功展示数据

查询分类数据并展示

查询文章数据并通过context传递给HTML
home.views.py

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)

在这里插入图片描述
在detail.html文件中使用模板语言展示文章数据

<!-- 分类 -->
<div class="collapse navbar-collapse" id="navbarNav">
    <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>
</div>


#详情数据展示
<div class="col-9">
	<!-- 标题及作者 -->
    <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;">
        <p><p>{{ article.content|safe }}</p></p>
    </div>

在这里插入图片描述

在这里插入图片描述

修改index.html首页跳转到详情页面的链接

<!-- 标题 -->
<h4>
    <b><a href="{% url 'home:detail' %}?id={{ article.id }}" style="color: black;">{{ article.title }}</a></b>
</h4>

在这里插入图片描述
运行程序
在这里插入图片描述

404页面展示

修改查询不到数据返回的响应

将404.html拖拽到templates中
在这里插入图片描述

在home.views.py文件中定义视图

try:
    article=Article.objects.get(id=id)
except Article.DoesNotExist:
    return render(request,'404.html')

在这里插入图片描述
修改404.html中的资源加载方式

 <!-- Header部分 -->
 <head>
    <!-- 网站采用的字符编码 -->
    <meta charset="utf-8">
    <!-- 网站标题 -->
    <title>404</title>
    {% load staticfiles %}
    <!-- 引入bootstrap的css文件 -->
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">

</head>


<!-- 导航栏商标 -->
<div>
    <a class="navbar-brand" href="{% url 'home:index' %}">个人博客</a>
</div>

<!--content-->
<div class="container" style="height: 600px;margin-top: 20px">
    <img src="{% static 'img/404.png' %}" alt="">
    <br>
    <div style="text-align: center;">
        <a href="{% url 'home:index' %}" class="primaryAction btn btn-primary">返回首页</a>
    </div>
</div>

在这里插入图片描述
在这里插入图片描述

运行程序,在导航栏输入一个不存在的id
在这里插入图片描述

推荐文章数据展示

添加文章浏览量数据
每次请求文章详情时给浏览量+1
home.views.py

try:
    article=Article.objects.get(id=id)
except Article.DoesNotExist:
    return render(request,'404.html')
else:
    article.total_views+=1
    article.save()

在这里插入图片描述
查询推荐文章并展示
查询推荐文章数据并通过context传递给HTML
home.views.py

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)

在这里插入图片描述

在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

在这里插入图片描述
迁移模型类

创建迁移文件

python manage.py makemigrations

执行迁移文件

python manage.py migrate

在这里插入图片描述

发表评论

发表评论接口设计

  1. 请求方式
选项方案
请求方法POST
请求地址/detail/
  1. 请求参数:表单
参数名类型是否必传说明
user_idstring发表评论的用户id
article_idstring评论的文字id
contentstring评论内容

3.响应结果:HTML

字段说明
提交失败响应错误提示
提交成功刷新页面展示

发表评论接口实现

发表评论实现
home.views.py

from home.models import Comment,Article
from django.urls import reverse
from django.shortcuts import redirect
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'))

在这里插入图片描述

detail.html修改

 <form method="POST">
    {% csrf_token %}
    <input type="hidden" name="id" value="{{ article.id }}">

在这里插入图片描述

详情评论数据展示

查询评论数据并展示

查询评论数据并通过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)

在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>&nbsp;<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>


<!-- 修改底部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>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

插入更多测试数据

可以通过蠕虫复制来插入更多测试数据

insert into tb_comment(content,created,article_id,user_id)
select content,created,article_id,user_id from tb_comment;
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值