Django【进阶篇】

2023-05-16,

目录

一、Model

二、admin

三、Form组件

四、Cookie

五、Session

六、分页

七、序列化

一、Model

数据库的配置

1、django默认支持sqlite,mysql, oracle,postgresql数据库。

 <1> sqlite

django默认使用sqlite的数据库,默认自带sqlite的数据库驱动 , 引擎名称:django.db.backends.sqlite3

<2> mysql

引擎名称:django.db.backends.mysql

2、mysql驱动程序

MySQLdb(mysql python2中用)
mysqlclient
MySQL
PyMySQL(纯python的mysql驱动程序,python3用)

3、在django的项目中会默认使用sqlite数据库,在settings里有如下设置:

创建数据库表的步骤:

1、创建model;

2、创建生成数据库的py文件:python manage.py makemigrations ;

3、创建数据库表:python manage.py migrate;

注意:记得在settings里的INSTALLED_APPS中加入'app01',然后再同步数据库。

打开pycharm右侧的databases,把创建好的db.sqlite3数据库拖过去,就可以操作数据库表了,这里我们不用操心数据库名的问题;

如果我们想要使用别的数据库,比如Mysql需要修改如下:

DATABASES = {

    'default': {

        'ENGINE': 'django.db.backends.mysql', 

        'NAME': 'books',    #你的数据库名称

        'USER': 'root',   #你的数据库用户名

        'PASSWORD': '', #你的数据库密码

        'HOST': '', #你的数据库主机,留空默认为localhost

        'PORT': '', #你的数据库端口

    }

}

注意:

NAME即数据库的名字,在mysql连接前该数据库必须已经创建,而上面的sqlite数据库下的db.sqlite3则是项目自动创建

USER和PASSWORD分别是数据库的用户名和密码。

设置完后,再启动我们的Django项目前,我们需要激活我们的mysql。

然后,启动项目,会报错:no module named MySQLdb

这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb对于py3有很大问题,所以我们需要的驱动是PyMySQL

所以,我们只需要找到项目名文件下的__init__,在里面写入:

import pymysql
pymysql.install_as_MySQLdb() 问题解决!

4、ORM(对象关系映射)

关系对象映射(Object Relational Mapping,简称ORM),用于实现面向对象编程语言里不同类型系统的数据之间的转换,换言之,就是用面向对象的方式去操作数据库的创建表以及增删改查等操作。

优点: 1、ORM使得我们的通用数据库交互变得简单易行,而且完全不用考虑该死的SQL语句。快速开发,由此而来。

2、可以避免一些新手程序猿写sql语句带来的性能问题。

缺点:1、性能有所牺牲,不过现在的各种ORM框架都在尝试各种方法,比如缓存,延迟加载登来减轻这个问题。效果很显著。

2、对于个别复杂查询,ORM仍然力不从心,为了解决这个问题,ORM一般也支持写raw sql。

3、通过QuerySet的query属性查询对应操作的sql语句

author_obj=models.Author.objects.filter(id=2)
print(author_obj.query)

ORM语法

1、创建表

基本结构

from django.db import models
class Userinfo(models.Model):
name = models.CharField(max_length=30)
email = models.EmailField()
memo = models.TextField()

模型常用的字段类型参数

<1> CharField
#字符串字段, 用于较短的字符串.
#CharField 要求必须有一个参数 maxlength, 用于从数据库层和Django校验层限制该字段所允许的最大字符数. <2> IntegerField
#用于保存一个整数. <3> FloatField
# 一个浮点数. 必须 提供两个参数:
#
# 参数 描述
# max_digits 总位数(不包括小数点和符号)
# decimal_places 小数位数
# 举例来说, 要保存最大值为 999 (小数点后保存2位),你要这样定义字段:
#
# models.FloatField(..., max_digits=5, decimal_places=2)
# 要保存最大值一百万(小数点后保存10位)的话,你要这样定义:
#
# models.FloatField(..., max_digits=19, decimal_places=10)
# admin 用一个文本框(<input type="text">)表示该字段保存的数据. <4> AutoField
# 一个 IntegerField, 添加记录时它会自动增长. 你通常不需要直接使用这个字段;
# 自定义一个主键:my_id=models.AutoField(primary_key=True)
# 如果你不指定主键的话,系统会自动添加一个主键字段到你的 model. <5> BooleanField
# A true/false field. admin 用 checkbox 来表示此类字段. <6> TextField
# 一个容量很大的文本字段.
# admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框). <7> EmailField
# 一个带有检查Email合法性的 CharField,不接受 maxlength 参数. <8> DateField
# 一个日期字段. 共有下列额外的可选参数:
# Argument 描述
# auto_now 当对象被保存时,自动将该字段的值设置为当前时间.通常用于表示 "last-modified" 时间戳.
# auto_now_add 当对象首次被创建时,自动将该字段的值设置为当前时间.通常用于表示对象创建时间.
#(仅仅在admin中有意义...) <9> DateTimeField
# 一个日期时间字段. 类似 DateField 支持同样的附加选项. <10> ImageField
# 类似 FileField, 不过要校验上传对象是否是一个合法图片.#它有两个可选参数:height_field和width_field,
# 如果提供这两个参数,则图片将按提供的高度和宽度规格保存.
<11> FileField
# 一个文件上传字段.
#要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径. 这个路径必须包含 strftime #formatting,
#该格式将被上载文件的 date/time
#替换(so that uploaded files don't fill up the given directory).
# admin 用一个<input type="file">部件表示该字段保存的数据(一个文件上传部件) . #注意:在一个 model 中使用 FileField 或 ImageField 需要以下步骤:
#(1)在你的 settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件.
# (出于性能考虑,这些文件并不保存到数据库.) 定义MEDIA_URL 作为该目录的公共 URL. 要确保该目录对
# WEB服务器用户帐号是可写的.
#(2) 在你的 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项,以告诉 Django
# 使用 MEDIA_ROOT 的哪个子目录保存上传文件.你的数据库中要保存的只是文件的路径(相对于 MEDIA_ROOT).
# 出于习惯你一定很想使用 Django 提供的 get_<#fieldname>_url 函数.举例来说,如果你的 ImageField
# 叫作 mug_shot, 你就可以在模板中以 {{ object.#get_mug_shot_url }} 这样的方式得到图像的绝对路径. <12> URLField
# 用于保存 URL. 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且
# 没有返回404响应).
# admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框) <13> NullBooleanField
# 类似 BooleanField, 不过允许 NULL 作为其中一个选项. 推荐使用这个字段而不要用 BooleanField 加 null=True 选项
# admin 用一个选择框 <select> (三个可选择的值: "Unknown", "Yes" 和 "No" ) 来表示这种字段数据. <14> SlugField
# "Slug" 是一个报纸术语. slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符.#它们通常用于URLs
# 若你使用 Django 开发版本,你可以指定 maxlength. 若 maxlength 未指定, Django 会使用默认长度: 50. #在
# 以前的 Django 版本,没有任何办法改变50 这个长度.
# 这暗示了 db_index=True.
# 它接受一个额外的参数: prepopulate_from, which is a list of fields from which to auto-#populate
# the slug, via JavaScript,in the object's admin form: models.SlugField
# (prepopulate_from=("pre_name", "name"))prepopulate_from 不接受 DateTimeFields. <13> XMLField
#一个校验值是否为合法XML的 TextField,必须提供参数: schema_path, 它是一个用来校验文本的 RelaxNG schema #的文件系统路径. <14> FilePathField
# 可选项目为某个特定目录下的文件名. 支持三个特殊的参数, 其中第一个是必须提供的.
# 参数 描述
# path 必需参数. 一个目录的绝对文件系统路径. FilePathField 据此得到可选项目.
# Example: "/home/images".
# match 可选参数. 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名.
# 注意这个正则表达式只会应用到 base filename 而不是
# 路径全名. Example: "foo.*\.txt^", 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif.
# recursive可选参数.要么 True 要么 False. 默认值是 False. 是否包括 path 下面的全部子目录.
# 这三个参数可以同时使用.
# match 仅应用于 base filename, 而不是路径全名. 那么,这个例子:
# FilePathField(path="/home/images", match="foo.*", recursive=True)
# ...会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif <15> IPAddressField
# 一个字符串形式的 IP 地址, (i.e. "24.124.1.30").
<16># CommaSeparatedIntegerField
# 用于存放逗号分隔的整数值. 类似 CharField, 必须要有maxlength参数.

