Python Django框架

Django框架

下载

  • 命令行

pip install django==1.11.29 -i https://pypi.tuna.tsinghua.edu.cn/simple

  • pycharm

 

创建项目

  • 命令行
django-admin startproject 项目名称
  • pycharm
点击文件->新建项目->Django->输入项目的目录

运行项目

  • 命令行
》 cd 项目根目录
》 python manage.py runserver
》 python manage.py runserver 80				# 换端口
》 python manage.py runserver 0.0.0.0:80		# 换ip和端口
  • pycharm
直接运行Django

目录介绍

web/
|- manage.py		# 管理文件
|- web				# 项目目录
	|- __init__.py	
    |- settings.py	# 配置
    |- urls.py		# 路由 -> url和函数的对应关系
    |- wsgi.py		# runserver命令就使用wsgiref模块做简单的web server

 

初始测试

在urls.py文件

from django.shortcuts import HttpResponse,render

def index(request):
    # 业务逻辑

    # 返回结果
    return HttpResponse('index')

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', index),
]

 

登陆页面

寻找HTML框架,添加到templates文件夹中。

HTML框架网址:

https://v3.bootcss.com/getting-started/#download

http://www.jq22.com/

添加HTML框架

  1. 在网站找到想要的框架
  2. ctrl+S下载该框架的代码,包括HTML文件以及CSS、js以及图片文件
  3. 在urls.py文件中加入新的url以及相关的函数
def index(request):
    return render(request,'index.html')

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', index),
]
  1. 在templates文件夹中新建新的html文件,把下载好的HTML代码复制到新的文件中。
  2. 引入静态文件(参考下面)

静态文件的引入

  • 在settings.py文件下加入静态文件路径
STATIC_URL = '/static/' # 静态文件别名

STATICFILES_DIRS = [
    os.path.join(BASE_DIR,'static'),
    os.path.join(BASE_DIR, 'static1'),
]
  • 在根目录下创建 static 文件夹,里面创建 css 文件夹、js 文件夹、imgs文件夹分别放入不同的静态文件。
  • 在templates文件夹创建login.html文件,通过link标签引入static 的静态文件
<link rel="stylesheet" href="/static/css/signin.css">

 

form表单主意要点

  • form标签的属性 action 指定提交的地址(不写默认当前地址),method请求方式(默认get),如果不想加入前端校验可以加入 novalidate属性
  • input标签要有name属性,有的标签还需value属性
  • 有一个button按钮或者type=‘submit’ 的input

post请求的必要操作

在settings.py中注释中间件,Django框架有一个csrf机制防止用户胡乱提交请求。

MIDDLEWARE = [  # 中间件
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

 

app创建和注册

创建app项目

在新创建的Django目录下创建app项目

  • 命令行形式
python manage.py startapp 项目名称
  • pycharm

Tools -> Run manage.py Task... 会出现一个新窗口 ->在新窗口的命令行输入

startapp 项目名称

 

ps:新创建app项目Django不能识别,需要在settings.py文件下加入

INSTALLED_APPS = [
	...
    'app01'     # 1.直接写app名称
    'app01.apps.App01Config'    # 2.写app路径(推荐)
]

 

目录介绍

app01/
|- migrations					
|- __init__.py	
|- admin.py			# admin 后台管理
|- apps.py			# app 相关的
|- models.py		# 数据库相关的 orm
|- tests.py			# 测试
|- views.py			# 写函数的
  

 

通过ORM控制数据库

使用ORM

  1. 在settings中配置数据库连接
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
  1. 在app目录下的models.py文件中写类
class User(models.Model):     # 数据表
    username = models.CharField(max_length=32)     # 创建一个字段username,为varchar(32)
    password = models.CharField(max_length=32)
  1. 执行迁移命令
python manage.py makemigrations	# 检测所有app文件下的models.py文件有什么变化,将变更记录制作成迁移文件
python manage.py migrate	# 根据迁移文件迁移到数据库中(变更记录同步到数据库)
  1. 测试ORM,在views.py文件下的函数进行测试
from app01 import models

def index(request):
    # orm测试
    # 1.获取表中所有数据,对象类型QuerySet ,对象列表
    ret = models.User.objects.all() 
    for i in ret:
        print(i,i.username,i.password,type(i.username))
    
    # 2.获取一条数据
    ret = models.User.objects.get(username='hp',password='123')
    
    # 3.获取不到或者获取到多条报错
    ret = models.User.objects.get(password='123')   
    # print(ret.username)
    
    # 4.获取满足条件的对象
    ret = models.User.objects.filter(password='123')   
    
    print(ret,type(ret))
    return render(request,'index.html')

 

使用MySQL数据库

  1. 先创建一个mysql数据库
命令行创建 login 的数据库
create database login;
  1. 配置settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'login',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'USER': 'root',
        'PASSWORD': '123456',
    }
}
  1. 使用pymysql模块连接mysql数据库

    习惯写入与项目同名的文件夹下的__init__.py(也可以写在setting.py文件中)。

# 导入pymysql模块
import pymysql
pymysql.install_as_MySQLdb()

  1. 在app下的models.py写model
class User(models.Model):     # 数据表
    username = models.CharField(max_length=32)     # 创建一个字段username,为varchar(32)
    password = models.CharField(max_length=32)
  1. 执行迁移命令
python manage.py makemigrations	# 检测所有app文件下的models.py文件有什么变化,将变更记录制作成迁移文件
python manage.py migrate	# 根据迁移文件迁移到数据库中(变更记录同步到数据库)
  1. 配置myslq数据库

如果连接出现:Error:java.lang.RuntimeExpection:com.mysql.cj.exceptions.InvalidConnection

URL改为:

jdbc:mysql://localhost:3306/要连接的数据库名?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC

 

实例:展示出版社

  • 创建bookmanager项目
  • 配置settings.py文件(配置mysql数据库,参考上面)
  • 创建mysql数据库(参考上面)
  • 创建简单的html文件展示出版社名称

构造模板返回数据库信息,在HTML网页显示

在views.py文件中函数返回值,构造一个字典返回数据库信息

return render(request,'publisher_list.html',{'all_publishers':all_publishers})

html文件获取返回对象的信息在网页上显示

模板:
1. {{ all_publishers }}	

2.  {% for i in all_publishers %}
    	(循环体)
		{{ forloop.counter }}	循环次数
		{{ i.id }}
		{{ i.name }}
    {% endfor %}
<body>
<table border="1">
    <thead>
        <tr>
           <th>序号</th>
           <th>id</th>
           <th>出版社名称</th>
        </tr>
    </thead>
    <tbody>
        {% for i in all_publishers %}
            <tr>
                <td>{{ forloop.counter }}</td>
                <td>{{ i.id }}</td>
                <td>{{ i.name }}</td>
            </tr>
        {% endfor %}
    </tbody>
</table>
</body>
  • 新增出版社

新创建一个publisher_add.html文件,form表单接收提交的内容

<body>
<form action="" method="post">
    出版社的名称:<input type="text" name="pub_name"><span>{{ error }}</span>
    <button>提交</button>
</form>
</body>

