当前位置: 首页 > news >正文

做网站首页图的规格网站建设的市场分析

做网站首页图的规格,网站建设的市场分析,微信分身网页版网址,校园互动网站建设模型和字段 一个模型#xff08;model#xff09;就是一个单独的、确定的数据的信息源#xff0c;包含了数据的字段和操作方法。通常#xff0c;每个模型映射为一张数据库中的表。 基本的原则如下#xff1a; 每个模型在Django中的存在形式为一个Python类每个类都是dja…模型和字段 一个模型model就是一个单独的、确定的数据的信息源包含了数据的字段和操作方法。通常每个模型映射为一张数据库中的表。 基本的原则如下 每个模型在Django中的存在形式为一个Python类每个类都是django.db.models.Model的子类模型类的每个字段属性代表数据表的某一列Django自动为你生成访问数据库的API 简单示例 下面的模型定义了一个 Person 类它具有first_name和last_name字段 from django.db import modelsclass Person(models.Model):first_name models.CharField(max_length30)last_name models.CharField(max_length30)每一个字段都是一个类属性每个类属性表示数据表中的一个列。 上面的代码相当于下面的原生SQL语句 CREATE TABLE myapp_person (id serial NOT NULL PRIMARY KEY,first_name varchar(30) NOT NULL,last_name varchar(30) NOT NULL );注意 表名myapp_person由Django自动生成默认格式为“项目名称下划线小写类名”你可以重写这个规则。Django会自动创建自增主键id当然你也可以自己指定主键。上面的SQL语句基于PostgreSQL语法。 通常我们会将模型编写在其所属app下的models.py文件中没有特别需求时请坚持这个原则不要自己给自己添加麻烦。 创建了模型之后在使用它之前你需要先在settings文件中的INSTALLED_APPS 处注册models.py文件所在的myapp。看清楚了是注册app不是模型也不是models.py。如果你以前写过模型可能已经做过这一步工作可跳过。 INSTALLED_APPS [#...myapp,#... ]当你每次对模型进行增、删、修改时请务必执行命令python manage.py migrate让操作实际应用到数据库上。这里可以选择在执行migrate之前先执行python manage.py makemigrations让修改动作保存到记录文件中方便github等工具的使用。 模型的属性 每个模型都可以有很多属性其中有Django内置的也可以有你自定义的。 模型当中最重要的属性是 Manager管理器。它是 Django 模型和数据库查询操作之间的API接口用于从数据库当中获取数据实例。如果没有指定自定义的 Manager 那么它默认名称是 objects这是Django自动为我们提供和生成的。Manager 只能通过模型类来访问不能通过模型实例来访问也就是说只能Person.objects不可以jack.objects。 模型还有一个不为人知的隐藏属性_state。 _state属性指向一个ModelState类实例它持续跟踪着模型实例的生命周期。 _state自己又有2个属性adding和db adding一个标识符如果当前的模型实例还没有保存到数据库内则为True否则为Falsedb一个字符串指向某个数据库当前模型实例是从该数据库中读取出来的。 故而 对于一个新创建的模型实例addingTrue并且dbNone对于从某个数据库中读取出来的模型实例addingFalse并且db‘数据库名’ 常用字段类型 Django内置了许多字段类型它们都位于django.db.models中例如models.CharField它们的父类都是Field类。这些类型基本满足需求如果还不够你也可以自定义字段。 下面列出了所有Django内置的字段类型但不包括关系字段类型字段名采用驼峰命名法初学者请一定要注意: AutoField 一个自动增加的整数类型字段。通常你不需要自己编写它Django会自动帮你添加字段id models.AutoField(primary_keyTrue)这是一个自增字段从1开始计数。如果你非要自己设置主键那么请务必将字段设置为primary_keyTrue。Django在一个模型中只允许有一个自增字段并且该字段必须为主键 BigAutoField 64位整数类型自增字段数字范围更大从1到9223372036854775807 BigIntegerField 64位整数字段看清楚非自增类似IntegerField -9223372036854775808 到9223372036854775807。在Django的模板表单里体现为一个NumberInput标签。 BinaryField 二进制数据类型。较少使用。 BooleanField 布尔值类型。默认值是None。在HTML表单中体现为CheckboxInput标签。如果设置了参数nullTrue则表现为NullBooleanSelect选择框。可以提供default参数值设置默认值。 CharField 最常用的类型字符串类型。必须接收一个max_length参数表示字符串长度不能超过该值。默认的表单标签是text input。 DateField class DateField(auto_nowFalse, auto_now_addFalse, **options) , 日期类型。一个Python中的datetime.date的实例。在HTML中表现为DateInput标签。在admin后台中Django会帮你自动添加一个JS日历表和一个“Today”快捷方式以及附加的日期合法性验证。两个重要参数参数互斥不能共存 auto_now:每当对象被保存时将字段设为当前日期常用于保存最后修改时间。auto_now_add每当对象被创建时设为当前日期常用于保存创建日期(注意它是不可修改的)。设置上面两个参数就相当于给field添加了editableFalse和blankTrue属性。如果想具有修改属性请用default参数。例子pub_time models.DateField(auto_now_addTrue)自动添加发布时间。 DateTimeField 日期时间类型。Python的datetime.datetime的实例。与DateField相比就是多了小时、分和秒的显示其它功能、参数、用法、默认值等等都一样。 DecimalField 固定精度的十进制小数。相当于Python的Decimal实例必须提供两个指定的参数参数max_digits最大的位数必须大于或等于小数点位数 。decimal_places小数点位数精度。 当localizeFalse时它在HTML表现为NumberInput标签否则是textInput类型。例子储存最大不超过999带有2位小数位精度的数定义如下models.DecimalField(…, max_digits5, decimal_places2)。 DurationField 持续时间类型。存储一定期间的时间长度。类似Python中的timedelta。在不同的数据库实现中有不同的表示方法。常用于进行时间之间的加减运算。但是小心了这里有坑PostgreSQL等数据库之间有兼容性问题 EmailField 邮箱类型默认max_length最大长度254位。使用这个字段的好处是可以使用Django内置的EmailValidator进行邮箱格式合法性验证。 FileField class FileField(upload_toNone, max_length100, **options)上传文件类型后面单独介绍。 FilePathField 文件路径类型后面单独介绍 FloatField 浮点数类型对应Python的float。参考整数类型字段。 IntegerField 整数类型最常用的字段之一。取值范围-2147483648到2147483647。在HTML中表现为NumberInput或者TextInput标签。 GenericIPAddressField class GenericIPAddressField(protocol‘both’, unpack_ipv4False, **options),IPV4或者IPV6地址字符串形式例如192.0.2.30或者2a02:42fe::4。在HTML中表现为TextInput标签。参数protocol默认值为‘both’可选‘IPv4’或者‘IPv6’表示你的IP地址类型。 JSONField JSON类型字段。Django3.1新增。签名为class JSONField(encoderNone,decoderNone,**options)。其中的encoder和decoder为可选的编码器和解码器用于自定义编码和解码方式。如果为该字段提供default值请务必保证该值是个不可变的对象比如字符串对象。 PositiveBigIntegerField 正的大整数0到9223372036854775807 PositiveIntegerField 正整数从0到2147483647 PositiveSmallIntegerField 较小的正整数从0到32767 SlugField slug是一个新闻行业的术语。一个slug就是一个某种东西的简短标签包含字母、数字、下划线或者连接线通常用于URLs中。可以设置max_length参数默认为50。 SmallAutoField Django3.0新增。类似AutoField但是只允许1到32767。 SmallIntegerField 小整数包含-32768到32767。 TextField 用于储存大量的文本内容在HTML中表现为Textarea标签最常用的字段类型之一如果你为它设置一个max_length参数那么在前端页面中会受到输入字符数量限制然而在模型和数据库层面却不受影响。只有CharField才能同时作用于两者。 TimeField 时间字段Python中datetime.time的实例。接收同DateField一样的参数只作用于小时、分和秒。 URLField 一个用于保存URL地址的字符串类型默认最大长度200。 UUIDField 用于保存通用唯一识别码Universally Unique Identifier的字段。使用Python的UUID类。在PostgreSQL数据库中保存为uuid类型其它数据库中为char(32)。这个字段是自增主键的最佳替代品后面有例子展示。 FileField class FileField(upload_toNone, max_length100, **options)上传文件字段不能设置为主键。默认情况下该字段在HTML中表现为一个ClearableFileInput标签。在数据库内我们实际保存的是一个字符串类型默认最大长度100可以通过max_length参数自定义。真实的文件是保存在服务器的文件系统内的。 重要参数upload_to用于设置上传地址的目录和文件名。如下例所示 class MyModel(models.Model):# 文件被传至MEDIA_ROOT/uploads目录MEDIA_ROOT由你在settings文件中设置upload models.FileField(upload_touploads/)# 或者# 被传到MEDIA_ROOT/uploads/2015/01/30目录增加了一个时间划分upload models.FileField(upload_touploads/%Y/%m/%d/)Django很人性化地帮我们实现了根据日期生成目录或文件的方式 upload_to参数也可以接收一个回调函数该函数返回具体的路径字符串如下例 def user_directory_path(instance, filename):#文件上传到MEDIA_ROOT/user_id/filename目录中return user_{0}/{1}.format(instance.user.id, filename)class MyModel(models.Model):upload models.FileField(upload_touser_directory_path)例子中user_directory_path这种回调函数必须接收两个参数然后返回一个Unix风格的路径字符串。参数instace代表一个定义了FileField的模型的实例说白了就是当前数据记录。filename是原本的文件名。 从Django3.0开始支持使用pathlib.Path 处理路径。 当你访问一个模型对象中的文件字段时Django会自动给我们提供一个 FieldFile实例作为文件的代理通过这个代理我们可以进行一些文件操作主要如下 FieldFile.name 获取文件名FieldFile.size 获取文件大小FieldFile.url 用于访问该文件的urlFieldFile.open(mode‘rb’) 以类似Python文件操作的方式打开文件FieldFile.close() 关闭文件FieldFile.save(name, content, saveTrue) 保存文件FieldFile.delete(saveTrue) 删除文件 这些代理的API和Python原生的文件读写API非常类似其实本质上就是进行了一层封装让我们可以在Django内直接对模型中文件字段进行读写而不需要绕弯子。 ImageField class ImageField(upload_toNone, height_fieldNone, width_fieldNone, max_length100, **options)用于保存图像文件的字段。该字段继承了FileField其用法和特性与FileField基本一样只不过多了两个属性height和width。默认情况下该字段在HTML中表现为一个ClearableFileInput标签。在数据库内我们实际保存的是一个字符串类型默认最大长度100可以通过max_length参数自定义。真实的图片是保存在服务器的文件系统内的。 height_field参数保存有图片高度信息的模型字段名。 width_field参数保存有图片宽度信息的模型字段名。 使用Django的ImageField需要提前安装pillow模块pip install pillow即可。 使用FileField或者ImageField字段的步骤 在settings文件中配置MEDIA_ROOT作为你上传文件在服务器中的基本路径为了性能考虑这些文件不会被储存在数据库中。再配置个MEDIA_URL作为公用URL指向上传文件的基本路径。请确保Web服务器的用户账号对该目录具有写的权限。添加FileField或者ImageField字段到你的模型中定义好upload_to参数文件最终会放在MEDIA_ROOT目录的“upload_to”子目录中。所有真正被保存在数据库中的只是指向你上传文件路径的字符串而已。可以通过url属性在Django的模板中方便的访问这些文件。例如假设你有一个ImageField字段名叫mug_shot那么在Django模板的HTML文件中可以使用{{ object.mug_shot.url }}来获取该文件。其中的object用你具体的对象名称代替。可以通过name和size属性获取文件的名称和大小信息。 安全建议:   无论你如何保存上传的文件一定要注意他们的内容和格式避免安全漏洞务必对所有的上传文件进行安全检查确保它们不出问题如果你不加任何检查就盲目的让任何人上传文件到你的服务器文档根目录内比如上传了一个CGI或者PHP脚本很可能就会被访问的用户执行这具有致命的危害。 FilePathField class FilePathField(path, matchNone, recursiveFalse, allow_filesTrue, allow_foldersFalse, max_length100, **options)一种用来保存文件路径信息的字段。在数据表内以字符串的形式存在默认最大长度100可以通过max_length参数设置。 它包含有下面的一些参数 path必须指定的参数。表示一个系统绝对路径。path通常是个字符串也可以是个可调用对象比如函数。match:可选参数一个正则表达式用于过滤文件名。只匹配基本文件名不匹配路径。例如foo.*.txt$只匹配文件名foo23.txt不匹配bar.txt与foo23.png。recursive:可选参数只能是True或者False。默认为False。决定是否包含子目录也就是是否递归的意思。allow_files:可选参数只能是True或者False。默认为True。决定是否应该将文件名包括在内。它和allow_folders其中必须有一个为True。allow_folders 可选参数只能是True或者False。默认为False。决定是否应该将目录名包括在内。 FilePathField(path/home/images, matchfoo.*, recursiveTrue)它只匹配/home/images/foo.png但不匹配/home/images/foo/bar.png因为默认情况只匹配文件名而不管路径是怎么样的。 UUIDField 数据库无法自己生成uuid因此需要如下使用default参数 import uuid # Python的内置模块 from django.db import modelsclass MyUUIDModel(models.Model):id models.UUIDField(primary_keyTrue, defaultuuid.uuid4, editableFalse)# 其它字段关系类型字段 Django还定义了一组关系类型字段用来表示模型与模型之间的关系.   多对一ForeignKey 多对一的关系通常被称为外键。外键字段类的定义如下: class ForeignKey(to, on_delete, **options)外键需要两个位置参数一个是关联的模型另一个是on_delete。在Django2.0版本后on_delete属于必填参数。 外键要定义在‘多’的一方 from django.db import modelsclass Manufacturer(models.Model):passclass Car(models.Model):manufacturer models.ForeignKey(Manufacturer, on_deletemodels.CASCADE)多对一字段的变量名一般设置为关联的模型的小写单数而多对多则一般设置为小写复数。 如果你要关联的模型位于当前模型之后则需要通过字符串的方式进行引用看下面的例子: from django.db import modelsclass Car(models.Model):manufacturer models.ForeignKey(Manufacturer, # 注意这里on_deletemodels.CASCADE,)class Manufacturer(models.Model):pass上面的例子中每辆车都会有一个生产工厂一个工厂可以生产N辆车于是用一个外键字段manufacturer表示并放在Car模型中。注意此manufacturer非彼Manufacturer模型类它是一个字段的名称。在Django的模型定义中经常出现类似的英文单词大小写不同一定要注意区分 如果要关联的对象在另外一个app中可以显式的指出。下例假设Manufacturer模型存在于production这个app中则Car模型的定义如下 class Car(models.Model):manufacturer models.ForeignKey(production.Manufacturer, # 关键在这里on_deletemodels.CASCADE,)如果要创建一个递归的外键也就是自己关联自己的的外键使用下面的方法 models.ForeignKey(‘self’, on_deletemodels.CASCADE) 核心在于‘self’这个引用。什么时候需要自己引用自己的外键呢典型的例子就是评论系统一条评论可以被很多人继续评论如下所示 class Comment(models.Model):title models.CharField(max_length128)text models.TextField()parent_comment models.ForeignKey(self, on_deletemodels.CASCADE)注意上面的外键字段定义的是父评论而不是子评论。为什么呢因为外键要放在‘多’的一方 在实际的数据库后台Django会为每一个外键添加_id后缀并以此创建数据表里的一列。在上面的工厂与车的例子中Car模型对应的数据表中会有一列叫做manufacturer_id。但实际上在Django代码中你不需要使用这个列名除非你书写原生的SQL语句一般我们都直接使用字段名manufacturer。 参数说明 on_delete 这个参数在Django2.0之后不可以省略了需要显式的指定这也是除了路由编写方式外Django2和Django1.x最大的不同点之一 当一个外键关联的对象被删除时Django将模仿on_delete参数定义的SQL约束执行相应操作。比如你有一个可为空的外键并且你想让它在关联的对象被删除时自动设为null可以如下定义 user models.ForeignKey(User,on_deletemodels.SET_NULL,blankTrue,nullTrue, )该参数可选的值都内置在django.db.models中全部为大写包括 CASCADE模拟SQL语言中的ON DELETE CASCADE约束将定义有外键的模型对象同时删除PROTECT:阻止上面的删除操作但是弹出ProtectedError异常SET_NULL将外键字段设为null只有当字段设置了nullTrue时方可使用该值。SET_DEFAULT:将外键字段设为默认值。只有当字段设置了default参数时方可使用。DO_NOTHING什么也不做。SET()设置为一个传递给SET()的值或者一个回调函数的返回值。注意大小写。 from django.conf import settings from django.contrib.auth import get_user_model from django.db import modelsdef get_sentinel_user():return get_user_model().objects.get_or_create(usernamedeleted)[0]class MyModel(models.Model):user models.ForeignKey(settings.AUTH_USER_MODEL,on_deletemodels.SET(get_sentinel_user),)RESTRICT: Django3.1新增。这个模式比较难以理解。它与PROTECT不同在大多数情况下同样不允许删除但是在某些特殊情况下却是可以删除的。看下面的例子多揣摩一下 # 假设有这样的三个模型以及外键关系class Artist(models.Model):name models.CharField(max_length10)class Album(models.Model):artist models.ForeignKey(Artist, on_deletemodels.CASCADE) # 注意这里class Song(models.Model):artist models.ForeignKey(Artist, on_deletemodels.CASCADE) # 注意这里album models.ForeignKey(Album, on_deletemodels.RESTRICT) # 注意这里尝试在Django的shell里测试下面的API artist_one Artist.objects.create(nameartist one)artist_two Artist.objects.create(nameartist two)album_one Album.objects.create(artistartist_one)album_two Album.objects.create(artistartist_two)song_one Song.objects.create(artistartist_one, albumalbum_one)song_two Song.objects.create(artistartist_one, albumalbum_two)album_one.delete() # Raises RestrictedError. 不可以删除artist_two.delete() # Raises RestrictedError. 不可以删除artist_one.delete() (4, {Song: 2, Album: 1, Artist: 1}) # 居然可以删除为什么artist_one可以被删除但是artist_two不可以Django设计的这个模式真的比较难以理解。 limit_choices_to 该参数用于限制外键所能关联的对象只能用于Django的ModelFormDjango的表单模块和admin后台对其它场合无限制功能。其值可以是一个字典、Q对象或者一个返回字典或Q对象的函数调用如下例所示 staff_member models.ForeignKey(User,on_deletemodels.CASCADE,limit_choices_to{is_staff: True}, )related_name 用于关联对象反向引用模型的名称。以前面车和工厂的例子解释就是从工厂反向关联到车的关系名称。 通常情况下这个参数我们可以不设置Django会默认以模型的小写加上_set作为反向关联名比如对于工厂就是car_set如果你觉得car_set还不够直观可以如下定义 class Car(models.Model):manufacturer models.ForeignKey(production.Manufacturer, on_deletemodels.CASCADE,related_namecar_producted_by_this_manufacturer, # 看这里)也许我定义了一个蹩脚的词但表达的意思很清楚。以后从工厂对象反向关联到它所生产的汽车就可以使用maufacturer.car_producted_by_this_manufacturer了。 如果你不想为外键设置一个反向关联名称可以将这个参数设置为“”或者以“”结尾如下所示 user models.ForeignKey(User,on_deletemodels.CASCADE,related_name, )related_query_name 反向关联查询名。用于从目标模型反向过滤模型对象的名称。过滤和查询在后续章节会介绍 这个参数的默认值是定义有外键字段的模型的小写名如果设置了related_name参数那么就是这个参数值如果在此基础上还指定了related_query_name的值则是related_query_name的值。三者依次有优先顺序。 要注意related_query_name和related_name的区别前者用于在做查询操作时候作为参数使用后者主要用于在属性调用时使用。 class Tag(models.Model):article models.ForeignKey(Article,on_deletemodels.CASCADE,related_nametags,related_query_nametag, # 注意这一行)name models.CharField(max_length255)# 现在可以使用‘tag’作为查询名了 Article.objects.filter(tag__nameimportant)to_field 默认情况下外键都是关联到被关联对象的主键上一般为id。如果指定这个参数可以关联到指定的字段上但是该字段必须具有uniqueTrue属性也就是具有唯一属性。 db_constraint 默认情况下这个参数被设为True表示遵循数据库约束这也是大多数情况下你的选择。如果设为False那么将无法保证数据的完整性和合法性。在下面的场景中你可能需要将它设置为False 有历史遗留的不合法数据没办法的选择你正在分割数据表 当它为False并且你试图访问一个不存在的关系对象时会抛出DoesNotExist 异常。 swappable 控制迁移框架的动作如果当前外键指向一个可交换的模型。使用场景非常稀少通常请将该参数保持默认的True。 多对多ManyToManyField class ManyToManyField(to, **options)多对多关系在数据库中也是非常常见的关系类型。比如一本书可以有好几个作者一个作者也可以写好几本书。多对多的字段可以定义在任何的一方请尽量定义在符合人们思维习惯的一方但不要同时都定义只能选择一个模型设置该字段比如我们通常将披萨上的配料字段放在披萨模型中而不是在配料模型中放置披萨字段。 建议为多对多字段名使用复数形式。 多对多关系需要一个位置参数关联的对象模型其它用法和外键多对一基本类似。 如果要创建一个关联自己的多对多字段依然是通过’self’引用。 在数据库后台Django实际上会额外创建一张用于体现多对多关系的中间表。默认情况下该表的名称是“多对多字段名包含该字段的模型名一个独一无二的哈希码”例如‘author_books_9cdf4’当然你也可以通过db_table选项自定义表名。 参数说明 related_name : 参考外键的相同参数。 related_query_name: 参考外键的相同参数。 limit_choices_to: 参考外键的相同参数。但是对于使用through参数自定义中间表的多对多字段无效。 symmetrical: 默认情况下Django中的多对多关系是对称的。看下面的例子 from django.db import modelsclass Person(models.Model):friends models.ManyToManyField(self)Django认为如果我是你的朋友那么你也是我的朋友这是一种对称关系Django不会为Person模型添加person_set属性用于反向关联。如果你不想使用这种对称关系可以将symmetrical设置为False这将强制Django为反向关联添加描述符。 through 如果你想自定义多对多关系的那张额外的关联表可以使用这个参数参数的值为一个中间模型。 最常见的使用场景是你需要为多对多关系添加额外的数据比如添加两个人建立QQ好友关系的时间。 通常情况下这张表在数据库内的结构是这个样子的 中间表的id列…模型对象的id列…被关联对象的id列 各行数据 如果自定义中间表并添加时间字段则在数据库内的表结构如下 中间表的id列…模型对象的id列…被关联对象的id列…时间对象列 看下面的例子 from django.db import modelsclass Person(models.Model):name models.CharField(max_length50)class Group(models.Model):name models.CharField(max_length128)members models.ManyToManyField(Person,throughMembership, ## 自定义中间表through_fields(group, person),)class Membership(models.Model): # 这就是具体的中间表模型group models.ForeignKey(Group, on_deletemodels.CASCADE)person models.ForeignKey(Person, on_deletemodels.CASCADE)inviter models.ForeignKey(Person,on_deletemodels.CASCADE,related_namemembership_invites,)invite_reason models.CharField(max_length64)上面的代码中通过class Membership(models.Model)定义了一个新的模型用来保存Person和Group模型的多对多关系并且同时增加了‘邀请人’和‘邀请原因’的字段。 through_fields 接着上面的例子。Membership模型中包含两个关联Person的外键Django无法确定到底使用哪个作为和Group关联的对象。所以在这个例子中必须显式的指定through_fields参数用于定义关系。 through_fields参数接收一个二元元组(‘field1’, ‘field2’)field1是指向定义有多对多关系的模型的外键字段的名称这里是Membership中的‘group’字段注意大小写另外一个则是指向目标模型的外键字段的名称这里是Membership中的‘person’而不是‘inviter’。 再通俗的说就是through_fields参数指定从中间表模型Membership中选择哪两个字段作为关系连接字段。 db_table 设置中间表的名称。不指定的话则使用默认值。 db_constraint 参考外键的相同参数。 swappable 参考外键的相同参数。 ManyToManyField多对多字段不支持Django内置的validators验证功能。 null参数对ManyToManyField多对多字段无效设置nullTrue毫无意义 一对一OneToOneField 一对一关系类型的定义如下 class OneToOneField(to, on_delete, parent_linkFalse, **options)从概念上讲一对一关系非常类似具有uniqueTrue属性的外键关系但是反向关联对象只有一个。这种关系类型多数用于当一个模型需要从别的模型扩展而来的情况。比如Django自带auth模块的User用户表如果你想在自己的项目里创建用户模型又想方便的使用Django的auth中的一些功能那么一个方案就是在你的用户模型里使用一对一关系添加一个与auth模块User模型的关联字段。 该关系的第一位置参数为关联的模型其用法和前面的多对一外键一样。 如果你没有给一对一关系设置related_name参数Django将使用当前模型的小写名作为默认值。 看下面的例子 from django.conf import settings from django.db import models# 两个字段都使用一对一关联到了Django内置的auth模块中的User模型 class MySpecialUser(models.Model):user models.OneToOneField(settings.AUTH_USER_MODEL,on_deletemodels.CASCADE,)supervisor models.OneToOneField(settings.AUTH_USER_MODEL,on_deletemodels.CASCADE,related_namesupervisor_of,)这样下来你的User模型将拥有下面的属性 user User.objects.get(pk1)hasattr(user, myspecialuser) Truehasattr(user, supervisor_of) TrueOneToOneField一对一关系拥有和多对一外键关系一样的额外可选参数只是多了一个不常用的parent_link参数。 跨模块的模型 有时候我们关联的模型并不在当前模型的文件内没关系就像我们导入第三方库一样的从别的模块内导入进来就好如下例所示 from django.db import models from geography.models import ZipCodeclass Restaurant(models.Model):# ...zip_code models.ForeignKey(ZipCode,on_deletemodels.SET_NULL,blankTrue,nullTrue,)多对多中间表详解 我们都知道对于ManyToMany字段Django采用的是第三张中间表的方式。通过这第三张表来关联ManyToMany的双方。下面我们根据一个具体的例子详细解说中间表的使用。 默认中间表 首先, 模型是这样 class Person(models.Model):name models.CharField(max_length128)def __str__(self):return self.nameclass Group(models.Model):name models.CharField(max_length128)members models.ManyToManyField(Person)def __str__(self):return self.name在Group模型中通过members字段以ManyToMany方式与Person模型建立了关系。 让我们到数据库内看一下实际的内容Django为我们创建了三张数据表其中的app1是应用名。 自定义中间表 一般情况普通的多对多已经够用无需自己创建第三张关系表。但是某些情况可能更复杂一点比如如果你想保存某个人加入某个分组的时间呢想保存进组的原因呢 Django提供了一个through参数用于指定中间模型你可以将类似进组时间邀请原因等其他字段放在这个中间模型内。例子如下 from django.db import modelsclass Person(models.Model):name models.CharField(max_length128)def __str__(self): return self.nameclass Group(models.Model):name models.CharField(max_length128)members models.ManyToManyField(Person, throughMembership)def __str__(self): return self.nameclass Membership(models.Model):person models.ForeignKey(Person, on_deletemodels.CASCADE)group models.ForeignKey(Group, on_deletemodels.CASCADE)date_joined models.DateField() # 进组时间invite_reason models.CharField(max_length64) # 邀请原因在中间表中我们至少要编写两个外键字段分别指向关联的两个模型。在本例中就是‘Person’和‘group’。 这里我们额外增加了‘date_joined’字段用于保存人员进组的时间‘invite_reason’字段用于保存邀请进组的原因。 使用中间表 针对上面的中间表下面是一些使用例子以欧洲著名的甲壳虫乐队成员为例 ringo Person.objects.create(nameRingo Starr)paul Person.objects.create(namePaul McCartney)beatles Group.objects.create(nameThe Beatles)m1 Membership(personringo, groupbeatles, ... date_joineddate(1962, 8, 16), ... invite_reasonNeeded a new drummer.)m1.save()beatles.members.all() QuerySet [Person: Ringo Starr]ringo.group_set.all() QuerySet [Group: The Beatles]m2 Membership.objects.create(personpaul, groupbeatles, ... date_joineddate(1960, 8, 1), ... invite_reasonWanted to form a band.)beatles.members.all() QuerySet [Person: Ringo Starr, Person: Paul McCartney]可以使用 add(), create(), 或 set() 创建关联对象只需指定 through_defaults 参数 beatles.members.add(john, through_defaults{date_joined: date(1960, 8, 1)})beatles.members.create(nameGeorge Harrison, through_defaults{date_joined: date(1960, 8, 1)})beatles.members.set([john, paul, ringo, george], through_defaults{date_joined: date(1960, 8, 1)})也可以直接创建中间模型实例。并且如果自定义中间模型没有强制设定 (model1, model2) 对的唯一性调用 remove() 方法会删除所有中间模型的实例 Membership.objects.create(personringo, groupbeatles, ... date_joineddate(1968, 9, 4), ... invite_reasonYouve been gone for a month and we miss you.)beatles.members.all() QuerySet [Person: Ringo Starr, Person: Paul McCartney, Person: Ringo Starr]# remove方法同时删除了两个 Ringo Starrbeatles.members.remove(ringo)beatles.members.all() QuerySet [Person: Paul McCartney]clear()方法能清空所有的多对多关系。 多对多中间表详解 阅读: 93896 评论34 我们都知道对于ManyToMany字段Django采用的是第三张中间表的方式。通过这第三张表来关联ManyToMany的双方。下面我们根据一个具体的例子详细解说中间表的使用。一、默认中间表 首先模型是这样的class Person(models.Model):name models.CharField(max_length128)def __str__(self):return self.nameclass Group(models.Model):name models.CharField(max_length128)members models.ManyToManyField(Person)def __str__(self):return self.name 在Group模型中通过members字段以ManyToMany方式与Person模型建立了关系。让我们到数据库内看一下实际的内容Django为我们创建了三张数据表其中的app1是应用名。然后我在数据库中添加了下面的Person对象再添加下面的Group对象让我们来看看中间表是个什么样子的首先有一列id这是Django默认添加的没什么好说的。然后是Group和Person的id列这是默认情况下Django关联两张表的方式。如果你要设置关联的列可以使用to_field参数。可见在中间表中并不是将两张表的数据都保存在一起而是通过id的关联进行映射。二、自定义中间表 一般情况普通的多对多已经够用无需自己创建第三张关系表。但是某些情况可能更复杂一点比如如果你想保存某个人加入某个分组的时间呢想保存进组的原因呢Django提供了一个through参数用于指定中间模型你可以将类似进组时间邀请原因等其他字段放在这个中间模型内。例子如下from django.db import modelsclass Person(models.Model):name models.CharField(max_length128)def __str__(self): return self.nameclass Group(models.Model):name models.CharField(max_length128)members models.ManyToManyField(Person, throughMembership)def __str__(self): return self.nameclass Membership(models.Model):person models.ForeignKey(Person, on_deletemodels.CASCADE)group models.ForeignKey(Group, on_deletemodels.CASCADE)date_joined models.DateField() # 进组时间invite_reason models.CharField(max_length64) # 邀请原因 在中间表中我们至少要编写两个外键字段分别指向关联的两个模型。在本例中就是‘Person’和‘group’。 这里我们额外增加了‘date_joined’字段用于保存人员进组的时间‘invite_reason’字段用于保存邀请进组的原因。下面我们依然在数据库中实际查看一下应用名为app2注意中间表的名字已经变成“app2_membership”了。Person和Group没有变化。但是中间表就截然不同了它完美的保存了我们需要的内容。三、使用中间表 针对上面的中间表下面是一些使用例子以欧洲著名的甲壳虫乐队成员为例 ringo Person.objects.create(nameRingo Starr)paul Person.objects.create(namePaul McCartney)beatles Group.objects.create(nameThe Beatles)m1 Membership(personringo, groupbeatles, ... date_joineddate(1962, 8, 16), ... invite_reasonNeeded a new drummer.)m1.save()beatles.members.all() QuerySet [Person: Ringo Starr]ringo.group_set.all() QuerySet [Group: The Beatles]m2 Membership.objects.create(personpaul, groupbeatles, ... date_joineddate(1960, 8, 1), ... invite_reasonWanted to form a band.)beatles.members.all() QuerySet [Person: Ringo Starr, Person: Paul McCartney] 可以使用 add(), create(), 或 set() 创建关联对象只需指定 through_defaults 参数 beatles.members.add(john, through_defaults{date_joined: date(1960, 8, 1)})beatles.members.create(nameGeorge Harrison, through_defaults{date_joined: date(1960, 8, 1)})beatles.members.set([john, paul, ringo, george], through_defaults{date_joined: date(1960, 8, 1)}) 也可以直接创建中间模型实例。并且如果自定义中间模型没有强制设定 (model1, model2) 对的唯一性调用 remove() 方法会删除所有中间模型的实例 Membership.objects.create(personringo, groupbeatles, ... date_joineddate(1968, 9, 4), ... invite_reasonYouve been gone for a month and we miss you.)beatles.members.all() QuerySet [Person: Ringo Starr, Person: Paul McCartney, Person: Ringo Starr]# remove方法同时删除了两个 Ringo Starrbeatles.members.remove(ringo)beatles.members.all() QuerySet [Person: Paul McCartney] clear()方法能清空所有的多对多关系。 # 甲壳虫乐队解散了beatles.members.clear()# 删除了中间模型的对象Membership.objects.all() QuerySet []一旦你通过创建中间模型实例的方法建立了多对多的关联你立刻就可以像普通的多对多那样进行查询操作 # 查找组内有Paul这个人的所有的组以Paul开头的名字Group.objects.filter(members__name__startswithPaul) QuerySet [Group: The Beatles]可以使用中间模型的属性进行查询 # 查找甲壳虫乐队中加入日期在1961年1月1日之后的成员Person.objects.filter( ... group__nameThe Beatles, ... membership__date_joined__gtdate(1961,1,1)) QuerySet [Person: Ringo Starr]可以像普通模型一样使用中间模型 ringos_membership Membership.objects.get(groupbeatles, personringo)ringos_membership.date_joined datetime.date(1962, 8, 16)ringos_membership.invite_reason Needed a new drummer.ringos_membership ringo.membership_set.get(groupbeatles)ringos_membership.date_joined datetime.date(1962, 8, 16)ringos_membership.invite_reason Needed a new drummer.这一部分内容需要结合后面的模型query如果暂时看不懂没有关系。 对于中间表有一点要注意默认情况下中间模型只能包含一个指向源模型的外键关系上面例子中也就是在Membership中只能有Person和Group外键关系各一个不能多。否则你必须显式的通过ManyToManyField.through_fields参数指定关联的对象。参考下面的例子 from django.db import modelsclass Person(models.Model):name models.CharField(max_length50)class Group(models.Model):name models.CharField(max_length128)members models.ManyToManyField(Person,throughMembership,through_fields(group, person),)class Membership(models.Model):group models.ForeignKey(Group, on_deletemodels.CASCADE)person models.ForeignKey(Person, on_deletemodels.CASCADE)inviter models.ForeignKey(Person,on_deletemodels.CASCADE,related_namemembership_invites,)invite_reason models.CharField(max_length64)Admin后台相关 使用了中间表后在admin中展示多对多关系会有如下问题 [(admin.E013) The value of fieldsets[2][1][fields] cannot include the ManyToManyField menu_field, because that field manually specifies a relationship model这个问题有Django的专门讨论链接以及stackoverflow的解决方法。 解决问题的核心是使用admin.InlineModelAdmin类和inlines属性将多对多中间表以内嵌框的形式展现出来。 下面是一个解决例子 from .models import Person,Group,Membershipadmin.register(Person) class PersonAdmin(admin.ModelAdmin):list_display [name]class MemberInlineAdmin(admin.TabularInline):model Group.members.throughadmin.register(Group) class GroupAdmin(admin.ModelAdmin):fieldsets ((None, {fields: (name,)}),)inlines (MemberInlineAdmin,)admin.register(Membership) class MemberShipAdmin(admin.ModelAdmin):list_display [group,person,invite_reason]模型的元数据Meta 模型的元数据指的是“除了字段外的所有内容”例如排序方式、数据库表名、人类可读的单数或者复数名等等。所有的这些都是非必须的甚至元数据本身对模型也是非必须的。但是我要说但是有些元数据选项能给予你极大的帮助在实际使用中具有重要的作用是实际应用的‘必须’。 想在模型中增加元数据方法很简单在模型类中添加一个子类名字是固定的Meta然后在这个Meta类下面增加各种元数据选项或者说设置项。参考下面的例子 from django.db import modelsclass Ox(models.Model):horn_length models.IntegerField()class Meta: # 注意是模型的子类要缩进ordering [horn_length]verbose_name_plural oxen上面的例子中我们为模型Ox增加了两个元据 ordering和verbose_name_plural分别表示排序和复数名下面我们会详细介绍有哪些可用的元数据选项。 强调每个模型都可以有自己的元数据类每个元数据类也只对自己所在模型起作用。 abstract 如果abstractTrue那么模型会被认为是一个抽象模型。抽象模型本身不实际生成数据库表而是作为其它模型的父类被继承使用。具体内容可以参考Django模型的继承。 app_label 如果定义了模型的app没有在INSTALLED_APPS中注册则必须通过此元选项声明它属于哪个app例如 app_label myappbase_manager_name 模型的_base_manager管理器的名字默认是’objects’。模型管理器是Django为模型提供的API所在。 db_table 指定在数据库中当前模型生成的数据表的表名。 db_table ‘my_freinds’ 如果你没有指定这个选项那么Django会自动使用app名和模型名通过下划线连接生成数据表名比如app_book。 不要使用SQL语言或者Python的保留字注意冲突。 友情建议使用MySQL和MariaDB数据库时db_table用小写英文。 db_tablespace 自定义数据库表空间的名字。默认值是项目的DEFAULT_TABLESPACE配置项指定的值。 default_manager_name 模型的_default_manager管理器的名字。 default_related_name 默认情况下从一个模型反向关联设置有关系字段的源模型我们使用model_name_set也就是源模型的名字下划线set。 这个元数据选项可以让你自定义反向关系名同时也影响反向查询关系名看下面的例子 from django.db import modelsclass Foo(models.Model):passclass Bar(models.Model):foo models.ForeignKey(Foo, on_deletemodels.CASCADE)class Meta:default_related_name bars # 关键在这里具体的使用差别如下 bar Bar.objects.get(pk1)# 不能再使用bar作为反向查询的关键字了。Foo.objects.get(barbar)# 而要使用你自己定义的bars了。Foo.objects.get(barsbar)get_latest_by Django管理器给我们提供有latest()和earliest()方法分别表示获取最近一个和最前一个数据对象。但是如何来判断最近一个和最前面一个呢也就是根据什么来排序呢 get_latest_by元数据选项帮你解决这个问题它可以指定一个类似 DateField、DateTimeField或者IntegerField这种可以排序的字段作为latest()和earliest()方法的排序依据从而得出最近一个或最前面一个对象。例如 # 根据order_date升序排列 get_latest_by order_date # 根据priority降序排列如果发生同序则接着使用order_date升序排列 get_latest_by [-priority, order_date] managed 该元数据默认值为True表示Django将按照既定的规则管理数据库表的生命周期。 如果设置为False将不会针对当前模型创建和删除数据库表也就是说Django暂时不管这个模型了。 在某些场景下这可能有用但更多时候你可以忘记该选项。 order_with_respect_to 这个选项不好理解。其用途是根据指定的字段进行排序通常用于关系字段。看下面的例子 from django.db import modelsclass Question(models.Model):text models.TextField()# ...class Answer(models.Model):question models.ForeignKey(Question, on_deletemodels.CASCADE)# ...class Meta:order_with_respect_to question上面在Answer模型中设置了order_with_respect_to ‘question’这样的话Django会自动提供两个APIget_RELATED_order()和set_RELATED_order()其中的RELATED用小写的模型名代替。假设现在有一个Question对象它关联着多个Answer对象下面的操作返回包含关联的Anser对象的主键的列表[1,2,3] question Question.objects.get(id1)question.get_answer_order() [1, 2, 3]我们可以通过set_RELATED_order()方法指定上面这个列表的顺序 question.set_answer_order([3, 1, 2])同样的关联的对象也获得了两个方法get_next_in_order()和get_previous_in_order()用于通过特定的顺序访问对象如下所示 answer Answer.objects.get(id2)answer.get_next_in_order() Answer: 3answer.get_previous_in_order() Answer: 1ordering 最常用的元数据之一了 用于指定该模型生成的所有对象的排序方式接收一个字段名组成的元组或列表。默认按升序排列如果在字段名前加上字符“-”则表示按降序排列如果使用字符问号“”表示随机排列。请看下面的例子 这个顺序是你通过查询语句获得Queryset后的列表内元素的顺序切不可和前面的get_latest_by等混淆。 ordering [pub_date] # 表示按pub_date字段进行升序排列 ordering [-pub_date] # 表示按pub_date字段进行降序排列 ordering [-pub_date, author] # 表示先按pub_date字段进行降序排列再按author字段进行升序排列。permissions 该元数据用于当创建对象时增加额外的权限。它接收一个所有元素都是二元元组的列表或元组每个元素都是(权限代码, 直观的权限名称)的格式。比如下面的例子 这个Meta选项非常重要和auth框架的权限系统紧密相关。 permissions ((can_deliver_pizzas, 可以送披萨),)default_permissions Django默认会在建立数据表的时候就自动给所有的模型设置(‘add’, ‘change’, ‘delete’)的权限也就是增删改。你可以自定义这个选项比如设置为一个空列表表示你不需要默认的权限但是这一操作必须在执行migrate命令之前。也是配合auth框架使用。 proxy 如果设置了proxy True表示使用代理模式的模型继承方式。具体内容与abstract选项一样参考模型继承. required_db_features 声明模型依赖的数据库功能。比如[‘gis_enabled’]表示模型的建立依赖GIS功能。 required_db_vendor 声明模型支持的数据库。Django默认支持sqlite, postgresql, mysql, oracle。 select_on_save 决定是否使用1.6版本之前的django.db.models.Model.save()算法保存对象。默认值为False。这个选项我们通常不用关心。 indexes 接收一个应用在当前模型上的索引列表如下例所示 from django.db import modelsclass Customer(models.Model):first_name models.CharField(max_length100)last_name models.CharField(max_length100)class Meta:indexes [models.Index(fields[last_name, first_name]),models.Index(fields[first_name], namefirst_name_idx),]unique_together 这个元数据是非常重要的一个它等同于数据库的联合约束 举个例子假设有一张用户表保存有用户的姓名、出生日期、性别和籍贯等等信息。要求是所有的用户唯一不重复可现在有好几个叫“张伟”的如何区别它们呢不要和我说主键唯一这里讨论的不是这个问题 我们可以设置不能有两个用户在同一个地方同一时刻出生并且都叫“张伟”使用这种联合约束保证数据库能不能重复添加用户也不要和我谈小概率问题。在Django的模型中如何实现这种约束呢 使用unique_together也就是联合唯一 比如: unique_together [[name, birth_day, address],......]这样哪怕有两个在同一天出生的张伟但他们的籍贯不同也就是两个不同的用户。一旦三者都相同则会被Django拒绝创建。这个元数据选项经常被用在admin后台并且强制应用于数据库层面。 unique_together接收一个二维的列表每个元素都是一维列表表示一组联合唯一约束可以同时设置多组约束。为了方便对于只有一组约束的情况下可以简单地使用一维元素例如 # 联合唯一无法作用于普通的多对多字段。 unique_together [name, birth_day, address]index_together 联合索引用法和特性类似unique_together。 constraints 为模型添加约束条件。通常是列表的形式每个列表元素就是一个约束。 from django.db import modelsclass Customer(models.Model):age models.IntegerField()class Meta:constraints [models.CheckConstraint(checkmodels.Q(age__gte18), nameage_gte_18),]上例中会检查age年龄的大小不得低于18。 verbose_name 最常用的元数据之一用于设置模型对象的直观、人类可读的名称用于在各种打印、页面展示等场景。可以用中文。例如 verbose_name story verbose_name 披萨如果你不指定它那么Django会使用小写的模型名作为默认值。 verbose_name_plural 英语有单数和复数形式。这个就是模型对象的复数名比如“apples”。因为我们中文通常不区分单复数所以保持和verbose_name一致也可以。 verbose_name_plural stories verbose_name_plural 披萨 verbose_name_plural verbose_name如果不指定该选项那么默认的复数名字是verbose_name加上‘s’. label 前面介绍的元数据都是可修改和设置的但还有两个只读的元数据label就是其中之一。 label等同于app_label.object_name。例如polls.Questionpolls是应用名Question是模型名。 label_lower 同上不过是小写的模型名。 模型继承 很多时候我们都不是从‘一穷二白’开始编写模型的有时候可以从第三方库中继承有时候可以从以前的代码中继承甚至现写一个模型用于被其它模型继承。这样做的好处我就不赘述了每个学习Django的人都非常清楚。 类同于Python的类继承Django也有完善的继承机制。 Django中所有的模型都必须继承django.db.models.Model模型不管是直接继承也好还是间接继承也罢。 你唯一需要决定的是父模型是否是一个独立自主的同样在数据库中创建数据表的模型还是一个只用来保存子模型共有内容并不实际创建数据表的抽象模型。 Django有三种继承的方式 抽象基类被用来继承的模型被称为Abstract base classes将子类共同的数据抽离出来供子类继承重用它不会创建实际的数据表多表继承Multi-table inheritance每一个模型都有自己的数据库表父子之间独立存在代理模型如果你只想修改模型的Python层面的行为并不想改动模型的字段可以使用代理模型。 注意同Python的继承一样Django也是可以同时继承两个以上父类的 抽象基类 只需要在模型的Meta类里添加abstractTrue元数据项就可以将一个模型转换为抽象基类。Django不会为这种类创建实际的数据库表它们也没有管理器不能被实例化也无法直接保存它们就是被当作父类供起来让子类继承的。抽象基类完全就是用来保存子模型们共有的内容部分达到重用的目的。当它们被继承时它们的字段会全部复制到子模型中。看下面的例子 from django.db import modelsclass CommonInfo(models.Model):name models.CharField(max_length100)age models.PositiveIntegerField()class Meta:abstract Trueclass Student(CommonInfo):home_group models.CharField(max_length5)Student模型将拥有nameagehome_group三个字段并且CommonInfo模型不能当做一个正常的模型使用。 那如果我想修改CommonInfo父类中的name字段的定义呢在Student类中创建一个name字段覆盖父类的即可。这其实就是很简单的Python语法。 那如果我不需要CommonInfo父类中的name字段呢在Student类中创建一个name变量值设为None即可。 抽象基类的Meta数据 如果子类没有声明自己的Meta类那么它将自动继承抽象基类的Meta类。 如果子类要设置自己的Meta属性则需要扩展基类的Meta from django.db import modelsclass CommonInfo(models.Model):class Meta:abstract Trueordering [name]class Student(CommonInfo):class Meta(CommonInfo.Meta): # 注意这里有个继承关系db_table student_info这里有几点要特别说明 抽象基类中有的元数据子模型没有的话直接继承抽象基类中有的元数据子模型也有的话直接覆盖子模型可以额外添加元数据抽象基类中的abstractTrue这个元数据不会被继承。也就是说如果想让一个抽象基类的子模型同样成为一个抽象基类那你必须显式的在该子模型的Meta中同样声明一个abstract True有一些元数据对抽象基类无效比如db_table首先是抽象基类本身不会创建数据表其次它的所有子类也不会按照这个元数据来设置表名。由于Python继承的工作机制如果子类继承了多个抽象基类则默认情况下仅继承第一个列出的基类的 Meta 选项。如果要从多个抽象基类中继承 Meta 选项必须显式地声明 Meta 继承。例如 from django.db import modelsclass CommonInfo(models.Model):name models.CharField(max_length100)age models.PositiveIntegerField()class Meta:abstract Trueordering [name]class Unmanaged(models.Model):class Meta:abstract Truemanaged Falseclass Student(CommonInfo, Unmanaged):home_group models.CharField(max_length5)class Meta(CommonInfo.Meta, Unmanaged.Meta):pass警惕related_name和related_query_name参数 如果在你的抽象基类中存在ForeignKey或者ManyToManyField字段并且使用了related_name或者related_query_name参数那么一定要小心了。因为按照默认规则每一个子类都将拥有同样的字段这显然会导致错误。为了解决这个问题当你在抽象基类中使用related_name或者related_query_name参数时它们两者的值中应该包含%(app_label)s和%(class)s部分 %(class)s用字段所属子类的小写名替换%(app_label)s用子类所属app的小写名替换 例如对于common/models.py模块 from django.db import modelsclass Base(models.Model):m2m models.ManyToManyField(OtherModel,related_name%(app_label)s_%(class)s_related,related_query_name%(app_label)s_%(class)ss,)class Meta:abstract Trueclass ChildA(Base):passclass ChildB(Base):pass对于另外一个应用中的rare/models.py: from common.models import Baseclass ChildB(Base):pass对于上面的继承关系 common.ChildA.m2m字段的reverse name反向关系名应该是 common_childa_relatedreverse query name(反向查询名)应该是common_childas。common.ChildB.m2m字段的反向关系名应该是common_childb_related反向查询名应该是common_childbs。rare.ChildB.m2m字段的反向关系名应该是rare_childb_related反向查询名应该是rare_childbs。 当然如果你不设置related_name或者related_query_name参数这些问题就不存在了。 多表继承 这种继承方式下父类和子类都是独立自主、功能完整、可正常使用的模型都有自己的数据库表内部隐含了一个一对一的关系。例如 from django.db import modelsclass Place(models.Model):name models.CharField(max_length50)address models.CharField(max_length80)class Restaurant(Place):serves_hot_dogs models.BooleanField(defaultFalse)serves_pizza models.BooleanField(defaultFalse)Restaurant将包含Place的所有字段并且各有各的数据库表和字段比如 Place.objects.filter(nameBobs Cafe)Restaurant.objects.filter(nameBobs Cafe)如果一个Place对象同时也是一个Restaurant对象你可以使用小写的子类名在父类中访问它例如 p Place.objects.get(id12) # 如果p也是一个Restaurant对象那么下面的调用可以获得该Restaurant对象。p.restaurant Restaurant: ...但是如果这个Place是个纯粹的Place对象并不是一个Restaurant对象那么上面的调用方式会弹出Restaurant.DoesNotExist异常。 让我们看一组更具体的展示注意里面的注释内容。 from app1.models import Place, Restaurant # 导入两个模型到shell里p1 Place.objects.create(namecoff,addressaddress1)p1 # p1是个纯Place对象 Place: Place objectp1.restaurant # p1没有餐馆属性 Traceback (most recent call last):File console, line 1, in moduleFile C:\Python36\lib\site-packages\django\db\models\fields\related_descriptors.py, line 407, in __get__self.related.get_accessor_name() django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist: Place has no restaurant.r1 Restaurant.objects.create(serves_hot_dogsTrue,serves_pizzaFalse)r1 # r1在创建的时候只赋予了2个字段的值 Restaurant: Restaurant objectr1.place # 不能这么调用 Traceback (most recent call last):File console, line 1, in module AttributeError: Restaurant object has no attribute placer2 Restaurant.objects.create(serves_hot_dogsTrue,serves_pizzaFalse, namepizza, addressaddress2)r2 # r2在创建时提供了包括Place的字段在内的4个字段 Restaurant: Restaurant objectr2.place # 可以看出这么调用都是非法的异想天开的 Traceback (most recent call last):File console, line 1, in module AttributeError: Restaurant object has no attribute placep2 Place.objects.get(namepizza) # 通过name我们获取到了一个Place对象p2.restaurant # 这个P2其实就是前面的r2 Restaurant: Restaurant objectp2.restaurant.address address2p2.restaurant.serves_hot_dogs Truelis Place.objects.all()lis QuerySet [Place: Place object, Place: Place object, Place: Place object]lis.values() QuerySet [{id: 1, name: coff, address: address1}, {id: 2, name: , address: }, {id: 3, name: pizza, address: address2}]lis[2] Place: Place objectlis[2].serves_hot_dogs Traceback (most recent call last):File console, line 1, in module AttributeError: Place object has no attribute serves_hot_dogslis2 Restaurant.objects.all()lis2 QuerySet [Restaurant: Restaurant object, Restaurant: Restaurant object]lis2.values() QuerySet [{id: 2, name: , address: , place_ptr_id: 2, serves_hot_dogs: True, serves_pizza: False}, {id: 3, name: pizza, address : address2, place_ptr_id: 3, serves_hot_dogs: True, serves_pizza: False}]其内部隐含的OneToOne字段形同下面所示 place_ptr models.OneToOneField(Place, on_deletemodels.CASCADE,parent_linkTrue, )可以通过创建一个OneToOneField字段并设置 parent_linkTrue自定义这个一对一字段。 从上面的API操作展示可以看出这种继承方式还是有点混乱的不如抽象基类来得直接明了。 Meta和多表继承 在多表继承的情况下由于父类和子类都在数据库内有物理存在的表父类的Meta类会对子类造成不确定的影响因此Django在这种情况下关闭了子类继承父类的Meta功能。这一点和抽象基类的继承方式有所不同。 但是还有两个Meta元数据属性特殊一点那就是ordering和get_latest_by这两个参数是会被继承的。因此如果在多表继承中你不想让你的子类继承父类的上面两种参数就必须在子类中显示的指出或重写。如下 class ChildModel(ParentModel):# ...class Meta:# 移除父类对子类的排序影响ordering []多表继承和反向关联 因为多表继承使用了一个隐含的OneToOneField来链接子类与父类所以象上例那样你可以从父类访问子类。但是这个OnetoOneField字段默认的related_name值与ForeignKey和 ManyToManyField默认的反向名称相同。如果你与父类或另一个子类做多对一或是多对多关系你就必须在每个多对一和多对多字段上强制指定related_name。如果你没这么做Django就会在你运行或验证(validation)时抛出异常。 仍以上面Place类为例我们创建一个带有ManyToManyField字段的子类 class Supplier(Place):customers models.ManyToManyField(Place)这会产生下面的错误 Reverse query name for Supplier.customers clashes with reverse query name for Supplier.place_ptr. HINT: Add or change a related_name argument to the definition for Supplier.customers or Supplier.place_ptr.解决方法是向customers字段中添加related_name参数. customers models.ManyToManyField(Place, related_nameprovider)。代理模型 使用多表继承时父类的每个子类都会创建一张新数据表通常情况下这是我们想要的操作因为子类需要一个空间来存储不包含在父类中的数据。但有时你可能只想更改模型在Python层面的行为比如更改默认的manager管理器或者添加一个新方法。 代理模型就是为此而生的。你可以创建、删除、更新代理模型的实例并且所有的数据都可以像使用原始模型非代理类模型一样被保存。不同之处在于你可以在代理模型中改变默认的排序方式和默认的manager管理器等等而不会对原始模型产生影响。 代理模型其实就是给原模型换了件衣服API实际操作的还是原来的模型和数据。 声明一个代理模型只需要将Meta中proxy的值设为True。 例如你想给Person模型添加一个方法。你可以这样做 from django.db import modelsclass Person(models.Model):first_name models.CharField(max_length30)last_name models.CharField(max_length30)class MyPerson(Person):class Meta:proxy Truedef do_something(self):# ...passMyPerson类将操作和Person类同一张数据库表。并且任何新的Person实例都可以通过MyPerson类进行访问反之亦然。 p Person.objects.create(first_namefoobar)MyPerson.objects.get(first_namefoobar) MyPerson: foobar下面的例子通过代理进行排序但父类却不排序 class OrderedPerson(Person):class Meta:# 现在普通的Person查询是无序的而OrderedPerson查询会按照last_name排序。ordering [last_name]proxy True一些约束 代理模型必须继承自一个非抽象的基类并且不能同时继承多个非抽象基类代理模型可以同时继承任意多个抽象基类前提是这些抽象基类没有定义任何模型字段。代理模型可以同时继承多个别的代理模型前提是这些代理模型继承同一个非抽象基类。 代理模型的管理器 如不指定则继承父类的管理器。如果你自己定义了管理器那它就会成为默认管理器但是父类的管理器依然有效。如下例子: from django.db import modelsclass NewManager(models.Manager):# ...passclass MyPerson(Person):objects NewManager()class Meta:proxy True如果你想要向代理中添加新的管理器而不是替换现有的默认管理器你可以创建一个含有新的管理器的基类并在继承时把他放在主基类的后面 # Create an abstract class for the new manager. from django.db import modelsclass NewManager(models.Manager):# ...passclass ExtraManagers(models.Model):secondary NewManager()class Meta:abstract Trueclass MyPerson(Person, ExtraManagers):class Meta:proxy True多重继承 多重继承和多表继承是两码事两个概念。 Django的模型体系支持多重继承就像Python一样。如果多个父类都含有Meta类则只有第一个父类的会被使用剩下的会忽略掉。 一般情况能不要多重继承就不要尽量让继承关系简单和直接避免不必要的混乱和复杂。 请注意继承同时含有相同id主键字段的类将抛出异常。为了解决这个问题你可以在基类模型中显式的使用AutoField字段。如下例所示 class Article(models.Model):article_id models.AutoField(primary_keyTrue)...class Book(models.Model):book_id models.AutoField(primary_keyTrue)...class BookReview(Book, Article):pass或者使用一个共同的祖先来持有AutoField字段并在直接的父类里通过一个OneToOne字段保持与祖先的关系如下所示 class Piece(models.Model):passclass Article(Piece):article_piece models.OneToOneField(Piece, on_deletemodels.CASCADE, parent_linkTrue)...class Book(Piece):book_piece models.OneToOneField(Piece, on_deletemodels.CASCADE, parent_linkTrue)...class BookReview(Book, Article):pass在Python语言层面子类可以拥有和父类相同的属性名这样会造成覆盖现象。但是对于Django如果继承的是一个非抽象基类那么子类与父类之间不可以有相同的字段名 比如下面是不行的 class A(models.Model):name models.CharField(max_length30)class B(A):name models.CharField(max_length30)如果你执行python manage.py makemigrations会弹出下面的错误 django.core.exceptions.FieldError: Local field name in class B clashes with field of the same name from base class A.但是如果父类是个抽象基类就没有问题如下 class A(models.Model):name models.CharField(max_length30)class Meta:abstract Trueclass B(A):name models.CharField(max_length30)用包来组织模型 在我们使用python manage.py startapp xxx命令创建新的应用时Django会自动帮我们建立一个应用的基本文件组织结构其中就包括一个models.py文件。通常我们把当前应用的模型都编写在这个文件里但是如果你的模型很多那么将单独的models.py文件分割成一些独立的文件是个更好的做法。 首先我们需要在应用中新建一个叫做models的包再在包下创建一个__init__.py文件这样才能确立包的身份。然后将models.py文件中的模型分割到一些.py文件中比如organic.py和synthetic.py然后删除models.py文件。最后在__init__.py文件中导入所有的模型。如下例所示 # myapp/models/__init__.pyfrom .organic import Person from .synthetic import Robot要显式明确地导入每一个模型而不要使用from .models import *的方式这样不会混淆命名空间让代码更可读更容易被分析工具使用。 验证器 在Django的模型字段参数中有一个参数叫做validators这个参数是用来指定当前字段需要使用的验证器也就是对字段数据的合法性进行验证比如大小、类型等。 Django的验证器可以分为模型相关的验证器和表单相关的验证器它们基本类似但在使用上有区别。 自定义验证器 一个验证器其实就是一个可调用的对象函数或类接收一个初始输入值作为参数对这个值进行一系列逻辑判断如果不满足某些规则或者条件则表示验证不通过抛出一个ValidationError异常。如果满足条件则通过验证不返回任何内容也就是默认的return None可以继续下一步。 验证器具有重要作用可以被重用在别的字段上是工具类型的逻辑封装。 下面是一个验证器的例子它只允许偶数通过验证 from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _def validate_even(value):if value % 2 ! 0:raise ValidationError(_(%(value)s is not an even number),params{value: value},)通过下面的方式将偶数验证器应用在字段上 from django.db import modelsclass MyModel(models.Model):even_field models.IntegerField(validators[validate_even])因为验证器运行之前输入的数据会被转换为 Python 对象因此我们可以将同样的验证器用在 Django form 表单中事实上Django为表单提供了另外一些验证器 from django import formsclass MyForm(forms.Form):even_field forms.IntegerField(validators[validate_even])你还可以通过Python的魔法方法__cal__()编写更复杂的可配置的验证器比如Django内置的RegexValidator验证器就是这么干的。 验证器也可以是一个类但这时候就比较复杂了需要确保它可以被迁移框架序列化确保编写了deconstruction()和__eq__()方法。这种做法很难找到参考文献和博文要靠自己摸索或者研究DJango源码。 from django.db import modelsclass Blog(models.Model):name models.CharField(max_length100)tagline models.TextField()def save(self, *args, **kwargs):if self.name ! blog:return # 只有实例的 bolg 的 name 为 blog 才可以保存else:super().save(*args, **kwargs) # 调用真正的save方法*args, **kwargs的参数设计确保我们自定义的save方法是个万金油不论Django源码中的save方法的参数怎么变我们自己的save方法不会因为参数定义的不正确而出现bug。
http://www.zqtcl.cn/news/191921/

