目录
一、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': '3306', #你的数据库端口 } }
注意:
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
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 == '123': # 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 == '123': #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"}}