Field重要参数

    <1> null : 数据库中字段是否可以为空

    <2> blank: django的 Admin 中添加数据时是否可允许空值

    <3> default:设定缺省值

    <4> editable:如果为假,admin模式下将不能改写。缺省为真

    <5> primary_key:设置主键,如果没有设置django创建表时会自动加上:
id = meta.AutoField('ID', primary_key=True)
primary_key=True implies blank=False, null=False and unique=True. Only one
primary key is allowed on an object. <6> unique:数据唯一 <7> verbose_name  Admin中字段的显示名称 <8> validator_list:有效性检查。非有效产生 django.core.validators.ValidationError 错误 <9> db_column,db_index 如果为真将为此字段创建索引 <10>choices:一个用来选择值的2维元组。第一个值是实际存储的值,第二个用来方便进行选择。
如SEX_CHOICES= (( ‘F’,'Female’),(‘M’,'Male’),)
gender = models.CharField(max_length=2,choices = SEX_CHOICES)

2、操作表

a、基本操作

---------------------增(create  ,  save) ---------------------

from app01.models import *

    #create方式一:   Author.objects.create(name='Alvin')

    #create方式二:   Author.objects.create(**{"name":"alex"})

    #save方式一:     author=Author(name="alvin")
author.save() #save方式二: author=Author()
author.name="alvin"
author.save() ---------------------删(delete) ------------------------- Book.objects.filter(id=1).delete() ---------------------改(update和save)------------------ # 方法一,get只能得到一个对象
book = Book.objects.get(author='charlie')
book.price = 200
book.save()
# 方法二,推荐使用
Book.objects.filter(name='python').update(price=100)
注意: <1> 第二种方式修改不能用get的原因是:update是QuerySet对象的方法,
get返回的是一个model对象,它没有update方法,而filter返回的是一个QuerySet对象
(filter里面的条件可能有多个条件符合,比如name='alvin',可能有两个name='alvin'的行数据)。 <2>在“插入和更新数据”小节中,我们有提到模型的save()方法,这个方法会
更新一行里的所有列。 而某些情况下,我们只需要更新行里的某几列。 ---------------------查(filter,value等) ---------------------- # <1>filter(**kwargs): 它包含了与所给筛选条件相匹配的对象 # <2>all(): 查询所有结果 # <3>get(**kwargs):
'''返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的
#对象超过一个或者没有都会抛出错误。''' #下面的方法都是对查询的结果再进行处理:比如 objects.filter.values()-------- # <4>values(*field):
'''返回一个ValueQuerySet——一个特殊的QuerySet,
运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列''' # <5>exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象 # <6>order_by(*field): 对查询结果排序 # <7>reverse(): 对查询结果反向排序 # <8>distinct(): 从返回结果中剔除重复纪录,all()后面跟去重没用,因为ID没有重复,只有在values()后面跟去重才有用,对某一个字段进行去重; # <9>values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列 # <10>count(): 返回数据库中匹配查询(QuerySet)的对象数量。 # <11>first(): 返回第一条记录 # <12>last(): 返回最后一条记录 # <13>exists(): 如果QuerySet包含数据,就返回True,否则返回False。

实例:

models文件内容

from django.db import models

class Book(models.Model):
name = models.CharField(max_length=20)
price = models.IntegerField()
pub_data = models.DateField()
author = models.CharField(max_length=20,null=False)
def __str__(self):
return self.name class Author(models.Model):
name = models.CharField(max_length=20)
age = models.IntegerField()

view文件内容

#view.py
def addbook(request):
# 添加表记录方法一
book = Book(name='python',price=99,pub_data='2018-2-8',author='charlie')
book.save()
# 方法二
Book.objects.create(name='php',price=88,pub_data='2018-10-8',author='oldboy')
# Book.objects.create(**dic)添加字典内容
return HttpResponse('添加成功') def update(request):
# 方法一,get用于只得到一个对象,如果对象重复就报错
# 这种方法会把所有的字段都重新赋值,效率低
book = Book.objects.get(id=1)
book.price = 998
book.save()
# 方法二,推荐使用,update是Queryset方法,只更新查询到的记录
Book.objects.filter(name='python').update(price=150)
return HttpResponse('修改成功') def select(request):
book_list = Book.objects.all()
book_list = Book.objects.all()[:3]#前三个
book_list = Book.objects.all()[::2]#每两个取一个
book_list = Book.objects.all()[::-1]#倒着取
# value只取指定的字段,得到一个查询集,内容是一个字典
ret = Book.objects.filter(name='charlie').values('name','price')
# 得到一个查询集,内容是一个列表,列表元素是元组
ret = Book.objects.filter(name='charlie').values_list('name','price')
return render(request,'index.html',locals())

提示:对于每次创建一个对象,想显示对应的raw sql,需要在settings加上日志记录部分:

LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}

b、利用双下划线方法进行模糊匹配

# 获取个数
models.Tb1.objects.filter(name='seven').count() # 大于,小于
#
models.Tb1.objects.filter(id__gt=1) # 获取id大于1的值
models.Tb1.objects.filter(id__gte=1) # 获取id大于等于1的值
models.Tb1.objects.filter(id__lt=10) # 获取id小于10的值
models.Tb1.objects.filter(id__lte=10) # 获取id小于10的值
models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值 # in
#
models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据
models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in # isnull
Entry.objects.filter(pub_date__isnull=True) #某字段是否可以为空 # contains
#
models.Tb1.objects.filter(name__contains="ven") #内容里包含某字符串
models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感
models.Tb1.objects.exclude(name__icontains="ven") #内容里没有某字段(不区分大小写) # range
#
models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and # 其他类似
#
startswith,istartswith(不区分大小写), endswith, iendswith, # order by
#
models.Tb1.objects.filter(name='seven').order_by('id') # asc
models.Tb1.objects.filter(name='seven').order_by('-id') # desc,反向 # group by
#
from django.db.models import Count, Min, Max, Sum
models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num'))
SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id" # limit 、offset
#
models.Tb1.objects.all()[10:20] # regex正则匹配,iregex 不区分大小写
#
Entry.objects.get(title__regex=r'^(An?|The) +')
Entry.objects.get(title__iregex=r'^(an?|the) +') # date
#
Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1))
Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1)) # year
#
Entry.objects.filter(pub_date__year=2005)
Entry.objects.filter(pub_date__year__gte=2005) # month
#
Entry.objects.filter(pub_date__month=12)
Entry.objects.filter(pub_date__month__gte=6) # day
#
Entry.objects.filter(pub_date__day=3)
Entry.objects.filter(pub_date__day__gte=3) # week_day
#
Entry.objects.filter(pub_date__week_day=2)
Entry.objects.filter(pub_date__week_day__gte=2) # hour
#
Event.objects.filter(timestamp__hour=23)
Event.objects.filter(time__hour=5)
Event.objects.filter(timestamp__hour__gte=12) # minute
#
Event.objects.filter(timestamp__minute=29)
Event.objects.filter(time__minute=46)
Event.objects.filter(timestamp__minute__gte=29) # second
#
Event.objects.filter(timestamp__second=31)
Event.objects.filter(time__second=2)
Event.objects.filter(timestamp__second__gte=31)

c、连表操作(了不起的双下划线)

外键参数设置

