阅读 127

django搭建个人博客(一)

django搭建个人博客(一)

准备阶段

首先熟悉项目的根本流程,下面是对项目的每个模块流程阶段具体分析


模块 功能

注册 图形验证、短信验证

登陆 状态保存、cookie、session

个人中心 图片上传、更新数据

发布博客 数据入库

博客首页 数据分页

博客详情 博客详情数据展示、评论工程

总体流程介绍完毕开始做准备工作需要数据库来存储说用到的数据,根据数据字典创建对象的表格


文件所使用的静态资源和一些配置以提供,方便大家使用


创建py项目

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







项目结构分析:

manange.py:以后和项目交互基本上都是基于这个文件。一般都是在终端输入python manage.py [子命令]。可以输入python manage.py help看下能做什么事情。除非你知道你自己在做什么,一般情况下不应该编辑这个文件。

settings.py:保存项目所有的配置信息。

urls.py:用来做url与视图函数映射的。以后来了一个请求,就会从这个文件中找到匹配的视图函数。

wsig.py:专门用来做部署的。不需要修改。

项目创建完之后 可以运行 启动项目

可能会出现的坑


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


后面有具体配置文件


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

更改地方


2.可能会有此问题query = query.decode(errors=‘replace‘)



项目运行成功显示界面



准备阶段


配置数据库MySQL

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


新建数据库


create database blog charset=utf8;

1

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


create user diangen identified by '123456';

1

授权新用户的权限


grant all on blog.* to 'diangen';

1

授权结束后刷新特权


 flush privileges;

1

数据库创建完毕


创建一个django项目找到配置文件里面的settings.py 连接数据库



DATABASES = {

    'default': {

        'ENGINE': 'django.db.backends.mysql', # 数据库引擎

        'HOST': '127.0.0.1', # 数据库主机

        'PORT': 3306, # 数据库端口

        'USER': 'itheima', # 数据库用户名

        'PASSWORD': '123456', # 数据库用户密码

        'NAME': 'blog' # 数据库名字

    },

}

1

2

3

4

5

6

7

8

9

10

可能出现的错误


Error loading MySQLdb module: No module named ‘MySQLdb’.

出现错误的原因:


Django中操作MySQL数据库需要驱动程序MySQLdb

目前项目虚拟环境中没有驱动程序MySQLdb


配置并使用数据库



配置Redis数据库

检查并安装django-redis拓展包


  pip install django-redis

1

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"

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

default:

默认的Redis配置项,采用0号Redis库。

session:

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

SESSION_ENGINE

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

SESSION_CACHE_ALIAS:

使用名为"session"的Redis配置项存储session数据。 配置完成后:运行程序,测试结果

电脑安装redis并进入redis文件目录

在命令行中运行



redis-server

1



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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

默认该设置是打开的,可以直接在此处修改为no


stop-writes-on-bgsave-error no

1

配置日志

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',  # 日志器接收的最低日志级别

        },

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

创建文件夹


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


FATAL/CRITICAL = 重大的,危险的

ERROR = 错误

WARNING = 警告

INFO = 信息

DEBUG = 调试

NOTSET = 没有设置

1

2

3

4

5

6

urls.py文件中


创建日志记录器


import logging

logger = logging.getLogger('django')

1

2

输出日志


logger.debug('测试logging模块debug')

logger.info('测试logging模块info')

logger.error('测试logging模块error')

1

2

3

配置静态资源

项目文件夹下创建目录static文件

settings.py 文件中添加

指定静态文件加载路径


STATIC_URL = '/static/'

1

配置静态文件加载路径

STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]

1



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



项目启动成功

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


创建用户模块应用—注册

创建应用users


python manage.py startapp users

1


创建完之后目录结构会出现一个文件夹



在工程的setting.py中 注册用户模块应用



定义用户注册视图

创建一个子目录 把注册的html放进去 并更改配置

将static文件夹下在register.html拖拽到templates文件中

设置模板路径


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


from django.views import View


class RegisterView(View):

    """用户注册"""


    def get(self, request):

        """

        提供注册界面

        :param request: 请求对象

        :return: 注册界面

        """

        return render(request, 'register.html')

1

2

3

4

5

6

7

8

9

10

11

12

定义用户注册路由

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

]

1

2

3

4

5

6

7

8

9

在工程的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')),

]

1

2

3

4

5

6

7

8

9

需要把static中的界面拖拽到templates文件夹中 因为我们配置的路径是template存放的界面


运行测试程序。

以上步骤是为了完成 第一个注册见面并且配置路由

首先配置user里面的视频viwes 和路由,然后在设置工程的urls.py总路由中添加子应用路由引导



修改静态文件加载方式

是由于静态资源加载是相对路径,因此我们需要修改静态资源的加载方式


使用static标签来加载静态文件。要使用static标签,首先需要{% load static %}


# 以下代码是html的header处修改

    {% load staticfiles %}

    <!-- 引入bootstrap的css文件 -->

    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">

    <!-- 引入vuejs -->

    <script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>

    <script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>

   

   # 以下代码是html的footer处修改

    <!-- 引入js -->

    <script type="text/javascript" src="{% static 'js/host.js' %}"></script>

    <script type="text/javascript" src="{% static 'js/common.js' %}"></script>

    <script type="text/javascript" src="{% static 'js/register.js' %}"></script>

# 运行测试程序,没有问题

1

2

3

4

5

6

7

8

9

10

11

12

13

14

定义用户模型类

自定义用户模型类


思考:为什么要自定义用户模型类?

观察注册界面会发现,个人博客注册页面中必须有手机号,而且在登录页面中也使用手机号进行认证。此外个人中心页面中有个人头像和个人简介字段。

说白了就是用户需要自己增加字段to


如何自定义用户模型类?


继承自AbstractUser(可通过阅读Django默认用户模型类的源码得知) 。

新增手机号字段,头像字段和简介字段


这个基类仅有少部分由于Django机制,而设定的函数和常量。


