本文共 21528 字,大约阅读时间需要 71 分钟。
Model
数据库操作:Form
强大的数据验证功能ModelForm
这个还没讲过,是上面两个的合集:非常方便,适合小型项目。或者是和 django 的 admin 相关的操作,admin就是通过ModelForm实现的。
但是,耦合非常强,不可拆分(比如数据库操作和业务操作不可分)。如果以后业务扩展了,这两部分就得拆开,那只能重写。讲师的博客:
在定义表结构的类里,再定义一个元类,可以实现自定义表名称、联合索引
class UserInfo(models.Model): nid = models.AutoField(primary_key=True) username = models.CharField(max_length=32) password = models.CharField(max_length=64) first_name = models.CharField(max_length=32) last_name = models.CharField(max_length=32) class Meta: # 指定数据库中生成的表的名称。默认是 app名称_类名 db_table = "table_name" # 联合索引 index_together = ( ("username", "password"), ) # 联合唯一(索引) unique_together = ( ("first_name", "last_name"), ) # admin中使用的表名称 verbose_name = "用户信息" # admin中最终显示的表名称,默认这个的值是verbose_name的值后面加了个's' verbose_name_plural = "用户信息表"
联合索引,是最左前缀模式,可以只匹配部分字段,但是必须是左边的字段。比如 ("username", "email"),
如果只有username可以命中索引,同时有username和email也能命中索引,但是缺少username只有email,无法命中联合索引。
多表之间的关联关系有:一对一(OneToOneField)、一对多(ForeignKey) 以及 多对多(ManyToManyField)。
一对一 和 多对多,都是基于 一对多 衍生出来的:ForeignKey(ForeignObject)
obj.表名_set.all()
,具体看下面的反向操作models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
默认的反向查找用下面的方式引用:
class Group(models.Model): name = models.CharField(max_length=32)class User(models.Model): name = models.CharField(max_length=32) gp = models.ForeignKey(to='Group', on_delete=models.CASCADE)# views.py 里groups = models.Group.objects.all()for group in groups: users = group.user_set.all() # 用 表名_set 进行反向查找models.Group.objects.all().values('name', 'user__name') # 用双下划线进行反向查找
这里也可以自定制反向查找使用的字段名:
class Group(models.Model): name = models.CharField(max_length=32)class User(models.Model): name = models.CharField(max_length=32) gp = models.ForeignKey(to='Group', on_delete=models.CASCADE, related_name='user1', related_query_name='user2')# views.py 里groups = models.Group.objects.all()for group in groups: users = group.user1.all() models.Group.objects.all().values('name', 'user2__name')
这种场景下,必须要指定反向引用的参数。大概的应用场景比如,人员表和人员表里其他的人做关联,被关联的就是当然人的领导。这里就不能使用User表名来引用关联数据的话,必须要指定一个新的表名(虽然还是自己)。这部分只是顺便讲了一下,下面的代码没验证过:
class User(models.Model): name = models.CharField(max_length=32) leader = models.ForeignKey(to='self', on_delete=models.CASCADE, related_name='leader_set', related_query_name='leader')
默认会在下拉列表里显示所有关联的数据,使用这个参数后,只会显示经过筛选后获得的数据:
limit_choices_to={'nid__gt': 5} # 简单的逻辑limit_choices_to=lambda : {'nid__gt': 5} # 使用匿名函数# 符合逻辑得导入Qfrom django.db.models import Qlimit_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')
一对一的类继承了一对多的类,所以ForeignKey里的参数这里都一样
OneToOneField(ForeignKey)源码的构造函数如下:def __init__(self, to, on_delete, to_field=None, **kwargs): kwargs['unique'] = True super().__init__(to, on_delete, to_field=to_field, **kwargs)
这里额外的加了一个 unique 约束。
ManyToManyField(RelatedField)
对于多对多的自关联,做如下操作时,不同的symmetrical会有不同的可选字段:
class BB(models.Model): code = models.CharField(max_length=12) m1 = models.ManyToManyField('self', symmetrical=False)# views.py 里models.BB.objects.filter(...)# 这里的filter里的可选字段只有:code,id,m1# 如果 symmetrical=False# 那么filter里的可选字段有:code,id,m1,bb。多一个表名
这里只是讲了一下参数的作用,貌似一般应用的时候用不到。通过关联的字段m1应该都是可以查找到所有的数据的。
如果是自定义的第三张表,那么很多操作就要自己写了。
但是自动生成的第三张表,只能有3列(算上id)。如果既要自定义第三张表,又想用django原生的部分方法,就需要按下面的方法来定义了。不过也只能做查的操作,依然不能做增删改的操作(因为自定义的第三张表往往超过3列,有其他自定义的字段,比如在新增数据的时候,那些字段的值是无法传过去的。查的操作是肯定可以的,其他操作用到的时候再测试吧,原因就是有额外的字段存在),不过能做查也已经很方便了:class Person(models.Model): name = models.CharField(max_length=16)class Group(models.Model): name = models.CharField(max_length=16) members = models.ManyToManyField( to='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)
原本models.ManyToManyField()
是会自动生成第三张表的,这里用through参数,指定了不要自动创建,而是连接到自定义的第三表去。要么不定义ManyToMany完全自己写操作,要么定义的时候加上through参数
只有查里面的一个剔除的方法是之前没有用过的
增models.Tb1.objects.create(c1='abc', c2='123')obj = models.Tb1(c1='abc', c2='123')obj.save()
查
models.Tb1.objects.get(id=1) # 不存在则会报错models.Tb1.objects.all()models.Tb1.objects.filter(c1='abc')models.Tb1.objects.exclude(c1='abc') # 和filter类似,但是逻辑是剔除
删
models.Tb1.objects.filter(c1='abc').delete()
改
models.Tb1.objects.filter(c1='abc').update(c2='321')obj = models.Tb1.objects.get(id=1)obj.c2 = '321'obj.save()
获取记录的个数
models.Tb1.objects.filter(c1='abc').count()models.Tb1.objects.filter(id__gt=1, id__lt=10) # 多个条件是与的关系
大于、小于
models.Tb1.objects.filter(id__gt=1)
in 属于
models.Tb1.objects.filter(id__in=[1, 3, 5]) # 获取id是 1 或 3 或 5 的记录models.Tb1.objects.exclude(id__in=[1, 3, 5]) # not in
isnull 是否为空
models.Tb1.objects.filter(c2__isnull=True) # 查找 c2 字典为空的记录
contains 包含
models.Tb1.objects.filter(c1__contains='ab') # 包含models.Tb1.objects.filter(c1__icontains='AB') # 前面加个i,不区分大小写models.Tb1.objects.exclude(c1__icontains='ab') # 不包含
rang 范围
models.Tb1.objects.filter(id__rang=[1, 2]) # 范围,SQL语句的 between and
其他(不举例了)
order by 排序
models.Tb1.objects.all().order_by('id') # 升序 ASCmodels.Tb1.objects.all().order_by('-id') # 降序 DESC,就前面加个减号models.Tb1.objects.all().order_by('id', '-c2') # 可以加多个参数,第一个一样的,再按第二个排序
limit offset
models.Tb1.objects.all()[10:20] # 相当于SQL里的 OFFSET 10 LIMIT 20,直接就按照列表切片更好理解嘛
正则匹配
models.Tb1.objects.filter(c1__regex=r'^(An?|The) +') # 正则匹配models.Tb1.objects.filter(c1__iregex=r'^(an?|the) +') # 正则匹配,忽略大小写
日期和时间
models.Tb1.objects.filter(c3__date=datetime.date(2005, 1, 1))
一共有下面这些:
group by
models.Tb1.objects.filter(c1='abd') # 先做数据的筛选models.Tb1.objects.filter(c1='abd').values('id') # 只显示id字段,后面如果跟annotate就不一样了models.Tb1.objects.filter(c1='abd').values('id').annotate() # 根据id进行分组from django.db.models import Count, Min, Max, Summodels.Tb1.objects.filter(c1='abd').values('id').annotate(Count) # 可以用 Count Min Max Sum 这些models.Tb1.objects.filter(c1='abd').values('id').annotate(c=Count) # 结果生成新的一列,字段名是c,相当去SQL的 AS "c"models.Tb1.objects.filter(c1='abd').values('id').annotate(c=Max('id')) # 具体聚合处理哪个字段# 更加复杂的例子# SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_idv = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id'))# SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1)# SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)# 上面这句distinct的效果是去重
在annotate前面的filter,相当于SQL的WHERE子句。
在annotate后面的filter,则相当于SQL的HAVING子句。这里讲QuerySet类型,用的最多的.all() 和 .filter() 是返回一个QuerySet类型的。并且只要是QuerySet类型就可以继续使用 .all() 或 .filter() 等这些方法。因为这些方法都是QuerySet里定义的方法。
这里找源码去翻一下,或者直接找到这个文件 django/db/models/query.py ,里面有个类class QuerySet
。这里面有看看有哪些方法。往下翻,找到all方法: ################################################################## # PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET # ################################################################## def all(self): """ Return a new QuerySet that is a copy of the current one. This allows a QuerySet to proxy for a model manager in some cases. """ return self._chain() def filter(self, *args, **kwargs): """ Return a new QuerySet instance with the args ANDed to the existing set. """ return self._filter_or_exclude(False, *args, **kwargs)
下面还有更多方法,就是我们可以用的
def all(self):
:获取所有的数据对象def filter(self, *args, **kwargs):
:条件查询,条件可以是:参数、字典、Qdef exclude(self, *args, **kwargs):
:条件查询,条件可以是:参数、字典、Qdef select_related(self, *fields):
:性能相关,下面会展开def prefetch_related(self, *lookups):
:性能相关,下面会展开def annotate(self, *args, **kwargs):
:用于实现聚合group by查询,上一小节有例子def distinct(self, *field_names):
:用于distinct去重def order_by(self, *field_names):
:用于排序def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None):
:下面会展开def reverse(self):
:倒序,要先order_by之后才有效果。models.User.objects.all().order_by('id').reverse()
。单独用无效def defer(self, *fields):
:映射中排除某列数据,和下面那个相反。只取几列,那么之后就只能用那几个字段,但是要再取其他字段也是可以的,不过django会再去发起一个SQL请求,这样性能就差了。def only(self, *fields):
:仅取某个表中的数据。models.UserInfo.objects.only('username','id')
。def using(self, alias):
:指定使用的数据库,参数为别名(setting中的设置)。不指定就是用默认数据库。下面有例子settings.py 里可以设置多个库:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } 'db1': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), }}
默认就是操作default这个库,可以通过using来指定操作其他库。这样就可以手工的实现读写分离。不过django还有自己的实现读写分离的方法
def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None):
构造额外的查询条件或者映射。
# 下面的%s是占位符,逗号后面的select_params是替代select里的占位符models.User.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))# params是替代where里的占位符。之前经常用的filter方法也是实现WHERE子句的功能。models.User.objects.extra(where=['headline=%s'], params=['Lennon'])# 用逗号隔开是and关系,OR就是or关系models.User.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])models.User.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])
跨表操作会有效率问题,看下面的情况:
users = models.User.objects.all() # 这里还不会发起SQL请求,只是定义了这个对象for user in users: print(user.name) # 这个时候会发起一次SQL请求,这次请求获取到的user里面,并没有跨表的user.group的数据 print(user.group.group_name) # 这里是跨表了,但是之前的请求user里面并没有跨表的数据,这里还要发起一次SQL请求# 所以上面的方法在效率上是不高的,要两次SQL请求。使用已经掌握的方法,可以这样做users = models.User.objects.all().values('name', 'group__group_name') # 这样用values指定获取哪些字段,包括跨表的字段,就是一次SQL请求了
values()方法有缺点,因为返回的是字典。如果要继续返回QuerySet对象的话,就不行了。
select_related()方法users1 = models.User.objects.all() # 默认值获取当前这张表的数据users2 = models.User.objects.all().select_ralated() # 把与当前表关联的数据也一次性都获取到了# 上面虽然方便,但是把不需要的关联数据也获取到同样不好,所以可以加参数,指定要获取哪个关联数据users3 = models.User.objects.all().select_ralated(‘group’)
prefetch_related()方法
上面的方法只发起了一次SQL请求,通过这个来提高效率的。通过使用prefetch_related()方法也可以提高效率,实现的方法和上面不同:users3 = models.User.objects.all().prefetch_related(‘group’)
首先会发起一次SQL请求获取User表的数据,这里是不跨表的。
然后再发起一次SQL请求,把获取到的数据里的Group表里的id,去Group表里获取一次。如果参数不是一个,而是多个,那么每个关联表都会发起一次SQL请求来获取关联的数据的id。上面一共是2次SQL请求,不跨表,所有相关的数据都放内存里了,之后就不会再发起SQL请求了。小结:上面的2个提高效率的方法,用哪一个或者一个都不用,不影响我们的代码的实现。只是在获取关联表的数据的时候,有不同的SQL请求的策略。最终获取到的数据还是一样的,用起来也一样。接着上面的其他操作里的源码继续往下看,截取一段如下:
################################################## # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS # ################################################## def raw(self, raw_query, params=None, translations=None, using=None): if using is None: using = self.db return RawQuerySet(raw_query, model=self.model, params=params, translations=translations, using=using)
具体就是有一下的方法:
def raw(self, raw_query, params=None, translations=None, using=None):
:执行原生SQLdef values(self, *fields, **expressions):
:获取每行数据为字典格式def values_list(self, *fields, flat=False, named=False):
:获取每行数据为元祖def dates(self, field_name, kind, order='ASC'):
:根据时间的某一部分进行去重查找并截取指定内容def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
:根据时间的某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间def none(self):
:返回一个空的 QuerySet 对象,貌似没啥用def dates(self, field_name, kind, order='ASC'):
函数里的各个参数如下:
models.DatePlus.objects.dates('ctime', 'day', 'DESC')
上面的只有日期,没有时间,下面的可以做更细力度的处理
def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
额外的参数如下:models.UserInfo.objects.datetimes('ctime', 'hour', tzinfo=pytz.UTC) # UTC时间,这个不重要models.UserInfo.objects.datetimes('ctime', 'hour', tzinfo=pytz.timezone('Asia/Shanghai')) # 使用我们这边的时区
上面用到了pytz这个模块,如果没有这个模块的还得安装一下。不过貌似安装django的时候默认就把这个也安装上了。如果不知道自己的时区具体是什么字符串,下面的方法可以列出所有的时区的字符串(稍微有点多):
>>> import pytz>>> pytz.all_timezones
def raw(self, raw_query, params=None, translations=None, using=None):
# 执行原生SQLmodels.UserInfo.objects.raw('select * from userinfo')
现在有一张User表,有3列数据:id、usrname、password
还有一张UserInfo表,有这些列:uid、name、email# 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名models.Tb.objects.raw('select id as nid from 其他表')# 对应上面的两张表的情况可以这样写models.User.objects.raw('select uid as id, name as username, email as password from userinfo')# 同样的效果也可以这样实现name_map = {'id': 'uid', 'username': 'name', 'password': 'email'}models.User.objects.raw('select * from userinfo', translations=name_map)
上面的代码,就是去查UserInfo表里的数据,然后把里面的字段名设置一个对应User表里的别名,这样查到的数据就可以作为User表的数据对象了。之后save()一下应该就可以添加到User表里了,或者是其他的方法。
另外还支持使用占位符(%s)来添加参数:name_map = {'id': 'uid', 'username': 'name', 'password': 'email'}models.UserInfo.objects.raw('select * from userinfo where uid>%s', params=[12,], translations=name_map)
using=None
:这个参数是指定数据库,之前已经讲过了
最后还有一部分方法,开始的位置如下:
#################################### # METHODS THAT DO DATABASE QUERIES # #################################### def _iterator(self, use_chunked_fetch, chunk_size): yield from self._iterable_class(self, chunked_fetch=use_chunked_fetch, chunk_size=chunk_size)
def aggregate(self, *args, **kwargs):
:聚合函数,获取字典类型聚合结果。这个是对整个表做聚合def count(self):
:获取个数def get(self, *args, **kwargs):
:获取单个对象def create(self, **kwargs):
:创建对象def bulk_create(self, objs, batch_size=None):
:批量插入,batch_size表示一次插入的个数def get_or_create(self, defaults=None, **kwargs):
:如果存在,则获取,否则,创建。defaults 指定创建时,其他字段的值def update_or_create(self, defaults=None, **kwargs):
:如果存在,则更新,否则,创建。defaults 指定创建时,其他字段的值def first(self):
:获取第一个def last(self):
:获取最后一个def in_bulk(self, id_list=None, *, field_name='pk'):
:根据主键ID进行查找。def delete(self):
:删除def update(self, **kwargs):
:更新def exists(self):
:是否存在聚合函数
from django.db.models import Count, Avg, Max, Min, Sumresult = models.UserInfo.objects.aggregate(n=Count('nid')) # 相当于下面这句原生SQL的效果# SELECT COUNT(nid) AS n FROM userinfo# 也可以统计多个数据,返回的是字典result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid')) # 前面那个是去重的,就是先出重再聚合>>> {'k': 3, 'n': 4}
聚合很有用,还可以算平均、最大值、最小值等等,上面都有导入了,可以去看看官网的例子:
批量插入
def bulk_create(self, objs, batch_size=None):
批量插入的好处是减少连接数据库的次数,原本可能是插入一条数据要连接一次数据库,现在一次连接可以插入多条数据 objs = [ models.UserInfo(name='Adam', email='adam@py.com'), models.UserInfo(name='Bob', email='bob@py.com)]models.UserInfo.objects.bulk_create(objs, 10)
batch_size参数是限制一次插入的记录数,例子中要插入的记录小于batch_size,就是一次操作。如果大于batch_size,那么一次插入一批,然后剩下的再连接一次数据库再插入一批,知道全部完成。避免一次插入过多的数据。
get_or_create 和 update_or_create如果记录存在,就是普通的查询,返回查询获取到的数据。如果记录不存在,则插入一条新数据并获取到这条新数据返回。新插入的数据的其他字段的值写在defaults里。这里有2个返回值,第一个是获取到的对象。第二个是这个对象是否是创建的。如果记录存在,返回的是False,这条不是创建的。obj, created = models.UserInfo.objects.get_or_create(username='Tom', defaults={'email': 'tom@py.com','u_id': 2})obj, created = models.UserInfo.objects.update_or_create(username='Tom', defaults={'email': 'tom@py.com','u_id': 2})
根据主键id进行查找
def in_bulk(self, id_list=None, *, field_name='pk'):
旧版本的django可能是这样的def in_bulk(self, id_list=None):
,下面是旧版的例子,不过第一个参数没变: id_list = [1, 2 ,3]models.User.objects.in_bulk(id_list)# 相当于下面的这句,所以不会也没关系models.User.objects.filter(id__in=id_list)
组合条件搜索,就是把一个个的filter()方法连起来用,这只能实现AND的逻辑。如果要实现复杂的逻辑,比如OR,就需要用到Q方法。当然ADN的逻辑也是可以用Q方法的。
fromdjango.db.modelsimports Qq=Q(question_startswith="What")models.Tb1.objects.filter(q)
简单的用法
Q(nid__gt=10)Q(nid=8) | Q(nid__gt=10)Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
一个个传入条件
这个用的比较灵活q1 = Q()q1.connector = 'OR'q1.children.append(('id', 1))q1.children.append(('id', 2))q1.children.append(('id', 3))models.Tb1.objects.filter(q1)
合并条件进行查询
下面的例子,首先创建了一个总的方法con。然后下面是2个内部是OR的Q方法q1和q2。最后在总的con里是用AND把所有的子方法合并起来:con = Q()q1 = Q()q1.connector = 'OR'q1.children.append(('id', 1))q1.children.append(('id', 2))q1.children.append(('id', 3))q2 = Q()q2.connector = 'OR'q2.children.append(('status', '在线'))con.add(q1, 'AND')con.add(q2, 'AND')models.Tb1.objects.filter(con)
model本身也有一部分的数据验证的功能,这部分的功能比较弱,但是还是来了解一下
首先建一个比较简单的表结构,# models.py 文件class UserInfo(models.Model): name = models.CharField(max_length=32) email = models.EmailField() # 这里的EmailFiled是希望之后能够做数据验证的
然后就是往表里添加数据了,添加数据有2中方法:
models.UserInfo.objects.create(name='Bob', email='bob') # 这种方法无法进行数据验证obj = models.UserInfo(name='Bob', email='bob') # 先创建一个对象,然后在送去save()之前,先调用下面的方法进行数据验证obj.full_clean() # 对这个数据对象进行验证,这里验证不通过会抛出异常,所以用的话要写一个try# 会抛出一个异常,在下一行# django.core.exceptions.ValidationError: {'email': ['Enter a valid email address.']}obj.save() # 创建数据
上面进行数据验证是调用一个full_clean()方法。而在这个full_clean()里面,还会调用一个self.clean()方法。上面如何捕获异常正好参考这里
try: self.clean() except ValidationError as e: errors = e.update_error_dict(errors)
而这个clean()方法里面是空的:
def clean(self): """ Hook for doing any extra model-wide validation after clean() has been called on every field by self.clean_fields. Any ValidationError raised by this method will not be associated with a particular field; it will have a special-case association with the field defined by NON_FIELD_ERRORS. """ pass
上面的 full_clean() 方法 和 clean() 方法都是Model这个类里提供方法。而这个clean()是django留给我们的一个钩子,让我们自己可以重构这个方法,从而实现自定义的数据验证:
from django.core.exceptions import ValidationError # 导入这个异常,之后由问题要抛出这个异常class UserInfo(models.Model): name = models.CharField(max_length=32) email = models.EmailField() # 重构数据验证的clean方法 def clean(self): if self.name == 'Bob': # 如果验证不通过我们要抛出ValidationError这个异常 raise ValidationError(message={'name': "用户名不能使用Bob"}) if self.email == 'bob': # 随便写两个异常,但是上面已经抛出异常了,并不会执行到这里 raise ValidationError(message={'email': "email能使用bob"})
这里ValidationError的异常类型,第一个参数message是错误信息,上面已经有了。第二个参数code是错误类型,这里没有定义,貌似也没什么效果。之前学习Form的时候,自定义错误信息,就用到过把原生的错误信息根据错误类型修改为自定义的错误信息,比如:"required"、"max_length"、"min_length"。
再次尝试写入数据库,这次抛出的异常的信息如下:django.core.exceptions.ValidationError: {'email': ['Enter a valid email address.'], 'name': ['用户名不能使用Bob']}
full_clean()方法里,是先 try self.clean_fields(exclude=exclude) 对每一个字段进行验证,然后再 try self.clean() 我们自定义的验证的。
另外,try self.clean_fields(exclude=exclude) 验证的时候,不会验证出一个错误后直接抛出异常,而是把所有的验证都完成,包括之后的 try self.clean() 的验证,所有验证都完成之后再抛出异常。把所有的验证不通过的信息一次全部抛出。但是我们自己写的 self.clean()方法按照上面的写法,则是一旦验证出错误就直接抛出异常了,如果有多个验证也不会验证后面的了。这里可以参考 clean_fields()方法的写法,把我们的clean()方法改的好一点:from django.core.exceptions import ValidationError # 导入这个异常,之后由问题要抛出这个异常class UserInfo(models.Model): name = models.CharField(max_length=32) email = models.EmailField() # 重构数据验证的clean方法 def clean(self): errors = {} # 这里用来存放我们的异常要抛出的字典 if self.name == 'Bob': errors['name'] = "用户名不能使用Bob" # 检测到异常先把信息添加到字典里去 if self.email == 'bob': errors['email'] = "email能使用bob" # 最后检查errors字典,如果有内容,则抛出异常,message参数的值就是errors字典 if errors: raise ValidationError(errors)
最后抛出的异常,是把所有验证的内容都验证了一遍,然后一次全部抛出的。包括我们自己写的clean()方法的多个验证错误
django.core.exceptions.ValidationError: {'email': ['Enter a valid email address.', 'email能使用bob'], 'name': ['用户名不能使用Bob']}
从结果分析,返回的错误信息是一个列表,如果一个字段有多条错误信息,那么是一条一条列在这个列表里的。我们自己重构的clean()方法里,如果对一个字段进行了多次验证,那么字典的value值(取出我们的错误信息)要写成一个列表。即使我们使用的不是列表而是字符串,最后抛出异常的时候也是把字符串转换成一个只有一个字符串成员的列表的。
转载于:https://blog.51cto.com/steed/2115407