#Django升级到2版本之后models.ForeignKey()需要填写on_delect参数
on_delete=models.CASCADE, # 删除关联数据,与之关联也删除
on_delete=models.DO_NOTHING, # 删除关联数据,什么也不做,最好不要;
on_delete=models.PROTECT, # 删除关联数据,引发错误ProtectedError
on_delete=models.SET_DEFAULT #设置默认值,前提是ForeignKey必须设置的默认值;
on_delete=models.SET(...)
'''设置给定值,或者如果传入了callable,则调用它的结果。
在大多数情况下,为了避免在导入models.py时执行查询,必须传递callable'''
on_delete=models.SET_NULL, # 删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空,一对一同理)
# 例如:models.ForeignKey('关联表', on_delete=models.SET_NULL, blank=True, null=True)

一对多操作
models.py

#models.py
from django.db import models class Book(models.Model):
'''书籍'''
name = models.CharField(max_length=20)
price = models.IntegerField()
pub_data = models.DateField()
publish = models.ForeignKey('Publish',on_delete=models.CASCADE) def __str__(self):
return self.name class Author(models.Model):
'''作者'''
name = models.CharField(max_length=20) class Publish(models.Model):
'''出版社'''
#一对多的外键要加在多的哪一张表里,一本书只能有一个出版社
name = models.CharField(max_length=30)
city = models.CharField(max_length=30)

view.py

from django.shortcuts import render,HttpResponse
from app.models import * #-----------------------增----------------------- # 方式一,给publish_id赋值,django默认给你的外键字段添加_id
Book.objects.create(name='linux运维',price=88,pub_data='2016-10-8',publish_id=1)
# 方式二,给publish字段直接赋值,需要先得到一个对象
pulish_obj = Publish.objects.filter(name='西湖出版社')[0]
Book.objects.create(name='Java', price=188, pub_data='2016-1-23', publish=pulish_obj) #-----------------------删----------------------- Book.objects.filter(publish__name='西湖出版社').delete() #-----------------------改----------------------- Book.objects.filter(publish__name='人民出版社').update(price=300) #-----------------------查----------------------- #第一种方式:通过对象(不推荐使用)
# 正向
book_obj = Book.objects.get(name='python')
publish_obj = book_obj.publish
publish_name = publish_obj.name
#反向
pub_obj = Publish.objects.filter(name='西湖出版社')[0]
#下面的到一个查询集,内容是字典,取第一个字典
pub_name = pub_obj.book_set.all().values('name','price')[0] #第二种方式:通过filter(__),推荐使用
#正向查询,指定出版社的书籍信息;从有外键的表查询为正向;
book_name = Book.objects.filter(publish__name='西湖出版社').values('name','price')[0]
# 反向查询,指定书籍的出版社信息,通过 类名__字段名
pub_name = Publish.objects.filter(book__name='python').values('name','city')[0]
#通过value(__)
book_name = Book.objects.filter(name='Java').values('publish__name','publish__city')[0]
# 查询所有在北京的出版社出版的书籍,得到一个列表
book_list = Publish.objects.filter(city='北京').values('book__name')
book_list2 = Book.objects.filter(publish__city='北京').values('name')

多对多操作、聚合查询aggregate( )、分组查询annotate()

from django.db import models

class Book(models.Model):
'''书籍'''
name = models.CharField(max_length=20)
price = models.IntegerField()
pub_data = models.DateField()
# 一对多,一本书只能有一个出版社
publish = models.ForeignKey('Publish',on_delete=models.CASCADE)
# 多对多,一本书可以有多个作者,系统会自动创建第三张表:app_book_authors
#但是不能直接对这张表进行操作,因为没有它的类
authors = models.ManyToManyField('Author')
def __str__(self):
return self.name class Publish(models.Model):
'''出版社'''
#一对多的外键要加在多的哪一张表里,一本书只能有一个出版社
name = models.CharField(max_length=30)
city = models.CharField(max_length=30)
def __str__(self):
return self.name class Author(models.Model):
'''作者'''
name = models.CharField(max_length=20)
age = models.IntegerField(default=20)
def __str__(self):
return self.name

models

from django.shortcuts import render,HttpResponse
from app.models import *
from django.db.models import Avg,Min,Max,Count,Sum #------------------增-------------------- #方式一
book_obj = Book.objects.get(id=4)
book_obj.authors.add(1)
book_obj.authors.add(*[2,3,])
#方式二
book_obj = Book.objects.get(id=4)
author_objs = Author.objects.all()
book_obj.authors.add(*author_objs) #------------------删-------------------- # 先得到一个书籍对象
book_obj = Book.objects.get(id=4)
#删除
book_obj.authors.remove(3)
book_obj.authors.remove(*[1,2,])
#清空
book_obj.authors.clear() #------------------改-------------------- #重置,以设置的为准,这里列表不加*,已经有的不动,新内容里没有的就删除
book_obj.authors.set([1,2,3]) #------------------查-------------------- #正向,找到id=2的书籍的所有作者
book_obj = Book.objects.get(id=2)
book_obj.authors.all()
#方式二
Book.objects.filter(id=2).values('authors__name')
# 反向,找到作者的所有书籍
author_obj = Author.objects.get(id=1)
author_obj.book_set.all()
#所有书籍名称和对应的作者名
book_list = Book.objects.all().values('name','authors__name')
#列举作者charlie的所有书籍
Book.objects.filter(authors__name="charlie") #-----------------聚合函数aggregate()------------------ #求所有书籍的平均价格
ret = Book.objects.all().aggregate(Avg('price'))#{'price__avg': 122.0}
# 也可以自定义名称,结果:{'avg_price': 122.0}
ret = Book.objects.all().aggregate(avg_price=Avg('price'))
#作者Charlie出的所有书,Count参数可以是book表里任意个字段,只是查有几条记录,查谁都一样
ret = Book.objects.filter(authors__name='charlie').aggregate(Count('name')) #-----------------分组函数annotate--------------------- #按作者名分组,求每个作者所有书籍的价格总和
ret = Book.objects.values('authors__name').annotate(Sum('price'))
'''
<QuerySet [{'authors__name': 'charlie', 'price__sum': 366},
{'authors__name': 'alex', 'price__sum': 88},
{'authors__name': 'james', 'price__sum': 188}]>
'''
#求每个出版社的最低价格的书
ret = Publish.objects.values('name').annotate(Min('book__price'))

对于多对多的操作,我们也可以手动创建第三张表,但是这样查询起来更加麻烦,所以不推荐;

#去掉authors = models.ManyToManyField('Author')
#创建新类
class Book_Author(models.Model):
#第三张表
book = models.ForeignKey('Book',on_delete=models.CASCADE)
author = models.ForeignKey('Author',on_delete=models.CASCADE)
def __str__(self):
return self.name #添加
Book_Author.objects.create(book_id=3,author_id=1)
# 利用对象查询
book_obj = Book.objects.get(id=2)
author_name = book_obj.book_author_set.all()[0].author
print(author_name)
#双下划线查询
book_list1 = Author.objects.filter(name='charlie').values('book_author__book')
print(book_list1)
book_list2 = Book.objects.filter(book_author__author__name='charlie').values('name')
print(book_list2)

F查询和Q查询

from django.db.models import F,Q

#F 使用查询条件的值,专门取对象中某列值的操作
#给所有的书价格加10
Book.objects.all().update(price=F('price') + 10) # Q 进行条件或的查询,| 或,满足任意条件
ret = Book.objects.filter(Q(price=110)|Q(name='GO'))
#~ 非,不满足条件
ret = Book.objects.filter(~Q(name='GO'))
#书籍名称中包含某个字母
ret = Book.objects.filter(Q(name__contains='J'))
#Q查询和关键字查询结合使用,Q查询一定要放前面
ret = Book.objects.filter(Q(price=110),name='GO')
print(ret)
#Q(name__startswith='P') 书籍名称以字母P开头

3、补充整理

跨多张表查询,可以连续使用双下划线

ForeignKey()和ManyToManyField()都可以通过related_name参数修改没有外键的那个表里隐藏的字段

    class Book(models.Model):