在views.py文件中,新创建一个函数,接收form表单提交的 内容然后更新到数据库中。

# 新增出版
def publisher_add(request):
    if request.method == 'POST':
        # post请求
        # 获取用户提交的数据
        pub_name = request.POST.get('pub_name')
        print(pub_name)
        if not pub_name:
            return render(request, 'publisher_add.html',{'error':'出版社名称不能为空'})

        if models.Publisher.objects.filter(name = pub_name):
            # 数据库中有重复的名字
            return render(request, 'publisher_add.html',{'error':'出版社名称已存在'})
        # 将数据新增到数据库中
        ret = models.Publisher.objects.create(name=pub_name)
        # 返回重定向到展示出版社页面
        return redirect('/publisher_list/')
    # get请求返回一个页面,页面包含form表单
    return render(request,'publisher_add.html')
  • 删除出版社

在publisher_list.html文件加入删除标签,通过get方式获取要删除的出版社id号

<a href="/publisher_del/?pk={{ i.id }}">删除</a>

在views.py文件中,新创建一个函数,接收get方式提交的id号,根据id号删除数据库对应的对象列表

# 删除出版社
def publisher_del(request):
    # 获取要删除数据的id
    pk = request.GET.get('pk')
    print(pk)
    # 根据id到数据库进行删除
    # models.Publisher.objects.get(id = pk).delete()  # 查询到对象,然后删除,get查询不存在会报错
    models.Publisher.objects.filter(id = pk).delete()  # 查询到对象,然后删除
    # 返回重定向到展示出版社的页面
    return redirect('/publisher_list/')
  • 编辑出版社

在publisher_list.html文件加入编辑标签,通过get方式获取要编辑的出版社id号

<a href="/publisher_edit/?pk={{ i.id }}">编辑</a>

新创建一个publisher_edit.html文件,通过get方式提交的id号,找到出版社的名称在input标签显示

<body>
<form action="" method="post">
    出版社的名称:<input type="text" name="pub_name" value="{{ pub_obj.name }}"><span>{{ error }}</span>

    <button>提交</button>
</form>
</body>

在views.py文件中,新创建一个函数,然后通过form表单提交的post请求,在数据库中修改

# 编辑出版社
def publisher_edit(request):
    # 获取要修改数据的id
    pk = request.GET.get('pk')
    pub_obj = models.Publisher.objects.get(id = pk)

    if request.method == 'GET':
        # get 返回一个页面,页面包含form表单 input有原始数据
        return render(request,'publisher_edit.html',{'pub_obj':pub_obj})
    else:
        # 获取用户提交的出版社名称
        pub_name = request.POST.get('pub_name')
        if not pub_name:
            return render(request, 'publisher_edit.html', {'error':'出版社名称不能为空','pub_obj':pub_obj})
        if models.Publisher.objects.filter(name = pub_name):
            # 数据库中有重复的名字
            return render(request, 'publisher_edit.html',{'error':'出版社名称已存在','pub_obj':pub_obj})
        # post 修改数据库中对应的数据
        pub_obj.name = pub_name     # 只是在内存中修改
        pub_obj.save()              # 将修改提交到数据库汇中
        # 返回重定向到展示出版社页面
        return redirect('/publisher_list/')

 

设置外键

根据出版社,设置书籍数据库,关联出版社名称

在model.py文件中设置书籍数据库,然后执行迁移命令。

class Book(models.Model):
    name = models.CharField(max_length=32)
    publisher = models.ForeignKey('Publisher',on_delete=models.CASCADE)   # 外键,一对多关系,默认级联删除
    '''
    on_delete:  (2.0版本后必填)
        models.CASCADE                   级联删除
        models.PROTECT                   保护
        models.SET(v)                    删除后设置某一个值
        models.SET_DEFAULT(),default=1   删除后设置为默认值1
        models.SET_NULL()                删除后设置为Null
    '''

外键的操作

# 基于对象的查询
book_obj = Book.objects.get(pk=1)

# 正向查询
book_obj.publisher		# 所关联的对象
book_obj.publisher_id	# 所关联的对象的id

# 反向查询
pub_obj = Publisher.objects.get(pk=1)
# 不指定related_name 使用:类名小写_set
pub_obj.book_set 	# 关联管理对象
pub_obj.book_set.all()	# 关联所有的书籍对象
# 指定related_name='books'
pub_obj.books.all()

# 基于字段的查询
ret = Book.objects.filter(name='xxx')
ret = Book.objects.filter(pub_name='出版社')
ret = Publisher.objects.filter(name='出版社')
# 不指定related_name 使用:类名小写_set
ret = Book.objects.filter(book__name='xxx')
# 指定related_name='books'
ret = Book.objects.filter(books_name='xxx')
# 指定related_query_name='xxx'
ret = Book.objects.filter(xxx_name='xxx')

设置多对多关系

例如:创建作者与书籍的关系

class Author(models.Model):
    name = models.CharField(max_length=32)
    books = models.ManyToManyField('Book')  # 多对多关系,不在Author表中穿件字段,会创建第三章表

多对对的操作

author_obj.books	# 管理对象
# all 查询所有关联的对象
author_obj.books.all()

# set 设置关系
author_obj.books.set([1,2])

# add 新增关系 
author_obj.books.add(1,2)

# remove 删除关系
author_obj.books.remove(1,2)

# clear 清空关系
author_obj.books.clear(1,2)

# create 新增一个所关联的对象,并且和当前的对象绑定关系
author_obj.books.create(name='xxx',pud_id='1')

 

model常用的字段

AutoField

自增整形字段,必填参数primary_key=True,称为数据库的主键,无该字段,Django自动创建主键。注意:一个model不能有两个AutoField字段。

pid = models.AutoField(primary_key=True)    # 自创主键,

 

IntegerField

整数类型。数值范围是-2147483648 ~ 2147483647

 

CharField

字符类型,提供max_lenggth参数。

 

DateField

日期类型,日期格式为YYYY-MM-DD,相当于python中的datetime.date。

参数:

  • auto_now:新增和每次修改时自动保存为当前时间
  • auto_now_add:新创建对象是自动保存当前时间
  • default:默认值,没有写入数据用默认值填充

注意:auto_now和auto_now_add和default参数是互斥的,不能同时设置

 

DateTimeField

日期时间类型,格式为YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于python中的datetime.datetime。

    birth = models.DateTimeField(auto_now_add=True) # 新增数据时自动保存当前时间

自定义字段

自定义char类型字段

class MyCharField(models.Field):
    """
    自定义的char类型的字段类
    """
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super(MyCharField, self).__init__(max_length=max_length, *args, **kwargs)
 
    def db_type(self, connection):
        """
        限定生成数据库表的字段类型为char,长度为max_length指定的值
        """
        return 'char(%s)' % self.max_length

字段的参数

null		数据库可以为空,默认为False,True则表示可以为null
blank		用户输入可以为空,默认为False。
unique		唯一约束
verbose_name提示信息
choices		让用户选择的数据 choices=((1,'男'),(2,'女'))
default		默认值
db_column	列名

model必须会的13条

from app01 import  models

# all 查询所有数据,QuerySet对象列表
ret = models.Person.objects.all()

