django模型层
1.基本操作
django 模型层 是Django自定义的一套独特的ORM技术。
1.基本操作
使用django开发的首要任务就是定义模型类及其属性。每个模型类都可以被映射为数据库中的一个数据表,而雷属性被映射为数据字段,除此之外
数据库的主键 外键 约束也通过类属性完成定义
from django.db import models class ModelName(models.Model): fiels1 = models.XXField(..) fiels2 = models.XXField(..) ... class Meta: db_table = ... pther_metas = ...
- 所有Django模型继承自django.db.models.Model
- 通过其中的属性定义模型字段,模型字段必须是某种models.XXField类型
- 通过模型类中的Meta子类定义模型元数据,比如数据库表名、数据默认排序方式
Meta类的属性名由Django预定义,常用的Meta类属性汇总如下: - abstract: True or False 标识其本身是否为抽象类
- app_label:定义本类所属的应用,比如 app_label=[myapp]
- db_table:映射的数据表名,比如db_table = [moments]
如果Meta中不提供db_table字段 ,则Django会为模型自动生成数据表名,生成的格式为[应用名_模型名],比如应用app的模型Comment
的默认数据表名称为app_comment. - db_tablespace:映射表空间名称,表空间的概念只在某些数据库中如Oracle中存在,不存在表空间概念的数据库将忽略本字段
- default_related_name: 定义本模型的反向关系引用名称,默认与模型名一致。本名称的含义将在后续的内容中说明。
- get_latest_by:定义那个字段值排列以获取模型的开始或结束记录,本属性值通常指向一个日期或整型的模型字段。
- managed:True或False 定义django的manage.py命令工具是否为管理本模型。本属性默认为True,如果将其设为False,则运行
python manage.py migrate时将不会在数据库中生成本模型的数据表,所以需要手工维护数据库的定义 - order_with_respect_to:定义本模型可以按照某外键引用的关系排序
- ordering:本模型记录的默认排序字段,可以设置多个字段,默认以升序排列,如果以降序排列需要在字段前加负号。
比如 如下定义按user_name升序和pub_date 降序
class Meta: ordering = ['user_name','-pub_date']
- default_permissions:模型操作权限,默认为default_permissions = ('add','change','delete')
- proxy:True或False本模型及所有继承自本模型的子模型是否为代理模型
- required_db_features:定义底层数据所必须具备额特性。比如required_db_features=[gis_enabled]只将本数据模型生成在满足gis_enabled特性数据库中
- required_db_vendor:定义底层数据库的类型,比如SQLite,PostgreSQL MySQL Oracle。如果定义了本属性则模型只能在其声明的数据库中维护。
- unique_together:用来设置不重复的字段组合,必须唯一(可以多个字段做联合唯一)
class Meta: unique_together=(('user_name','pub_date'))
上述代码定义每个user_name 在同一个pub_date中只能有一条数据表记录。因为unique_together本身是一个元祖,所以可以设置为多个这样的唯一约束。
- index_together:定义联合索引字段,可以设置多个
class Meta: index_together=[["pub_date",'deadline'],]
- verbose_name:指明一个易于理解和表述的单数形式的对象名称。如果这个值没有被设置,则Django会将使用该model类名的分词形式作为它的对象表述名,即CamelCase 将会被转换为camel case
- verbose_name_plural:指明一个易于理解和表述的复数形式的对象名称
2.普通字段类型
普通字段是指模型类中除外键关系外的数据字段属性。数据字段为Django使用模型时提供如下信息。
- 在数据库中用什么类型定义模型字段 比如INTER VARCHAR等。
- 用什么样的HTML标签显示模型字段比如等
- 需要什么样的HTML表单数据验证
所有数据字段的属性必须继承自抽象类django.db.modles.Field开发者可以定义自己继承该类的字段类型,也可以使用django预定义的一系列Field子类。常用 的Django预定义字段类型描述如下。 - AutoField 一个自动递增的整型字段,添加记录时他会自动增长。AutoFiled字段通常只用于充当数据表的主键;如果在模型中没有指定主键字段,则Django会自动添加一个AutoField字段
- BigIntegerField:64位整型字段
- BinaryField: 二进制数据字段,只能通过bytes对其进行赋值
- BooleanField:布尔字段,相对应的HTML标签是
- CharField:字符串字段,用于较短的字符串,相对应的HTML标签是单行输入框
- TextField :大容量文本字段,相对应的HTML标签是多行编辑框
- CommaSeparatedIntegerField:用于存放逗号分割的整数值,相对于普通的CharField他有特殊的表单数据验证要求
- DateField:日期字段对应的HTML标签一个js日历和一个Today快捷键 有下列可选项auto_now当对象被保存时,将该字段的值设置为当前时间 auto_now_add当对象首次被创建时将该字段设置为当前时间
- DateTimeField:类似于DateField但是同时支持时间输入
- DurationField:存储时间周期 用Python的timedelate类型构建。
- EmailField:一个带有检查Email合法性的CharField
- FileField:一个文件上传字段,在定义文本字段时必须传入参数upload_to用于保存上文件的服务器文件系统路径 这个路径必须包含strftime formattiing,该格式将被上传文件的date/time替换
- FilePathField:按目录限制规则选择文件,定义本字段时必须传入参数path用以限定目录
- FloatField:浮点型字段。定义本字段时必须传入参数max_digits和decimal_places,用于定义总位数(不包含小数点和符号)和小数位数
- ImageField:类似于FileField类似于FileField,同时验证上传对象是否是一个合法的图片。他有两个可选参数即height_field 和 width_field。如果提供这两个参数,则图片按照提供的宽度和高度保存。该字段要求安装Python Imaging库
- IntegerField:用于保存一个整数
- IPAddressField:一个字符串形式的IP地址,比如'192.168.150.2'
- NullBooleanField:类似于BooleanField,但是比其多一个None选项
- PhoneNumberField 带有美国风格的电话号码校验的CharField(xxx-xxx-xxxx)
- PositiveIntegerField 只能输入非负数的IntegerField。
- SlugField:只包含字母数字下划线和连字符的输入字段,它通常用于URL
- SmallIntegerField:类似于IntegerField,但是只具有较小的输入范围,具体范围依赖于所使用的数据库
- TimeField:时间字段,类似于DateTimeField,但只能表达和输入时间
- URLField:用于保存URL
- USStateField:美国州名的缩短字段,由两个字母组成
- XMLField:XML字符字段,具有XML核发验证的TextField
3常用字段参数
每个字段类型都有一些特定的HTML标签和表单验证参数,比如Height_field path。但同时有一些每个字段都可以设置的公共餐胡,比如通过primary_key参数可以设置的一个模型的主键字段:
from django.db import models class Moment(models.Model): id = models.AuthField(primary_key=True)
其他这样的参数如下
- null:定义是否允许相对应的数据库字段为Null,默认设置为False
- balnk:定义字段是否可以为空。blank和null的区别,null是一个非空约束,blank用于字段的HTML表单验证,即判断用户是否可以不输入数据。
- choices:定义字段的可选值。本字段的值应该是一个包含二维元素的元组。元组的每个元素中的第一个值实际存储的值,第二个值是HTML页面中选择时显示的值
from django.db import models LEVEALS = ( ('1','Very Good'), ('2','Good'), ('3','Normal'), ('4','Bad'), ) class Moment(models.Model): id = models.AutoField(primary_key=True) level = models.CharField(max_length=1,choices=LEVELS) """ 代码中定义了level字段用于让用户选择满意度,其中1 2 3 4 是在数据库中实际存储的数据,而Very good good normal bad等是在HTML的列表控件正提供给用户的选项 """ + default:设定默认值,例如 default='please input here' + help_text:HTML页面中输入控件的帮助字符串。 + primary_key:定义字段是否为主键,为Trye或False + unique:是否为字段定义数据库的唯一约束 除了这些有名称的字段设置,django中的所有Field数据类型还有一个无名参数,可以设置该字段在HTML页面中人性化的名称 class Comment(models.Model): id = models.AutoField(primary_key=True) level = models.CharField("请为本条信息评级",max_length=1,choices=LEVELS) """ 本例中开发者为level字段定义了任性化名称"请为本条信息评级",如果不设置本参数,这字段名称本身将被显示在HTML页面中作为输入提示 """
4.基本查询
定义如下的 Django model 用于演示Django模型的基本查询技术
from django.db import models class Comment(models.Model): id = models.AutoField(primary_key=True) headline = models.CharField(max_length=255) body_text = models.TextField() pub_date = models.DateField() n_visits = models.IntegerField() def __str__(self): return self.headline
Django使用objects对象实现模型数据查询,比如查询Comment模型的所有数据
Comment.objects.all()
Django有两种过滤器用于筛选记录
- filter(**kwargs):返回符合筛选条件的数据集
- exclude(**kwargs):返回不符合筛选条件的数据集
比如,如下语句用于查询所有pub_date的年字段是2018的Comment:
Comment.objects.filter(pub_date__year=2018)
技巧:
多个filter和exclude可以连接在一起查询,比如Comment.objects.filter(pub_date__year=2018).exclude(pub_date__month=1).exclude(n_visits_exact=0) 查询所有2018年非1月的n_visit不为0的记录
pub_date__year 他不是模型中定义的一个字段,而是django中定义的一中独特的字段查询(field lookup)表达方式,本例中该查询的含义是pub_date字段的year属性为2018 field lookup的基本表现形式为字段名称__谓词
Django谓词表
谓词 含义 示例
exact 精确等于 Comment.objects.filter(id__exact=14)
iexact 大小写不敏感等于 Comment.objects.filter(headline_iexact='i like this')
contains 模糊匹配 Comment.objects.filter(headline_contains="good")
in 包含 Comment.objects.filter(id_in=[1,5,9])
gt 大于
gte 大于等于 Comment。objects.filter(n_visits__gte=30)
lt 小于
lte 小于等于
startswith 以...开头 Commet.objects.filter(body_text__startswith="Hello")
endswith 以...结尾
range 在...范围内 start_date=datetime.date(2018,1,1)
end_date = datetime.date(2018,2,1)
Comment.objects.filter(pub_date__range=(start_date,end_date))
year 年
month 月 Comment.objects.filter(pub_date__year=2018)
day 日
week_day 星期几
isnull 是否为空 Comments.obejcts.filter(pub_date__isnull=True)
除了all() filter() exclude()等返回数据集的函数,Django还提供了了get()用于查询单条记录,比如获取id为3的记录
Comment.objects.get(id_exact=1)
Django还提供了了用于查询指定条数的数据集的下边操作,该特性使得Django模型能够支持标准SQL中limit和OFFSET谓词
Comment.objects.all()[:10]
Comment.objects.all()[10:20]
Comment.objects.all()[1]
Comment.objects.order_by('headline')#返回数据集,并按照headline字段排序
5数据保存与删除
Django的一个较大的优势是定义了一个统一的方法save(),用于完成模型的insert和update操作,在执行模型实例的save()函数时django会根据主键判断记录是否存在,如果存在则执行Update操作否则执行insert操作 delete方法用于删除记录,也可以用于数据集,也可以用单条记录
Comments.objects.filter(pub_date__year=2017).delete()
Comments.objects.get(id=3).delete()
2.关系操作
利用数据表之间的关系进行数据建模和业务开发是关系数据库的最主要的功能。django模型层对3种关系(1:1,1:N,M:N)都有强大的支持
1.一对一关系
from django.db import models class Account(models.Model): user_name = models.CharField(max_length=80) password = models.CharField(max_length=255) reg_date = models.DateField() def __unicode__(self): return f'Account: {self.user_name}' class Contact(models.Model): account = models.OneToOneField( Account, on_delete = models.CASCADE, primary_key=True, ) zip_code = models.CharField(max_length=10) address = models.CharField(max_length=80) mobile = models.CharField(amx_length=20) def __unicode__(self): return f'{self.account.user_name}{mobile}' """ 两个模型的关系通过Contact模型中的account字段进行定义。 OneToOneField()的第一个参数定义被关联的模型名 on_delete 参数定义当被关联模型(Account)的记录被删除时本模型的记录如何处理 models.CASCADE用于定义此时本记录(Contact)也被删除 每个模型的__unicode__()函数用于定义模型的显示字符串 """ a1 = Account(user_name='david') a1.save() a2 = Account(user_name='Rose') a2.save() # 用a1初始化Contact的account字段并保存 c1 = Contact(account=a1,mobile='1331234222') c1.save() print(a1.contact) print(c1.account) #因为a2没有定义任何Contact建立关系,所以他没有contact属性/字段 print(hasattr(a2,"contact")) #由于定义了on_delete=model.CASCADE因此如下语句在从数据中删除a1对象的同时,也将c1对象从数据库中删除 a1.delete()
一对多关系
在SQL语言中,1:N关系通过在附表 中设置到主表的外键引用来完成。在django模型层,可以用models.ForeginKey类型的字段定义外键
from django.db import models class Account(models.Model): user_name = models.CharField(max_length=80) password = models.CharField(max_length=255) reg_date = models.DateField() def __unicode__(self): return "Account %s"%self.user_name class Contact(models.Model): account = models.ForeignKey(Account,on_delete = models.CASCADE) zip_code = models.CharField(max_length=10) address = models.CharField(max_length=80) mobile = models.CharField(amx_length=20) def __unicode__(self) return "%s,%s"%(self.account.user_name,mobile) """ 上述代码与一对一关系唯一不同的是用models.ForeignKey 定义了Contact模型中的account字段 。这样每个Account对象就 可以与多个Contact对象关联了。在一对多关系中,每个主模型对象可以关联多个子对象,所以本例中从主模型Account对象中寻找 附模型Contact的属性是contact_set 即通过 一个集合返回关联结果 技巧:xxx_set是django设定的通过主模型对象访问附模型对象集合的属性名 """
3.多对多关系
from django.db import models class Account(models.Model): user_name = models.CharField(max_length=80) password = models.CharField(max_length=255) reg_date = models.DateField() def __unicode__(self): return "Account %s"%self.user_name class Contact(models.Model): account = models.ManyToManyField(Account) zip_code = models.CharField(max_length=10) address = models.CharField(max_length=80) mobile = models.CharField(amx_length=20) def __unicode__(self) return "%s,%s"%(self.account.user_name,mobile) """ 上述代码与一对一关系唯一不同的是用models.ForeignKey 定义了Contact模型中的account字段 。这样每个Account对象就 可以与多个Contact对象关联了。在一对多关系中,每个主模型对象可以关联多个子对象,所以本例中从主模型Account对象中寻找 附模型Contact的属性是contact_set 即通过 一个集合返回关联结果 技巧:xxx_set是django设定的通过主模型对象访问附模型对象集合的属性名 """
3面对对象ORM
Django ORM的一个强大指出就是对模型继承的支持,该技术将Python面对对象的编程方法与数据库面向关系表的数据就够有机的结合。
Django支持三种风格的模型继承
- 抽象类继承:父类继承自models.Model,但是不会在底层数据中生成相应的数据表,父类的属性列村粗在其子类的数据表中
- 多表继承:多表继承的每个模型类都在底层数据库中生成相应的数据表管理数据
- 代理模型继承:父类用于在底层数据库中管理数据表 而子类不定义数据列,只定义查询数据集的排序方式等元数据
1.抽象类继承
抽象类继承的作用是在多个表有若干个相同的字段时,可以使开发者将这些字段同意定义在抽象基类中,免于重复定义这些字段。抽象基类的定义通过在模型的Meta中定义属性abstract来实现。抽象基类的举例如下:
from django.db import models class MessageBase(mdoels.Model): id = models.AutoField() content = models.CharField(max_length=100) user_name = models.CharField(amx_length=80) pub_date = models.DateField() class Meta: abstract=True #定义本类为抽象基类 class Moment(MessageBase): headline = models.CharField(max_length=50) LEVELS = ( ('1','very good'), ('2','good'), ('3','normal'), ('4','bad'), ) class Comment(MessageBase): level = models.CharField(choices = LEVELS,max_length=1) """ 本例中就定义了一个抽象基类MessageBase 用于保存消息的4个公用字段 id content user_name pub_date 子类 Moment Comment 继承自MessageBase 并分别定义自己的一个字段 本例中三个类映射到数据表中之后会被定义为两个数据表 数据表moment 有 id user_name content pub_date headline 5个字段 数据表Comment 有id user_name content pub_date level 5个字段 在子类模型中直接可以饮用父类定义的字段 m1 = Moment(user_name='gyz',headline='hello')#新建Moment对象 m1.content = 'reference parent field in subclass' a1.save() """
2.多表继承
在多表继承技术中,无论是父表还是子表都会用数据库中相对应的数据表维护模型数据,父类中的字段不会重复的在多个子类的相关数据表中进行定义。从这种意义上讲,多表继承才是真正面向对象的ORM技术
多表继承的定义不需要特殊的关键字。在django内部通过在父模型和子模型之间建立一对一的关系来实现多表继承技术。如下代码定义了MessageBase及其子类
from django.db import models class MessageBase(mdoels.Model): id = models.AutoField() content = models.CharField(max_length=100) user_name = models.CharField(amx_length=80) pub_date = models.DateField() class Moment(MessageBase): headline = models.CharField(max_length=50) LEVELS = ( ('1','very good'), ('2','good'), ('3','normal'), ('4','bad'), ) class Comment(MessageBase): level = models.CharField(choices = LEVELS,max_length=1) """ 本例中会在数据库中实际生成3个数据表 数据表MessageBase:有id content user_name pub_date 4个字段 数据表 Moment 有id headline两个字段 数据表 Comment 有id 和level两个字段 在对模型的编程过程中,子类仍然可以直接引用父类定义的字段同时子类可以通过父类对象应用访问父类实例 #新建Moment对象 直接在子类中引用父类字段 m1 = Moment(user_name="Terry",headline="hello world") m1.content = "reference parent field in subclass" a1.save() #通过父类实例引用父类字段 print(m1.messagebase.content) 技巧:多表继承时 在子类事例中通过小写的父类名称可以引用父类的实例 """
3代理模型继承
在前两种继承模型中子类模型都有实际的存储数据的作用;而在代理模型继承中子类只用于管理父类的数据,而不是实际存储数据。代理模型继承通过在子类的Meta中定义proxy=True 属性来实现,举例如下:
from django.db import models class Moment(models.Model): id = models.AutoField() headline = models.CharField(max_length=50) content = models.CharField(max_length=100) user_name = models.CharField(amx_length=80) pub_date = models.DateField() class OrderedMoment(Moment): class Meta: proxy=True ordering = ["-pub_date"] """ 在本例中定义父类模型Moment用于存储数据,而后定义了子类模型OrderedMomnet用于管理根据pub_date倒序排列的Moment 使用代理模型继承的原因是子类中新的特性不会影响父类模型及其已有代码的行为 """