'''书籍'''
name = models.CharField(max_length=20)
price = models.IntegerField()
pub_data = models.DateField()
authors = models.ForeignKey('Author',on_delete=models.CASCADE)
class Author(models.Model):
'''作者'''
name = models.CharField(max_length=20)
age = models.IntegerField(default=20)
country = models.ForeignKey('Country',on_delete=models.CASCADE)
class Country(models.Model):
'''国家'''
name = models.CharField(max_length=20)
#所有中国籍作者出的所有书籍
Book.objects.filter(authors__country__name='China')
#正向查找:Book.objects.filter(authors__name='charlie')
#反向查找:
obj = Authors.objects.filter(name='charlie').first()
obj.book_set.all()
#没有外键的表里其实隐藏了一个字段:类名_set,也可以修改这个字段名
class Book(models.Model):
authors = models.ForeignKey('Author',on_delete=models.CASCADE,related_name='book')
obj = Authors.objects.filter(name='charlie').first()
book_list = obj.book.all()#作者对应的所有书籍对象查询集[obj(name,price,..),obj(name,price,..),]

4、惰性机制

所谓惰性机制:Publisher.objects.all()或者.filter()等都只是返回了一个QuerySet(查询结果集对象),它并不会马上执行sql,而是当调用QuerySet的时候才执行。

QuerySet特点:<1>  可迭代的;<2>  可切片

QuerySet的高效使用:

<1>Django的queryset是惰性的
Django的queryset对应于数据库的若干记录(row),通过可选的查询来过滤。例如,下面的代码会得
到数据库中名字为‘Dave’的所有的人:person_set = Person.objects.filter(first_name="Dave")
上面的代码并没有运行任何的数据库查询。你可以使用person_set,给它加上一些过滤条件,或者将它传给某个函数,
这些操作都不会发送给数据库。这是对的,因为数据库查询是显著影响web应用性能的因素之一。 <2>要真正从数据库获得数据,你可以遍历queryset或者使用if queryset,总之你用到数据时就会执行sql.
为了验证这些,需要在settings里加入 LOGGING(验证方式)
obj=models.Book.objects.filter(id=3)
for i in obj:
print(i) if obj:
print("ok") <3>queryset是具有cache的
当你遍历queryset时,所有匹配的记录会从数据库获取,然后转换成Django的model。这被称为执行
(evaluation).这些model会保存在queryset内置的cache中,这样如果你再次遍历这个queryset,
你不需要重复运行通用的查询。但是如果你修改了数据库,就需要再查询一次,否则你查到的还是上次的
  缓存数据。
obj=models.Book.objects.filter(id=3)
for i in obj:
print(i)
models.Book.objects.filter(id=3).update(title="GO")
obj_new=models.Book.objects.filter(id=3)
for i in obj:
print(i) #LOGGING只会打印一次 <4>简单的使用if语句进行判断也会完全执行整个queryset并且把数据放入cache,虽然你并不需要这些
数据!为了避免这个,可以用exists()方法来检查是否有数据: obj = Book.objects.filter(id=4)
# exists()的检查可以避免数据放入queryset的cache。
if obj.exists():
print("hello world!") <5>当queryset非常巨大时,cache会成为问题 处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统
进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法
来获取数据,处理完数据就将其丢弃。
objs = Book.objects.all().iterator()
# iterator()可以一次只从数据库获取少量数据,这样可以节省内存
for obj in objs:
print(obj.name)
#BUT,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了
for obj in objs:
print(obj.name) 当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使
用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询。 总结:
queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。
使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能
会造成额外的数据库查询。总之一句话,如果查询数据量巨大时,使用迭代器;如果数据量很小,而且又需要重复查询
时,使用查询集。

二、admin

admin是django强大功能之一,它能从数据库中读取数据,呈现在页面中,进行管理。默认情况下,它的功能已经非常强大,如果你不需要复杂的功能,它已经够用,但是有时候,一些特殊的功能还需要定制,比如搜索功能,下面这一系列文章就逐步深入介绍如何定制适合自己的admin应用。

如果你觉得英文界面不好用,可以在setting.py 文件中修改以下选项:

LANGUAGE_CODE = 'en-us'  #LANGUAGE_CODE = 'zh-hans'

使用django admin 则需要以下步骤:

创建后台管理员
配置url
注册和配置django admin后台管理页面

1、创建后台管理员

python manage.py createsuperuser

2、配置后台管理url

url(r'^admin/', admin.site.urls)

3、注册和配置django admin 后台管理页面

a、注册:在admin中执行如下配置

from django.contrib import admin

from app01 import  models

admin.site.register(models.UserType)
admin.site.register(models.UserInfo)
admin.site.register(models.UserGroup)
admin.site.register(models.Asset)

b、设置数据表字段的显示名称

#方式一
class UserType(models.Model):
name = models.CharField(max_length=50)
#meta表示数据库中显示的信息
class Meta:
db_table = 'UserType' #数据库表名
verbose_name = '用户类型' #数据库字段名
verbose_name_plural = '用户类型' #方式二
#修改models
class Book(models.Model):
'''书籍'''
name = models.CharField(max_length=20,verbose_name='名称')
price = models.IntegerField('价格')

c、自定义页面展示

from django.contrib import admin
from app.models import * #自定制admin类
class BookAdmin(admin.ModelAdmin):
#不可以显示多对多的关联字段,注意这些都是元组,注意末尾的逗号
list_display = ('id','name','price','pub_date',)
#可编辑
# list_editable = ('name','price','pub_date',)
# 设置字段可垂直搜索
filter_horizontal = ('authors',)
#设置每页显示的条目
# list_per_page = 2
#根据字段搜索,关联字段加__,三个字段中重复的部分都会被搜索到
search_fields = ('id','name','publish__name',)
#根据字段过滤
list_filter = ('pub_date','publish',)
#根据字段排序,可以多个字段,依次排序,‘-id’表示降序排
ordering = ('id',)
#添加书籍时隐藏字段,列表里必须是元组
fieldsets = [
#默认显示name,fieldes是固定的,后面跟列表
(None,{'fields':['name',]}),
#将下列字段以折叠的方式显示
('other information',{'fields':['price','pub_date','publish'],'classes':['collapse',]}),
] #将模型注册到admin
admin.site.register(Book,BookAdmin)

4、注册medel类到admin的两种方式:

使用register的方法

admin.site.register(Book,MyAdmin)

使用register的装饰器

@admin.register(Book)

5、装饰器使用方法

from django.contrib import admin
from app01.models import *
# Register your models here. # @admin.register(Book)#----->单给某个表加一个定制
class MyAdmin(admin.ModelAdmin):
list_display = ("title","price","publisher")
search_fields = ("title","publisher")
list_filter = ("publisher",)
ordering = ("price",)
fieldsets =[
(None, {'fields': ['title']}),
('price information', {'fields': ['price',"publisher"], 'classes': ['collapse']}),
] admin.site.register(Book,MyAdmin)
admin.site.register(Publish)
admin.site.register(Author)

三、Form组件

1、django中的Form一般有一下几种功能:

生成HTML标签

验证用户输入:ajax和form表单提交

HTML Form提交保留上次提交数据

初始化页面显示内容

2、用form表单提交的基本流程

a、在views.py中创建一个类:F(forms.Form),如果正规操作,应该是在APP中新建一个forms.py文件,再导入;

b、类中创建字段(包含正则表达式和HTML插件)

c、用户以GET方式请求:

把obj=F()发送到前端,这里不用传入任何参数

{{ obj.user }}...前端自动生成input标签

d、用户以POST方式请求:

obj = Form1(request.POST),对象中包含用户输入信息和错误信息

先验证用户输入是否正确,如果正确就跳转页面,如果错误就发送obj

{{ obj.errors.user.0 }} 前端显示错误信息

补充:

#mark_safe,不用加safe就可以直接渲染HTML标签
from django.utils.safestring import mark_safe
txt = mark_safe("<input type='text'/") # novalidate忽略浏览器的验证
<form action="/add_user/" method="POST" novalidate> # 上传文件记得加上enctype
<form action="/test/" method="POST" enctype="multipart/form-data" novalidate> # form表单自动生成input标签,编辑时将数据库查询内容作为input标签默认值
data = UserInfo.objects.filter(id=nid).first()
obj = F1({'username':data.username,'email':data.email})

实例:

views.py中创建Form类和函数

from django.shortcuts import render,redirect,HttpResponse
from django import forms
from django.forms import fields class Form1(forms.Form):
user = fields.CharField(
min_length=6,
max_length=18,
required=True,
error_messages={
'required':'用户名不能为空',
'min_length':'用户名太短',
'max_length':'用户名太长',
}
)
pwd = fields.CharField(
min_length=10,
required=True,
error_messages = {
'required': '密码不能为空',
'min_length': '密码太短',
}
)
age = fields.IntegerField(
required=True,
error_messages={
'required': '年龄不能为空',
'invalid': '必须为数字',
}
)
email = fields.EmailField(
min_length=8,
required=True,
error_messages={
'required': '邮箱不能为空',
'invalid': '格式错误'
}
) def f1(request):
if request.method == 'GET':
#自动生成input标签
obj = Form1()
return render(request,'f1.html',{'obj': obj})
if request.method == 'POST':
obj = Form1(request.POST)
#验证是否成功
if obj.is_valid():
#如果成功,打印用户提交的数据,跳转
print("验证成功",obj.cleaned_data)
return redirect('http://www.baidu.com')
else:
print("验证失败", obj.errors)
return render(request, 'f1.html', {'obj': obj})

f1.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
{% load staticfiles %}
</head>
<body>
<form action="/f1.html" method="POST" id="fm">
<p>用户名{{ obj.user }}{{ obj.errors.user.0 }}</p>
<p>密码{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p>
<p>年龄{{ obj.age }}{{ obj.errors.age.0 }}</p>
<p>邮箱{{ obj.email }}{{ obj.errors.email.0 }}</p>
<input type="submit" value="提交"/>
<input type="button" value="ajax提交" id="ajaxSubmit"/>
</form>
</body>
</html>

3、创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;

常用的Django内置字段

Field
required=True, 是否允许为空
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
validators=[], 自定义验证规则
localize=False, 是否支持本地化
disabled=False, 是否可以编辑
label_suffix=None Label内容后缀 #字符串
CharField(Field)
max_length=None, 最大长度
min_length=None, 最小长度
strip=True 是否移除用户输入空白 #整型
IntegerField(Field)
max_value=None, 最大值
min_value=None, 最小值 #浮点型,十进制小数
DecimalField(IntegerField)
max_value=None, 最大长度
min_value=None, 最小长度
max_digits=None, 总长度
decimal_places=None, 小数位长度 #下拉框
ChoiceField(Field)
choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),)
required=True, 是否必填
widget=None, 插件,默认select插件
label=None, Label内容
initial=None, 初始值
help_text='', 帮助提示 TypedChoiceField(ChoiceField)
coerce = lambda val: val 对选中的值进行一次转换
empty_value= '' 空值的默认值 #多选框
MultipleChoiceField(ChoiceField) TypedMultipleChoiceField(MultipleChoiceField)
coerce = lambda val: val 对选中的每一个值进行一次转换
empty_value= '' 空值的默认值 #时间
DateField(BaseTemporalField) 格式:2015-09-01
TimeField(BaseTemporalField) 格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12 #邮箱
EmailField(CharField) #自定制正则表达式
RegexField(CharField)
regex, 自定制正则表达式
max_length=None, 最大长度
min_length=None, 最小长度
error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'} #上传文件
FileField(Field)
allow_empty_file=False 是否允许空文件 ImageField(FileField)
...
注:需要PIL模块,pip3 install Pillow
以上两个字典使用时,需要注意两点:
- form表单中 enctype="multipart/form-data"
- view函数中 obj = MyForm(request.POST, request.FILES) #IP
GenericIPAddressField
protocol='both', both,ipv4,ipv6支持的IP格式
unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用 #数据库操作相关字段
ModelChoiceField(ChoiceField)
... django.forms.models.ModelChoiceField
queryset, # 查询数据库中的数据
empty_label="---------", # 默认空显示内容
to_field_name=None, # HTML中value的值对应的字段
limit_choices_to=None # ModelForm中对queryset二次筛选 ModelMultipleChoiceField(ModelChoiceField)
... django.forms.models.ModelMultipleChoiceField

实例:

#views.py