# get 获取一个有且唯一的数据对象,没有或多个就报错
ret = models.Person.objects.get(pid=1)

# filter 获取满足条件的数据对象列表
ret = models.Person.objects.filter(age=18)

# order_by 排序,默认升序 字段前加-号为降序,还可以多字段排序
ret = models.Person.objects.all().order_by('-age','-pid')

# reverse 对已经排序的对象列表进行翻转
ret = models.Person.objects.all().order_by('-age').reverse()

# values 获取数据所有的字段和值,还可以指定字段 [{},{}]
ret = models.Person.objects.all().values('pid','name')

# values_list 获取数据所有的值,还可以指定字段 [(),()]
ret = models.Person.objects.all().values_list('name','pid')

# distinct  去重
ret = models.Person.objects.all().distinct()

# count  计数
ret = models.Person.objects.all().count()

# first  获取第一个元素
ret = models.Person.objects.all().first()

# last  获取最后一个元素
ret = models.Person.objects.all().last()

# exists  判断条件是否有结果
ret = models.Person.objects.filter(age=20).exists()

# exclude  获取不满足条件的数据对象列表
ret = models.Person.objects.exclude(pk=1)

单表双下划线

from app01 import models
# 字段__条件 =    lt是less than小于的意思
ret = models.Person.objects.filter(pid__lt=5) 
# 字段__条件 =    gt是greater than大于的意思
ret = models.Person.objects.filter(pid__gt=5) 
# 字段__条件 =    lte是less than equal小于等于 的意思
ret = models.Person.objects.filter(pid__lte=5) 
# 字段__条件 =    gte是greater than equal大于等于 的意思
ret = models.Person.objects.filter(pid__gte=5) 

# 范围
ret = models.Person.objects.filter(pid__range=[1,3])    
# 成员判断
ret = models.Person.objects.filter(pid__in=[1,3])    
# 包含
ret = models.Person.objects.filter(name__contains='hp') 
# 包含,忽略大小写
ret = models.Person.objects.filter(name__icontains='hp')    

# 以什么开头
ret = models.Person.objects.filter(name__startswith='m')  
# 以什么开头,忽略大小写
ret = models.Person.objects.filter(name__istartswith='h')    
# 以什么结尾
ret = models.Person.objects.filter(name__endswith='p')    
# 以什么结尾,忽略大小写
ret = models.Person.objects.filter(name__iendswith='x')    

 # 查年份
ret = models.Person.objects.filter(birth__year='2018') 
# 查月份
ret = models.Person.objects.filter(birth__contains='-02-')    
# 是否为null
ret = models.Person.objects.filter(name__isnull=False)    

 

聚合和分组

from django.db.models import  Max, Min, Count, Sum, Avg


# 聚合 aggregate 终止子句
ret = models.Book.objects.all().aggregate(Max('price'))
ret = models.Book.objects.all().aggregate(Min('price'))


# 分组 group by
# annotate 注释   过程中使用了分组
# 统计每本书的作者个数
ret = models.Book.objects.annotate(Count('authors')).values()

# 统计出每个出版社卖的最便宜的书的价格
# 方法一
ret = models.Publisher.objects.annotate(Min('books__price')).values()
# for i in ret:
#     print(i)

# 方法二
# 按照 pub_id 与 pub_name 分组
ret = models.Book.objects.values('pub','pub__name').annotate(Min('price'))
# for i in ret:
#     print(i)


# 统计不止一个作者的图书
ret = models.Book.objects.annotate(count = Count('authors')).filter(count__gt=0)
for i in ret:
    print(i)

 

F与Q查询

from django.db.models import F,Q

# F 用法
# 1.获得该字段与其他字段进行对比
ret = models.Book.objects.filter(sale__gt=F('kucun'))

# 2.获得该字段数值进行计算
ret = models.Book.objects.filter(id__lt=3).update(sale=F('sale')*2 + 13)
# print(ret)


# Q 用法
# |    或
# &    与
# ~    非
ret = models.Book.objects.filter(Q(id__lt=3)|Q(id__gt=5))

ret = models.Book.objects.filter((Q(id__lt=3)|Q(id__gt=5))&~Q(name__startswith='p'))
print(ret)

 

事务

from django.db.models import F
from django.db import transaction

try:
    # 事务
    with transaction.atomic():
        # 一系列操作
        models.Book.objects.all().update(kucun=F('kucun')-10)
        models.Book.objects.all().update(sale=F('sale')+10)
except Exception as e:
    print(e)

 

 

Django模板

Django模板中有两种特殊符号:

{{ }}{% %}

{{ }}:表示变量,在模板渲染的时候换成对应的值

{% %}:表示逻辑相关的操作

变量

{{ 变量名 }}

变量名由字母数字和下划线组成。

在模板中点(.)有特殊的含义,用来获取对象的对应属性值。

{{ 变量名.索引 }}
{{ 变量名.key }}
{{ 变量名.属性 }}
{{ 变量名.方法 }} 
ps:方法后不加括号,模板会自动识别

模板点(.)的优先级

字典查询 > 属性或方法 > 数字索引

Filters过滤器

Filters过滤器用来修改变量的显示结果

语法:{{ 变量名|filter_name:参数 }}

注意:':'左右两边没有空格

 

filter_name常见的值

default默认值

value值没有传值的情况下默认显示nothing

{{ value|default:"nothing"}}

 

filesizeformat

将值格式化为”人类可读“文件尺寸(例如:'13KB','4.1MB'等)

{{ value|filesizeformat}}

如果value是1024,输出为1KB

 

add

数字加法或字符串和列表拼接

给变量加参数

{{ value|add:"2"}}

如果value值是数字4,则输出结果为6

{{ value|add:second}}

如果value值是[1,2,3],second值为[4,5,6],则输出结果[1,2,3,4,5,6]

 

lower

小写

{{ value|lower}}

 

upper

大写

{{ value|upper}}

 

title

标题

{{ value|title}}

 

length

返回变量的长度

{{ value|length}}

 

slice

切片

{{ value|slice:"2:-1"}}

 

first

取第一个元素

{{ value|first}}

 

last

取最后一个元素

{{ value|last}}

 

join

用字符串拼接列表,与python的str.join(list)相同

{{ value|join:"//"}}

 

truncatechars

如果字符串的字符多与指导的字符数量,会被截断,截断的字符串将以省略号(...)结尾

{{ value|truncatechars:"9"}}

 

date

日期格式化

{{ value|date:"Y-m-d H:i:s"}}

也可以修改settings.py

USE_L10N = False
DATETIME_FORMAT = 'Y-m-d H:i:s'
TIME_FORMAT = 'H:i:s'
DATE_FORMAT = 'Y-m-d'

 

自定义过滤器

  1. 在app下创建一个名为templatetags的python包(文件包名必须是templatetags)
  2. 创建一个python文件,文件名自定义(mytags.py)
  3. 在python文件中写入
from django import template

register = template.Library()	# 变量名必须是register
  1. 写函数加上装饰器

filter(只能接收一个参数)

@register.filter
def add_arg(value, arg):
    # 功能,字符拼接
    return "{}_{}".format(value, arg)

