Web框架Django(二)
一、Model
到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞:
- 创建数据库,设计表结构和字段
- 使用 MySQLdb 来连接数据库,并编写数据访问层代码
- 业务逻辑层去调用数据访问层执行数据库操作
import MySQLdb def GetList(sql): db = MySQLdb.connect(user='root', db='wupeiqidb', passwd='1234', host='localhost') cursor = db.cursor() cursor.execute(sql) data = cursor.fetchall() db.close() return data def GetSingle(sql): db = MySQLdb.connect(user='root', db='wupeiqidb', passwd='1234', host='localhost') cursor = db.cursor() cursor.execute(sql) data = cursor.fetchone() db.close() return data
django为使用一种新的方式,即:关系对象映射(Object Relational Mapping,简称ORM)。
PHP:activerecord
Java:Hibernate
C#:Entity Framework
django中遵循 Code Frist 的原则,即:根据代码中定义的类来自动生成数据库表。
1、创建表
1.1 基本结构
from django.db import models class userinfo(models.Model): name = models.CharField(max_length=30) email = models.EmailField() memo = models.TextField()
1.1.1 字段
AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bigint自增列,必须填入参数 primary_key=True 注:当model中如果没有自增列,则自动会创建一个列名为id的列 from django.db import models class UserInfo(models.Model): # 自动创建一个列名为id的且为自增的整数列 username = models.CharField(max_length=32) class Group(models.Model): # 自定义自增列 nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) SmallIntegerField(IntegerField): - 小整数 -32768 ~ 32767 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正小整数 0 ~ 32767 IntegerField(Field) - 整数列(有符号的) -2147483648 ~ 2147483647 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正整数 0 ~ 2147483647 BigIntegerField(IntegerField): - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 自定义无符号整数字段 class UnsignedIntegerField(models.IntegerField): def db_type(self, connection): return 'integer UNSIGNED' PS: 返回值为字段在数据库中的属性,Django字段默认的值为: 'AutoField': 'integer AUTO_INCREMENT', 'BigAutoField': 'bigint AUTO_INCREMENT', 'BinaryField': 'longblob', 'BooleanField': 'bool', 'CharField': 'varchar(%(max_length)s)', 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', 'DateField': 'date', 'DateTimeField': 'datetime', 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'DurationField': 'bigint', 'FileField': 'varchar(%(max_length)s)', 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'double precision', 'IntegerField': 'integer', 'BigIntegerField': 'bigint', 'IPAddressField': 'char(15)', 'GenericIPAddressField': 'char(39)', 'NullBooleanField': 'bool', 'OneToOneField': 'integer', 'PositiveIntegerField': 'integer UNSIGNED', 'PositiveSmallIntegerField': 'smallint UNSIGNED', 'SlugField': 'varchar(%(max_length)s)', 'SmallIntegerField': 'smallint', 'TextField': 'longtext', 'TimeField': 'time', 'UUIDField': 'char(32)', BooleanField(Field) - 布尔值类型 NullBooleanField(Field): - 可以为空的布尔值 CharField(Field) - 字符类型 - 必须提供max_length参数, max_length表示字符长度 TextField(Field) - 文本类型 EmailField(CharField): - 字符串类型,Django Admin以及ModelForm中提供验证机制 IPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制 GenericIPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 - 参数: protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6" unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both" URLField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证 URL SlugField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) CommaSeparatedIntegerField(CharField) - 字符串类型,格式必须为逗号分割的数字 UUIDField(Field) - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 FilePathField(Field) - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能 - 参数: path, 文件夹路径 match=None, 正则匹配 recursive=False, 递归下面的文件夹 allow_files=True, 允许文件 allow_folders=False, 允许文件夹 FileField(Field) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage ImageField(FileField) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage width_field=None, 上传图片的高度保存的数据库字段名(字符串) height_field=None 上传图片的宽度保存的数据库字段名(字符串) DateTimeField(DateField) - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] DateField(DateTimeCheckMixin, Field) - 日期格式 YYYY-MM-DD TimeField(DateTimeCheckMixin, Field) - 时间格式 HH:MM[:ss[.uuuuuu]] DurationField(Field) - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型 FloatField(Field) - 浮点型 DecimalField(Field) - 10进制小数 - 参数: max_digits,小数总长度 decimal_places,小数位长度 BinaryField(Field) - 二进制类型
1.1.2 参数
null 数据库中字段是否可以为空 db_column 数据库中字段的列名 db_tablespace default 数据库中字段的默认值 primary_key 数据库中字段是否为主键 db_index 数据库中字段是否可以建立索引 unique 数据库中字段是否可以建立唯一索引 unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引 unique_for_month 数据库中字段【月】部分是否可以建立唯一索引 unique_for_year 数据库中字段【年】部分是否可以建立唯一索引 verbose_name Admin中显示的字段名称 blank Admin中是否允许用户输入为空 editable Admin中是否可以编辑 help_text Admin中该字段的提示信息 choices Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作 如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1) error_messages 自定义错误信息(字典类型),从而定制想要显示的错误信息; 字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date 如:{'null': "不能为空.", 'invalid': '格式错误'} validators 自定义错误验证(列表类型),从而定制想要的验证规则 from django.core.validators import RegexValidator from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\ MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator 如: test = models.CharField( max_length=32, error_messages={ 'c1': '优先错信息1', 'c2': '优先错信息2', 'c3': '优先错信息3', }, validators=[ RegexValidator(regex='root_\d+', message='错误了', code='c1'), RegexValidator(regex='root_112233\d+', message='又错误了', code='c2'), EmailValidator(message='又错误了', code='c3'), ] )
1.1.3 元信息
class UserInfo(models.Model): nid = models.AutoField(primary_key=True) username = models.CharField(max_length=32) class Meta: # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名 db_table = "table_name" # 联合索引 index_together = [ ("pub_date", "deadline"), ] # 联合唯一索引 unique_together = (("driver", "restaurant"),) # admin中显示的表名称 verbose_name # verbose_name加s verbose_name_plural 更多:https://docs.djangoproject.com/en/1.10/ref/models/options/
1.1.4 拓展知识
1.触发Model中的验证和错误提示有两种方式: a. Django Admin中的错误信息会优先根据Admiin内部的ModelForm错误信息提示,如果都成功,才来检查Model的字段并显示指定错误信息 b. 调用Model对象的 clean_fields 方法,如: # models.py class UserInfo(models.Model): nid = models.AutoField(primary_key=True) username = models.CharField(max_length=32) email = models.EmailField(error_messages={'invalid': '格式错了.'}) # views.py def index(request): obj = models.UserInfo(username='11234', email='uu') try: print(obj.clean_fields()) except Exception as e: print(e) return HttpResponse('ok') # Model的clean方法是一个钩子,可用于定制操作,如:上述的异常处理。 2.Admin中修改错误提示 # admin.py from django.contrib import admin from model_club import models from django import forms class UserInfoForm(forms.ModelForm): username = forms.CharField(error_messages={'required': '用户名不能为空.'}) email = forms.EmailField(error_messages={'invalid': '邮箱格式错误.'}) age = forms.IntegerField(initial=1, error_messages={'required': '请输入数值.', 'invalid': '年龄必须为数值.'}) class Meta: model = models.UserInfo # fields = ('username',) fields = "__all__" class UserInfoAdmin(admin.ModelAdmin): form = UserInfoForm admin.site.register(models.UserInfo, UserInfoAdmin)
1.2、连表结构
- 一对多:models.ForeignKey(其他表)
- 多对多:models.ManyToManyField(其他表)
- 一对一:models.OneToOneField(其他表)
应用场景: 一对多:当一张表中创建一行数据时,有一个单选的下拉框(可以被重复选择) 例如:创建用户信息时候,需要选择一个用户类型【普通用户】【金牌用户】【铂金用户】等。 多对多:在某表中创建一行数据是,有一个可以多选的下拉框 例如:创建用户信息,需要为用户指定多个爱好 一对一:在某表中创建一行数据时,有一个单选的下拉框(下拉框中的内容被用过一次就消失了 例如:原有含10列数据的一张表保存相关信息,经过一段时间之后,10列无法满足需求,需要为原来的表再添加5列数据
字段以及参数
ForeignKey(ForeignObject) # ForeignObject(RelatedField) to, # 要进行关联的表名 to_field=None, # 要关联的表中的字段名称 on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为 # 如: - models.CASCADE,删除关联数据,与之关联也删除 - models.DO_NOTHING,删除关联数据,引发错误IntegrityError - models.PROTECT,删除关联数据,引发错误ProtectedError - models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空) - models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值) - models.SET,删除关联数据, a. 与之关联的值设置为指定值,设置:models.SET(值) b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象) def func(): return 10 class MyModel(models.Model): user = models.ForeignKey( to="User", to_field="id" on_delete=models.SET(func),) related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all() related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名') limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件: # 如: - limit_choices_to={'nid__gt': 5} - limit_choices_to=lambda : {'nid__gt': 5} from django.db.models import Q - limit_choices_to=Q(nid__gt=10) - limit_choices_to=Q(nid=8) | Q(nid__gt=10) - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') db_constraint=True # 是否在数据库中创建外键约束 parent_link=False # 在Admin中是否显示关联数据 OneToOneField(ForeignKey) to, # 要进行关联的表名 to_field=None # 要关联的表中的字段名称 on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为 ###### 对于一对一 ###### # 1. 一对一其实就是 一对多 + 唯一索引 # 2.当两个类之间有继承关系时,默认会创建一个一对一字段 # 如下会在A表中额外增加一个c_ptr_id列且唯一: class C(models.Model): nid = models.AutoField(primary_key=True) part = models.CharField(max_length=12) class A(C): id = models.AutoField(primary_key=True) code = models.CharField(max_length=1) ManyToManyField(RelatedField) to, # 要进行关联的表名 related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all() related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名') limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件: # 如: - limit_choices_to={'nid__gt': 5} - limit_choices_to=lambda : {'nid__gt': 5} from django.db.models import Q - limit_choices_to=Q(nid__gt=10) - limit_choices_to=Q(nid=8) | Q(nid__gt=10) - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') symmetrical=None, # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段 # 做如下操作时,不同的symmetrical会有不同的可选字段 models.BB.objects.filter(...) # 可选字段有:code, id, m1 class BB(models.Model): code = models.CharField(max_length=12) m1 = models.ManyToManyField('self',symmetrical=True) # 可选字段有: bb, code, id, m1 class BB(models.Model): code = models.CharField(max_length=12) m1 = models.ManyToManyField('self',symmetrical=False) through=None, # 自定义第三张表时,使用字段用于指定关系表 through_fields=None, # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表 from django.db import models class Person(models.Model): name = models.CharField(max_length=50) class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField( Person, through='Membership', through_fields=('group', 'person'), ) class Membership(models.Model): group = models.ForeignKey(Group, on_delete=models.CASCADE) person = models.ForeignKey(Person, on_delete=models.CASCADE) inviter = models.ForeignKey( Person, on_delete=models.CASCADE, related_name="membership_invites", ) invite_reason = models.CharField(max_length=64) db_constraint=True, # 是否在数据库中创建外键约束 db_table=None, # 默认创建第三张表时,数据库中表的名称
2、操作表
2.1、基本操作
基本操作
#增 # models.User_type.objects.create(title="黑金用户") #方法二,比较少用 # obj = models.User_type(title="小白用户") # obj.save() #删 #models.User_type.objects.filter(title="小白用户").delete() # 删除指定条件的数据 #改 #models.User_type.objects.filter(title="黑金用户").update(title="黑卡用户") # 修改指定条件的数据 #方法二,比较少用 # obj = models.Tb1.objects.get(id=1) # obj.c1 = '111' # obj.save() # 修改单条数据 #查 # models.User_type.objects.get(title="大白用户") # 获取单条数据,不存在则报错(不建议) # models.User_type.objects.all() # 获取全部 # models.User_type.objects.filter(title="小白用户") # 获取指定条件的数据 # models.User_type.objects.exclude(title="黄金用户") # 排除指定条件的数据
2.2、进阶操作(了不起的双下划线)
利用双下划线将字段和对应的操作连接起来
进阶操作
# 获取个数 # # 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 #后面出现filter代表having from django.db.models import Count #models.User_info.objects.values("name","age").annotate() #没有起作用 #SELECT "app01_user_info"."name", "app01_user_info"."age" FROM "app01_user_info" #models.User_info.objects.values("age").annotate(xxx=Count("age")) #SELECT "app01_user_info"."age", COUNT("app01_user_info"."age") AS "xxx" FROM "app01_user_info" GROUP BY "app01_user_info"."age" #models.User_info.objects.values("age").annotate(xxx=Count("age")).filter(xxx__gt=2) #年龄相同次数大于2的查出来 #SELECT "app01_user_info"."age", COUNT("app01_user_info"."age") AS "xxx" FROM "app01_user_info" GROUP BY "app01_user_info"."age" HAVING COUNT("app01_user_info"."age") > 2 #注意两次filter 第一次代表where 第二次代表having #models.User_info.objects.filter(id__gt=2).values("age").annotate(xxx=Count("age")).filter(xxx__gt=2) #SELECT "app01_user_info"."age", COUNT("app01_user_info"."age") AS "xxx" FROM "app01_user_info" WHERE "app01_user_info"."id" > 2 GROUP BY "app01_user_info"."age" HAVING COUNT("app01_user_info"."age") > 2 # 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)
2.3、其他操作
F、Q
# F # from django.db.models import F # models.User_info.objects.all().update(age=F("age")+5) # age列加5 # Q # 方式一: # Q(nid__gt=10) # Q(nid=8) | Q(nid__gt=10) # Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') #-------------------------------以下三种查找方式相同效果 # obj = models.User_info.objects.filter(id=307,name="egon") # condition ={ # 'id':307, # 'name':'egon', # } # obj = models.User_info.objects.filter(**condition) #obj = models.User_info.objects.filter(Q(id=307) & Q(name="egon")) --------------------------------- # 方式二: # q1 = Q() # q1.connector = 'OR' # q1.children.append(('id', 1)) # q1.children.append(('id', 10)) # q1.children.append(('id', 9)) # q2 = Q() # q2.connector = 'OR' # q2.children.append(('c1', 1)) # q2.children.append(('c1', 10)) # q2.children.append(('c1', 9)) # q3 = Q() # q3.connector = 'AND' # q3.children.append(('id', 1)) # q3.children.append(('id', 2)) # q1.add(q3, 'OR') # con = Q() # con.add(q1, 'AND') # con.add(q2, 'AND') #(id=1 or id=10 or id=9 or (id=1 and id=2)) and (c1=1 or c1=10 or c1=9) #等于上面 # models.Tb1.objects.filter(con) # 执行原生SQL # # from django.db import connection, connections # cursor = connection.cursor() # cursor = connections['default'].cursor() # cursor.execute("""SELECT * from auth_user where id = %s""", [1]) # row = cursor.fetchone() # Q演示 condition_dict = { 'k1':[1,2,3,4], 'k2':[1,], 'k3':[11,] } con = Q() for k,v in condition_dict.items(): q = Q() q.connector = 'OR' for i in v: q.children.append(('id',1)) con.add(q,'AND') models.User_info.objects.filter(con)
extra
# extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None) # a. 映射 select select_params=None select 此处 from 表 # b. 条件 where=None params=None select * from 表 where 此处 # c. 表 table select * from 表,此处 # d. 排序 prder_by=None select * from 表 order by 此处 result = models.User_info.objects.extra( select={"newid":"selet count(1) from app01_usertype where id>%s"}, select_params=[1,], where = ["age>%s"], params=[18,], order_by=["-age"], tables=["app01_usertype"] ) SELECT (selet count(1) from app01_usertype where id>1) AS "newid", "app01_user_info"."id", "app01_user_info"."name", "app01_user_info"."age", "app01_user_info"."ut_id" FROM "app01_user_info" , "app01_usertype" WHERE (age>18) ORDER BY "app01_user_info"."age" DESC #------------------------------------- v = models.User_info.objects.all().extra( select={'n':"select count(1) from app01_user_type where uid >2"} ) SELECT (select count(1) from app01_user_type where uid >2) AS "n", "app01_user_info"."id", "app01_user_info"."name", "app01_user_info"."age", "app01_user_info"."ut_id" FROM "app01_user_info" #------------------------------------- v = models.User_info.objects.all().extra( select={'n':"select count(1) from app01_user_type where uid=%s or uid=%s"}, select_params=[1,3] ) SELECT (select count(1) from app01_user_type where uid=1 or uid=3) AS "n", "app01_user_info"."id", "app01_user_info"."name", "app01_user_info"."age", "app01_user_info"."ut_id" FROM "app01_user_info" #------------- select_params ------------------------ v = models.User_info.objects.all().extra( select={ 'm':"select count(1) from app01_user_type where uid=%s or uid=%s", 'n':"select count(1) from app01_user_type where uid=%s or uid=%s", },select_params=[1,2,3,4]) SELECT (select count(1) from app01_user_type where uid=1 or uid=2) AS "m", (select count(1) from app01_user_type where uid=3 or uid=4) AS "n", "app01_user_info"."id", "app01_user_info"."name", "app01_user_info"."age", "app01_user_info"."ut_id" FROM "app01_user_info" #-------------- where ----------------------- v = models.User_info.objects.extra( where=["id=%s or id=%s","name=%s"], params=[1,307,"egon"] ) SELECT "app01_user_info"."id", "app01_user_info"."name", "app01_user_info"."age", "app01_user_info"."ut_id" FROM "app01_user_info" WHERE (id=1 or id=307) AND (name=egon) #-------------- table ----------------------- #笛卡尔积 #示例一: result = models.User_info.objects.extra( tables=["app01_user_type"], ) SELECT "app01_user_info"."id", "app01_user_info"."name", "app01_user_info"."age", "app01_user_info"."ut_id" FROM "app01_user_info" , "app01_user_type" #示例二: result = models.User_info.objects.extra( tables=["app01_user_type"], where=["app01_usertype.id = app01_userinfo.ut_id"] ) SELECT "app01_user_info"."id", "app01_user_info"."name", "app01_user_info"."age", "app01_user_info"."ut_id" FROM "app01_user_info" , "app01_user_type" WHERE (app01_usertype.id = app01_userinfo.ut_id)
示例:
a、 一对多ForeignKey联表操作
from django.db import models class UserType(models.Model): title = models.CharField(max_length=32) class UserInfo(models.Model): name = models.CharField(max_length=16) age = models.IntegerField() ut = models.ForeignKey('UserType')
models.UserType.objects.create(title='普通用户') models.UserType.objects.create(title='二逼用户') models.UserType.objects.create(title='牛逼用户') models.UserInfo.objects.create(name='方少伟',age=18,ut_id=1) models.UserInfo.objects.create(name='由秦兵',age=18,ut_id=2) models.UserInfo.objects.create(name='刘庚',age=18,ut_id=2) models.UserInfo.objects.create(name='陈涛',age=18,ut_id=3) models.UserInfo.objects.create(name='王者',age=18,ut_id=3) models.UserInfo.objects.create(name='杨涵',age=18,ut_id=1) # 正向操作 obj = models.UserInfo.objects.all().first() print(obj.name, obj.age, obj.ut.title) # 反向操作 obj.表名小写_set.all() obj = models.UserType.objects.all().first() for row in obj.user_info_set.all(): print(row.name, row.age) # obj.表名小写_set.filter() result = models.UserType.objects.all() for item in result: print(item.title, item.user_info_set.all()) print(item.user_info_set.filter(name="方少伟")) # 字典格式values result = models.UserInfo.objects.all().values("id", "name") for row in result: print(row) # 字典格式正向跨表 result = models.UserInfo.objects.all().values("id", "name", "ut__title") for row in result: print(row["id"], row["name"], row["ut__title"]) # 字典格式反向跨表 result = models.UserType.objects.values('id', 'title', 'userinfo__name','userinfo__age') for row in result: print(row["id"], row["name"], row["userinfo__name"],row["userinfo__age"]) # 元组格式values_list result = models.UserInfo.objects.all().values_list("id","name") for row in result: print(row) # 元组格式正向跨表 result = models.UserInfo.objects.all().values_list("id", "name","ut__title") for row in result: print(row) # 元组格式反向跨表 result = models.UserType.objects.all().values_list('id', 'title', 'userinfo__name','userinfo__age') for row in result: print(row) # 查询时过滤正向跨表 models.UserInfo.objects.filter(ut__title='超级用户').values('id', 'name', 'ut__title') # 查询时过滤正向跨表?????????????????????????? ######models.UserType.objects.filter(userinfo__title='超级用户').values('id', 'name', 'userinfo__title')
b、 多对多操作
方式一:通过外键创建第三张表
class Boy(models.Model): name = models.CharField(max_length=32) class Girl(models.Model): nick = models.CharField(max_length=32) class Love(models.Model): b = models.ForeignKey("Boy") g = models.ForeignKey("Girl") class Meta: unique_together = [ ("b","g"), ]
SQL演示
#表里插入数据 objs = [ models.Boy(name='方少伟'), models.Boy(name='游勤斌'), models.Boy(name='于浩'), models.Boy(name='陈涛'), ] models.Boy.objects.bulk_create(objs,4) result = [ models.Girl(nick='于浩姐姐'), models.Girl(nick='景甜'), models.Girl(nick='刘亦非'), models.Girl(nick='苍老师'), ] models.Girl.objects.bulk_create(result, 4) models.Love.objects.create(b_id=1,g_id=1) models.Love.objects.create(b_id=1,g_id=2) models.Love.objects.create(b_id=1,g_id=3) models.Love.objects.create(b_id=2,g_id=4) ################### 查找和我有关系的女孩 四种方式 ################ obj = models.Boy.objects.filter(name="方少伟").first() love_list = obj.love_set.all() for row in love_list: print(row.g.nick) love_list = models.Love.objects.filter(b__name="方少伟") for row in love_list: print(row.g.nick) #下面两个效果好 love_list = models.Love.objects.filter(b__name="方少伟").values("g__nick") for item in love_list: print(item["g__nick"]) love_list = models.Love.objects.filter(b__name="方少伟").select_related("g") for obj in love_list: print(obj.g.nick)
方式二:通过 ManyToManyField 创建第三张表
class Boy(models.Model): name = models.CharField(max_length=32) m = models.ManyToManyField("Girl") class Girl(models.Model): nick = models.CharField(max_length=32)
SQL 演示
obj = models.Boy.objects.filter(name="方少伟").first() # print(obj.id,obj.name) # obj.m.add(2) # obj.m.add(1,3) # obj.m.add(*[4,]) # obj.m.remove(2) # obj.m.remove(1,3) # obj.m.remove(*[4,]) # obj.m.set([1,4,]) # girl_list = obj.m.all() # girl_list = obj.m.filter(nick="苍老师") # obj.m.clear() obj = models.Girl.objects.filter(nick="苍老师").first() v = obj.boy_set.all()
方式三:通过 外键 和 ManyToManyField 创建
class Boy(models.Model): name = models.CharField(max_length=32) m = models.ManyToManyField("Girl",through="Love",through_fields=("b","g",)) class Girl(models.Model): nick = models.CharField(max_length=32) class Love(models.Model): b = models.ForeignKey("Boy") g = models.ForeignKey("Girl") class Meta: unique_together = [ ("b","g"), ]
SQL 操作
obj = models.Boy.objects.filter(name="方少伟").first() #只可以查或清空 obj.m.clear() obj.m.all()
c. 关联
1. 方式一:
ForeignKey 关联两个表
class UserInfo(models.Model): nickname = models.CharField(max_length=32) username = models.CharField(max_length=32) password = models.CharField(max_length=64) gender_choices = ( (1,'男'), (2,'女'), ) gender = models.IntegerField(choices=gender_choices) # obj对象男.a.all() 反向查找 # obj对象女.b.all() 反向查找 class U2U(models.Model): b = models.ForeignKey("UserInfo",related_name="a") g = models.ForeignKey("UserInfo",related_name="b") # obj对象男.a_set.all() 反向查找 # obj对象女.b_set.all() 反向查找 # class U2U(models.Model): # b = models.ForeignKey("UserInfo",related_query_name="a") # g = models.ForeignKey("UserInfo",related_query_name="b")
插入性别两种方式
# models.U2U.objects.create(b_id=2,g_id=5) # models.U2U.objects.create(b_id=2,g_id=4) # models.U2U.objects.create(b_id=2,g_id=6) #这种方式也是插入的ID # boy = models.UserInfo.objects.filter(gender=1,id=2).first() # girl = models.UserInfo.objects.filter(gender=2,id=5).first() # models.U2U.objects.create(b_id=boy,g_id=girl)
查找数据
two = models.UserInfo.objects.filter(id=2).first() result = two.girls.all() for u in result: print(u.g.nickname)
方式二:
class UserInfo(models.Model): nickname = models.CharField(max_length=32) username = models.CharField(max_length=32) password = models.CharField(max_length=64) gender_choices = ( (1,'男'), (2,'女'), ) gender = models.IntegerField(choices=gender_choices) m = models.ManyToManyField("UserInfo") #-------------------------------------------- # 男生查女生 # xz.m 用的第二张表的第一列 xz = models.UserInfo.objects.filter(id=1).first() u = xz.m.all() for row in u: print(row.nickname) # 女生查男生 # xz.userinfo_set 用的第二张表的第二列 xz = models.UserInfo.objects.filter(id=4).first() v = xz.userinfo_set.all() for row in v: print(row.nickname)
2.4、连表操作(了不起的双下划线)
利用双下划线和 _set 将表之间的操作连接起来
表结构实例
class UserProfile(models.Model): user_info = models.OneToOneField('UserInfo') username = models.CharField(max_length=64) password = models.CharField(max_length=64) def __unicode__(self): return self.username class UserInfo(models.Model): user_type_choice = ( (0, u'普通用户'), (1, u'高级用户'), ) user_type = models.IntegerField(choices=user_type_choice) name = models.CharField(max_length=32) email = models.CharField(max_length=32) address = models.CharField(max_length=128) def __unicode__(self): return self.name class UserGroup(models.Model): caption = models.CharField(max_length=64) user_info = models.ManyToManyField('UserInfo') def __unicode__(self): return self.caption class Host(models.Model): hostname = models.CharField(max_length=64) ip = models.GenericIPAddressField() user_group = models.ForeignKey('UserGroup') def __unicode__(self): return self.hostname
一对一操作
user_info_obj = models.UserInfo.objects.filter(id=1).first() print user_info_obj.user_type print user_info_obj.get_user_type_display() print user_info_obj.userprofile.password user_info_obj = models.UserInfo.objects.filter(id=1).values('email', 'userprofile__username').first() print user_info_obj.keys() print user_info_obj.values()
一对多操作
类似一对一 1、搜索条件使用 __ 连接 2、获取值时使用 . 连接
多对多操作
user_info_obj = models.UserInfo.objects.get(name=u'徐亚平') user_info_objs = models.UserInfo.objects.all() group_obj = models.UserGroup.objects.get(caption='CEO') group_objs = models.UserGroup.objects.all() # 添加数据 #group_obj.user_info.add(user_info_obj) #group_obj.user_info.add(*user_info_objs) # 删除数据 #group_obj.user_info.remove(user_info_obj) #group_obj.user_info.remove(*user_info_objs) # 添加数据 #user_info_obj.usergroup_set.add(group_obj) #user_info_obj.usergroup_set.add(*group_objs) # 删除数据 #user_info_obj.usergroup_set.remove(group_obj) #user_info_obj.usergroup_set.remove(*group_objs) # 获取数据 #print group_obj.user_info.all() #print group_obj.user_info.all().filter(id=1) # 获取数据 #print user_info_obj.usergroup_set.all() #print user_info_obj.usergroup_set.all().filter(caption='CEO') #print user_info_obj.usergroup_set.all().filter(caption='DBA')
扩展:
a、自定义上传
def upload_file(request): if request.method == "POST": obj = request.FILES.get('fafafa') f = open(obj.name, 'wb') for chunk in obj.chunks(): f.write(chunk) f.close() return render(request, 'file.html')
b、Form上传文件实例
Form
class FileForm(forms.Form): ExcelFile = forms.FileField()
Model
from django.db import models class UploadFile(models.Model): userid = models.CharField(max_length = 30) file = models.FileField(upload_to = './upload/') date = models.DateTimeField(auto_now_add=True)
View
def UploadFile(request): uf = AssetForm.FileForm(request.POST,request.FILES) if uf.is_valid(): upload = models.UploadFile() upload.userid = 1 upload.file = uf.cleaned_data['ExcelFile'] upload.save() print upload.file
二、Form
django中的Form一般有两种功能:
- 输入html
- 验证用户输入
Form
#!/usr/bin/env python # -*- coding:utf-8 -*- import re from django import forms from django.core.exceptions import ValidationError def mobile_validate(value): mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$') if not mobile_re.match(value): raise ValidationError('手机号码格式错误') class PublishForm(forms.Form): user_type_choice = ( (0, u'普通用户'), (1, u'高级用户'), ) user_type = forms.IntegerField(widget=forms.widgets.Select(choices=user_type_choice, attrs={'class': "form-control"})) title = forms.CharField(max_length=20, min_length=5, error_messages={'required': u'标题不能为空', 'min_length': u'标题最少为5个字符', 'max_length': u'标题最多为20个字符'}, widget=forms.TextInput(attrs={'class': "form-control", 'placeholder': u'标题5-20个字符'})) memo = forms.CharField(required=False, max_length=256, widget=forms.widgets.Textarea(attrs={'class': "form-control no-radius", 'placeholder': u'详细描述', 'rows': 3})) phone = forms.CharField(validators=[mobile_validate, ], error_messages={'required': u'手机不能为空'}, widget=forms.TextInput(attrs={'class': "form-control", 'placeholder': u'手机号码'})) email = forms.EmailField(required=False, error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'}, widget=forms.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))
View
def publish(request): ret = {'status': False, 'data': '', 'error': '', 'summary': ''} if request.method == 'POST': request_form = PublishForm(request.POST) if request_form.is_valid(): request_dict = request_form.clean() print request_dict ret['status'] = True else: error_msg = request_form.errors.as_json() ret['error'] = json.loads(error_msg) return HttpResponse(json.dumps(ret))
扩展:ModelForm
在使用Model和Form时,都需要对字段进行定义并指定类型,通过ModelForm则可以省去From中字段的定义
class AdminModelForm(forms.ModelForm): class Meta: model = models.Admin #fields = '__all__' fields = ('username', 'email') widgets = { 'email' : forms.PasswordInput(attrs={'class':"alex"}), }
三、XSS和CSRF跨站请求伪造
XSS: 对用户提交的数据验证 1.后端设置 def test(request): from django.utils.safestring import mark_safe temp = "<a href='http://www.baidu.com'>百度</a>" newtemp = mark_safe(temp) return render(request,'test.html',{'temp':temp}) 2.前端设置 {{ item|safe }}
CSRF: CSRF(Cross-site request forgery)跨站请求伪造,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。 尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。 csrf_token防止从别的网站向自己网站发post请求, 客户来访问网站,网站会向客户发送随机字符串,然后客户带随机字符串发送post请求。 只有带随机字符串来,网站才认,一般是post请求才要求带随机字符串,其它网站第一次来不会带随机字符串。
MIDDLEWARE = [ 'django.middleware.csrf.CsrfViewMiddleware', ]
b.HTML中使用
{% csrf_token %} #只要写上{% csrf_token %} 会有一个隐藏的input随机字符串,在cookie也有一个随机的字符串,form表单提交数据时,一般会使用 {{ csrf_token }} #生成随机的字符串
c.django中设置防跨站请求伪造功能有分为全局和局部
#局部 from django.views.decorators.csrf import csrf_exempt,csrf_protect @csrf_protect settings中没有设置全局中间件,为当前函数强制设置防跨站请求伪造功能。 @csrf_exempt settings中设置了全局中间件,取消当前函数防跨站请求伪造功能。
#fbv @csrf_protect def func(object): pass #cbv from django.views import View from django.utils.decorators import method_decorator @method_decorator(csrf_exempt,name="dispatch") class foo(View) pass
#方式一 类上加装饰器: def wrapper(func): def inner(*args,**kwargs): return func(*args,**kwargs) return inner @method_decorator(wrapper,name="get") @method_decorator(wrapper,name="post") class foo(View): def get(self,request): pass def post(self,request): pass #方式二 类上“dispatch”加装饰器: def wrapper(func): def inner(*args,**kwargs): return func(*args,**kwargs) return inner @method_decorator(wrapper,name="dispatch") class foo(View): def dispatch(self,request,*args,**kwargs): return xxx def get(self,request): pass def post(self,request): pass #方式三 方法上加装饰器: def wrapper(func): def inner(*args,**kwargs): return func(*args,**kwargs) return inner class foo(View): @method_decorator(wrapper) def get(self,request): pass def post(self,request): pass
d. Ajax提交数据 携带CSRF
1. 通过获取隐藏的input标签中的字符串 放置在data中发送
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="POST" action="/csrf1.html"> {% csrf_token %} <input id="user" type="text" name="user"/> <a onclick="submitForm();">Ajax提交</a> </form> <script src="/static/jquery-1.12.4.js"></script> <script> function submitForm() { var csrf = $('input[name="csrfmiddlewaretoken"]').val(); var user = $("#user").val(); $.ajax({ url:'/csrf1.html', type:'POST', data:{"user":user,"csrfmiddlewaretoken":csrf}, success:function (arg) { console.log(arg); } }) } </script> </body> </html>
urlpatterns = [ url(r'^csrf1.html', views.csrf1), ]
def csrf1(request): if request.method == 'GET': return render(request,'csrf1.html') else: return HttpResponse('ok')
2. 通过获取返回的cookie中的字符串 放置在 请求头 中发送
通过在console中 document.cookie可以获取 csrftoken=JPv1gIdrBiAlK2RCrgFs0OKwsncPXvwPfMhEWIVzMdMFymIayiuGu2GkBAu57moL 但需要切割字符串,通过引入jquery.cookie.js对cookie操作,使用$.cookie("csrftoken")
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="POST" action="/csrf1.html"> {% csrf_token %} <input id="user" type="text" name="user"/> <a onclick="submitForm();">Ajax提交</a> </form> <script src="/static/jquery-1.12.4.js"></script> <script src="/static/jquery.cookie.js"></script> <script> function submitForm() { var token = $.cookie("csrftoken"); var user = $("#user").val(); $.ajax({ url:'/csrf1.html', type:'POST', headers:{"X-CSRFToken":token}, data:{"user":user}, success:function (arg) { console.log(arg); } }) } </script> </body> </html>
3. 直接在ajax里发送
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="POST" action="/csrf1.html"> <input id="user" type="text" name="user"/> <a onclick="submitForm();">Ajax提交</a> </form> <script src="/static/jquery-3.2.1.js"></script> <script> function submitForm() { var user = $("#user").val(); $.ajax({ url:'/index/', type:'POST', data:{"user":user,"csrfmiddlewaretoken":"{{ csrf_token }}"}, success:function (arg) { console.log(arg); } }) } </script> </body> </html>
四、Cookie
a. 简单应用
def login(request): if request.method=="GET": return render(request,"login.html") else: name = request.POST.get("name") password = request.POST.get("password") if name == "alex" and password == "123": obj = redirect("/classes/") obj.set_cookie("ticket","123456",max_age=10) return obj else: return redirect("/login/") def classes(request): sk = request.COOKIES print(sk) if not sk: return redirect("/login/")
b. 设置cookie的过期时间
方式一: obj.set_cookie("ticket","123456",max_age=10) 方式二: import datetime from datetime import timedelta #时间的加减 ct = datetime.datetime.utcnow() #获取当前日期 v= timedelta(seconds=10) #10秒 value = ct + v obj.set_cookie("ticket","123456",expires=value)
c. 限制路径
obj.set_cookie("ticket","123456",max_age=10,path="/") #所有路径都可以访问 obj.set_cookie("ticket","123456",max_age=10,path="/class") #只有class访问
d. cookie签名
#加 obj.set_signed_cookie("ticket","123456",salt="abc") #解 sk = request.get_signed_cookie("ticket",salt="abc")
五、Session
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、扩展:Session用户验证
def login(func): def wrap(request, *args, **kwargs): # 如果未登陆,跳转到指定页面 if request.path == '/test/': return redirect('http://www.baidu.com') return func(request, *args, **kwargs) return wrap
7、简单应用
流程:客户登录网址,验证成功后,服务端生成一个随机字符串和随机字符串对应的键值,然后把随机字符串通过cookie发送给客户端。
客户端拿着随机字符串通过cookie再次登陆,服务端拿着随机字符串和保存在本地的数据对应,以确定用户的登录状态。
Cookie是什么? 保存在客户端浏览器上的键值对 Session是什么? 保存在服务端的数据(本质是键值对) { “aaaaa":{'id':1,'name':'alex',email='xxxx'} "bbbbb":{'id':2,'name':'egon',email='0000'} } 应用:依赖cookie 作用:保持会话(Web网站) 好处:敏感信息不会直接给客户端 梳理: 1. 保存在服务端的数据(本质是键值对) 2. 配置文件中: - 存储位置 - 超时时间、每次刷新更新时间
用户访问http:
/
/
127.0
.
0.1
:
8000
/
login
/
urlpatterns = [ url(r'^index/', views.index), url(r'^login/', views.login), ]
def login(request): if request.method == 'GET': return render(request,'login.html') else: u = request.POST.get('user') p = request.POST.get('pwd') obj = models.UserAdmin.objects.filter(username=u,password=p).first() if obj: # 1. 生成随机字符串 # 2. 通过cookie发送给客户端 # 3. 服务端保存 # { # 随机字符串1: {'username':'alex','email':x''...} # } request.session['username'] = obj.username return redirect('/index/') else: return render(request,'login.html',{'msg':'用户名或密码错误'}) def index(request): # 1. 获取客户端端cookie中的随机字符串 # 2. 去session中查找有没有随机字符 # 3. 去session对应key的value中查看是否有 username v = request.session.get('username') if v: return HttpResponse('登录成功:%s' %v) else: return redirect('/login/')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/login/" method="POST"> <input type="text" name="user"> <input type="text" name="pwd"> <input type="submit" value="提交">{{ msg }} </form> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>index page</h1> </body> </html>
六、cbv、fbv
cbv(class-base-view) 基于类的视图 fbv(func-base-view) 基于函数的视图
cbv基本演示
根据源码执行get post 方法前会先执行dispatch操作url请求前可以自定义dispatch方法,当作装饰器使用。
urlpatterns = [ url(r'^login.html$', views.Login.as_view()), ]
from django.views import View class Login(View): def dispatch(self, request, *args, **kwargs): print('before') obj = super(Login,self).dispatch(request,*args,**kwargs) print("after") return obj def get(self,request): return render(request,"login.html") def post(self,request): print(request.POST) return HttpResponse("Login.post")
七、分页
1、Django内置分页
适应于:上一页 下一页
#浏览器访问 http://127.0.0.1:8000/index.html/?page=9
def index(request): """ 分页 :param request: :return: """ current_page = request.GET.get('page') user_list = models.UserInfo.objects.all() paginator = Paginator(user_list,10) # per_page: 每页显示条目数量 # count: 数据总个数 # num_pages:总页数 # page_range:总页数的索引范围,如: (1,10),(1,200) # page: page对象 try: posts = paginator.page(current_page) except PageNotAnInteger as e: posts = paginator.page(1) except EmptyPage as e: posts = paginator.page(1) # has_next 是否有下一页 # next_page_number 下一页页码 # has_previous 是否有上一页 # previous_page_number 上一页页码 # object_list 分页之后的数据列表 # number 当前页 # paginator paginator对象 return render(request,'index.html',{'posts':posts})
<h1>用户列表</h1> <ul> {% for row in posts.object_list %} <li>{{ row.name }}</li> {% endfor %} </ul> <div> {% if posts.has_previous %} <a href="/index.html?page={{ posts.previous_page_number }}">上一页</a> {% endif %} {% if posts.has_next %} <a href="/index.html?page={{ posts.next_page_number }}">下一页</a> {% endif %} </div>
2、自定义分页
分页功能在每个网站都是必要的,对于分页来说,其实就是根据用户的输入计算出应该在数据库表中的起始位置。
1、设定每页显示数据条数
2、用户输入页码(第一页、第二页...)
3、根据设定的每页显示条数和当前页码,计算出需要取数据表的起始位置
4、在数据表中根据起始位置取值,页面上输出数据
需求又来了,需要在页面上显示分页的页面。如:[上一页][1][2][3][4][5][下一页]
1、设定每页显示数据条数
2、用户输入页码(第一页、第二页...)
3、设定显示多少页号
4、获取当前数据总条数
5、根据设定显示多少页号和数据总条数计算出,总页数
6、根据设定的每页显示条数和当前页码,计算出需要取数据表的起始位置
7、在数据表中根据起始位置取值,页面上输出数据
8、输出分页html,如:[上一页][1][2][3][4][5][下一页]
分页实例
#!/usr/bin/env python # _*_coding:utf-8_*_ from django.utils.safestring import mark_safe class PageInfo(object): def __init__(self,current,totalItem,peritems=5): self.__current=current self.__peritems=peritems self.__totalItem=totalItem def From(self): return (self.__current-1)*self.__peritems def To(self): return self.__current*self.__peritems def TotalPage(self): #总页数 result=divmod(self.__totalItem,self.__peritems) if result[1]==0: return result[0] else: return result[0]+1 def Custompager(baseurl,currentPage,totalpage): #基础页,当前页,总页数 perPager=11 #总页数<11 #0 -- totalpage #总页数>11 #当前页大于5 currentPage-5 -- currentPage+5 #currentPage+5是否超过总页数,超过总页数,end就是总页数 #当前页小于5 0 -- 11 begin=0 end=0 if totalpage <= 11: begin=0 end=totalpage else: if currentPage>5: begin=currentPage-5 end=currentPage+5 if end > totalpage: end=totalpage else: begin=0 end=11 pager_list=[] if currentPage<=1: first="<a href=''>首页</a>" else: first="<a href='%s%d'>首页</a>" % (baseurl,1) pager_list.append(first) if currentPage<=1: prev="<a href=''>上一页</a>" else: prev="<a href='%s%d'>上一页</a>" % (baseurl,currentPage-1) pager_list.append(prev) for i in range(begin+1,end+1): if i == currentPage: temp="<a href='%s%d' class='selected'>%d</a>" % (baseurl,i,i) else: temp="<a href='%s%d'>%d</a>" % (baseurl,i,i) pager_list.append(temp) if currentPage>=totalpage: next="<a href='#'>下一页</a>" else: next="<a href='%s%d'>下一页</a>" % (baseurl,currentPage+1) pager_list.append(next) if currentPage>=totalpage: last="<a href=''>末页</a>" else: last="<a href='%s%d'>末页</a>" % (baseurl,totalpage) pager_list.append(last) result=''.join(pager_list) return mark_safe(result) #把字符串转成html语言
总结,分页时需要做三件事:
- 创建处理分页数据的类
- 根据分页数据获取数据
- 输出分页HTML,即:[上一页][1][2][3][4][5][下一页]
分页模板应用:
class PageInfo(object): def __init__(self,current_page,all_count,per_page,base_url,show_page=11): """ :param current_page: :param all_count: 数据库总行数 :param per_page: 每页显示行数 :return: """ try: self.current_page = int(current_page) except Exception as e: self.current_page = 1 self.per_page = per_page a,b = divmod(all_count,per_page) if b: a = a +1 self.all_pager = a self.show_page = show_page self.base_url = base_url def start(self): return (self.current_page-1) * self.per_page def end(self): return self.current_page * self.per_page def pager(self): # v = "<a href='/custom.html?page=1'>1</a><a href='/custom.html?page=2'>2</a>" # return v page_list = [] half = int((self.show_page-1)/2) # 如果数据总页数 < 11 if self.all_pager < self.show_page: begin = 1 stop = self.all_pager + 1 # 如果数据总页数 > 11 else: # 如果当前页 <=5,永远显示1,11 if self.current_page <= half: begin = 1 stop = self.show_page + 1 else: if self.current_page + half > self.all_pager: stop = self.all_pager + 1 begin = self.all_pager - self.show_page + 1 else: begin = self.current_page - half stop = self.current_page + half + 1 if self.current_page <= 1: prev = "<li><a href='#'>上一页</a></li>" else: prev = "<li><a href='%s?page=%s'>上一页</a></li>" %(self.base_url,self.current_page-1,) page_list.append(prev) for i in range(begin,stop): if i == self.current_page: temp = "<li class='active'><a href='%s?page=%s'>%s</a></li>" %(self.base_url,i,i,) else: temp = "<li><a href='%s?page=%s'>%s</a></li>" %(self.base_url,i,i,) page_list.append(temp) if self.current_page >= self.all_pager: nex = "<li><a href='#'>下一页</a></li>" else: nex = "<li><a href='%s?page=%s'>下一页</a></li>" %(self.base_url,self.current_page+1,) page_list.append(nex) return ''.join(page_list)
</head> <body> <h1>用户列表</h1> <ul> {% for row in user_list %} <li>{{ row.name }}</li> {% endfor %} </ul> <nav aria-label="Page navigation"> <ul class="pagination"> {{ page_info.pager|safe }} </ul> </nav> </body> </html>
from django.db import models class UserType(models.Model): """ 用户类型 """ title = models.CharField(max_length=32) class UserInfo(models.Model): """ 用户表 """ name = models.CharField(max_length=16) age = models.IntegerField() ut = models.ForeignKey('UserType')
from utils.pager import PageInfo def custom(request): # 表示用户当前想要访问的页码: 8 all_count = models.UserInfo.objects.all().count() page_info = PageInfo(request.GET.get('page'),all_count,10,'/custom.html',11) user_list = models.UserInfo.objects.all()[page_info.start():page_info.end()] return render(request,'custom.html',{'user_list':user_list,'page_info':page_info})
八、缓存
由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将一个某个views的返回值保存至内存或者memcache中,5分钟内再有人来访问时,则不再去执行view中的操作,而是直接从内存或者Redis中之前缓存的内容拿到,并返回。
Django中提供了6种缓存方式:
- 开发调试
- 内存
- 文件
- 数据库
- Memcache缓存(python-memcached模块)
- Memcache缓存(pylibmc模块)
1、配置
a、开发调试
# 此为开始调试用,实际内部不做任何操作 # 配置: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', # 引擎 'TIMEOUT': 300, # 缓存超时时间(默认300,None表示永不过期,0表示立即过期) 'OPTIONS':{ 'MAX_ENTRIES': 300, # 最大缓存个数(默认300) 'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3) }, 'KEY_PREFIX': '', # 缓存key的前缀(默认空) 'VERSION': 1, # 缓存key的版本(默认1) 'KEY_FUNCTION' 函数名 # 生成key的函数(默认函数会生成为:【前缀:版本:key】) } } # 自定义key def default_key_func(key, key_prefix, version): """ Default function to generate keys. Constructs the key used by all other methods. By default it prepends the `key_prefix'. KEY_FUNCTION can be used to specify an alternate function with custom key making behavior. """ return '%s:%s:%s' % (key_prefix, version, key) def get_key_func(key_func): """ Function to decide which key function to use. Defaults to ``default_key_func``. """ if key_func is not None: if callable(key_func): return key_func else: return import_string(key_func) return default_key_func
b、内存
# 此缓存将内容保存至内存的变量中 # 配置: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'unique-snowflake', } } # 注:其他配置同开发调试版本
c、文件
# 此缓存将内容保存至文件 # 配置: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': '/var/tmp/django_cache', } } # 注:其他配置同开发调试版本
d、数据库
# 此缓存将内容保存至数据库 # 配置: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', 'LOCATION': 'my_cache_table', # 数据库表 } } # 注:执行创建表命令 python manage.py createcachetable
e、Memcache缓存(python-memcached模块)
# 此缓存使用python-memcached模块连接memcache CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', } } CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': 'unix:/tmp/memcached.sock', } } CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11211', ] } }
f、Memcache缓存(pylibmc模块)
# 此缓存使用pylibmc模块连接memcache CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 'LOCATION': '127.0.0.1:11211', } } CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 'LOCATION': '/tmp/memcached.sock', } } CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11211', ] } }
2、应用
a. 全站使用
使用中间件,经过一系列的认证等操作,如果内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户,当返回给用户之前,判断缓存中是否已经存在,如果不存在则UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存 MIDDLEWARE = [ 'django.middleware.cache.UpdateCacheMiddleware', # 其他中间件... 'django.middleware.cache.FetchFromCacheMiddleware', ] CACHE_MIDDLEWARE_ALIAS = "" CACHE_MIDDLEWARE_SECONDS = "" CACHE_MIDDLEWARE_KEY_PREFIX = ""
b. 单独视图缓存
方式一: from django.views.decorators.cache import cache_page @cache_page(60 * 15) def my_view(request): ... 方式二: from django.views.decorators.cache import cache_page urlpatterns = [ url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)), ]
c、局部视图使用
a. 引入TemplateTag {% load cache %} b. 使用缓存 {% cache 5000 缓存key %} 缓存内容 {% endcache %}
九、序列化
关于Django中的序列化主要应用在将数据库中检索的数据返回给客户端用户,特别的Ajax请求一般返回的为Json格式。
1、serializers
from django.core import serializers ret = models.BookType.objects.all() data = serializers.serialize("json", ret)
2、json.dumps
import json #ret = models.BookType.objects.all().values('caption') ret = models.BookType.objects.all().values_list('caption') ret=list(ret) result = json.dumps(ret)
由于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 o.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(field, date): return o.strftime('%Y-%m-%d') else: return json.JSONEncoder.default(self, field) # ds = json.dumps(d, cls=JsonCustomEncoder)
十、信号
Django中提供了“信号调度”,用于在框架执行操作时解耦。通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者。
1、Django内置信号
Model signals pre_init # django的modal执行其构造方法前,自动触发 post_init # django的modal执行其构造方法后,自动触发 pre_save # django的modal对象保存前,自动触发 post_save # django的modal对象保存后,自动触发 pre_delete # django的modal对象删除前,自动触发 post_delete # django的modal对象删除后,自动触发 m2m_changed # django的modal中使用m2m字段操作第三张表(add,remove,clear)前后,自动触发 class_prepared # 程序启动时,检测已注册的app中modal类,对于每一个类,自动触发 Management signals pre_migrate # 执行migrate命令前,自动触发 post_migrate # 执行migrate命令后,自动触发 Request/response signals request_started # 请求到来前,自动触发 request_finished # 请求结束后,自动触发 got_request_exception # 请求异常后,自动触发 Test signals setting_changed # 使用test测试修改配置文件时,自动触发 template_rendered # 使用test测试渲染模板时,自动触发 Database Wrappers connection_created # 创建数据库连接时,自动触发
对于Django内置的信号,仅需注册指定信号,当程序执行相应操作时,自动触发注册函数:
from django.core.signals import request_finished from django.core.signals import request_started from django.core.signals import got_request_exception from django.db.models.signals import class_prepared from django.db.models.signals import pre_init, post_init from django.db.models.signals import pre_save, post_save from django.db.models.signals import pre_delete, post_delete from django.db.models.signals import m2m_changed from django.db.models.signals import pre_migrate, post_migrate from django.test.signals import setting_changed from django.test.signals import template_rendered from django.db.backends.signals import connection_created def callback(sender, **kwargs): print("xxoo_callback") print(sender,kwargs) xxoo.connect(callback) # xxoo指上述导入的内容
from django.core.signals import request_finished from django.dispatch import receiver @receiver(request_finished) def my_callback(sender, **kwargs): print("Request finished!")
2、自定义信号
a. 定义信号
import django.dispatch pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])
b. 注册信号
def callback(sender, **kwargs): print("callback") print(sender,kwargs) pizza_done.connect(callback)
c. 触发信号
from 路径 import pizza_done pizza_done.send(sender='seven',toppings=123, size=456)
由于内置信号的触发者已经集成到Django中,所以其会自动调用,而对于自定义信号则需要开发者在任意位置触发。
更多:猛击这里
Django装饰器 登录 views.py from django.contrib.auth.decorators import login_required @login_required def index(request): return render(request,'index.html') settings.py LOGIN_URL = '/login/' 或者直接@login_required(login_url='/login/') 取消csrftoken的验证 from django.views.decorators.csrf import csrf_exempt @csrf_exempt 认证user views.py from django.contrib.auth import authenticate,login,logout def acc_login(request): error = '' if request.method == "POST": username = request.POST.get('username') password = request.POST.get('password') user = authenticate(username=username,password=password) # authenticate认证,认证成功,但此时没登录 if user: login(request, user) # 登录成功,django内部把session和cookie封装到request return redirect(request.GET.get('next') or '/') else: error = "Wrong username or password!" return render(request,'login.html',{'error':error }) @login_required def acc_logout(request): logout(request) # 退出成功,把session删除 return redirect('/login/') models.py from django.contrib.auth.models import User django登录成功后不用手写cookie,session,因为登录成功后django内部帮你把cookie写到浏览器,把session写到服务端据库中 取: request.user ,所有的请求信息都在,前后端都可以直接取,前端不需要返回就可以取到 原子操作回滚 from django.db.transaction import atomic @atomic 相当于: from django.db import transaction try: with transaction.atomic(): #事务,回滚操作,代码里遇到错误会自动执行回滚到之前的状态,但是也会报错 models.UpDown.objects.filter(user_id=user_id,article_id=article_id).delete() models.Article.objects.filter(nid=article_id).update(down_count=F('down_count')-1) except Exception as e: response['status']=False response['msg']=str(e)