from django.shortcuts import render
from django import forms
from django.forms import fields,widgets class TestForm(forms.Form):
user = fields.CharField(
required=True,
min_length=3,
max_length=12,
error_messages={'required':'不能为空',},
#widgets定制HTML插件
# widget=widgets.Select,
label = '用户名',
label_suffix='->',
)
age = fields.IntegerField(max_value=100,min_value=18)
email = fields.EmailField()
#FileField和ImageField功能一样
img = fields.FileField()
#生成下拉框,initial默认选中,或者obj = TestForm({'city':2})也可以默认选择
city = fields.ChoiceField(
choices=[(1,'北京'),(2,'上海'),],
initial=2,
)
#多选框
hobby = fields.MultipleChoiceField(
choices=[(1,'足球'),(2,'篮球'),],
initial=[1,2],
)
country = fields.TypedChoiceField(
choices=[(1, '中国'), (2, '美国'), ],
initial=2,
#将传入的参数做一个数据类型转换 'country': 2
coerce=lambda x:int(x),
empty_value='null',#空值的默认值
) def test(request):
if request.method == 'GET':
obj = TestForm()
return render(request, 'test.html',locals())
else:
#上传的文件在request.FILES中
obj = TestForm(request.POST,request.FILES)
obj.is_valid()
print(obj.cleaned_data)
return render(request,'test.html',locals()) #test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{#上传文件记得加上enctype#}
<form action="/test/" method="POST" enctype="multipart/form-data" novalidate>
{% csrf_token %}
<p>{{ obj.user.label }}{{ obj.user }}</p>
<p>{{ obj.age.label }}{{ obj.age }}</p>
<p>{{ obj.email.label }}{{ obj.email }}</p>
<p>{{ obj.img.label }}{{ obj.img }}</p>
<p>{{ obj.city.label }}{{ obj.city }}</p>
<p>{{ obj.hobby.label }}{{ obj.hobby }}</p>
<p>{{ obj.country.label }}{{ obj.country }}</p>
<input type="submit" value="提交"/>
</form>
{# 可以全部自动生成一个标签,但是页面排版无法控制,不建议使用#}
{# {{ obj.as_p }}#}
</body>
</html>

4、Django内置插件

调用方法

#widgets定制HTML插件,每一个字段都有自己的默认插件,还可以定制属性
widget=widgets.TextInput(attrs={'class':'c1'}),

Django内置插件

TextInput(Input)
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget

5、常用的选择插件

# 单radio,值为字符串
user = fields.CharField(
initial=2,
widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
) # 单radio,值为字符串
user = fields.ChoiceField(
choices=((1, '上海'), (2, '北京'),),
initial=2,
widget=widgets.RadioSelect
) # 单select,值为字符串
user = fields.CharField(
initial=2,
widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
) # 单select,值为字符串
user = fields.ChoiceField(
choices=((1, '上海'), (2, '北京'),),
initial=2,
widget=widgets.Select
) # 多选select,值为列表
user = fields.MultipleChoiceField(
choices=((1,'上海'),(2,'北京'),),
initial=[1,],
widget=widgets.SelectMultiple
) # 单checkbox
user = fields.CharField(
widget=widgets.CheckboxInput()
) # 多选checkbox,值为列表
user = fields.MultipleChoiceField(
initial=[2, ],
choices=((1, '上海'), (2, '北京'),),
widget=widgets.CheckboxSelectMultiple
)

6、在使用选择标签时,需要注意choices的选项是需要从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,那么需要自定义__init__构造方法,方法给widget.choices重新从数据库中取值,这样每次页面一刷新就实例化一次form对象,就会执行一次init函数,也就是会从数据库中取一次值。

方式一:

from django.shortcuts import render
from django import forms
from django.forms import fields,widgets
from app.models import *#models.py中的类
class UserInfo(models.Model):
username = models.CharField(max_length=32)
email = models.EmailField(max_length=32)
def __str__(self):
return self.username #创建自定义form
class LoveForm(forms.Form):
price = fields.IntegerField()
user_id = fields.IntegerField(widget=widgets.Select)
#静态字段 获取的值无法实时更新,需要自定义构造方法
def __init__(self,*args,**kwargs):
# super必须在上面,它拷贝了所有的静态字段,下面才能去内部取字段
super(LoveForm,self).__init__(*args,**kwargs)
self.fields['user_id'].widget.choices = UserInfo.objects.values_list('id','username') def love(request):
obj = LoveForm()
return render(request,'love.html',locals())

方式二:

#使用django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现

from django.forms.models import ModelChoiceField

class LoveForm(forms.Form):
user_id2 = ModelChoiceField(
#这里无法显示数据库表中的ID以外的字段,需要models中的类加上个__str__方法,返回某个字段
#虽然一样可以实时更新,但是与数据库的格式关系太大,不能灵活显示每个字段,不建议使用
queryset=UserInfo.objects.all()
)

7、使用ajax提交

无法自动跳转页面,就是在views函数中设置redirect,ajax也不会听从,需要在前端ajax的回调函数自己使用js代码跳转,window.location.href = 'http://www.baidu.com'

错误信息需要自己显示到页面,obj.errors 类型<class 'django.forms.utils.ErrorDict'>继承dict,所以用json.dumps()不会报错。

class AjaxForm(forms.Form):
price = fields.IntegerField()
user_id = fields.IntegerField(
widget=widgets.Select(choices=[(1, '中国'), (2, '美国'), ],)
) def ajax(request):
if request.method == 'GET':
#自动生成input标签,并没有开始做验证
obj = AjaxForm()
return render(request,'ajax.html',{'obj': obj})
if request.method == 'POST':
import json
response = {'status':True,'msg':None}
#并没有开始做验证
obj = AjaxForm(request.POST)
#is_valid做的验证
if obj.is_valid():
print("验证成功",obj.cleaned_data)
#ajax无法自动跳转,需要在前端手动设置
return HttpResponse(json.dumps(response))
else:
#<class 'django.forms.utils.ErrorDict'>继承dict
print("验证失败", obj.errors,type(obj.errors))
response['status'] = False
response['msg'] = obj.errors
return HttpResponse(json.dumps(response))

ajax.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
{% load staticfiles %}
</head>
<body>
<form action="/ajax/" method="POST" novalidate id="fm">
{% csrf_token %}
{{ obj.as_p }}
<input type="button" value="ajax提交" id="ajaxSubmit"/>
</form>
<script src="{% static 'js/jquery-1.12.4.js' %}"></script>
<script>
$(function () {
$('#ajaxSubmit').click(function () {
$.ajax({
url:'/ajax/',
type:'POST',
data:$('#fm').serialize(),
dataType:'JSON',
success:function (arg) {
if(arg.status){
window.location.href = 'http://www.baidu.com';
}
console.log(arg);
}
})
})
})
</script>
</body>
</html>

8、在根据源码流程自定义方法

clean_字段名,只能验证该字段自己的内容;

from django.core.exceptions import ValidationError

class AjaxForm(forms.Form):
username = fields.CharField()
user_id = fields.IntegerField(
widget=widgets.Select(choices=[(1, '中国'), (2, '美国'), ],)
)
#自定义方法clean_字段名,
#必须返回值self.cleaned_data['username']
#如果出错:raise ValidationError('用户名已存在')
def clean_username(self):
v = self.cleaned_data['username']
if UserInfo.objects.filter(username=v).count():
#如果用户信息中有一个字段错了,整体就错误,显示具体错误的详细信息
raise ValidationError('用户名已存在')
return v
def clean_user_id(self):
return self.cleaned_data['user_id']

自定义self.clean()方法,对整体错误验证,错误信息放在__all__中,前端调用方法arg.msg.__all__

    def clean(self):
'''
obj.errors错误信息
{
__all__:[],
username:[],
user_id:[],
}
'''
# Django的整体错误信息放在__all__中
v1 = self.cleaned_data.get('username')
v2 = self.cleaned_data.get('user_id')
if v1=='charlie' and v2==1:
raise ValidationError('整体错误信息')
return self.cleaned_data

__all__源码分析

from django.core.exceptions import NON_FIELD_ERRORS

#这里添加错误的时候k就是None
self.add_error(None, e)
#但是None又被替换成__all__,所以前端需要通过.all来取
NON_FIELD_ERRORS = '__all__' # js代码
console.log(arg.msg.__all__); #HTML模板
#模板语言里不支持双下划线格式,所以用obj.non_field_errors.0

9、is_valid()验证表单时,一共给我们留了三个钩子

# 自定义方法名必须是clean_字段名
def _clean_fields(self):
for name, field in self.fields.items():
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
if field.disabled:
value = self.get_initial_for_field(field, name)
else:
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, FileField):
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)
# 同时验证多个字段或者是联合字段,返回整体错误信息,放在__all__中
def _clean_form(self):
try:
cleaned_data = self.clean()
except ValidationError as e:
self.add_error(None, e)
else:
if cleaned_data is not None:
self.cleaned_data = cleaned_data
# 没有捕捉异常的代码,所以这个方法不能出错误
def _post_clean(self):
"""
An internal hook for performing additional cleaning after form cleaning
is complete. Used for model validation in model forms.
"""
pass

10、扩展:ModelForm

在使用Model和Form时,都需要对字段进行定义并指定类型,通过ModelForm则可以省去From中字段的定义

from django import forms
from crm import models class CustomerForm(forms.ModelForm):
class Meta:
model = models.CustomerInfo
# fields = ['name','consultant','status']
# 所有字段
fields = '__all__' widgets = {
'email' : forms.PasswordInput(attrs={'class':"alex"}),
}

  实例:

# forms.py

class EnrollmentForm(forms.ModelForm):
"""审核学员报名信息"""
def __new__(cls, *args, **kwargs):
# cls.base_fields获取数据库表里的所有字段名
for field_name in cls.base_fields:
field_obj = cls.base_fields.get(field_name)
# 定制生成的标签的class属性
field_obj.widget.attrs.update({'class': 'form-control'})
# 只读标签不可编辑
if field_name in cls.Meta.readonly_fields:
field_obj.widget.attrs.update({'disabled': 'true'})
return forms.ModelForm.__new__(cls) class Meta:
model = models.StudentEnrollment
fields = '__all__'
exclude = ['contract_approved_date']
readonly_fields = ['contract_agreed',] def clean(self):
"""负责验证disabled字段的值是否被改动了"""
if self.errors:
# 全局的错误信息,通过customer_form.errors调用
raise forms.ValidationError(("Please fix errors before re-submit."))
if self.instance.id is not None:
# 说明这是一个被修改过的表单,需要验证disabled字段
for field in self.Meta.readonly_fields:
old_field_val = getattr(self.instance,field) # 数据库里的数据
form_val = self.cleaned_data.get(field) # 表单里提交的数据
if old_field_val != form_val:
# 给单个字段添加错误信息
self.add_error(field,"Readonly Field:field should be '{value}',not'{new_value}'".
format(**{'value':old_field_val,'new_value':form_val}))

views.py