simple_tage(可以接收多个参数)

@register.simple_tage
def str_join(*args,**kwargs):
    return "{}_{}".format('_'.join(arg),'*'.join(kwargs.values()))

inclusion_tage(接收文件)

@register.inclusion_tag('page.html')
def pagination(num):
    return {'num':range(1,num+1)}

page.html文件

<nav aria-label="Page navigation">
  <ul class="pagination">
    <li>
        <a href="#" aria-label="Previous">
            <span aria-hidden="true">&laquo;</span>
        </a>
    </li>
        {% for page in num%}
            <li><a href="#">{{ page }}</a></li>
        {% endfor %}
    <li>
        <a href="#" aria-label="Next">
            <span aria-hidden="true">&raquo;</span>
        </a>
    </li>
  </ul>
</nav>
  1. 模板引用模块

filter

{% load mytags %}	导入模块
{{ value|add_arg:"111" }}	引用

simple_tage

{% load mytags %}
{% str_join 'a' 'b' 'c' k1='d' k2='e' %} 

inclusion_tag

{% load mytags %}
{% pagination 3 %}

 

逻辑操作

for循环

<ul>
    {%  for user in user_list %}
	<li>{{forloop.counter}}&nbsp{{ user }}</li>
    {% empty %}
    	Null(循环没结果才显示)	
	{% endfor %}
</ul>

for循环先关变量

变量解析
forloop.counter 当前循环的序号 从1开始
forloop.counter0 当前循环的序号 从0开始
forloop.revcounter 当前循环的序号(倒序) 从1结束
forloop.revcounter0 当前循环的序号(倒序) 从0结束
forloop.first 是否是第一次循环(返回布尔值)
forloop.last 是否是最后一次循环(返回布尔值)
forloop.parentloop 当前循环的外层循环的变量

 

if elif 和 else

{% if user_list %}
	用户认识:{{ user_list|length}}
{% elif %}
	黑名单数:{{ black_list|length }}
{% else %}
	没有用户
{% endif %}

if语句支持and、or、==、>、<、!=、<=、>=、in、not in、is、is not判断。

if语句不支持运算和连续判断

 

with

定义一个中间变量

{% with name=user_list.name %}
	{{ name }}
{% endwith %}

 

csrf_token

用于跨站伪造保护

<form action="" method="post">
    {% csrf_token %}
    <input type="text" name="k1">
    <button>提交</button>
</form>

 

母版

一个包含多个页面的公共部分

定义多个block块,让子页面进行覆盖

继承

继承母版

{% extends '母板名字' %}	

重写block块

{% block 块的变量 %}
	与母版不同的部分
{% endblock %}

注意:

  1. {% extends '母板名字' %} 母版名字要带引号
  2. {% extends '母板名字' %} 写在第一行,上面不再写内容
  3. 要显示的内容写在block块中

 

组件

把共用的HTML文本写入一个HTML文件,nev.html

在需要该组件模板中添加

{% include 'nev.html' %}

 

静态文件

如果修改settings.py文件下的STATIC_URL = '/static/',那么其他文件的link标签内的css与js都需要修改,为了防止link标签写死路径可以通过{% load static %}

{% load static %}
<link rel="stylesheet" href="{% static '/plugins/bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static '/css/bsd.css' %}">

 

展示数据

给模板一个querySet对象列表,循环出对象obj

  1. 普通字段

    obj.字段名 ---> 数据库中的数据

  2. 外键

    obj.外键 ---> 外键对象,给类定义__str__方法展示

    obj.外键.字段名

  3. 带choices参数的字段

    obj.字段名 ---> 数据库中的数据

    obj.get_字段名_display() 显示choices定义好的结果

  4. 自定方法

    在该数据库对应的类下定义新的方法

    from django.utils.safestring import mark_safe
    # mark_safe 自定义html标签在游览器中显示
    def show_publish_status(self):    
        color_dict = {True:'green',False:'#c35353'}    
        return mark_safe('<span style="background-color: {};color: white;padding: 3px">{}</span>'.format(color_dict[self.publish_status],self.get_publish_status_display()))
    

    在html文件直接obj.该方法的名字

    {% obj.show_publish_status %}
    

     

Django的View(视图)

一个视图函数(类),简称视图,是一个简单的Python函数(类),它接收Web请求并返回Web响应。

  • FBV(function based view),函数书写
  • CBV(class based view),类书写

views.py文件做逻辑处理

from django.views import View

class Xxx(View):
    def get(self,request):
    	# 专门处理get请求
        return response
    
    def post(self,request):
    	# 专门处理post请求
        return response

urls.py文件修改

url(r'/xx',xxx.as_view())

as_view()的流程

  • 项目运行时加载urls.py的文件,执行类 .as_view()方式

  • as_view()执行之后,内部定义了一个view函数,并返回。

  • 请求到来的时候执行view函数:

    • 实例化类 -----> self

    • self.request = request

    • 执行self.dispatch(request, *args, **kwargs)方法

      • 判断请求方式是否允许

        1. 允许:

          通过反射获取对应请求方法 ----> handler

          获取不到 self.http_method_not_allowed ---> handler

        2. 不允许:

          self.http_method_not_allowed ---> handler

      • 执行handler,返回结果

 

CVB加装饰器

需要使用一个装饰器方法

from django.utils.decorators import method_decorator
  1. 加载方法上
@method_decorator(自己写的装饰器)
def get(self, request):
    ...
  1. 加载dispatch方法上(注意不要修改源码,另外写dispatch方法)
@method_decorator(timer)
    def dispatch(self, request, *args, **kwargs):
        # 操作之前
        ret = super().dispatch(request, *args, **kwargs)    # 执行View中的dispatch方法
        # 之后的操作
        return ret
# 或者
# 不另外写dispatch方法,直接在加载类上
@method_decorator(timer,name='dispatch')
class PublisherAdd(View):
  1. 加载在类上
@method_decorator(timer,name='get')
@method_decorator(timer,name='post')
class PublisherAdd(View):
    ......

 

request方法

request.method			# 请求方法GET,POST
request.GET				# URL携带参数
request.PSOT			# POST请求提交的数据,编码方式URLencode
request.path_info		# 路径信息,不包含IP和端口,也不包含参数
request.get_full_path	# 完整路径信息,不包含IP和端口,包含参数
request.body			# 请求体
request.COOKIES			# cookie信息
request.session			# session信息
request.FILES			# 长传的文件
request.META			# 头的信息
request.is_ajax()		# 请求是否是ajax请求

 

上传文件

html文件

<form action="" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <input type="file" name="f1">
    <button>上传</button>
</form>

注意:修改上传文件的编码 enctype="multipart/form-data"

python文件

class Upload(View):
    def get(self,request):
        return render(request,'upload.html')

    def post(self,request):
        # print(request.POST)
        # print(request.FILES)

        file = request.FILES.get('f1')
        print(file)
        return HttpResponse('ok')

通过request.FILES获得文件的对象,通过get方法获取文件名

 

Django的路由系统

URL的配置,类似于书本的目录,本质是URL调用的视图函数之间的映射表。

基本格式