相关文章:

  • a032网站模版自己建立网站怎么建
  • wordpress.商品厦门做网站优化价格
  • 学校网站建设源码视频生成链接网站
  • 江苏建设工程招投标网站wordpress 全部tags
  • 十堰网站建设有哪些公司wordpress删除摘要
  • 网站的功能和特色网页设计公司哪个济南兴田德润实惠吗
  • 汕头建站模板泰安建设银行网站
  • 服装平台网站有哪些网站开发 零基础
  • 致设计网站官网建设购物网站需要多少费用
  • 网站后台程序河南政务网站建设排名
  • 重庆建站网站建设平台wordpress插件使用数量
  • 规范网站建设情况的报告政务服务网站建设性建议
  • 麻涌做网站个人证书查询网全国联网
  • 做毕业设计网站的步骤那家做网站比较好
  • 网站开发学习网wordpress 数据库 插件
  • 企业公司官网网站做网站怎样做
  • 网站建设 今网科技电商网站建设布局
  • 最优惠的网站优化管理培训机构
  • p2p网站建设广州深圳网站设计公司哪家好
  • 福州网站设计哪里好泰安网站建设入门推荐
  • 北京网站软件制作外卖网站开发
  • 个人网站建设与实现建立个公司网站
  • 南昌招商网站建设临沂兰山建设局网站
  • 母婴网站建设怎么样可以做网站
  • 二手车 网站开发wordpress 定时 检查
  • 淮南官网济南seo优化外包
  • 沈阳网站建设莫道网络网站建设常用六大布局
  • 网站建设外文版要求网站关键字优化销售
  • 马来西亚做公路投标网站设计网页多少钱
  • 织梦网站多少钱广告多的网站