from django.shortcuts import render,HttpResponse,redirect
from django.contrib.auth.decorators import login_required # 必须登陆才能看到页面
from crm import forms @login_required
def contract_audit(request,enrollment_id):
'''学员报名审核页'''
enrollment_obj = models.StudentEnrollment.objects.filter(id=enrollment_id).first()
if request.method == 'POST':
enrollment_form = forms.EnrollmentForm(instance=enrollment_obj,data=request.POST)
if enrollment_form.is_valid():
enrollment_form.save()
# 报名成功后,把学员加入数据库,stu_obj = (<Student: 铁锤>, True)
stu_obj = models.Student.objects.get_or_create(customer=enrollment_obj.customer)[0] # 加入学生表
stu_obj.class_grades.add(enrollment_obj.class_grade_id) # 加入班级表
stu_obj.customer.status = 1 # 修改状态
stu_obj.save()
# 修改报名表,是否审核通过,和审核通过时间
enrollment_obj.contract_approved = True
enrollment_obj.contract_approved_date = datetime.now()
enrollment_obj.save()
# 给用户发邮件
return redirect('/kingadmin/crm/customerinfo/%s/change/'%enrollment_obj.customer.id)
else:
# 创建form表单,发到前端
customer_form = forms.CustomerForm(instance=enrollment_obj.customer)
enrollment_form = forms.EnrollmentForm(instance=enrollment_obj)
return render(request,'crm/contract_audit.html',locals())

四、Cookie

cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上cookie,这样服务器就能通过cookie的内容来判断这个是“谁”了。

1、获取Cookie:

request.COOKIES['key']
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
参数:
default: 默认值
salt: 加密盐
max_age: 后台控制过期时间

2、设置Cookie:

rep = HttpResponse(...) 或 rep = render(request, ...)

rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密盐',...)
参数:
key, 键
value='', 值
max_age=None, 超时时间
expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
path='/', Cookie生效的路径,/ 表示根路径,特殊的:跟路径的cookie可以被任何url的页面访问
domain=None, Cookie生效的域名
secure=False, https传输
httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

由于cookie保存在客户端的电脑上,所以,JavaScript和jquery也可以操作cookie。

<script src='/static/js/jquery.cookie.js'></script>
$.cookie("list_pager_num", 30,{ path: '/' });

3、应用

from django.shortcuts import render,redirect
import datetime def login(request):
if request.method == 'POST':
name = request.POST.get('user')
pwd = request.POST.get('pwd')
if name == 'charlie' and pwd == '':
# redirect render 都有返回值,是一个字典,里面有键值对'sessionid':'5225rffg5hh5'
ret = redirect('/index/')
#给cookie添加一个键值对,下次再来发送请求的时候会带着它来,
#如果和这个一样,就不用重新登陆了,前提是在同一个客户端登陆
#设置cookie有效时间5秒
ret.set_cookie('username',name,max_age=5)
#设置有效期三天
ret.set_cookie('username',name,expires=datetime.datetime.utcnow()
+datetime.timedelta(days=3))
return ret
return render(request,'login.html') def index(request):
if request.COOKIES.get('username',None):
name = request.COOKIES.get('username',None)
return render(request,'index.html',locals())
else:
return redirect('/login/')

五、Session

cookie虽然在一定程度上解决了“保持状态”的需求,但是由于cookie本身最大支持4096字节,以及cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是session(session默认在服务器端保存15天)。

Django中默认支持Session,其内部提供了5种类型的Session供开发者使用:

数据库(默认)
缓存
文件
缓存+数据库
加密cookie

1、数据库Session

Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。

a. 配置 settings.py

    SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)

    SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认) b. 使用 def index(request):
# 获取、设置、删除Session中数据
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1'] # 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems() # 用户session的随机字符串
request.session.session_key # 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired() # 检查 用户session的随机字符串 在数据库中是否
request.session.exists("session_key") # 删除当前用户的所有Session数据
request.session.delete("session_key") request.session.set_expiry(value)
* 如果value是个整数,session会在些秒数后失效。
* 如果value是个datatime或timedelta,session就会在这个时间后失效。
* 如果value是0,用户关闭浏览器session就会失效。
* 如果value是None,session会依赖全局session失效策略。

2、缓存Session

a. 配置 settings.py

    SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置 SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名
SESSION_COOKIE_SECURE = False # 是否Https传输cookie
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输
SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期
SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存 b. 使用同上

3、文件Session

a. 配置 settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎
SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T
SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名
SESSION_COOKIE_SECURE = False # 是否Https传输cookie
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输
SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期
SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存 b. 使用同上

4、缓存+数据库Session

数据库用于做持久化,缓存用于提高效率
a. 配置 settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎
b. 使用同上

5、加密cookie Session

a. 配置 settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎
b. 使用同上

更多参考:猛击这里 和 猛击这里

6、cookie 和Session的结合使用的两种方式

存储在服务端:通过cookie存储一个session_id,然后具体的数据则是保存在session中。如果用户已经登录,则服务器会在cookie中保存一个session_id,下次再次请求的时候,会把该session_id携带上来,服务器根据session_id在session库中获取用户的session数据。就能知道该用户到底是谁,以及之前保存的一些状态信息。这种专业术语叫做server side session。

将session数据加密,然后存储在cookie中。这种专业术语叫做client side session。flask采用的就是这种方式,但是也可以替换成其他形式。

7、应用

数据库session,需要执行python manage.py makemigrations命令,需要Django帮我们创建session表,用来保存session内容,否则会报错;

cookie是一个字典,里面默认有两个键值对:‘sessionid’,‘csrftoken’,session是一个字典对象,两个都可以用request.session['username']=name方法来添加键值对;

删除session:del request.session[key]

from django.shortcuts import render,redirect
import datetime def login(request):
if request.method == 'POST':
name = request.POST.get('user')
pwd = request.POST.get('pwd')
if name == 'charlie' and pwd == '':
#cookie + session
request.session['is_login']=True
request.session['user']=name
return redirect('/index/')
return render(request,'login.html') def index(request):
#cookie + session,加上None防止找不到报错
if request.session.get('is_login',None):
name = request.session.get('user',None)
return render(request,'index.html',locals())
else:
return redirect('/login/')

六、分页

1、代码实现简单的上一页、下一页

#index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
{% for row in user_list %}
<li>{{ row.name }} - {{ row.age }}</li>
{% endfor %}
</ul>
<a href="/index.html?p={{ prev_page }}">上一页</a>
<a href="/index.html?p={{ next_page }}">下一页</a>
</body>
</html> #views.py from django.shortcuts import render USER_LIST = []
for i in range(1,1000):
temp = {'name':'root' + str(i),'age':i}
USER_LIST.append(temp) def index(request):
#简单的分页
per_page_count = 10
current_page = int(request.GET.get('p'))
#p=1 索引0-10
#p=2 索引10-20
#http://127.0.0.1:8005/index.html?p=1
start = (current_page-1)*per_page_count
end = current_page*per_page_count
user_list = USER_LIST[start:end]
if current_page <= 1:
prev_page = 1
next_page = current_page + 1
else:
prev_page = current_page - 1
next_page = current_page + 1
return render(request,'index.html',locals())

2、Django内置分页+扩展

  -- Django自带的内置分页只能显示上一页和下一页按钮,不能显示中间的页码,所以这里需要扩展一下

views.py

from django.shortcuts import render
from django.core.paginator import Paginator,EmptyPage,PageNotAnInteger USER_LIST = []
for i in range(1,230):
temp = {'name':'root' + str(i),'age':i}
USER_LIST.append(temp) #扩展自带分页
class CustomPaginator(Paginator):
def __init__(self,current_page,per_pager_num,*args,**kwargs):
#当前页
self.current_page = int(current_page)
#每页最多显示多少页码
self.per_pager_num = int(per_pager_num)
super(CustomPaginator,self).__init__(*args,**kwargs)
def page_num_range(self):
#如果总页数小于每页最多显示页码数量,就显示1-总页码
if self.num_pages < self.per_pager_num:
return range(1,self.num_pages+1)
#如果总页数有很多
part = int(self.per_pager_num/2)
if self.current_page <= part:
return range(1,self.per_pager_num+1)
#最后一页只显示最后10个页码即可
if (self.current_page+part) > self.num_pages:
return range(self.num_pages-self.per_pager_num+1,self.num_pages+1)
return range(self.current_page-part,self.current_page+part+1) def index1(request):
#django自带分页
current_page = request.GET.get('p')
#Paginator对象,每页显示10个页码,10条数据
paginator = CustomPaginator(current_page,11,USER_LIST,10)
try:
#Page对象
posts = paginator.page(current_page)
except PageNotAnInteger:
#如果输入不是数字,就显示第一页
posts = paginator.page(1)
except EmptyPage:
#如果输入为空或数字过大,就显示最后一页
posts = paginator.page(paginator.num_pages)
return render(request,'index1.html',locals())