Django 1.1版本的格式

from django.conf.urls import url

urlpatterns = [
    url(正则表达式, views视图, 参数, 别名),
]

例子:

from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^upload/',views.Upload.as_view()),
    url(r'^index/',views.index),
]

 

正则表达式

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^blog/$', views.blog),
    url(r'^blog/[0-9]{4}/\d{2}/$', views.blogs),
]

^			以什么开头
$			以什么结尾
\d			数字
\w			数字字母下划线
{}
[a-z0-9]
.			匹配换行符之外的标志
+			一个或多个
?			0个或1个
*			0个或多个

注意

# 是否开启URL访问地址后面不为/跳转至带有/的路径的配置项
APPEND_SLASH=True

 

分组

url(r'^blog/([0-9]{4})/\d{2}/$', views.blogs),
url地址上捕获的参数会按照位置传参方式传递给视图函数
	....
def blogs(request,year):
    ...

命名分组

 url(r'^blog/(?P<year>[0-9]{4})/(?P<month>\d{2})/$', views.blogs),
url地址上捕获的参数会按照关键字传参方式传递给视图函数
	....
def blogs(request,year,month):
    ...

注意:分组和命名分组不能混用

 

路由分发

当项目越做越大的时候,urls.py的url目录会越来越多,当修改url的时候,可能不小心删除别人写的url地址,导致项目出现问题,为了避免这些问题进行路由分发。

  • 在项目目录(app01)创建urls.py文件,写上该项目的url
from django.conf.urls import url
from app01 import views

urlpatterns = [
    url(r'^upload/',views.Upload.as_view()),
    url(r'^index/',views.index),
]
  • 根目录下的urls.py,通过include包含项目目录的url
from django.conf.urls import url,include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^blog/',include('app01.urls')),
]

 

url的命名和方向解析

如果修改urls.py文件下的url,该url对应的HTML文件里面的相关的a标签的url也要进行修改,为了避免这些问题,给url进行命名与反向解析。

第一种:静态路由

url命名

url(r'^index/',views.index,name='index'),	

反向解析

模板:

<a href="{% url 'index' %}"></a>

views.py文件中:

# 导入reverse
from django.shortcuts import render,reverse
#或者
from django.urls import reverse

def index(request,*args,**kwargs):
    ....
    url = reverse('index')	# /app01/index/
    ....

 

第二种

分组url命名

 url(r'^blog/([0-9]{4})/(\d{2})/$', views.blogs,name='blogs'),

命名分组url命名

 url(r'^blog/(?P<year>[0-9]{4})/(?P<month>\d{2})/$', views.blogs,name='blogs'),

反向解析

模板:

分组:
<a href="{% url 'blogs' '2020' '06' %}"></a>
命名分组:
<a href="{% url 'blogs' year='2020' month='06' %}"></a>
对应的地址--->/app01/blog/2020/02/ 

views.py文件中:

命名分组和分组用法相同,但命名分组还可以用字典

# 导入reverse
from django.shortcuts import render,reverse
#或者
from django.urls import reverse

def blog(request,*args,**kwargs):
    ....
    url= reverse('blogs',args=('2020','06'))	# /app01/blog/2020/02/ 
    ....
    url= reverse('blogs',kwargs={'year':'2020','month':'06'})	# /app01/blog/2020/02/ 

 

namespace

如果两个项目的url的name命名相同,进行反向解析的时候,下面的url会把上面的url进行覆盖。

  • 根目录的urls.py命名
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^app01/',include('app01.urls',namespace='app01')),
    url(r'^app02/',include('app02.urls',namespace='app02')),
]
  • 反向解析

html文件

<a href="{% url 'app01:blogs'%}"></a>	/app01/blog
<a href="{% url 'app02:blogs'%}"></a>
/app02/blogs

 

Cookie

保存在浏览器本地上的一组键值对

特性:

  1. 由服务器让游览器进行设置的
  2. cookie信息保存在游览器本地的,游览器有权不保存
  3. 游览器再次访问时自动携带对应的cookie

Django中操作cookie

通过装饰器检测是否有cookie值 来判断是否登录成功

from functools import wraps

# 检测登陆状态装饰器
def login_required(func):
    @ wraps(func)
    def inner(request,*args,**kwargs):
        print(request.COOKIES)
        # 获取cookie信息:request.COOKIES
        # is_login = request.COOKIES.get('is_login')
        is_login = request.get_signed_cookie('is_login',salt='12355',default='')
        if is_login != '1':
            # 没有登陆
            return redirect('/login/?url={}'.format(request.path_info))	# 登录后跳转到上一个页面
        ret = func(request,*args,**kwargs)
        return ret
    return inner

# 登陆功能
def login(request):
    error = ''
    if request.method == 'POST':
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        if user == 'hp' and pwd == '123':
            # 登陆成功后保存登陆状态到cookie中
            # 判断是否有url(url为要访问页面的地址,检测没登录,跳转到登录页面,登录成功后跳转到要访问的页面
            url = request.GET.get('url')
            if url:
                return_url = url
            else:
                return_url = reverse('publisher')

            ret = redirect(return_url)
            ret.set_cookie('is_login','1')
        	ret.set_signed_cookie('is_login','1',salt='12355')
            return ret
        else:
            error = '用户名或密码错误'
    return render(request,'login.html', {'error': error})

由上面代码总结:

# 设置cookie
response.set_cookie(key,value)	# 普通cookie	# 响应头:Set-Cookie: is_login=1
response.set_signed_cookie('key',value,salt='加盐')	# 加密cookie

# 获取cookie
request.COOKIES	# 等到的是cookie字典		# 请求头:Cookie: is_login=1
request.get_signed_cookie(key,salt='加盐',default='')	# 解密cookie,default为默认值,如果获取不到给默认值

设置cookie的参数

参数解释
key
value=''
max_age=None 超时时间
expires=None 超时时间
path='/' Cookie生效的路径,/表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
domain=None Cookie生效的域名
secure=False https传输
httponly=False 只能http协议传输,无法JavaScript获取(不是绝对,底层抓包可以获取也可以被覆盖)

删除cookie

# 本质是设置cookie值为空 超时时间为0
response.delete_cookie(key)

 

Session

保存在服务器上的一组组键值对,依赖于cookie使用。

为什么要用session

  1. cookie是保存在游览器本地,不太安全
  2. 游览器会对cookie的大小和个数有一定限制

Django中操作session

# 设置 session
request.session[key] = value

# 获取
request.session.get(key)
request.session[key]

# 删除
del request.session[key]
request.session.pop(key)

# 会话session的key
request.session.session_key

# 将所有session失效日期小于当天日期的数据删除
request.session.clear_expired()

# 检测会话session的key在数据库中是否存在
request.session.existe("session_key")

# 删除当前用户的所有的session数据
request.session.delete()	# 不删除cookie
request.session.flush()		# 删除cookie

# 设置会话session和cookie的超时时间
request.session.set_expiry(value)
	1. value是整数,session会在些秒后失效
    2. value是datatime或者timedelta,session就会在这个时间后失效
    3. value是0,用户关闭游览器session就会失效
    4. value是None,session会依赖全局session失效策略