如果连这个都不想继承的话,直接用自定义,这将有可能导致其余的Django组件无法正常工作。


当然无论是继承还是自定义,都必须在settings中设置AUTH_USER_MODEL


用户信息

from django.db import models


# Create your models here.

from django.contrib.auth.models import AbstractUser


# 用户信息

class User(AbstractUser):


    # 电话号码字段

    # unique 为唯一性字段

    mobile = models.CharField(max_length=20, unique=True,blank=True)


    # 头像

    # upload_to为保存到响应的子目录中

    avatar = models.ImageField(upload_to='avatar/%Y%m%d/', blank=True)


    # 个人简介

    user_desc = models.TextField(max_length=500, blank=True)


    # 修改认证的字段

    USERNAME_FIELD = 'mobile'


    #创建超级管理员的需要必须输入的字段

    REQUIRED_FIELDS = ['username','email']


    # 内部类 class Meta 用于给 model 定义元数据

    class Meta:

        db_table='tb_user'              #修改默认的表名

        verbose_name='用户信息'         # Admin后台显示

        verbose_name_plural=verbose_name # Admin后台显示


    def __str__(self):

        return self.mobile

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

指定本项目用户模型类

AUTH_USER_MODEL = 'users.User'

1



迁移用户模型类


执行迁移文件 python manage.py migrate


运行测试程序


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

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


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


安装Python处理图片的库



图形验证码后端接口设计


1.请求方式


选项 方案

请求方法 GET

请求地址 imagecode?uuid=xxxxx-xxxx-xxxxxx

2.请求参数:路径参数


参数名 类型 是否必传 说明

uuid string 是 唯一编号

3.响应结果:image/jpeg


图形验证码后端实现

1.图形验证码视图


from django.http import HttpResponseBadRequest,HttpResponse

from libs.captcha.captcha import captcha

from django_redis import get_redis_connection


class ImageCodeView(View):


    def get(self,request):

        #获取前端传递过来的参数

        uuid=request.GET.get('uuid')

        #判断参数是否为None

        if uuid is None:

            return HttpResponseBadRequest('请求参数错误')

        # 获取验证码内容和验证码图片二进制数据

        text, image = captcha.generate_captcha()

        # 将图片验内容保存到redis中,并设置过期时间

        redis_conn = get_redis_connection('default')

        redis_conn.setex('img:%s' % uuid, 300, text)

        # 返回响应,将生成的图片以content_type为image/jpeg的形式返回给请求

        return HttpResponse(image, content_type='image/jpeg')

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

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

]

1

2

3

4

5

6

7

8

9

3.子路由


from django.urls import path

from users.views import ImageCodeView


urlpatterns = [

    # 参数1:路由

    # 参数2:视图函数

    # 参数3:路由名,方便通过reverse来获取路由

    path('imagecode/', ImageCodeView.as_view(),name='imagecode'),

]

1

2

3

4

5

6

7

8

9

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

1.html中的原代码如下


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

1

2.修改如下


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

1

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


短信验证码

短信验证码 使用的是容联云官网 注册 获取




1.集成短信SDK到库中


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


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




短信验证码后端逻辑实现

from django.http import JsonResponse

from utils.response_code import RETCODE

from random import randint

from libs.yuntongxun.sms import CCP

import logging

logger=logging.getLogger('django')


class SmsCodeView(View):


    def get(self,request):

        # 接收参数

        image_code_client = request.GET.get('image_code')

        uuid = request.GET.get('uuid')

        mobile=request.GET.get('mobile')


        # 校验参数

        if not all([image_code_client, uuid,mobile]):

            return JsonResponse({'code': RETCODE.NECESSARYPARAMERR, 'errmsg': '缺少必传参数'})


        # 创建连接到redis的对象

        redis_conn = get_redis_connection('default')

        # 提取图形验证码

        image_code_server = redis_conn.get('img:%s' % uuid)

        if image_code_server is None:

            # 图形验证码过期或者不存在

            return JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '图形验证码失效'})

        # 删除图形验证码,避免恶意测试图形验证码

        try:

            redis_conn.delete('img:%s' % uuid)

        except Exception as e:

            logger.error(e)

        # 对比图形验证码

        image_code_server = image_code_server.decode()  # bytes转字符串

        if image_code_client.lower() != image_code_server.lower():  # 转小写后比较

            return JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '输入图形验证码有误'})


        # 生成短信验证码:生成6位数验证码

        sms_code = '%06d' % randint(0, 999999)

        #将验证码输出在控制台,以方便调试

        logger.info(sms_code)

        # 保存短信验证码到redis中,并设置有效期

        redis_conn.setex('sms:%s' % mobile, 300, sms_code)

        # 发送短信验证码

        CCP().send_template_sms(mobile, [sms_code, 5],1)


        # 响应结果

        return JsonResponse({'code': RETCODE.OK, 'errmsg': '发送短信成功'})

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

当点击验证码的时候 pycharm控制台中也会发送一个



添加response_code文件

在工程中新建utils包,将response_code文件复制到utils中





用户注册实现

from users.models import User

from django.db import DatabaseError

#注册视图

