Django框架

2019-10-29  本文已影响0人  jxvl假装

软件框架

定义

在这里插入图片描述

一个软件是由其中各个软件模块组成的,每一个模块都有特定的功能,模块与模块之间通过相互配合来完成软件的开发。

软件框架是针对某一类软件设计问题而产生的

Django遵循的是MVC思想

MVC框架

MVC的产生理念:分工,让专门的人去做专门的事情(比如:输入、处理、输出)

MVC的核心思想:解耦

MVC是三个模块的简称:

以通过浏览器注册用户信息为例:


在这里插入图片描述

Django简介

Django,发音为[`dʒæŋɡəʊ],是用python语言写的开源web开发框架,并遵循MVC设计。

MVT

Django是遵循MVC的一个Web框架,但是他有自己的一个名字,叫做MVT

快速开发DRY原则。Do not repeat yourself。不要自己去重复一些工作

M:Model,模型,和数据库进行交互,与MVC中的M功能相同
V:View,视图,接收请求,进行处理,与M和T交互,返回应答,与MVC中的C功能相同
T:template,模板,产生html页面,和MVC终的V功能相同

MVT的各部分功能:

在这里插入图片描述 django官方网站:https://www.djangoproject.com/

虚拟环境

在Linux上,如果用pip3安装的包,会安装在/user/local/lib/python3.xx/dis-packages下面。当安装同一个包的不同版本的时候,后安装的版本会把原来安装的版本覆盖掉。

如何解决这个问题?
使用虚拟环境,所谓虚拟环境,就是真实python环境的一个复制版本。在虚拟环境中使用的python是复制的python,安装python包也是安装在复制的python中

ps:如果是在pycharm里的设置里面安装的,而不是用pip命令安装的,会默认安装在一个虚拟环境里面

Linux下安装虚拟环境的命令

在虚拟环境中的一些命令:

Windows环境安装虚拟环境

项目创建

django-admin startproject 项目名字(注意:需要先进入虚拟环境,Linux和Windows相同)

创建成功后,目录结构如下:

在这里插入图片描述

其中:

创建django应用

一个项目由很多个应用组成,每一个应用完成一个功能模块

创建应用的命令如下:python manage.py startapp 应用名

每创建一个应用,里面就会有如下文件:


在这里插入图片描述

其中:

在我们建立应用之后,必须建立应用和项目之间的联系,即:对应用进行注册:在settings.py中修改installed_apps配置项,eg:注册booktest应用

在这里插入图片描述
运行项目

运行一个web项目,就需要一个web服务器,而django本身给我们提供了一个web服务器,使用python manage.py runsevser运行该服务器,然后即可根据提示用浏览器访问

ORM

O:object,对象
R:relations,关系
M:mapping,映射

ORM的作用:建立类和表的对应关系

ORM让我们能够通过对类和对象的操作实现对表的操作,就不需要再去写sql语句。这是元类的一种最经典的应用

在创建的项目文件夹下的models.py文件中,我们设计和表对应的类,这个类被称做模型类

ORM的另一个作用:根据设计的类生成数据库中的表

模型(M)

模型类设计和表生成

模型类设计

在项目文件夹下的models.py文件中,比如我们定义一个图书类

from django.db import models
# Create your models here.
class BookInfo(models.Model):
    # 必须继承了Model类之后,他才是一个模型类
    # 模型类的名字就对应的表的名字为:应用名_小写的模型类名
    # 模型类中的类属性就对应于表中的一个字段(列)
    btitle = models.CharField(max_length=20) #书名
    #通过models.CharField体现btitle的类型是一个字符串,max_length制定字符串的最大长度
    bpub_date = models.DateField()  #出版日期
    #models.DateField说明其是一个日期类型
    
    #在django中,id不需要我们定义,他会帮我们自动生成

模型类的字段属性和选项

模型类属性命名限制

字段类型

使用时需要引入django.db.models包

选项

作为Field()中的参数

通过选项实现的字段的约束,选项如下:

更多的可以见官方文档

对比:null是数据库的概念,blank是后台管理页面表单验证范畴的

注意:当修改模型类之后,如果添加的选项不影响表的结构,则不需要重新做迁移,eg:blank和default

模型类生成表

一、 生成迁移文件

迁移文件是根据模型类生成的,生成的迁移文件存放在migrations文件夹下

命令:python manage.py makemigrations

二、执行迁移生成表

命令:python manage.py migrate

根据迁移文件生成表

注意:django默认使用的数据库是sqlite3,在项目文件夹下,与项目同名的文件夹下的settings.py文件中DATABASES字段处可以看到

当文件迁移后,我们就可以在项目文件夹下看到一个名为db.sqlite3的文件,即我们的数据库文件,可以直接使用相应软件打开

通过模型类操作数据表

建立两张表之间的关系:外键

from django.db import models
# Create your models here.
class BookInfo(models.Model):
    # 必须继承了Model类之后,他才是一个模型类
    # 模型类中的类属性就对应于表中的一个字段(列)
    btitle = models.CharField(max_length=20) #书名
    #通过models.CharField体现btitle的类型是一个字符串,max_length制定字符串的最大长度
    bpub_date = models.DateField()  #出版日期
    #models.DateField说明其是一个日期类型

    #在django中,id不需要我们定义,他会帮我们自动生成

class HeroInfo(models.Model):
    hname = models.CharField(max_length=20) #名字
    hgender = models.BooleanField(default=False)    #性别,布尔类型,default制定默认值
    hcomment = models.CharField(max_length=128) #备注
    # 在有一对多的关系的两个类中,需要在“多”的类中定义外键,建立“一”和“多”的关系

    # 关系属性对应的表的字段名格式:关系属性名_id
    hbook = models.ForeignKey('BookInfo',on_delete=models.CASCADE)   # 建立了图书类和英雄人物类之间的关系,注意:如果两个类不在一个应用里面,则需要写成 应用.类名,eg:booktest.BookInfo

注意:往“多”类的属性里面赋值的时候,相关联属性其对应值必须是与其关联的类的对象,eg:


在这里插入图片描述

对该属性进行查看,发现其是一对象:


在这里插入图片描述 在这里插入图片描述
查看与“一”相关联的“多”的信息(eg:此处与图书相关联的英雄的信息) 在这里插入图片描述

注意:这里的heroinfo是小写

查询表中的所有内容:类名.objects.all(),返回的是一个列表,其中的元素为每一条数据的对象

后台管理

在所创建应用的对应文件夹下,有一个admin.py,(eg:本案例中为:/myProject/booktest/admin.py)通过它,我们实现后台的管理

步骤:

admin文件:


在这里插入图片描述

注册后的管理界面:


在这里插入图片描述 但是注意:表中的记录名为对应对象的字符串,如何使其显示我们能够看懂的内容?答:重写str方法,eg: 在这里插入图片描述 在这里插入图片描述
class BookInfoAdmin(admin.ModelAdmin):
    # 自定义模型管理类,名字可以随便取,但是通常是:表的名字+Admin
    list_display = ['id','btitle','bpub_date']  # 固定写法,列表中写要在页面中显示的内容
    # 同时,在注册的时候,我们需要让该模型类知道自己的管理类是谁
admin.site.register(BookInfo, BookInfoAdmin)    # 注意,该语句不能分两次写,否则会报错

视图(V)

在django中,通过浏览器去请求一个页面的时候,是使用视图函数来处理这个请求的,视图函数处理后,要给浏览器返回页面内容

视图函数的使用

一、定义视图函数

在应用下的views.py中,eg:定义一个名为index的视图函数(注意:视图函数必须有一个参数:request)

from django.http import HttpResponse
def index(request):
    # 视图函数必须有一个request参数,它是一个HttpRequest对象
    # 当用户输入http://127.0.0.1:8000/index的时候,要给浏览器返回一个内容
    # 视图的作用,就是在里面会进行处理,如果要用到数据库,就与M交互,如果要产生页面,就与T交互
    # 视图函数必须返回一个HttpResponse对象,需要从django.http中导入
    # 所谓要产生页面就与T(template)进行交互:template就是一段html代码,当V拿到tempalte后,
    # 将里面的部分数据用从M(数据库)拿来的数据进行替换,然后返回,浏览器就可以直接解析这段html代码从而在浏览器上显示对应内容
    return HttpResponse("<h1>hello django!</h1>")
    # 我们知道,一个地址对应一个处理函数,那么如何让django知道哪个地址是对应哪个处理函数?
    # 答:进行url路由的配置

二、进行url的配置

首先我们要在应用对应的目录下建立urls.py文件,然后在其中创建一个列表,如下:

from django.conf.urls import url
from booktest import views
urlpatterns = [
    # 通过url函数设置url路由的配置项
    url(r'^index', views.index) #建立/index和视图index之间的关系
]

同时,我们也需要在项目的urls里面添加配置项

from django.contrib import admin
from django.urls import path,include
from django.conf.urls import url

urlpatterns = [
    path('admin/', admin.site.urls),    # 配置项
    url(r'^', include('booktest.urls')) # 将booktest应用中的urls文件包含进来
    # url函数第一个参数是一个正则表达式
]

效果图如下:


在这里插入图片描述

流程分析:

在这里插入图片描述

注意:在应用的urls文件中进行url匹配的时候,一定要严格匹配开头结尾,避免出现意外

模板(T)

django中的模板不仅仅是个html文件,还可以在其中定义变量,也可以写类似于编程语言的语句

模板文件的使用

eg:

模板文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>模板文件</title>
</head>
<body>
    <h1>这是一个模板文件</h1>
    <p>我在这里使用了模板变量,在模板中使用变量,如果不是在代码段内,就要使用两个花括号括起来:{{content}}</p>
    <ul>模板中的代码段要写在两个%中间:{% for i in list %}
        <li>{{i}}</li>
        {% endfor %}
<!--        在模板中,如果使用代码,有开始就一定有结束,比如endfor,比如endif-->
    </ul>
</body>
</html>

应用程序中的views文件

# -*- coding:utf-8 -*-
from django.shortcuts import render

# Create your views here.
from django.http import HttpResponse
from django.template import loader,RequestContext
def index(request):
    # # 1. 加载模板文件
    # temp = loader.get_template('booktest/index.html') #注意:这里的路径是相对与templates的
    # # 返回值是一个模板对象
    #
    # # 2. 定义模板上下文:给模板文件传数据。所谓定义模板上下文,就是创建一个RequestContext对象
    # context = RequestContext(request, {})    # RequestContext的第一个对象需要是一个request
    # context.push(locals())
    # # 要给模板文件传递的数据就放在第二个参数里面,其是一个字典,通过键-值对的方式传递
    # # 由于我们这里没有用到变量,所以传空字典即可
    #
    # # 3. 模板渲染:产生标准的html内容
    # # 模板对象temp里面有一个render方法,能够把RequestContext对象中对应的位置替换成对应的值
    # # 然后返回替换后的内容:一个标准的html文件
    # res_html = temp.render(request=request,context=locals())
    # # 4. 返回给浏览器
    #
    # return HttpResponse(res_html)
    return render(request,'booktest/index.html',{"content":"hello world", "list": list(range(10))})

    # 注意:实际上以上几个过程都已经被进一步封装到了render中,我们可以直接写成:render(request, '/booktest/index.html',{})
    # 第三个参数是我们要传递给模板文件的数据,但是建议自己手动替换。如果不传的话也可以直接不写

效果图:

在这里插入图片描述
知识补充
from django.conf.urls import url
from booktest import views
urlpatterns = [
    url(r'^index', views.index),
    url(r'^show_books$', views.show_books), 
    url(r'^show_books/(\d+)$', views.detail),
    # 在配置url的时候,只要对正则表达式分组,django就会将该分组作为参数传递给后面的参数,这里就是将匹配到的数字传递给views.detail函数
    # ps:新版django可以使用path来代替url
]
<h1>{{ book.btitle }}</h1>
英雄信息如下:<br>
<ul>
    {% for hero in heros %}
        <li>{{hero.hname}}---{{hero.hcomment}}</li>
    {% empty %}
    <!-- empty:用在for循环中,如果遍历东西为空,就会执行这里的东西 -->
    <li>没有英雄信息</li>
    {% endfor %}
</ul>

Django中数据库的配置

在django中,通过方便的配置就可以进行数据库的切换,直接更改项目的settings文件

DATABASES = {
    'default': {
        # 'ENGINE': 'django.db.backends.sqlite3',
        # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        """使用mysql数据库"""
        """注意:在使用mysql数据库的时候需要使用一个叫MySQLdb的模块,我们直接安装pymysql即可"""
        """然后在项目文件夹下的settings文件中加上如下代码:
            import pymysql
            pymysql.install_as_MySQLdb()
        """
        'ENGINE': 'django.db.backends.mysql',   #说明使用mysql数据库
        'NAME': 'bj18',    #说明要使用的数据库的名字,注意,数据库必须事先手动创建
        # 配置用户名和密码
        'USER': 'root',
        'PASSWORD': "",
        'HOST': 'localhost',    #指定数据库所在的ip地址,因为通常我们用的数据库并不是在本地,如果是连本机,可以直接使用localhost
        'PORT': 3306,   #配置数据库的端口
    }
}

关于报错问题

if version < (1, 3, 3):
    raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)

重定向

这里说的重定向,就是当一个视图函数处理完请求后,继续返回到某一页面

代码示例:

from datetime import date
from django.http import HttpResponseRedirect
def create(request):
    """新增一本图书"""
    b = BookInfo()
    b.btitle = "流行蝴蝶剑"
    b.bpub_date = date(2000,11,12)
    b.save()
    # return HttpResponse('ok')
    # 返回应答:让浏览器再访问/index/,这就需要用到:from django.http import HttpResponseRedirect
    return  HttpResponseRedirect(redirect_to='/index')
    # HttpResponseRedirect也有简单的写法:
    # from django.shortcuts import redirect
    # return redirect('index')

相应html代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>图书信息</title>
</head>
<body>
    <h1>图书信息如下:</h1>
    <a href="/create">新增</a>
<!--    注意:这里的/create相当于127.0.0.1:8000/create-->
<!-- 但是如果不加/,就是和之前的地址进行拼接,比如,如果之前的地址是127.0.0.1:8000/index,那么此时就是127.0.0.1:8000/index/create -->
    <ul>
        {% for book in books %}
            <li>{{ book.btitle }}---{{ book.bpub_date }}</li>
        {% empty %}
            <li>信息为空</li>
        {% endfor %}
    </ul>
</body>
</html>

当我们点击添加的时候,本来会跳转到127.0.0.1:8000/create,但是由于其视图函数处理后进行了重定向,所以我们在浏览器就看不到这个跳转的过程

在这里插入图片描述

通过模型类查询表中的数据

更改配置使其产生日志文件

在Linux里面让mysql产生日志文件mysql.log

查询函数

通过 模型类.objects属性可以调用如下函数,实现对模型类对应的数据表的查询,注意,要是模型类,而不是它的实例

类名就相当于(注意,不是)表的名字,模型类.xxx就是在表中查找数据

以下格式:(函数名:功能;返回值;说明)

查询时使用的条件

条件的格式:模型类的属性名__条件名=值\

在查询时,可以传多个条件,他们之间是且的关系

# 判等:exact
BookInfo.objects.get(id=1)  #完全的写法为:BookInfo.objects.get(id__exact=1)

# 模糊查询
# 包含:contains
BookInfo.objects.filter(btitle__contains='传')   #查询名字中含有“传”字的书
# 以xxx结尾:endswith
# 以xxx开始:starswith

# 空查询:isnull
BookInfo.objects.filter(btitle__isnull=False)   #查询书名不为空的数据

# 范围查询:in
# eg:查询id为1或3或5的图书
BookInfo.objects.filter(id__in=[1,3,5])

# 比较查询
# gt:great than,大于    lt:less than,小于    gte:great euqal,大于等于    lte:less than equal,小于等于
# eg:查询编号大于3的图书
BookInf.objects.filter(id__gt=3)

# 日期查询
BookInfo.objects.filter(bpub_date__year=19990)  #date有一个属性为year
from datetime import date
BookInfo.objects.filter(bpub_date__gt=date(1980,1,1))

对于QuerySet,可以直接继续调用上面的方法

eg:把id大于3的图书信息按阅读量从大到小排序显示:BookInfo.objects.filter(id__gt=3).order_by('-bread'),因为filter返回的是一个QuerySet,所以可以直接继续调用order_by

Q对象

作用:用于查询时条件之间的逻辑关系:not and or ,可以对Q对象进行& | -操作,~取反

使用之前需要先导入:from django.db.models import Q

eg:

BookInfo.objects.fitler(Q(id=1) | Q(id=2))
BookInfo.objects.fitler(Q(id=1) | Q(id=2))

F对象

作用:用于类属性之间的比较

使用之前需要先导入:from django.db.models import F

eg:查询图书阅读量大于评论量

BookInfo.objects.filter(bread__gt=F('bcomment'))

# F对象还可以进行算数的运算
# 查询图书阅读量大于两倍评论量的信息
BookInfo.objects.filter(bread__gt=2F('bcomment')*2)

聚合函数

作用:对查询结果进行聚合操作

sum count avg max min

aggregate:调用这个函数来使用聚合。返回值是一个字典

使用之间需要先导入聚合类:from django.db.models import Sum,Count,Max,Min,Avg

案例:

# 查询所有图书的数目
from django.db.models import Count,Sum
BookInfo.objects.all().aggregate(Count('id'))
# 注意:count的参数不能为*

# 查询所有图书阅读量的总和
BookInfo.objects.aggregate(Sum('bread'))

count函数,注意,这里说的不是上面的Count

返回值是一个数

eg:查询所有图书的数目

BookInfo.objects.all().count()
# 同样的,如果是对一个表中的所有数据进行查询,.all()都可以省略
# 即:可以写成:BookInfo.objects.count()

查询集

all filter exclude order_by 调用这些函数会产生一个查询集

查询集特性

限制查询集

可以对一个查询集进行 取下标或者切片 操作来限制查询集的结果

对一个查询集进行切片操作会产生一个新的查询集,下标不允许为负

eg:

books = BookInfo.objects.all()
bk2 = books[0:3]

取出查询集第一条数据的两种方式:

exists:判断一个查询集中是否有数据,返回结果为True或False

用法:查询集.exists()

模型类之间的关系

和表一样,模型类之间也有三种关系

一对多关系

例:图书类-英雄类

models.ForeignKey() 定义在多的类中

多对多关系

例:新闻类-新闻类型类,一篇新闻可能是体育新闻,还可能是国际新闻,同样,体育新闻下也有多篇新闻

models.ManyToManyField() 定义在哪个类中都可以

一对一关系

例:员工基本信息类-员工详细信息类

models.OneToOneField 定义在哪个类中都可以

关联查询(一对多)

我们把在“多”的类中定义的属性叫做关联属性

案例:

# 查询图书id为1的图书关联的所有英雄的信息
b = BookInfo.objects.get(id=1)
b.heroinfo_set.all()

# 查询id为1的英雄关联的图书信息
h = HeroInfo.objects.get(id=1)
h.hbook()
在这里插入图片描述
通过模型类实现关联查询

例:查询图书信息,要求图书管理的英雄的描述包含“八”

Book.Info.objects.filter(heroinfo__hcoment__contains=8)

heroinfo是对应的“多”的类名的小写,hcomment是“多”的属性(相当于表中的字段),contains是条件表达式

最终要拿到哪个表中的数据,就通过哪个表来查

例:查询图书信息,要求图书中的英雄的id大于3

BookInfo.objects.filter(heroinfo__id__gt=3)

例:查询书名为“天龙八部”的所有英雄

Hero.objects.filter(hbook__bititle="天龙八部“)

注意:如果前面要查询的类中没有该关系属性,后面的参数中就必须写类名;如果前面类中有该关系属性,写的应该是属性名

插入、更新和删除

调用一个模型类对象的save方法的时候就可以实现对模型类对应数据表的插入和更新

调用一个模型类对象的delete方法的时候,就可以实现对模型类对应数据的删除

自关联

自关联是一种特殊的一对多的关系,只是这些一对多的关系都在一张表里面

示例:

class AreaInfo(models.Model):
    """地区模型类"""
    atitle = models.CharField(max_length=20)
    # 关系属性,代表当前地区的父级地区
    aParent = models.ForeignKey('self', null=True, blank=True,on_delete=models.CASCADE)   #代表这个类与他自身有了这种关联

管理器

BookInfo.objects.all() --> objects到底是一个什么东西?

答:objects是django帮助我们自动生成的管理器对象,通过这个管理器可以实现对数据的查询

objects是models.Manager类的一个对象。自定义管理器后Django不再帮助我们生成默认的objects管理器

示例:

class BookInfo(models.Model):
    """图书模型类"""
    btitle = models.CharField(max_length=20)
    bpub_date = models.DateField()
    bread = models.IntegerField(default=0)
    bcomment = models.IntegerField(default=0)
    isDelete = models.BooleanField(default=False)   #删除的时候,执行逻辑删除,而非真正的删除,默认不删除
    book = models.Manager()     #自定义一个管理器对象

此时,我们就不能够通过BookInfo.objects.xxx来查询数据,而是使用:BookInfo.book.xxx

在这里插入图片描述
通常,我们是自己定义一个类,继承models.Manager类,然后让其作为管理器类

自定义管理器类的作用:

代码示例:不返回逻辑上被删除了的数据

class BookInfoManager(models.Manager):
    def all(self):
        # 1. 调用父类的all方法获取所有数据
        books = super().all()
        books.filter(isDelete=False)
        return books

代码示例:

class BookInfoManager(models.Manager):
    def all(self):
        # 1. 调用父类的all方法获取所有数据
        books = super().all()
        books.filter(isDelete=False)
        return books
    # 2. 封装函数:操作模型类对应的数据表(增删查改)

    def create_book(self, btitle, bpub_date):
        obj = BookInfo()
        obj.btitle = btitle
        obj.bpub_date = bpub_date
        obj.save()
        return obj

效果:


在这里插入图片描述

注意:在models.Manager里面默认给我们封装好了一个create方法,不过必须要通过关键字传参

以上代码还有 一个问题:即,一旦模型类的名字发生改变,我们就必须手动更改管理器内的代码,我们可以通过self.model来获得模型类的名称

代码示例:

class BookInfoManager(models.Manager):
    def all(self):
        # 1. 调用父类的all方法获取所有数据
        books = super().all()
        books.filter(isDelete=False)
        return books
    # 2. 封装函数:操作模型类对应的数据表(增删查改)

    def create_book(self, btitle, bpub_date):
        model_class = sel.model #获得model所在的模型类
        obj = model_class() #创建对象
        obj.btitle = btitle
        obj.bpub_date = bpub_date
        obj.save()
        return obj

模型管理器类和模型类的关系

在这里插入图片描述

元选项

情景:由于我们生成的表的名字为”应用名_小写的模型类名",那么当我们的应用名一旦发生改变,该模型类对应的表名就跟着改变,但是这个类对应的表名已经生成了,这就会导致无法访问原表,会报错

如何解决:让模型类对应的表名不依赖于应用的名字:通过元选项指定表名,在模型类里面定义一个元类,通过该类里面的db_table属性来定义表名

代码示例:

class BookInfo(models.Model):
    """图书模型类"""
    btitle = models.CharField(max_length=20)
    bpub_date = models.DateField()
    bread = models.IntegerField(default=0)
    bcomment = models.IntegerField(default=0)
    isDelete = models.BooleanField(default=False)   #删除的时候,执行逻辑删除,而非真正的删除,默认不删除
    objects = BookInfoManager()     #自定义一个管理器对象
    class Meta:
        db_table = "bookinfo"   # 指定模型类对应的表名为bookinfo

如果仅仅是要建立与表的对应关系(模型类中的名字与鲜美 存表中的名字不一致),直接这样即可;如果我们是要用此法更改名字,需要我们再重新做一下迁移

视图(V)

视图的功能:接收请求,进行处理,与M和T进行交互,返回应答

返回html内容HttpResponse,一可能重定向redirect

视图函数的使用

使用

使用

url配置过程

匹配过程

在这里插入图片描述
  1. 去除域名和后面的参数,剩下/aindex,再把前面的/去掉,剩下aindex
  2. 拿aindex先到项目的url.py文件中进行从上到下的匹配,匹配成功之后执行后面对应的处理动作,就是把匹配成功的部分a字符去除,然后拿剩下的部分index到应用的urls.py文件中再进行从上到下的匹配。
  3. 如果匹配成功则调用相应的视图产生内容返回给客户端。如果匹配失败则产生404错误。

错误视图

当我们访问一个不存在的页面时,会看到一个如下的错误信息


在这里插入图片描述

这通常是为了调试方便,会将我们网站的地址配置显示出来,小实际应用中是不可取的,根据红色框内的提示信息,我们可以通过更改配置来显示标准的404页面

404错误通常源于:

如果是自己的视图里面出错,在浏览器端会提示500错误,如果我们不想使用默认的,也可以自己在templates下面自定义一个名为500的html

通常,我们在开发过程中,要打开DEBUG模式

捕获url参数

进行url匹配时,把所需要的捕获的部分设置成一个正则表达式组,这样django框架就会自动把匹配成功后相应组的内容作为参数传递给视图函数。一旦进行了捕获,在视图函数中就必须声明。

视图函数中的request参数

request就是HttpRequest类型的对象,其中包含着浏览器请求的一些信息,就是把WSGI模型中的application函数中的env进行了一层包装

案例:模拟登录

先在settings文件中注释掉'django.middleware.csrf.CsrfViewMiddleware',,否则会出现403错误。也可以from django.views.decorators.csrf import csrf_exempt,然后用csrf_exempt装饰对应的视图函数

应用urls配置文件:

from booktest import views
from django.urls import path
urlpatterns = [
    path(r'index', views.index),
    path(r'login', views.login),
    path(r'login_check',views.login_check)
]

将应用urls包含到项目urls文件中的代码:略

views文件代码:

# -*- coding:utf-8 -*-
from django.shortcuts import render,redirect
# Create your views here.
from datetime import date
from django.http import HttpResponse, HttpResponseRedirect

def index(request):
    return HttpResponse("hello world")
    
def login(request):
    return render(request,'booktest/login.html',{})

def login_check(request):
    # 1. 获取提交的用户名和密码
    # 2. 进行登录的校验
    # 3. 返回应答
    """
    request对象有两个属性:他们的是QueryDict类型的对象,和字典很相似,可以通过键取出值
    request.POST:保存的是post的提交参数
    request.GET:保存的是get方式提交的参数
    QueryDict的用法:
        from django.http.request import QueryDict
        q = QueryDict('a=1&b=2&c=3')    #实例化了一个QueryDict对象,他里面存储了abc三个属性
        q['a']  #就能够获得a的值1,还可以通过q.get('a")获得
        # 同字典一样,如果是用中括号取,如果键不存在,会抛异常,如果是get方法取,键不存在会返回none,不会报错
        # get方法也可以默认值

        #QueryDict和字典的本质区别
            #在字典中,一个键只能对应一个值,而QueryDict一个键可以对应多个值
            #eg:
            q1 = QueryDict('a=1&a=2&a=3&b=4')
            q1['a'] #返回3
            q1.get('a') #返回3
            q1.getlist('a') #返回一个列表,其中元素为1、2、3
    """
    username = request.POST.get('username')    #post提交的数据中,键就是表单的name值
    password = request.POST.get('password')
    #实际情况下,用户名和密码应该是到数据库中去查找
    #模拟:用户名为smart,密码为123
    if username=="smart" and password == "123":
        #正常情况下,我们登录成功后,会跳转到一个页面,这里我们跳转到首页
        return redirect('/index')
    else:
        #用户和密码错误就还是跳转到登录页面
        return redirect('/login')

login.html代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<!--表单的两种提交方式:-->
<!--1. POST:提交的参数在请求头里面,如果数据比较重要,采用POST-->
<!--2.GET:提交的参数在url中-->
<form method="post" action="/login_check">
<!--    表单中的action就指定我们提交的地址-->
<!--    method指定表单提交的方式-->
    用户名:<input type="text", name="username"><br>
    密码:<input type="password", name="password"><br>
    <input type="submit", value="登录">
</form>
</body>
</html>

注意:此种方式中,我们采用的是全局刷新如果登录页面内容过多,产生的体验相当不好,此问题可以结合ajax采用局部刷新

ajax实现局部刷新的流程:


在这里插入图片描述

注意:后台返回的数据就在function的data里面

ps:css、js、images等静态文件,在django中,都要放在项目下的static文件夹中,然后到settings中进行配置,如下:

#在最后一行:
STATICFILES_DIRS = [os.path.join(BASE_DIR,'static')]    #设置静态文件的保存目录

通常,每种静态文件都放在一个文件夹下面,eg:js就放在js文件夹下面,css、images类似

ajax请求

在项目文件夹下的static/js下,有jquery文件

ajax就是异步的javascript,其异步体现在,当发起了ajax请求之后,不等待回调函数的执行,而是继续往下执行代码

当然,也可以发起同步的ajax请求,只需添加:'async':false即可

注意:ajax的请求都是在后台

test_ajax.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ajax</title>
    <script src="/static/js/jquery-3.4.1.js"></script>
    <script>
        $(function(){
            $('#btnAjax').click(function(){ //当点击该按钮的时候,发起ajax请求
                $.ajax({
                    'url':'/ajax_handle',
                    'type':'get',
                 // type不写的话默认就是get
                    'dataType':'json'
                }).done(function(data){
                    //进行处理
                    alert(data.res)
                })
            })
        })
    </script>
</head>
<body>
<input type="button" value="ajax请求" id="btnAjax">
<!--一点击按钮的时候,就发起一个ajax的请求-->
</body>
</html>

应用下的urls文件

from booktest import views
from django.urls import path
urlpatterns = [
    path(r'index', views.index),
    # path(r'create', views.create),  #新增一本图书
    # path(r'areas',views.areas),
    path(r'login', views.login),
    path(r'login_check',views.login_check),
    path(r'test_ajax',views.ajax_test), #显示ajax页面
    path(r'ajax_handle',views.ajax_handle),
]

应用下的views.py

from django.shortcuts import render,redirect
def ajax_test(request):
    return render(request,'booktest/test_ajax.html')

from django.http import HttpResponse, JsonResponse
def ajax_handle(request):
    # 注意:是return 一个JsonResponse的对象
    # 假设返回的是{'res':1}
    return JsonResponse({'res':1})

效果图:


在这里插入图片描述

同时,如果我们用浏览器,查看“网络”,会发现它并没有请求整个页面,即:实现了局部刷新

在使用ajax实现局部刷新时,我们首先要分析出访问地址时需要携带的参数以及视图函数处理完成之后,所返回的json格式

HttpRequest对象讲解

属性

状态保持

http协议是无状态的,下一次去访问一个页面时,并不知道上一次对这个页面做了什么

cookie

cookie是由服务器生成,保存在浏览器端的一小段文本信息

cookie的特点:

  1. 以键值对方式进行存储。
  2. 通过浏览器访问一个网站时,会将浏览器存储的跟网站相关的所有cookie信息发送给该网站的服务器。request.COOKIES
  3. cookie是基于域名安全的。www.baidu.com www.tudou.com
  4. cookie是有过期时间的,如果不指定,默认关闭浏览器之后cookie就会过期。

设置cookie:需要一个HttpRespose类的对象,或者是它的子类对象,通过它的set_cookie方法,我们可以设置cookie

浏览器发给服务器的cookie保存在request对象的COOKIES里面,它是一个标准的字典

HttpResponse有哪些子类?答:HttpResponseRedirect以及JsonResponse都是

代码示例:

from datetime import datetime,timedelta
from django.http import HttpResponse, HttpResponseRedirect
def set_cookie(request):
    response = HttpResponse('set_cookie')
    # 设置一个cookie信息
    response.set_cookie('num',1, expires=datetime.now()+timedelta(days=14))    
    #第一个参数就是"键",第二个参数就是"值";后面是指定过期时间为14天
    #指定过期时间也可以使用max_age,单位为s,但是可以直接写计算式
    response.set_cookie('num2',2, expires=datetime.now()+timedelta(days=14))    #可以设置多个cookie
    return response
    # 当我们return一个response的时候,这个cookie会交给浏览器保存

def get_cookie(request):
    num = request.COOKIES['num']
    return HttpResponse(num)

除了以上代码外,只需要配置urls文件即可

session

session与cookie最大的区别是cookie保存在浏览器端而session保存在服务器端

对于敏感、重要的信息,建议要储在服务器端,不能存储在浏览器中,如用户名、余额、等级、验证码等信息。

在这里插入图片描述
session的特点
  1. session是以键值对进行存储的。
  2. session依赖于cookie。唯一的标识码保存在sessionid cookie中。
  3. session也是有过期时间,如果不指定,默认两周就会过期。

在用django创建的mysql数据库中,有一张django_session表,专门用来存储session

代码示例:

def get_cookie(request):
    num = request.COOKIES['num']
    return HttpResponse(num)

from django.http import HttpResponse
def set_session(request):
    request.session['username'] = 'smart'
    request.session['password'] =  '123'
    return HttpResponse('设置session')

def get_session(request):
    username = request.session['username']
    password = request.session['password']
    return HttpResponse(username+":"+password)

除此之外,只需要设置urls即可

效果图:数据是经过base64编码的

在这里插入图片描述 浏览器端查看:
在这里插入图片描述
session的其他方法

模板(T)

模板的功能

产生html页面,控制页面上展示的内容,模板文件不仅仅是一个html文件。

模板文件包含两个部分的内容:

模板文件的加载顺序

模板语言

模板文件中的动态内容即由模板语言产生

模板变量

模板变量名是由数字,字母,下划线和组成的,不能以下划线开头。

使用模板变量:{{模板变量名}},如果是在{%%}的代码区,可以直接使用

模板变量的解析顺序:例如:{{ book.btitle }}

例如:{{book.数字}}

如果解析失败,不会报错,而是用空字符串进行替换

使用模板变量时,.前面的可能是一个字典,可能是一个对象,还可能是一个列表。

模板标签

{% 代码段 %}
    for循环:
    {% for x in 列表 %}
    # 列表不为空时执行
    {% empty %}
    # 列表为空时执行
    {% endfor %}
可以通过{{ forloop.counter }}得到for循环遍历到了第几次。

{% if 条件 %}
{% elif 条件 %}
{% else %}
{% endif %}

关系比较操作符:> < >= <= == !=
注意:进行比较操作时,比较操作符两边必须有空格。

逻辑运算:not and or

过滤器

过滤器用于对模板变量进行操作。

过滤器的本质是一个函数,可能需要参数

使用格式:模板变量|过滤器:参数

常用过滤器:

{% for book in books %}
    {% if book.id <= 2 %}
        <li class = 'red'>{{ book.btitle }}---{{ book.bpub_date | date:'Y年-m月-d日' }}</li>
        <!-- Ymd是date函数的参数,Y表示以4位显示年,m表示以两位数字显示月,d... -->
    {% endif %}
{% endfor %}

个人对过滤器的理解,用过滤器过滤后的返回值替换原来的变量

更多相关内容见官网

自定义过滤器

自定义过滤器的参数至少一个(前面的模板变量),最多两个

代码示例:

from django.template import Library

register = Library()    #创建一个Library对象

@register.filter    #用该对象中的filter装饰自己定义的函数,使其成为一个过滤器
def mod(num):   #在使用过滤器的时候,前面的部分就会作为第一个参数自动传递给过滤器,如果是需要多个参数,则使用方法如下:表达式或变量 | 过滤器:第二个参数(过滤器最多两个参数)
    return num % 2 == 0 #过滤掉奇数

模板注释

注意:模板注释在浏览器上查看网页源代码的时候是无法看到的,但是html注释的内容是可以看到的

,至少一个(前面的模板变量),最多两个

代码示例:

base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}Base{% endblock title %}</title>
</head>
<body>
<h1>导航栏</h1>
{#页面不同的地方,需要在父模板中预留位置,这个位置我们称为预留快#}
{#子模板就可以重写父模板中预留块的内容#}
{% block block_name %}
父模板的预留块中可以写内容,也可以不写,这里我们直接令预留块的名字为block_name
{% endblock block_name %}
<h1>版权信息</h1>
</body>
</html>

child.html

{% extends 'booktest/base.html' %}
{#注意:路径是相对于templates的#}
{#继承了之后,里面就不能再写其他内容#}

{#重写父模板中的预留块#}
{#如果不重写预留块中的内容,就直接按照父模板中的内容显示#}

{% block block_name %}
{#    在子模板中,可以使用{{ block.super }}获取父模板中预留块的内容#}
    {{ block.super }}<br>
    这是子模板中的内容
{% endblock block_name %}

html转义

在视图函数中对模板传参时(eg:render中),会对以下字符自动转义

小于号< 转换为 &lt;

大于号> 转换为 &gt;

单引号' 转换为 &#39;

双引号" 转换为 &quot;

与符号& 转换为 &amp;

如何关闭转义:

法一:使用safe过滤器(不需要第二个参数)

法二:使用autoescape标签,off表示关闭转义,on表示打开转义。用法如下:

{% autoescape off %}    
模板语言代码
{% endautoescape %}

safe和autoescape的区别:safe是对一个变量起作用,而autoescape是对其内部代码段中的所有变量都关闭转义

模板硬编码中的字符串默认不会经过转义

何为模板硬编码中的字符串?即,写“死”了的部分,示例如下:

{{ test | default:'<h1>hello</h1>' }}

其中,默认的参数就不会被转义

要对硬编码进行转义,必须手动执行,eg:将<手动写成&lt;

scrf攻击

即:跨站请求的伪造

登录装饰器

情景示例

在进行网站开发的时候,有些页面是用户登录后才能访问的,假如用户访问了这个地址,需要进行登录的判断,如果用户登录的话,可以进行后续的操作,如果没有登录,跳转到登录页。
我们就可以把这个登录验证加在对应页面的视图函数中
但是如果页面过多,每个都要自己写,就很麻烦,我们可以将其放在一个装饰器里面

在这里插入图片描述

csrf伪造

在这里插入图片描述

上图说明:

当我们访问正常网站时,一切正常,在我们的电脑上保存有sessionid,假设我们更改了在该网站的密码(假设之后没有退出,即浏览器一直保存有该sessionid);而当我们访问另一个网站时,假设我们点击了其上面的某按钮或图片时,该网站向第一个网站发送了一个请求,该网站就能够伪造我们的身份更改我们在第一个网站的密码

csrf伪造成功的两个关键点:

这即所谓的跨站请求伪造

Django默认启用了csfr防护,即settings中的MIDDLEWARE_CLASSES中的django.middleware.csfr.CsrfViewMiddleware'项。该防护只针对post提交。所以重要的数据用post提交

但是我们会发现打开之后,当我们直接访问/change_pwd后,访问/change_pwd_action也会失败(403)。此时,就需要我们在post提交数据时加上{% csrf_token %}标签,eg,在表单提交post数据时,在表单内加上这个标签:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form method="post" action="/login_check">
    {% csrf_token %}
    用户名:<input type="text", name="username" value={{username}}><br>
    密码:<input type="password", name="password" value={{password}}><br>
    <input type="checkbox" name="remember">记住用户名<br>
    <input type="submit", value="登录">
</form>
</body>
</html>

防护的原理

验证码

在用户注册、登录页面,为了防止暴力请求(不断尝试密码),可以加入验证码功能,如果验证码错误,则不需要继续处理,可以减轻业务服务器、数据库服务器的压力。(只是因为程序识别验证码中的图片的难度比较大,所以降低了暴力请求的概率,而不是绝对防止)

可以用Pillow这个包产生验证码

from PIL import Image, ImageDraw, ImageFont
from django.utils.six import BytesIO
...
def verify_code(request):
    #引入随机函数模块
    import random
    #定义变量,用于画面的背景色、宽、高
    bgcolor = (random.randrange(20, 100), random.randrange(
        20, 100), 255)  #用rgb方式定义颜色
    width = 100
    height = 25
    #创建画面对象,并设置宽高
    im = Image.new('RGB', (width, height), bgcolor)
    #创建画笔对象
    draw = ImageDraw.Draw(im)
    #调用画笔的point()函数绘制噪点
    for i in range(0, 100): #循环遍历100次,在画面上添加噪点
        xy = (random.randrange(0, width), random.randrange(0, height))
        fill = (random.randrange(0, 255), 255, random.randrange(0, 255))
        draw.point(xy, fill=fill)   #指定在哪个点画,画的颜色是什么
    #定义验证码的备选值
    str1 = 'ABCD123EFGHIJK456LMNOPQRS789TUVWXYZ0'
    #随机选取4个值作为验证码
    rand_str = ''
    for i in range(0, 4):
        rand_str += str1[random.randrange(0, len(str1))]
    #构造字体对象,ubuntu的字体路径为“/usr/share/fonts/truetype/freefont”
    font = ImageFont.truetype('FreeMono.ttf', 23)
    #构造字体颜色
    fontcolor = (255, random.randrange(0, 255), random.randrange(0, 255))
    #绘制4个字
    draw.text((5, 2), rand_str[0], font=font, fill=fontcolor)
    draw.text((25, 2), rand_str[1], font=font, fill=fontcolor)
    draw.text((50, 2), rand_str[2], font=font, fill=fontcolor)
    draw.text((75, 2), rand_str[3], font=font, fill=fontcolor)
    #释放画笔
    del draw
    #存入session,用于做进一步验证(对比用户输入的验证码对不对)
    request.session['verifycode'] = rand_str
    #内存文件操作
    buf = BytesIO()
    #将图片(im)保存在内存中,文件类型为png
    im.save(buf, 'png')
    #将内存中的图片数据返回给客户端,MIME类型为图片png
    return HttpResponse(buf.getvalue(), 'image/png')

效果图:

在这里插入图片描述
注意:如果报OSError: cannot open resource错误,通常是view中使用的字体不存在,如果是windows,直接采用Windows/Fonts下面存在的字体即可

校验用户输入的验证码时,从session中取出正确值,再和用户的输入值相对比即可

url反向解析

比如,当我们使用超链接在多个页面间跳转的时候,需要指呆跳转到何处,如果我们直接写“死”了,当页面的地址发生变化时,我们就必须手动更改,非常麻烦。

这种情况下,我们没有必要把它写“死”,可以在链接处动态获取。

用法:

  1. 在项目的urls文件中进行include时,指定namespace


    在这里插入图片描述
  2. 在应用的urls文件中配置url时,指定name
在这里插入图片描述
  1. 在模板文件中使用该地址时,格式如下:

eg:

<a href="{{ url 'booktest:index'}}">首页</a>
<!-- 其中,booktest是在项目urls文件中include时指定的namespace的名字,index是在应用url中指定的name -->

在重定向的时候使用反向解析

from django.core.urlresolvers import reverse

重定向示例:

from django.core.urlresolvers import reverse
def test_redirect(request):
    url = reverse("booktest:index")
    # namespace:name
    # 如果有位置参数:url = reverse('booktest:show_args",args=(1,2))    #将参数以元组的形式传递过去
    # 关键字参数:url = reverse('booktest:show_kwargs',kwargs={c':3,'d':4}),则在调用对应的视图函数的时候,会以关键字的形式进行传参,传递给对应的视图函数
    return redirect(url)    #导入redirect的语句省略

其他技术

静态文件

使用

在网页使用的css文件,js文件和图片叫做静态文件

如何在模板文件(html)中动态获取STATIC_URL?

示例:

<!DOCTYPE html>
{% load staticfiles %} }
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>模板文件</title>
</head>
<body>
    <img src="{% static 'images/mm.jpg' %}">
{#    完成staticurl和路径的拼接#}
{#    static就对应这staticurl的配置#}
</body>
</html>

中间件

中间件函数是django框架给我们预留的函数接口,让我们可以干预请求和应答的过程。这个函数的名字和参数都已经固定好了,必须按照规定的格式写

eg:阻止某ip访问自己的网站

获取浏览器端的ip地址

使用request对象的META属性:request.META.['REMOTE_ADDR']

阻止某ip访问本网站的所有页面的方法一

利用装饰器,当发现请求的ip是某个我们想阻止的ip时,进行另外的处理

阻止某ip访问本网站的所有页面的方法二

代码示例(middleware.py):

from django.http import HttpResponse
class BolckedIPSMiddleware(object):
    # 中间件类
    EXCLUDE_IPS = ['192.168.14.74']
    def process_view(self,request, view_func, *view_args, **view_kwargs):
        # 中间件函数
        user_ip = request.META['REMOTE_ADDR']
        if user_ip in BolckedIPSMiddleware.EXCLUDE_IPS:
            return HttpResponse('forbidden')

详解

中间件类的名字可以自己取(常以MIDDLECLASS)结尾,但是里面的中间件函数名称是固定的

常用中间件预留函数: 名字和参数都是固定的

在类中,只定义需要用到的即可,不必全部定义

中间件函数的执行流程: 其中的红色箭头部分表示人为的干预,让其提前返回

在这里插入图片描述

以下部分待改

上传图片

配置上传文件保存目录

需要先创建一个目录,配置说明上传文件就保存在该目录下方
示例:
新建目录:(目录的位置和名字不固定)


在这里插入图片描述

在项目settings中配置目录:


在这里插入图片描述

通过后台管理页面上传

以图片为例:

  1. 设计模型类
class PicClass(models.Model):
    gpic = models.ImageField(upload_to = "booktest")    #upload_to指定上传目录。注意:是上传到我们配置了的目录下的哪个目录(我们这里配置的是static/media目录)

假设我们通过浏览器上传了图片,就会发现该表中多了一跳纪律,它里面存储的是相对路径

用户自定义上传图片

在模板文件show_upload.html中包含如下代码:

<form method='post' actioon='/upload_handle' enctype='multipart/form-data'>
<!-- 在/upload_handle这个地址对应的视图函数里面保存文件 -->
    {% csrf_token %}
    <input type='file' name='pic'><br>
    <input type='submit' value='上传'>
</form>

视图函数:

from django.conf import settings    #获取配置的上传目录
def show_upload(request):
    """"显示上传图片的页面"""
    return render(request,'booktest/show_upload.html')
def upload_handle(request):
    """上传图片处理"""

url的配置以及部分模块的导入省略

admin站点

控制管理页展示

类ModelAdmin可以控制模型在Admin界面中的展示方式,主要包括在列表页的展示方式、添加修改页的展示方式。

在应用下的admin.py中,注册模型类前定义管理类。eg:AreaAdmin

管理类有两种使用方式:

注册参数:在应用下的admin.py文件中,注册模型类:


上一篇 下一篇

猜你喜欢

热点阅读