Django中配置session

在settings.py文件配置session

from django.conf import global_settings

SESSION_COOKIE_NAME = 'sessionid'	# session的键名
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2	# 默认超时时间
SESSION_SAVE_EVERY_REQUEST = True	# 每次请求后更新超时时间
SESSION_EXPIRE_AT_BROWSER_CLOSE = True	# 关闭游览器后cookie失效
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # session存储位置
# 默认是存储在数据库中,还可以是:缓存、缓存+数据库、文件、加密cookie

 

中间件

中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插拔系统,用于全局范围内改变Django的输入输出。

简单的说,就是在视图函数执行之前和执行之后都做一些额外的操作,它本质是自定义类,类定义了5个方法,特定时执行这些方法。

5个方法:

  • process_request(self,request)
  • process_view(self, request, view_func, view_args, view_kwargs)
  • process_template_response(self, reuqest, response)
  • process_exception(self, request, exception)
  • process_response(self, request, response)

4个特征:执行时间,执行顺序,参数,返回值

 

5个方法

process_request(self,request)

process_request(self,request)	# 执行请求
# 执行时间:
	视图函数之前
# 参数:
	request请求的对象和视图函数是同一个对象
# 执行顺序:
	按照注册顺序,顺序执行
# 返回值:
	None--正常流程
    HTTPresponse--后面process_request方法、路由匹配、视图函数都不执行,直接执行当前中间的process_response方法

process_response(self, request, response)

process_response(self, request, response)	# 执行响应
# 执行时间:
	视图函数之后
# 参数:
	request请求的对象和视图函数是同一个对象
    response 响应对象
# 执行顺序:
	按照注册顺序,倒序执行
# 返回值:
	HTTPresponse必须返回

process_view(self, request, view_func, view_args, view_kwargs)

process_view(self, request, view_func, view_args, view_kwargs)		# 执行视图
# 执行时间:
	路由匹配之后,视图函数之前
# 参数:
	request请求的对象和视图函数是同一个对象
    view_func 视图函数
    view_args 视图函数的位置参数
    view_kwargs 视图函数关键字参数
# 执行顺序:
	按照注册顺序,顺序执行
# 返回值:
	None 正常执行
	HTTPresponse之后中间件的process_view、视图都不执行,执行最后的process_response方法

process_exception(self, request, exception)

process_exception(self, request, exception)		# 执行异常

# 执行时间:
	视图函数有异常才执行(触发条件)
# 参数:
	request请求的对象和视图函数是同一个对象
    exception 异常对象
# 执行顺序:
	按照注册顺序,倒序执行
# 返回值:
	None 当前中间件没有处理异常,交个下一个中间件处理,如果都没处理,django处理异常。 
	HTTPresponse 当前中间件处理了异常,后面的中间件的process_exception就不执行,执行最后一个中间件的process_response方法

process_template_response(self, reuqest, response)

process_template_response(self, reuqest, response)
# 执行时间:
	视图函数返回的对象是TemplateResponse对象(触发条件)
# 参数:
	request请求的对象和视图函数是同一个对象
    response TemplateResponse的对象
# 执行顺序:
	按照注册顺序,倒序执行
# 返回值: 
	HTTPresponse 返回的是TemplateResponse对象
    过程处理模板的名字、参数
    response.template_name='index1.html'
    response.context_data['user']='sj'

 

自定义中间键

  1. 在项目目录下(app01)创建一个文件夹名为middlewares,在该文件夹下创建(文件名).py的文件。

在该(文件名).py文件下定义中间键

from django.utils.deprecation import MiddlewareMixin	# 导入创建中间键需要的包
# 通过类的继承,从新写中间键的5个方法
class MD1(MiddlewareMixin):
    def process_request(self, request):
        # print(id(request))
        print('MD1 process_request')
        # return HttpResponse('MD1 process_request')

    def process_response(self, request, response):
        print('MD1 process_response')
        # print('index', id(response))
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        # print(view_func)
        # print(view_args)
        # print(view_kwargs)
        print('MD1 process_view')
        # return HttpResponse('MD1 process_view')

    def process_exception(self, request, exception):
        print('MD1 process_exception')
        print(exception)

    def process_template_response(self, reuqest, response):
        print('MD1 process_template_response')
        response.template_name = 'index1.html'
        response.context_data['user'] = 'sj'
        return response
  1. 在setting文件中导入自定义的中间件
MIDDLEWARE = [
	......
    'app01.middlewares.my_middleware.MD1',
]

例子:限制访问频率

自定义限制访问频率的中间件

第一种写法:

from django.utils.deprecation import MiddlewareMixin	# 导入创建中间键需要的包
import time

visit_history = {
    # ip: [ time,time,time ]     # 存放ip的访问时间的列表
}

class Throttle(MiddlewareMixin):

    def process_request(self,request):
        # print('Throttle')
        # print(request.path_info)
        ip = request.META.get('REMOTE_ADDR')  # 获取用户的ip

        history = visit_history.get(ip,[])  # 第一次获取ip的列表为空列表
        # print(history)
        # 第一次 history 为空         []
        # 第二次 history 有一个时间戳  [时间戳]
        # 第三次 history 有两个时间戳  [时间戳,时间戳]
        # 第四次 history 有三个时间戳  [时间戳,时间戳,时间戳]
        # 第五次(超过5秒内访问) history 为空  []

        now = time.time()   # 记录开始访问的时间
        new_history = []    # 定义一个新列表空列表存放ip的访问时间

        for i in history:
            if now - i < 5: # 判断访问是否在5秒内
                new_history.append(i)   # 如果是5秒内则把这个时间戳存放带列表里

        visit_history[ip] = new_history # 记录当前的ip
        if len(new_history) >= 3:   # 判断记录ip的时间是否大于等于3
            return HttpResponse('频繁访问,请歇息一下')	# 等于三则会返回,不会往下执行
        new_history.append(now)    # 加上当前的ip访问时间到新列表内

第二种写法

from django.utils.deprecation import MiddlewareMixin	# 导入创建中间键需要的包
import time

visit_history = {
    # ip: [ time,time,time ]     # 存放ip的访问时间的列表
}
class Throttle(MiddlewareMixin):

    def process_request(self,request):
        # print('Throttle')
        # print(request.path_info)
        ip = request.META.get('REMOTE_ADDR')  # 获取用户的ip

        history = visit_history.get(ip,[])  # 第一次获取ip的列表为空列表

        now = time.time()   # 记录开始访问的时间

        # 判断是否有时间戳,以及最新访问的时间戳减去最前的时间戳是否在5秒内
        while history and now - history[-1] > 5:	
            history.pop()	# 如果不在5秒内删除最前的时间戳


        if len(history) >= 3:   # 判断记录ip的时间是否大于等于3
            return HttpResponse('频繁访问,请歇息一下')   # 等于三则会返回,不会往下执行

        history.insert(0,now)	# 插入最新访问的时间戳到索引为0的位置上
        visit_history[ip] = history # 更新ip的时间戳列表

 

在settings.py文件加入该中间件的路径