class RegisterView(View):


    def get(self,request):


        return render(request,'register.html')


    def post(self,request):

        """

        1.接收数据

        2.验证数据

            2.1 参数是否齐全

            2.2 手机号的格式是否正确

            2.3 密码是否符合格式

            2.4 密码和确认密码要一致

            2.5 短信验证码是否和redis中的一致

        3.保存注册信息

        4.返回响应跳转到指定页面

        :param request:

        :return:

        """

        # 1.接收数据

        mobile=request.POST.get('mobile')

        password=request.POST.get('password')

        password2=request.POST.get('password2')

        smscode=request.POST.get('sms_code')

        # 2.验证数据

        #     2.1 参数是否齐全

        if not all([mobile,password,password2,smscode]):

            return HttpResponseBadRequest('缺少必要的参数')

        #     2.2 手机号的格式是否正确

        if not re.match(r'^1[3-9]\d{9}$',mobile):

            return HttpResponseBadRequest('手机号不符合规则')

        #     2.3 密码是否符合格式

        if not re.match(r'^[0-9A-Za-z]{8,20}$',password):

            return HttpResponseBadRequest('请输入8-20位密码,密码是数字,字母')

        #     2.4 密码和确认密码要一致

        if password != password2:

            return HttpResponseBadRequest('两次密码不一致')

        #     2.5 短信验证码是否和redis中的一致

        redis_conn = get_redis_connection('default')

        redis_sms_code=redis_conn.get('sms:%s'%mobile)

        if redis_sms_code is None:

            return HttpResponseBadRequest('短信验证码已过期')

        if smscode != redis_sms_code.decode():

            return HttpResponseBadRequest('短信验证码不一致')

        # 3.保存注册信息

        # create_user 可以使用系统的方法来对密码进行加密

        try:

            user=User.objects.create_user(username=mobile,

                                      mobile=mobile,

                                      password=password)

        except DatabaseError as e:

            logger.error(e)

            return HttpResponseBadRequest('注册失败')


        from django.contrib.auth import login

        login(request,user)

        # 4.返回响应跳转到指定页面

        # 暂时返回一个注册成功的信息,后期再实现跳转到指定页面


        # redirect 是进行重定向

        # reverse 是可以通过 namespace:name 来获取到视图所对应的路由

        response = redirect(reverse('home:index'))

        # return HttpResponse('注册成功,重定向到首页')


        #设置cookie信息,以方便首页中 用户信息展示的判断和用户信息的展示

        response.set_cookie('is_login',True)

        response.set_cookie('username',user.username,max_age=7*24*3600)


        return response

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

在HTML表单中添加csrf_token




首页展示

1.创建首页应用:home


python manage.py startapp home

1

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

2.1.请求方式


选项 方案

请求方法 GET

请求地址 /?cat_id=xxx&page_num=xxx&page_size=xxx

2.2.请求参数


参数名 类型 是否必传 说明

cat_id string 分类id

page_num string 文章分页页码

page_size string 文章每页条目数

2.3.响应结果:HTML


字段 说明

失败 响应错误提示

成功 展示数据

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


from django.urls import reverse

from django.views import View

# Create your views here.


class IndexView(View):

    """首页广告"""


    def get(self, request):

        """提供首页广告界面"""

        return render(request, 'index.html')

1

2

3

4

5

6

7

8

9

10

4.配置首页路由


在home子应用中创建urls.py文件,并定义子路由


from django.urls import path

from home.views import IndexView

urlpatterns = [

    path('', IndexView.as_view(),name='index'),

]

1

2

3

4

5

6

7

在工程的urls.py总路由中添加子应用路由引导


from django.urls import path, include


urlpatterns = [


    path('', include(('home.urls','home'),namespace='home')),

]

1

2

3

4

5

6

5.重定注册界面的跳转到首页


# 响应注册结果

return redirect(reverse('home:index'))

1

2

用户登陆

登录页面展示

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

from django.views import View


class LoginView(View):


    def get(self,request):

        return render(request,'login.html')

1

2

3

4

5

6

2.在users.urls.py文件中定义路由


from users.views import LoginView

urlpatterns = [

    # 参数1:路由

    # 参数2:视图函数

    # 参数3:路由名,方便通过reverse来获取路由

    path('login/', LoginView.as_view(),name='login'),

]

1

2

3

4

5

6

7

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>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

登录接口实现

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

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

注册登陆设置成功之后现在开始对首页进行设置用户名显示到首页



首页用户名展示

用户名写入到cookie

Vue渲染首页用户名

1.index.html


<!-- 如果用户已经登录,则显示用户名下拉框 -->

<li class="nav-item dropdown" v-if="is_login">

    <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" @click="show_menu_click">[[username]]</a>

    <div class="dropdown-menu" aria-labelledby="navbarDropdown" style="display: block" v-show="show_menu">

        <a class="dropdown-item" href="../static/write_blog.html">写文章</a>

        <a class="dropdown-item" href='../static/center.html'>个人信息</a>

        <a class="dropdown-item" href='#'>退出登录</a>

    </div>

</li>

<!-- 如果用户未登录,则显示登录按钮 -->

<li class="nav-item" v-else>

    <a class="nav-link" href="login.html">登录</a>

</li>

1

2

3

4

5

6

7

8

9

10

11

12

13

2.index.js


mounted(){

    //获取用户名信息

    this.username=getCookie('username');

    //获取是否登录信息

    this.is_login=getCookie('is_login');

},

1

2

3

4

5

6



退出登录

logout()方法介绍

退出登录:


对session操作的,也就是操作redis,所以返回的要么是空,要么成功,不会出现异常


logout()方法:


只需要传入一个request对象就行,就会把当前用户的session清除


logout()位置:


django.contrib.auth.init.py文件中


from django.contrib.auth import logout

class LogoutView(View):


    def get(self,request):

        # 1.session数据清除

        logout(request)

        # 2.删除部分cookie数据

        response=redirect(reverse('home:index'))

        response.delete_cookie('is_login')

        #3.跳转到首页

        return response

1

2

3

4

5

6

7

8

9

10

11

提示:


由于首页中登录状态是从cookie中读取的。

所以退出登录时,需要将cookie中登录状态清除。


实现退出登录


<div class="dropdown-menu" aria-labelledby="navbarDropdown" style="display: block" v-show="show_menu">

    <a class="dropdown-item" href="../static/write_blog.html">写文章</a>

    <a class="dropdown-item" href='../static/center.html'>个人信息</a>

    <a class="dropdown-item" href='{% url 'users:logout' %}'>退出登录</a>

</div>

1

2

3

4

5

路由配置 users.urls.py


urlpatterns = [

    # 参数1:路由

    # 参数2:视图函数

    # 参数3:路由名,方便通过reverse来获取路由

    path('register/',RegisterView.as_view(),name='register'),

    path('imagecode/', ImageCodeView.as_view(),name='imagecode'),

    # 短信发送

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

    # 登录路由

    path('login/', LoginView.as_view(), name='login'),

    # 退出登录

    path('logout/', LogoutView.as_view(), name='logout'),

]