index1.html,在templates下新建include/pager.html,把所有关于分页的代码单独放到一个文件中,以后可以随时调用

#注意:这里使用include引用了文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
{% for row in posts %}
<li>{{ row.name }} - {{ row.age }}</li>
{% endfor %}
</ul>
{% include 'include/pager.html' %}
</body>
</html>

引用pager.html文件

<a href="/index1.html?p=1">首页</a>
{% if posts.has_previous %}
<a href="/index1.html?p={{ posts.previous_page_number }}">上一页</a>
{% endif %}
{% for i in paginator.page_num_range %}
{% if i == posts.number %}
<a style="font-size: 20px;color: blue;" href="/index1.html?p={{ i }}">{{ i }}</a>
{% else %}
<a href="/index1.html?p={{ i }}">{{ i }}</a>
{% endif %}
{% endfor %}
{% if posts.has_next %}
<a href="/index1.html?p={{ posts.next_page_number }}">下一页</a>
{% endif %}
<span>
[{{ posts.number }}/{{ paginator.num_pages }}]
</span>
<a href="/index1.html?p={{ paginator.num_pages }}">尾页</a>

2、自定义分页

  —   分页功能在每个网站都是必要的,对于分页来说,其实就是根据用户的输入计算出应该在数据库表中的起始位置,并且需要在页面上显示分页的页面。如:[上一页][1][2][3][4][5][下一页]

1、设定每页显示数据条数

2、用户输入页码(第一页、第二页...)

3、设定显示多少页号

4、获取当前数据总条数

5、根据设定显示多少页号和数据总条数计算出,总页数

6、根据设定的每页显示条数和当前页码,计算出需要取数据表的起始位置

7、在数据表中根据起始位置取值,页面上输出数据

8、输出分页html,如:[上一页][1][2][3][4][5][下一页]

实例:在APP中创建pager.py

#在APP中创建pager.py

class Pagination(object):
def __init__(self,total_count,current_page,per_page_item_num=20,max_page_num=7):
#数据总条目
self.total_count = total_count
#当前页
try:
v = int(current_page)
if v <= 0:
v = 1
self.current_page = v
except Exception as e:
self.current_page = 1
#每页显示条目
self.per_page_item_num = per_page_item_num
#每页最多显示页码
self.max_page_num = max_page_num def start(self):
return (self.current_page-1)*self.per_page_item_num def end(self):
return self.current_page*self.per_page_item_num @property
def num_pages(self):
# 总页数
a,b = divmod(self.total_count,self.per_page_item_num)
if b == 0:
return a
return a+1 def page_num_range(self):
#如果总页数小于每页最多显示页码数量,就显示1-总页码
if self.num_pages < self.max_page_num:
return range(1,self.num_pages+1)
#如果总页数有很多
part = int(self.max_page_num/2)
if self.current_page <= part:
return range(1,self.max_page_num+1)
#最后一页只显示最后10个页码即可
if (self.current_page+part) > self.num_pages:
return range(self.num_pages-self.max_page_num+1,self.num_pages+1)
return range(self.current_page-part,self.current_page+part+1) def page_str(self):
page_list = []
first_page = '<li><a href="/index2.html?p=1">首页</a></li>'
page_list.append(first_page)
if self.current_page == 1:
prev_page = '<li><a href="#">上一页</a>'
else:
prev_page = '<li><a href="/index2.html?p=%s">上一页</a></li>'%(self.current_page-1)
page_list.append(prev_page)
for i in self.page_num_range():
if i == self.current_page:
temp = '<li class="active"><a href="/index2.html?p=%s">%s</a></li>' % (i, i)
else:
temp = '<li><a href="/index2.html?p=%s">%s</a></li>'%(i,i)
page_list.append(temp)
if self.current_page == self.num_pages:
next_page = '<li><a href="#">下一页</a></li>'
else:
next_page = '<li><a href="/index2.html?p=%s">下一页</a></li>' % (self.current_page + 1)
page_list.append(next_page)
last_page = '<li><a href="/index2.html?p=%s">尾页</a></li>'%self.num_pages
page_list.append(last_page)
return ''.join(page_list)

创建views函数

from django.shortcuts import render
from django.core.paginator import Paginator,EmptyPage,PageNotAnInteger USER_LIST = []
for i in range(1,600):
temp = {'name':'root' + str(i),'age':i}
USER_LIST.append(temp) def index2(request):
from app.pager import Pagination
current_page = request.GET.get('p')
page_obj = Pagination(600,current_page)
data = USER_LIST[page_obj.start():page_obj.end()]
return render(request,'index2.html',locals())

创建index2.html,这里需要引入bootstrap的一个组件

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"/>
</head>
<body>
<ul>
{% for row in data %}
<li>{{ row.name }} - {{ row.age }}</li>
{% endfor %}
</ul>
<ul class="pagination">
{{ page_obj.page_str|safe }}
</ul>
</body>
</html>

总结,分页时需要做三件事:

创建处理分页数据的类

根据分页数据获取数据

输出分页HTML,即:[上一页][1][2][3][4][5][下一页]

七、序列化

  序列化就是把对象转换成可以保存在本地文件中的数据类型,关于Django中的序列化主要应用在将数据库中检索的数据返回给客户端用户,特别的Ajax请求一般返回的为Json格式。

1、Python中dumps就是序列化,loads就是反序列化;

2、JavaScript中

    对象转换成字符串 -- str = JSON.stringify({'k':'v'})
    字符串转换成对象 -- dict = JSON.parse(str)

3、三种情况

QuerySet内部是:

对象(用all,filter获取数据):serializers.serializer('json',QuerySet)

字典(用values获取):list(QuerySet)

元组(用values_list获取):list(QuerySet)

from django.core import serializers
import json def get_data(request):
response = {'status': True, 'data': None}
try:
user_list = UserInfo.objects.all()
# serializers只能序列化queryset对象,转换为字符串
response['data'] = serializers.serialize('json', user_list)
#如果queryset内部是字典或元组,values和values_list获取,道理一样
user_list = UserInfo.objects.values('id','username')
#只需要把外部的queryset变成列表形式,就可以直接json序列化了
response['data'] = list(user_list)
except Exception as e:
response['status'] = False
ret = json.dumps(response)
return HttpResponse(ret)

4、由于json.dumps时无法处理datetime日期,所以可以通过自定义处理器来做扩展,如:

import json
from datetime import date
from datetime import datetime class JsonCustomEncoder(json.JSONEncoder):
def default(self, field):
if isinstance(field, datetime):
return field.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(field, date):
return field.strftime('%Y-%m-%d')
elif isinstance(field, Response):
return field.__dict__ # 对象以字典的方式输出
else:
return json.JSONEncoder.default(self, field) class Response:
def __init__(self):
self.status = True
self.data = 'charlie' data = {
'k1': datetime.now(),
'k2': Response(),
}
ds = json.dumps(data, cls=JsonCustomEncoder)
print(ds)
# {"k1": "2019-03-08 16:18:28", "k2": {"status": true, "data": "charlie"}}

Django【进阶篇】的相关教程结束。

《Django【进阶篇】.doc》

下载本文的Word格式文档,以方便收藏与打印。