MIDDLEWARE = [
    'app01.middlewares.my_middleware.Throttle',
	......
    'app01.middlewares.my_middleware.MD1',
]

 

AJAX介绍

AJAX(Asynchronous JavaScript and XML)就是“异步的Javascrip 和 XML”。即使用Javascrip与服务器进行异步交互,传输的数据为XML。

AJAX不是新的编程语言,而是一种使用现有标准的新方法。

AJAX最大的优点就是不重新加载整个页面的情况下可以与服务器交换数据并更新部分的网页内容

AJAX不需要任何游览器插件,但是需要用户允许JavaScript在游览器上执行。

 

同步交互与异步交互:

  • 同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求。
  • 异步交互:客户端发出一个请求后,不需要服务器响应结束,就可以发出第二个请求。

 

AJAX使用

html页面的书写,需导入jquery.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<input type="text" name="i1">+
<input type="text" name="i2">=
<input type="text" name="i3">
<button id="b1">计算</button>
    
<!-- 导入jquery.js -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.js"></script>

<!-- AJAX书写 -->
<script>
    $('#b1').click(function () {

        $.ajax({
            url:'/calc/',   // ajax要访问的地址
            type:'get',     // 请求方式
            data:{          // 发送的数据
                'x1': $('[name="i1"]').val(),  // 获取input框name="i1"的值
                'x2': $('[name="i2"]').val(),   // 获取input框name="i2"的值
            },
            success:function (data) {       // 回调函数,data响应体
                $('[name="i3"]').val(data)
            }
        });
    });
</script>

    
</body>
</html>

 

views.py文件的书写

def calc(request):
    x1 = request.GET.get('x1')
    x2 = request.GET.get('x2')
    print(x1)
    print(x2)
    x3 = int(x1) + int(x2)

    return HttpResponse(x3)

 

AJAX传收参数

HTML文件书写

<script>
$('#b2').click(function () {

        $.ajax({
            url:'/test/',   // ajax要访问的地址
            type:'get',     // 访问的类型
            data:{          // 发送的数据
                name: 'hp',
                age: '18',
                // 第一种书写方式
                hobby: ['阅读','学习','看视频']
                // 第二种书写方式
                hobby: JSON.stringify(['阅读','学习','看视频']) //通过JSON序列化
            },
            success:function (data) {       // 回调函数,data响应体
    			//alert(111)	// 弹窗显示111
                console.log(data)	// 打印接收的返回值对象		
            }
        });
    });
</script>

数据的发送

data:{     // 发送的数据
	name: 'hp',
    age: '18',
    // 第一种书写方式
     hobby: ['阅读','学习','看视频']
     // 第二种书写方式
     hobby: JSON.stringify(['阅读','学习','看视频']) //通过JSON序列化
},

服务端接收到的数据为

第一种方式:
<QueryDict: {'name': ['hp'], 'age': ['18'], 'hobby[]': ['阅读', '学习', '看视频']}>
    
'name'与'age'都可以直接接收
name = request.GET.get('name')
age = request.GET.get('age')

hobby通过变形成'hobby[]',需通过'hobby[]'获取
hobby = request.GET.getlist('hobby[]')

第二种方式:
<QueryDict: {'name': ['hp'], 'age': ['18'], 'hobby': ['["阅读","学习","看视频"]']}>

hobby 为json格式,需要通过反序列化获取
import json
hobby = json.loads(request.GET.get('hobby'))
print(hobby)

返回数据

from django.http.response import JsonResponse

def test(request):
    ...
    return JsonResponse({'status': 200,'msg':'ok','error':''}) # 返回json格式的对象

 

AJAX上传文件

html文件书写

{% csrf_token %}
<input type="file" id="f1">
<button id="b1">上传</button>

<!-- 导入jquery.js -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.js"></script>


<!-- AJAX书写 -->
<script>
    $('#b1').click(function () {

        var formData = new FormData();      // 该方式接收的文件默认编码为multipart/form-data
        formData.append('name','hp');   // 第一组键值对
        formData.append('age','18');   // 第二组键值对
        formData.append('f1',$('#f1')[0].files[0]); // 第三组键值对,上传的文件

        $.ajax({
            url: '/upload/',
            type: 'post',
            data: formData,
            processData: false, // ajax不处理数据编码,不加这个,会默认进行编码
            contentType: false,  // 不修改content_type请求头,不加这个,会默认进行编码
            success: function (data) {
                alert(data)
            }
        })
    });
</script>

views.py文件书写

def upload(request):
    if request.method == 'POST':
        print(request.POST)
        print(request.FILES)
        f1 = request.FILES.get('f1')
        with open(f1.name,'wb') as f:
            for i in f1:
                f.write(i)

        return HttpResponse('ok')
    return render(request, 'upload.html')

 

AJAX通过csrf校验

from django.views.decorators.csrf import csrf_exempt, csrf_protect, ensure_csrf_cookie

csrf_exempt:装饰器加在视图上,该视图不需要进行csrf校验
csrf_protect:装饰器加在视图上,该视图必须进行csrf校验
ensure_csrf_cookie:装饰器加在视图上,设置csrftoken的cookie

csrf的校验

csrf中间件的流程:

  • process_request

    从cookie中获取csrftoken的值,将该值放入request.META中

  • process_view

    • 判断视图是否使用csrf_exempt的装饰器,使用就不在进行csrf校验

    • 判断请求方式是否是get/head/potions/trace

      • 如果是,直接接收该请求不进行csrf校验

      • 如果不是,需进行csrf校验

        1. csrf_token = request.META.get('CSRF_COOKIE')从cookie中获取csrftoken的值

        2. request_csrf_token = ""

        3. 请求是POST,尝试从request.POST中获取csrfmiddlewaretoken的值

          request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
          
        4. 如果POST获取不到request_csrf_token = ""还是为空,再从请求头中获取X-CSRFTOKEN的值

          request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '')
          
        5. 比较csrf_token和request_csrf_token的值

          1. 比较成功通过校验
          2. 比较失败,拒绝请求

总结:

1.从cookie中获取csrftoken的值
2.从request.POST中获取csrfmiddlewaretoken的值,或者从请求头中获取x-csrftoken的值
3.把这两个值进行对比,如果对比成功接收请求,否则拒绝

csrftoken的cookie添加

  1. 在模板加入{% csrf_token %}
  2. 使用ensure_csrf_cookie装饰器,加载在视图上

 

AJAX通过django的csrf的校验

前提:必须有csrftoken的cookie

  1. 给data添加csrfmiddlewaretoken键值对(需加入{% csrf_token %})
        var formData = new FormData(); 
        formData.append('f1',$('#f1')[0].files[0]);
		formData.append('csrfmiddlewaretoken',$('[name="csrfmiddlewaretoken"]').val());

        $.ajax({
            url: '/upload/',
            type: 'post',
            data: formData,
            processData: false,
            contentType: false,
            success: function (data) {
                alert(data)
            }
        })
  1. 给headers添加x-csrftoken的键值对(或者导入文件的方式)

方式一:直接添加csrftoken

.ajax({
	url:'/calc/',   // ajax要访问的地址
    type:'post',     // 访问的类型
    header: {
    	'x-csrftoken':$('[name="csrfmiddlewaretoken"]').val()
    }
    data:{},
    success:function (data) {}
});