1

2

3

4

5

6

7

8

9

10

11

12

13

忘记密码

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


from django.views import View


class ForgetPasswordView(View):


    def get(self, request):


        return render(request, 'forget_password.html')

1

2

3

4

5

6

7

2.在users.urls.py文件中定义路由


from users.views import ForgetPasswordView

path('forgetpassword/', ForgetPasswordView.as_view(),name='forgetpassword'),

1

2

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

4.修改login.html中的忘记密码的跳转连接


<small class="form-text text-muted ml-1"><a class="secondaryAction layui-text" href="{% url 'users:forgetpassword' %}">忘记密码?</a>

1

忘记密码接口实现


参数名 类型 是否必传 说明

mobile string 用户名

password string 密码

password2 string 确认密码

sms_code string 短信验证码

代码


class ForgetPasswordView(View):


    def post(self, request):

        # 接收参数

        mobile = request.POST.get('mobile')

        password = request.POST.get('password')

        password2 = request.POST.get('password2')

        smscode = request.POST.get('sms_code')


        # 判断参数是否齐全

        if not all([mobile, password, password2, smscode]):

            return HttpResponseBadRequest('缺少必传参数')


        # 判断手机号是否合法

        if not re.match(r'^1[3-9]\d{9}$', mobile):

            return HttpResponseBadRequest('请输入正确的手机号码')


        # 判断密码是否是8-20个数字

        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):

            return HttpResponseBadRequest('请输入8-20位的密码')


        # 判断两次密码是否一致

        if password != password2:

            return HttpResponseBadRequest('两次输入的密码不一致')


        # 验证短信验证码

        redis_conn = get_redis_connection('default')

        sms_code_server = redis_conn.get('sms:%s' % mobile)

        if sms_code_server is None:

            return HttpResponseBadRequest('短信验证码已过期')

        if smscode != sms_code_server.decode():

            return HttpResponseBadRequest('短信验证码错误')


        # 根据手机号查询数据

        try:

            user = User.objects.get(mobile=mobile)

        except User.DoesNotExist:

            # 如果该手机号不存在,则注册个新用户

            try:

                User.objects.create_user(username=mobile, mobile=mobile, password=password)

            except Exception:

                return HttpResponseBadRequest('修改失败,请稍后再试')

        else:

            # 修改用户密码

            user.set_password(password)

            user.save()


        # 跳转到登录页面

        response = redirect(reverse('users:login'))


        return response

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

路由配置 urts.py


 # 忘记密码

    path('forgetpassword/', ForgetPasswordView.as_view(),name='forgetpassword'),


1

2

3



用户中心展示

页面展示

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


from django.views import View


class UserCenterView(View):


    def get(self,request):


        return render(request,'center.html')

1

2

3

4

5

6

7

2.在users.urls.py文件中定义路由


from users.views import UserCenterView

urlpatterns = [

    # 参数1:路由

    # 参数2:视图函数

    # 参数3:路由名,方便通过reverse来获取路由

    path('center/', UserCenterView.as_view(),name='center'),

]

1

2

3

4

5

6

7

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>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

4.修改index.html中的的跳转连接


<a class="dropdown-item" href='{% url 'users:center' %}'>个人信息</a>

1



判断用户是否登录

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

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

1

2

3

4

5

6

7

8

2.设置未登录用户跳转的路由


#在工程的settings.py文件中,添加以下配置。

LOGIN_URL = '/login/'

1

2

3.根据登录的next参数设置登录跳转路由


实现状态保持


login(request, user)

1

响应登录结果

next = request.GET.get('next')

if next:

    response= redirect(next)

else:

    response =  redirect(reverse('home:index'))

1

2

3

4

5

1.获取用户信息,模板渲染数据users.views.py 中添加功能


from django.contrib.auth.mixins import LoginRequiredMixin

class UserCenterView(LoginRequiredMixin,View):


    def get(self,request):

        # 获取用户信息

        user = request.user


        #组织模板渲染数据

        context = {

            'username': user.username,

            'mobile': user.mobile,

            'avatar': user.avatar.url if user.avatar else None,

            'user_desc': user.user_desc

        }


        return render(request,'center.html',context=context)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

2.修改center.html中的数据显示


<form method="post" enctype="multipart/form-data">

    <!-- username -->

    <div class="form-group col-md-4">

        <label for="username">用户名</label>

        <input type="text" class="form-control" id="username" name="username" value="{{ username }}" >

    </div>

    {% if avatar %}

        <br> <div class="col-md-4">头像</div>

        <img src="{{ avatar }}" style="max-width: 20%; border-radius: 15%;" class="col-md-4"><br>

        {% else %}

        <br><h5 class="col-md-4">暂无头像</h5><br>

    {% endif %}

    <!-- avatar -->

    <div class="form-group col-md-4">

        <label for="avatar">上传头像</label>

        <input type="file" class="form-control-file" name="avatar" id="avatar">

    </div>


    <!-- phone -->

    <div class="form-group col-md-4">

        <label for="phone">电话</label>

        <input type="text" class="form-control" disabled="disabled" id="phone" name="phone" value="{{ mobile }}">

    </div>

    <!-- desc -->

    <div class="form-group col-md-4">

        <label for="desc">简介</label>

        <!-- 文本区域 -->

        <textarea type="text" class="form-control" id="desc" name="desc" rows="12" >{{ user_desc }}</textarea>

    </div>

    <!-- 提交按钮 -->

    <button type="submit" class="btn btn-primary" style="margin-left: 12px" >修改</button>

</form>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

用户中心修改

用户中心接口设计

1.请求方式

选项 方案

请求方法 POST

请求地址 /center/

2.请求参数:表单


参数名 类型 是否必传 说明

username string 用户名

avatar file 头像

desc string 个人简介

3.响应结果:HTML


字段 说明

修改失败 响应错误提示

修改成功 刷新展示

用户中心修改接口实现

from django.contrib.auth.mixins import LoginRequiredMixin

class UserCenterView(LoginRequiredMixin,View):


    def post(self,request):

        # 接收数据

        user = request.user

        avatar = request.FILES.get('avatar')

        username = request.POST.get('username',user.username)

        user_desc = request.POST.get('desc',user.user_desc)


        # 修改数据库数据

        try:

            user.username=username

            user.user_desc=user_desc

            if avatar:

                user.avatar=avatar

            user.save()

        except Exception as e:

            logger.error(e)

            return HttpResponseBadRequest('更新失败,请稍后再试')


        # 返回响应,刷新页面

        response = redirect(reverse('users:center'))

        #更新cookie信息

        response.set_cookie('username',user.username,max_age=30*24*3600)

        return response

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

用户中心头像的上传和展示

1.在settings.py文件中设置图片上传的路径并新建文件夹media

MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

1

在settings.py文件中设置


图片的统一路由


MEDIA_URL = '/media/'

1

设置路由匹配规则。在工程的urls.py文件中设置


from django.contrib import admin

from django.urls import path,include


urlpatterns = [

    path('admin/', admin.site.urls),

    # include 参数1要设置为元组(urlconf_module, app_name)

    # namespace 设置命名空间

    path('', include(('users.urls', 'users'), namespace='users')),

    path('', include(('home.urls','home'),namespace='home')),

]

#以下代码为设置图片访问路由规则

from django.conf import settings

from django.conf.urls.static import static

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

1

2

3

4

5

6

7

8

9

10

11

12

13

14



写博客页面展示

页面展示

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

from django.views import View


class WriteBlogView(LoginRequiredMixin,View):


    def get(self,request):


        return render(request,'write_blog.html')

1

2

3

4

5

6

7

2.在users.urls.py文件中定义路由


from users.views import WriteBlogView

urlpatterns = [

    # 参数1:路由

    # 参数2:视图函数

    # 参数3:路由名,方便通过reverse来获取路由

    path('writeblog/', WriteBlogView.as_view(),name='writeblog'),

]

1

2

3

4

5

6

7

3.修改center.html中的资源加载方式


<!-- Header部分 -->

{% load staticfiles %}

<!-- 引入bootstrap的css文件 -->

<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">

<!-- 引入vuejs -->

<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>

<script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>

<!-- Footer部分 -->

<!--ckeditor-->

<script type="text/javascript" src="{% static 'ckeditor/ckeditor-init.js' %}" data-ckeditor-basepath="{% static 'ckeditor/ckeditor/' %}" id="ckeditor-init-script"></script>

<script type="text/javascript" src="{% static 'ckeditor/ckeditor/ckeditor.js' %}"></script>

<!-- 引入js -->

<script type="text/javascript" src="{% static 'js/host.js' %}"></script>

<script type="text/javascript" src="{% static 'js/common.js' %}"></script>

<script type="text/javascript" src="{% static 'js/write_blog.js' %}"></script>

<!-- 页面跳转部分 -->

<a class="dropdown-item" href="{% url 'users:writeblog' %}">写文章</a>

<a class="dropdown-item" href='{% url 'users:center'%}'>个人信息</a>

<a class="dropdown-item" href='{% url 'users:center' %}'>退出登录</a>


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

4.修改index.html中的的跳转连接


<a class="dropdown-item" href="{% url 'users:writeblog' %}">写文章</a>

1



文章分类模型

定义模型类

在home子应用的models.py模型中定义文章分类模型

from django.db import models

from django.utils import timezone


class ArticleCategory(models.Model):

    """

    文章分类

    """

    # 栏目标题

    title = models.CharField(max_length=100, blank=True)

    # 创建时间

    created = models.DateTimeField(default=timezone.now)


    def __str__(self):

        return self.title


    class Meta:

        db_table='tb_category'

        verbose_name = '类别管理'

        verbose_name_plural = verbose_name

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

迁移模型类

1.创建迁移文件


python manage.py makemigrations

2.执行迁移文件


python manage.py migrate

1

2

3

4

5

6

文章分类后台管理

当刚创建的时候,首页是没有数据,我们需要从后台管理界面进行文章分类,从而展示到主界面

网站的管理员负责查看、添加、修改、删除数据


Django能够根据定义的模型类自动地生成管理模块

登陆站点:http://127.0.0.1:8000/admin

在setting中设置中文信息


LANGUAGE_CODE = 'zh-Hans'   #原配置信息为'en-us'


TIME_ZONE = 'Asia/Shanghai'#原配置信息为'UTC'

1

2

3

创建管理员

1.我们需要在User模型中设置 REQUIRED_FIELDS

在 users.modle.py中设置配置信息


#创建超级管理员的需要必须输入的字段

REQUIRED_FIELDS = ['username','email']

1

2



2.在终端创建超级管理员


创建管理员的命令 :


  python manage.py createsuperuser

1


然后重新登陆进入 站点服务

注册模型类

在应用的admin.py文件中注册模型类


需要导入模型模块 :from home.models import ArticleCategory



模型注册完之后我们即可在站点管理进行对分类操作


模型类展示我们输入的内容是因为我们在模型中实现了__str_方法_

home.model.py


class ArticleCategory(models.Model):

    """

    文章分类

    """

    # 栏目标题

    title = models.CharField(max_length=100, blank=True)

    # 创建时间

    created = models.DateTimeField(default=timezone.now)


    def __str__(self):

        return self.title

1

2

3

4

5

6

7

8

9

10

11

写博客页面展示分类

查询数据并展示

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

from home.models import ArticleCategory

class WriteBlogView(LoginRequiredMixin,View):


    def get(self,request):

        # 获取博客分类信息

        categories = ArticleCategory.objects.all()


        context = {

            'categories': categories

        }

        return render(request,'write_blog.html',context=context)

1

2

3

4

5

6

7

8

9

10

11

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


<!-- 文章栏目 -->