方式二:导入文件

在static文件夹下创建一个ajax_setup.js文件

function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');


function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
  beforeSend: function (xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader("X-CSRFToken", csrftoken);
    }
  }
});

在模板中导入该文件

<script src="/static/ajax_setup.js"></script>

 

 

form组件

form组件定义

from django import forms

class RegForm(forms.Form):
    user = forms.CharField(label='用户名',min_length=6)
    pwd = forms.CharField(label='密码')

使用

def reg2(request):
    form_obj = RegForm()
    if request.method == 'POST':
        # 对提交的数据做校验
        # 从新实例化组件,获取提交的内容
        form_obj = RegForm(request.POST)    
        # is_valid()对form表单内设置的校验规则进行校验,校验成功返回True
        if form_obj.is_valid(): 
            # 校验成功
            return HttpResponse('注册成功')
        
    # form_obj包含了提交的数据,也包含了校验信息
    return render(request,'reg2.html',{'form_obj':form_obj})   

模板显示

<form action="" method="post">
    {% csrf_token %}

	{#{{ form_obj.as_p }}#}

    <p>
        <label for="{{ form_obj.user.id_for_label }}">{{ form_obj.user.label }}</label>
        {{ form_obj.user }}
        <span>{{ form_obj.user.errors.0 }}</span>
    </p>
    
    <p>
        <label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label>
        {{ form_obj.pwd }}
        <span>{{ form_obj.pwd.errors.0 }}</span>
    </p>

	{#{{ form_obj.errors }}#}
    <button>注册</button>
</form>
{{ form_obj.as_p }}			一次性生成所有input框

{{ form_obj.user }}		 		该字段的input框
{{ form_obj.user.label }}		该字段的提示信息
{{ form_obj.user.id_for_label }} 该字段的input框的id

{{ form_obj.user.errors }}		该字段的所有错误
{{ form_obj.user.errors.0 }}	该字段的第一个错误
{{ form_obj.errors }}		所有字段的错误信息

 

常用的字段

CharField 	文本输入框
ChoiceField		单选框	默认是select(下拉框)
MultipleChoiceField		多选框 默认是select(下拉框)

字段参数

# 提示
label='用户名'			   

# 用户选择的数据
choices=(('1','男'),('2','女'),('3','不详'))

 # 默认值
initial="请输入用户名"

# 自定义错误信息
error_messages={
    "required":"不能为空",
    "invalid":"格式错误",
    "min_length":"用户名最短6位"}

# 插件 修改input框类型
widget=forms.PasswordInput	# 密文
widget=forms.RadioSelect	# 单选
widget=forms.CheckboxSelectMultiple	# 多选

 # 是否允许为空
required=True

# 是否可以编辑
disabled=False             

 

校验

校验器

  1. 定义校验函数

    from django.core.exceptions import ValidationError
    def checke_name(value):
        # 不符合校验规则
        if 'hp' in value:
            raise ValidationError('hp不符合校验规则')
        # 符合校验规则不做任何操作
    
    ....
    user = forms.CharField(
        	...
            validators=[checke_name],   # 校验器
           )
    
  2. 使用内置的校验器

    from django.core.validators import RegexValidator
    
    ......
        phone = forms.CharField(validators=[RegexValidator(r'1[3-9]\d{9}$','手机格式不正确')])
    
    

 

钩子函数

  1. 局部钩子

    # 一定要写在form表单的类以内
    class RegForm(forms.Form):
        ...
        def clean_user(self):
            value = self.cleaned_data.get('user')
            # 不符合校验规则
            if 'hp' in value:
                raise ValidationError('hp不符合校验规则')
            # 符合校验规则 返回该字段的值
            return value
    

     

  2. 全局钩子

    # 一定要写在form表单的类以内
    class RegForm(forms.Form):
        ......
    	# 全局钩子
        def clean(self):
            # 不符合校验规则
            pwd = self.cleaned_data.get('pwd')
            re_pwd = self.cleaned_data.get('re_pwd')
            if pwd != re_pwd:
                # 将错误信息加入到某种字段中
                self.add_error('re_pwd','两次密码不一致')
                raise ValidationError('两次密码不一致')
            # 符合校验规则 返回所有字段的值
            return self.cleaned_data
    

     

 

 

定义modelform

from django import forms
"""
继承ModelForm
通过modles数据库中的字段生成对应的form表单
"""
class RegForm(forms.ModelForm):
    class Meta:
        model = models.User
        # fields = ['username','password']
        fields = '__all__'    # __all__ :models中所有字段
        exclude = ['last_time'] # 排除last_time字段
        labels = {	# 修改提示信息
            'username': '用户名:',
            'password': '密码:',
        }
        widgets = {		# 插件
            'password': forms.PasswordInput,
        }
        error_messages = {	# 错误提示信息
            'username':{
                'required': '必填项',
            }
        }

 

上传头像

在models.py文件中的用户表中加入头像字段

avatar = models.ImageField(upload_to='img/avatar',default='img/avatar/default.jpg')

ImageField 依赖pillow模块,需要下载

pip install pillow

在根目录创建一个media文件夹,然后在该文件夹下创建img文件夹,再在img文件夹下创建avatar文件夹

上传图片路径

 

在settings.py配置头像路径

MEDIA_URL = '/media/'

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

配置urls.py

from django.views.static import serve
from django.conf import settings

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include('app01.urls')),
    url(r'^media/(?P<path>.*)', serve,{'document_root': settings.MEDIA_ROOT}),
]

 

 

使用django-ckeditor(富文本编辑器)

下载django-ckeditor模块

pip install django-ckeditor

注册app,在settings.py文件在写入

INSTALLED_APPS = [
	....
    'ckeditor',
    'ckeditor_uploader',
]

配置urls.py文件

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    ......
    url(r'^ckeditor/', include('ckeditor_uploader.urls')),
]

配置models.py

# 引入富文本编辑器
from ckeditor_uploader.fields import RichTextUploadingField
class ArticleDetail(models.Model):
    """
    文章详情表
        文章内容
    """
    content = RichTextUploadingField(verbose_name='文章内容')

配置modelform

# 新增文章,文章详情
class ArticleDetailForm(forms.ModelForm):
    class Meta:
        model = models.ArticleDetail
        fields = "__all__"

# views.py文件实例化该对象
form_obj = ArticleDetailForm

html模板

{{ form_obj.content }}

引入静态文件
{% load static %}
<script type="text/javascript" src="{% static "ckeditor/ckeditor-init.js" %}"></script>
<script type="text/javascript" src="{% static "ckeditor/ckeditor/ckeditor.js" %}"></script>

上传文件需要认证,取消认证

# 将装饰器staff_member_required取消掉即可
urlpatterns = [
    url(r'^upload/', staff_member_required(views.upload), name='ckeditor_upload'),
    url(r'^browse/', never_cache(staff_member_required(views.browse)), name='ckeditor_browse'),
]

 

posted @ 2021-03-09 15:56  Hp_mzx  阅读(130)  评论(0)    收藏  举报