<div class="form-group">

    <label for="column">栏目</label>

    <select class="form-control col-3" id="category" name="category">

            {% for category in categories %}

                <option value="{{ category.id }}">{{ category.title }}</option>

            {% endfor %}

    </select>

</div>

1

2

3

4

5

6

7

8

9



文章模型

在home子应用的models.py模型中定义文章模型


from users.models import User 

class Article(models.Model):

    """

    文章

    """

    # 定义文章作者。 author 通过 models.ForeignKey 外键与内建的 User 模型关联在一起

    # 参数 on_delete 用于指定数据删除的方式,避免两个关联表的数据不一致。

    author = models.ForeignKey(User, on_delete=models.CASCADE)

    # 文章标题图

    avatar = models.ImageField(upload_to='article/%Y%m%d/', blank=True)

    # 文章栏目的 “一对多” 外键

    category = models.ForeignKey(

        ArticleCategory,

        null=True,

        blank=True,

        on_delete=models.CASCADE,

        related_name='article'

    )

    # 文章标签

    tags = models.CharField(max_length=20,blank=True)

    # 文章标题。

    title = models.CharField(max_length=100,null=False,blank=False)

    # 概要

    sumary = models.CharField(max_length=200,null=False,blank=False)

    # 文章正文。

    content = models.TextField()

    # 浏览量

    total_views = models.PositiveIntegerField(default=0)

    # 文章评论数

    comments_count = models.PositiveIntegerField(default=0)

    # 文章创建时间。

    # 参数 default=timezone.now 指定其在创建数据时将默认写入当前的时间

    created = models.DateTimeField(default=timezone.now)

    # 文章更新时间。

    # 参数 auto_now=True 指定每次数据更新时自动写入当前时间

    updated = models.DateTimeField(auto_now=True)


    # 内部类 class Meta 用于给 model 定义元数据

    class Meta:

        # ordering 指定模型返回的数据的排列顺序

        # '-created' 表明数据应该以倒序排列

        ordering = ('-created',)

        db_table='tb_article'

        verbose_name='文章管理'

        verbose_name_plural=verbose_name

    # 函数 __str__ 定义当调用对象的 str() 方法时的返回值内容

    # 它最常见的就是在Django管理后台中做为对象的显示值。因此应该总是为 __str__ 返回一个友好易读的字符串

    def __str__(self):

        # 将文章标题返回

        return self.title

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

迁移模型类

1.创建迁移文件


python manage.py makemigrations

1

2.执行迁移文件


python manage.py migrate

1

博客保存

博客保存接口设计

1.请求方式


选项 方案

请求方法 POST

请求地址 /writeblog/

2.请求参数:表单


参数名 类型 是否必传 说明

title string 标题

avatar file 标题图

category string 栏目分类

tags string 标签

sumary string 文章摘要

content string 文章内容

3.响应结果:HTML


字段 说明

提交失败 响应错误提示

提交成功 跳转到详情页面

用户中心修改接口实现

from home.models import ArticleCategory,Article

class WriteBlogView(LoginRequiredMixin,View):


    def post(self,request):

        #接收数据

        avatar=request.FILES.get('avatar')

        title=request.POST.get('title')

        category_id=request.POST.get('category')

        tags=request.POST.get('tags')

        sumary=request.POST.get('sumary')

        content=request.POST.get('content')

        user=request.user


        #验证数据是否齐全

        if not all([avatar,title,category_id,sumary,content]):

            return HttpResponseBadRequest('参数不全')


        #判断文章分类id数据是否正确

        try:

            article_category=ArticleCategory.objects.get(id=category_id)

        except ArticleCategory.DoesNotExist:

            return HttpResponseBadRequest('没有此分类信息')


        #保存到数据库

        try:

            article=Article.objects.create(

                author=user,

                avatar=avatar,

                category=article_category,

                tags=tags,

                title=title,

                sumary=sumary,

                content=content

            )

        except Exception as e:

            logger.error(e)

            return HttpResponseBadRequest('发布失败,请稍后再试')


        #返回响应,跳转到文章详情页面

        #暂时先跳转到首页

        return redirect(reverse('home:index'))

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41



首页分类数据展示

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

2.1.请求方式


选项 方案

请求方法 GET

请求地址 /?cat_id=xxx&page_num=xxx&page_size=xxx

2.2.请求参数


参数名 类型 是否必传 说明

cat_id string 分类id

page_num string 文章分页页码

page_size string 文章每页条目数

2.3.响应结果:HTML


字段 说明

失败 响应错误提示

成功 展示数据

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

home.views.py



from home.models import ArticleCategory

from django.http import HttpResponseNotFound


class IndexView(View):

    """首页广告"""


    def get(self, request):

        """提供首页广告界面"""

        #?cat_id=xxx&page_num=xxx&page_size=xxx

        cat_id=request.GET.get('cat_id',1)


        #判断分类id

        try:

            category = ArticleCategory.objects.get(id=cat_id)

        except ArticleCategory.DoesNotExist:

            return HttpResponseNotFound('没有此分类')


        # 获取博客分类信息

        categories = ArticleCategory.objects.all()


        context = {

            'categories':categories,

            'category':category

        }


        return render(request, 'index.html',context=context)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

2.在index.html文件中使用模板语言展示分类数据


<ul class="nav navbar-nav">

    {% for cat in categories %}

        {% if cat.id == category.id %}

            <li class="nav-item active">

                <a class="nav-link mr-2" href="/?cat_id={{ cat.id }}">{{ cat.title }}</a>

            </li>

        {% else %}

            <li class="nav-item">

                <a class="nav-link mr-2" href="/?cat_id={{ cat.id }}">{{ cat.title }}</a>

            </li>

        {% endif %}

    {% endfor %}

</ul>

1

2

3

4

5

6

7

8

9

10

11

12

13

查询分页数据并展示


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)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

2.在index.html文件中使用模板语言展示分类数据


<div class="container">

    <!-- 列表循环 -->

     {% for article in articles %}

        <div class="row mt-2">

            <!-- 文章内容 -->

            <!-- 标题图 -->

            <div class="col-3">

                <img src="{{ article.avatar.url }}" alt="avatar" style="max-width:100%; border-radius: 20px">

            </div>

            <div class="col">

                <!-- 栏目 -->

                <a  role="button" class="btn btn-sm mb-2 btn-warning">{{ article.category.title }}</a>

            <!-- 标签 -->

                <span>

                        <a class="badge badge-secondary">{{ article.tags }}</a>

                </span>

                <!-- 标题 -->

                <h4>

                    <b><a href="./detail.html" style="color: black;">{{ article.title }}</a></b>

                </h4>

                <!-- 摘要 -->

                <div>

                    <p style="color: gray;">

                        {{ article.sumary }}

                    </p>

                </div>

                <!-- 注脚 -->

                <p>

                    <!-- 查看、评论、时间 -->

                    <span><i class="fas fa-eye" style="color: lightskyblue;"></i>{{ article.total_views }}&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>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

3.修改底部js分页代码


<script type="text/javascript">

    $(function () {

        $('#pagination').pagination({

            currentPage: {{ page_num }},

            totalPage: {{ total_page }},

            callback:function (current) {


                location.href = '/?cat_id={{ category.id }}&page_size={{ page_size }}&page_num='+current;

            }

        })

    });

</script>

1

2

3

4

5

6

7

8

9

10

11

12

博客详情

详情页面展示


页面展示

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

from django.views import View


class DetailView(View):


    def get(self,request):



        return render(request,'detail.html')

1

2

3

4

5

6

7

8

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


from users.views import DetailView

urlpatterns = [

    # 参数1:路由

    # 参数2:视图函数

    # 参数3:路由名,方便通过reverse来获取路由

    path('detail/', DetailView.as_view(),name='detail'),

]

1

2

3

4

5

6

7

3.修改detail.html中的资源加载方式


    <!-- Header部分 -->

       {% load staticfiles %}

    <!-- 引入bootstrap的css文件 -->

    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">

    <!--详情页面导入-->

    <script src="{% static 'ckeditor/ckeditor/plugins/prism/lib/prism/prism_patched.min.js' %}"></script>

    <link rel="stylesheet" href="{% static 'prism/prism.css' %}">

    <!--导入css-->

    <link rel="stylesheet" href="{% static 'common/common.css' %}">

    <link rel="stylesheet" href="{% static 'common/jquery.pagination.css' %}">

    <!-- 引入vuejs -->

    <script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>

    <script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>

    <script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script>

    ...

    <!-- Footer部分 -->

    <!--ckeditor-->

    <script type="text/javascript" src="{% static 'ckeditor/ckeditor-init.js' %}" data-ckeditor-basepath="{% static 'ckeditor/ckeditor/' %}" id="ckeditor-init-script"></script>

    <script type="text/javascript" src="{% static 'ckeditor/ckeditor/ckeditor.js' %}"></script>

    <!-- 引入js -->

    <script type="text/javascript" src="{% static 'js/host.js' %}"></script>

    <script type="text/javascript" src="{% static 'js/common.js' %}"></script>

    <script type="text/javascript" src="{% static 'js/detail.js' %}"></script>

    <script type="text/javascript" src="{% static 'js/jquery.pagination.min.js' %}"></script>

    ...

    <!-- 页面跳转部分 -->

    <a class="dropdown-item" href="{% url 'users:writeblog' %}">写文章</a>

    <a class="dropdown-item" href='{% url 'users:center'%}'>个人信息</a>

    <a class="dropdown-item" href='{% url 'users:center' %}'>退出登录</a>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

查询分类数据并展示

1.查询文章数据并通过context传递给HTML

class DetailView(View):


    def get(self,request):

        # detail/?id=xxx&page_num=xxx&page_size=xxx

        #获取文档id

        id=request.GET.get('id')


        # 获取博客分类信息

        categories = ArticleCategory.objects.all()


        try:

            article=Article.objects.get(id=id)

        except Article.DoesNotExist:

            return render(request,'404.html')


        context = {

            'categories':categories,

            'category':article.category,

            'article':article,

        }


        return render(request,'detail.html',context=context)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

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


#分类数据展示

 <div>

    <ul class="nav navbar-nav">

        {% for cat in categories %}

            {% if cat.id == category.id %}

                <li class="nav-item active">

                    <a class="nav-link mr-2" href="/?cat_id={{ cat.id }}">{{ cat.title }}</a>

                </li>

            {% else %}

                <li class="nav-item">

                    <a class="nav-link mr-2" href="/?cat_id={{ cat.id }}">{{ cat.title }}</a>

                </li>

            {% endif %}

        {% endfor %}

    </ul>

</div>


#详情数据展示

 <!-- 标题及作者 -->

<h1 class="mt-4 mb-4">{{ article.title }}</h1>

<div class="alert alert-success"><div>作者:<span>{{ article.author.username }}</span></div><div>浏览:{{ article.total_views }}</div></div>

<!-- 文章正文 -->

<div class="col-12" style="word-break: break-all;word-wrap: break-word;">

    {{ article.content|safe }}

</div>

<br>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26



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


<!-- 标题 -->

<h4>

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

</h4>

1

2

3

4

5

推荐文章数据展示

添加文章浏览量数据

1.每次请求文章详情时给浏览量+1

try:

    article=Article.objects.get(id=id)

except Article.DoesNotExist:

    return render(request,'404.html')

else:

    article.total_views+=1

    article.save()

1

2

3

4

5

6

7

查询推荐文章并展示

1.查询推荐文章数据并通过context传递给HTML

class DetailView(View):


    def get(self,request):

        # detail/?id=xxx&page_num=xxx&page_size=xxx

        #获取文档id

        id=request.GET.get('id')


        # 获取博客分类信息

        categories = ArticleCategory.objects.all()


        try:

            article=Article.objects.get(id=id)

        except Article.DoesNotExist:

            return render(request,'404.html')

        else:

            article.total_views+=1

            article.save()


        # 获取热点数据

        hot_articles = Article.objects.order_by('-total_views')[:9]


        context = {

            'categories':categories,

            'category':article.category,

            'article':article,

            'hot_articles':hot_articles

        }

        return render(request,'detail.html',context=context)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

2.在detail.html文件中使用模板语言展示推荐数据


<div class="sidebar__inner">

    <h4><strong>推荐</strong></h4>

    <hr>

    {% for hot_article in hot_articles %}

        <a href="{% url 'home:detail' %}?id={{ hot_article.id }}" style="color: black">{{ hot_article.title }}</a><br>

    {% endfor %}

    </div>

</div>

1

2

3

4

5

6

7

8

评论模型

定义模型类

在home子应用的models.py模型中定义评论模型

class Comment(models.Model):

    #评论内容

    content=models.TextField()

    #评论的文章

    article=models.ForeignKey(Article,

                              on_delete=models.SET_NULL,

                              null=True)

    #发表评论的用户

    user=models.ForeignKey('users.User',

                           on_delete=models.SET_NULL,

                           null=True)

    #评论发布时间

    created=models.DateTimeField(auto_now_add=True)


    def __str__(self):

        return self.article.title


    class Meta:

        db_table='tb_comment'

        verbose_name = '评论管理'

        verbose_name_plural = verbose_name

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

迁移模型类

1.创建迁移文件

python manage.py makemigrations

1

2.执行迁移文件


python manage.py migrate

1

发表评论

发表评论接口设计

1.请求方式

选项 方案

请求方法 POST

请求地址 /detail/

2.请求参数:表单


参数名 类型 是否必传 说明

user_id string 发表评论的用户id

article_id string 评论的文字id

content string 评论内容

3.响应结果:HTML


字段 说明

提交失败 响应错误提示

提交成功 刷新页面展示

发表评论接口实现

1.发表评论实现

from home.models import Comment,Article

class DetailView(View):


    def post(self,request):

        #获取用户信息

        user=request.user


        #判断用户是否登录

        if user and user.is_authenticated:

            #接收数据

            id=request.POST.get('id')

            content=request.POST.get('content')


            #判断文章是否存在

            try:

                article = Article.objects.get(id=id)

            except Article.DoesNotExist:

                return HttpResponseNotFound('没有此文章')


            #保存到数据

            Comment.objects.create(

                content=content,

                article=article,

                user=user

            )

            #修改文章评论数量

            article.comments_count+=1

            article.save()

            #拼接跳转路由

            path=reverse('home:detail')+'?id={}'.format(article.id)

            return redirect(path)

        else:

            #没有登录则跳转到登录页面

            return redirect(reverse('users:login'))

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

2.detail.html修改


 <form method="POST">

    {% csrf_token %}

    <input type="hidden" name="id" value="{{ article.id }}">

    <div class="form-group"><label for="body"><strong>我也要发言:</strong></label>

        <div>

            <div class="django-ckeditor-widget" data-field-id="id_body" style="display: inline-block;">

                <textarea cols="40" id="id_body" name="content" rows="10" required data-processed="0" :data-config="data_config" data-external-plugin-resources="[]" data-id="id_body" data-type="ckeditortype">


                </textarea>

            </div>

        </div>

    </div>

    <!-- 提交按钮 -->

    <button type="submit" class="btn btn-primary ">发送</button>

</form>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

详情评论数据展示


查询评论数据并展示

1.查询评论数据并通过context传递给HTML

home.views.py

from home.models import Comment

from django.shortcuts import redirect,reverse

class DetailView(View):


    def get(self,request):

        # detail/?id=xxx&page_num=xxx&page_size=xxx

        #获取文档id

        id=request.GET.get('id')

        page_num=request.GET.get('page_num',1)

        page_size=request.GET.get('page_size',5)

        # 获取博客分类信息

        categories = ArticleCategory.objects.all()


        try:

            article=Article.objects.get(id=id)

        except Article.DoesNotExist:

            return render(request,'404.html')

        else:

            article.total_views+=1

            article.save()


        # 获取热点数据

        hot_articles = Article.objects.order_by('-total_views')[:9]


        # 获取当前文章的评论数据

        comments = Comment.objects.filter(

            article=article

        ).order_by('-created')

        #获取评论总数

        total_count = comments.count()


        # 创建分页器:每页N条记录

        paginator = Paginator(comments, page_size)

        # 获取每页商品数据

        try:

            page_comments = paginator.page(page_num)

        except EmptyPage:

            # 如果page_num不正确,默认给用户404

            return HttpResponseNotFound('empty page')

        # 获取列表页总页数

        total_page = paginator.num_pages


        context = {

            'categories':categories,

            'category':article.category,

            'article':article,

            'hot_articles':hot_articles,

            'total_count': total_count,

            'comments': page_comments,

            'page_size': page_size,

            'total_page': total_page,

            'page_num': page_num,

        }


        return render(request,'detail.html',context=context)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55



2.在index.html文件中使用模板语言展示分类数据


<!-- 显示评论 -->

<h4>共有{{ total_count }}条评论</h4>

<div class="row">

     {% for comment in comments %}

        <div class="col-12" >

            <hr><p><strong style="color: pink"></strong></p>

            <div>

                <div><span><strong>{{ comment.user.username }}</strong></span>&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>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

3.修改底部js分页代码


<script type="text/javascript">

    $(function () {

        $('#pagination').pagination({

           currentPage: {{ page_num }},

            totalPage: {{ total_page }},

            callback:function (current) {

                location.href = '/detail/?id={{ article.id }}&page_size={{ page_size }}&page_num='+current;

            }

        })

    });

</script>

1

2

3

4

5

6

7

8

9

10

11

现在就是完成基本使用功能一个完整的**登录 注册 退出 发表文章 评论文章 **

后期待完善

————————————————

版权声明:本文为CSDN博主「嘴巴嘟嘟」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/qq_41961239/article/details/115736805


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