dongliang1983
4/28/2019 - 9:19 AM

[Django 模型]#学习笔记 #Web编程 #Django

[Django 模型]#学习笔记 #Web编程 #Django

[Django 模型]#学习笔记 #Web编程 #Django

学习笔记

模型是关于您的数据的单一的、确定的信息来源。它包含您存储的数据的基本字段和行为。通常,每个模型映射到单个数据库表。

基础知识:

  • 每个模型都是一个 Python 的类,这些类继承 django.db.models.Model
  • 模型类的每个属性都相当于一个数据库的字段。
  • 综上诉说,Django 给你一个自动生成访问数据库的 API;请参阅 Making queries。

简单的例子

这个样例模型定义了一个 Person, 其拥有 first_name 和 last_name:

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

firstnamelastname 是模型的字段。每个字段都被指定为 class 属性,每个属性映射到数据库列。
上面的 Person 模型会创建一个这样的数据库表:

CREATE TABLE myapp_person (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
);

一些技术上的说明:

  • 该表的名称 “myapp_person” 是自动从某些模型元数据中派生出来,但可以被改写。有关更多详细信息,请参阅:表命名。
  • 一个 id 字段会被自动添加,但是这种行为可以被改写。请参阅:默认主键字段。
  • The CREATE TABLE SQL in this example is formatted using PostgreSQL syntax, but it's worth noting Django uses SQL tailored to the database backend specified in your settings file.

使用模型

一旦你定义了你的模型,你需要告诉 Django 你将会使用这些模型。通过编辑你的设置文件和改变 INSTALLED_APPS 设置来添加包含你的 models.py 的模块的名称来实现这一点。
例如,如果您的应用程序的模型存在于模块myapp.models(为应用程序创建的包结构 manage.py startapp 脚本),INSTALLED_APPS** 应该阅读部分内容:

 INSTALLED_APPS = [
    #...
    'myapp',
    #...
]

当你添加新的 APP 到 INSTALLED_APPS, 去报先执行manage.py migrate,有时候需要限制性 manage.py makemigrations.

字段

模型中最重要的部分——以及模型中唯一需要的部分——是它定义的数据库字段列表。字段由 class 属性指定。注意不要选择与模型 API 冲突的字段名,如clean, save 或者 delete.
例子

from django.db import models

class Musician(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    instrument = models.CharField(max_length=100)

class Album(models.Model):
     artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    release_date = models.DateField()
    num_stars = models.IntegerField()

字段类型

模型中的每个字段都应该是适当字段类的一个实例。Django 使用field类类型来确定一些事情:

  • 列类型,它告诉数据库要存储什么样的数据(例如 INTEGER、VARCHAR、TEXT)。
  • 默认的 HTML 小部件在呈现表单字段时使用(例如:<input type="text">,<select>)。
  • 在 Django 的管理员和自动生成的表单中使用的最小验证需求。

Django 配备了数十种内置的字段类型;您可以在模型字段引用中找到完整的列表。如果 Django 的内置函数不奏效,您可以轻松地编写自己的字段;参见编写定制模型字段。

字段选项

每个字段都接受一组特定于字段的参数(在模型字段引用中记录)。例如,CharField(及其子类)需要一个 max_length 参数,该参数指定用于存储数据的VARCHAR 数据库字段的大小。
对于所有字段类型,也有一组通用的参数。都是可选的。它们在参考文献中得到了充分的解释,但这里有一个对最常用的解释的快速总结:
null 如果是 True,Django 会将空置的值存储为 NULL。默认是 False。
blank
如果是 True,这个字段是空白的。默认是 False。
注意,这与 null 不同。null 与数据库相关,而 blank 则是与验证相关的。如果一个字段有 blank=True ,表单验证就允许输入空值。如果一个字段有blank=False ,则需要字段。
choices
2 元组的可迭代(例如,列表或元组),用作此字段的选项。如果给出了这个,则默认表单小部件将是一个选择框而不是标准文本字段,并将限制对给定选项的选择。
选择列表如下:

YEAR_IN_SCHOOL_CHOICES = (
    ('FR', 'Freshman'),
    ('SO', 'Sophomore'),
    ('JR', 'Junior'),
    ('SR', 'Senior'),
    ('GR', 'Graduate'),
)

每个元组中的第一个元素是储存在数据库中的值。第二个元素由 field 的 form 小部件显示。
给定一个模型实例,可以使用 get_FOO_display()方法访问带有选择的字段的显示值。例如:

from django.db import models

class Person(models.Model):
    SHIRT_SIZES = (
        ('S', 'Small'),
        ('M', 'Medium'),
        ('L', 'Large'),
    )
    name = models.CharField(max_length=60)
    shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)

如果将 models 加载到 admin 后台,显示如下:
default
字段的默认值。这可以是一个值或者一个可调用的对象。如果每次创建新对象时都将调用 callable。
help_text 额外的“帮助”文本将显示在 form 小部件中。即使你的字段没有在表单上使用,它对文档也很有用。

name = models.CharField(max_length=60, default=None, help_text="个人姓名")

primary_key
如果 True,此字段是模型的主键
如果你没有指定你的模型中的任何字段的 primarykey=True,Django 会自动添加 IntegerField 来保存主键,所以除非你想要覆盖默认的主键行为,否则你不需要在任何字段上设置 primarykey=True。更多信息,请参见自动主键字段。
主键字段是只读的。如果您将主键的值更改为现存对象,然后保存它,那么将会在旧物件旁边创建一个新物件。例如:

from django.db import models

class Fruit(models.Model):
    name = models.CharField(max_length=100, primary_key=True)

unique
如果是真的,这个字段必须在整个表中是唯一的。

自动主键字段

在默认情况下,Django 提供了以下字段:

id = models.AutoField(primary_key=True)

这是一个自动递增的主键。
如果您想要指定一个定制的主键,请在您的一个字段中指定 primarykey=True。如果 Django 看到你已经明确地设置了字段。主键,它不会添加自动 id 列。
每个模型只需要一个字段来拥有 primarykey=True(要么显式声明,要么自动添加)。

详细字段名称

除了和 之外 ForeignKey, 每个字段类型都采用可选的第一个位置参数 - 一个详细的名称。如果没有给出详细名称,Django 将使用字段的属性名称自动创建它,将下划线转换为空格。
ManyToManyFieldOneToOneField

  • 在此示例中,详细名称为:"person's first name"
first_name = models.CharField("person's first name", max_length=30)

  • 在此示例中,详细名称为:"first name"
first_name = models.CharField(max_length=30)

ForeignKey, ManyToManyField and OneToOneField 都要求第一个参数是一个模型类,所以使用verbosename关键字参数:

poll = models.ForeignKey(
    Poll,
    on_delete=models.CASCADE,
    verbose_name="the related poll",
)
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(
    Place,
    on_delete=models.CASCADE,
    verbose_name="related place",
)

公约并不是要利用verbose_name的第一个字母。Django 会自动将第一个字母大写。

关联关系

显然,关系数据库的功能在于将表相互关联起来。Django 提供了定义三种最常见的数据库关系类型的方法:多对一、多对多和一对一。

多对一

要定义多对一关系,请使用django.db.models.ForeignKey。您可以像使用任何其他 Field 类型一样使用它:将其包含为模型的类属性。
ForeignKey 需要一个位置参数:模型相关的类。
例如,如果一个“汽车”模型有一个“制造商”——也就是说,“制造商”生产多辆汽车,但每辆车都只有一个“制造商”——使用以下定义:

from django.db import models

class Manufacturer(models.Model):
    # ...
    pass

class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
    # ...

多对多

要定义多对多关系,请使用 ManyToManyField。您可以像使用任何其他 Field 类型一样使用它 :将其包含为模型的类属性。
ManyToManyField 需要一个位置参数:模型相关的类。
例如,如果一个“pizza”有多个“topping”的对象——也就是说,“topping”可以在多个 pizza 上,每个“pizza”都有多种 topping——以下是你如何表示:

class Topping(models.Model):
    # ...
    pass

class Pizza(models.Model):
    # ...
    toppings = models.ManyToManyField(Topping)</pre>

多对多关系中的额外字段

当您只处理简单的多对多关系时,例如混合和匹配比萨饼和浇头,ManyToManyField 您只需要一个标准 。但是,有时您可能需要将数据与两个模型之间的关系相关联。
例如,考虑应用程序跟踪音乐家所属的音乐组的情况。一个人与他们所属的团体之间存在多对多的关系,因此您可以使用aManyToManyField来表示这种关系。但是,您可能希望收集的成员资格有很多详细信息,例如此人加入该组的日期。
对于这些情况,Django 允许您指定将用于管理多对多关系的模型。然后,您可以在中间模型上添加额外的字段。中间模型与ManyToManyField使用 through参数指向将充当中介的模型相关联 。对于我们的音乐家示例,代码看起来像这样:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):
        return self.name

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership')

    def __str__(self):
        return self.name

class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)

设置中间模型时,您明确指定多对多关系中涉及的模型的外键。此显式声明定义了两个模型的关联方式。
中间模型有一些限制:

  • 您的中间模型必须包含一个 - 且只有一个 - 源模型的外键(这将Group在我们的示例中),或者您必须显式指定 Django 应该用于关系的外键ManyToManyField.through_fields。如果您有多个外键through_fields但未指定,则会引发验证错误。类似的限制适用于目标模型的外键(这将 Person 在我们的示例中)。
  • 对于通过中间模型与自身具有多对多关系的模型,允许同一模型的两个外键,但它们将被视为多对多关系的两个(不同)侧。如果有更多的比两个外键虽然,你还必须指定through_fields如上,或验证错误将得到提升。
  • 在使用中间模型定义从模型到自身的多对多关系时,必须使用 symmetrical=False(请参阅 模型字段引用)。

现在您已经设置了ManyToManyField使用中间模型(Membership 在本例中),您已准备好开始创建一些多对多关系。您可以通过创建中间模型的实例来完成此操作:

一对一

定义一对一, use OneToOneField. 您可以像任何其他“字段”类型一样使用它:将其包含为模型的类属性。
当该对象以某种方式“扩展”另一个对象时,这在对象的主键上是最有用的。
OneToOneField 需要一个位置参数:模型相关的类。
例如,如果您正在构建“地点”数据库,您将在数据库中构建非常标准的内容,例如地址,电话号码等。然后,如果你想在这些地方建立一个餐馆数据库,而不是重复自己并在Restaurant模型中复制这些字段,你可以做Restaurant一个OneToOneFieldto Place(因为一个餐馆“是一个”地方;事实上,处理这通常使用 继承,它涉及隐式的一对一关系)。
与此同时ForeignKey,可以定义递归关系,并且可以对尚未定义的模型进行引用

跨文件的模型

将模型与另一个应用程序中的模型相关联是完全可以的。为此,请在定义模型的文件顶部导入相关模型。然后,只需在需要的地方引用其他模型类。例如:

from django.db import models
from geography.models import ZipCode

class Restaurant(models.Model):
    # ...
    zip_code = models.ForeignKey(
        ZipCode,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

字段名称限制

django 对模型字段名称只有两个限制:
1.字段名称不能是 Python 保留字,因为这会导致 Python 语法错误。例如:

class Example(models.Model):
   pass = models.IntegerField() # 'pass' is a reserved word!

2.由于 Django 的查询查找语法的工作方式,字段名称不能在一行中包含多个下划线。例如:

class Example(models.Model):
  foo__bar = models.IntegerField() # 'foo__bar' has two underscores!

但是,这些限制可以解决,因为您的字段名称不一定必须与您的数据库列名称匹配。请参阅 db_column 选项。
SQL 保留字(例如 join,where 或 select)被允许作为模型字段名称,因为 Django 会转义每个基础 SQL 查询中的所有数据库表名和列名。它使用特定数据库引擎的引用语法。

Meta 选项

使用内部提供模型元数据,如下所示:class Meta

class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:
        ordering = ["horn_length"]
        verbose_name_plural = "oxen"

模型元数据是“任何不是字段的东西”,例如排序选项(ordering),数据库表名(db_table)或人类可读的单数和复数名称(verbose_name和 verbose_name_plural)。不需要,添加到模型是完全可选的。class Meta
Meta 可以在模型选项参考中找到所有可能选项的完整列表。

模型属性

objects
模型最重要的属性是 Manager。它是为 Django 模型提供数据库查询操作的接口,用于 从数据库中检索实例。如果Manager未定义自定义,则默认名称为 objects。管理员只能通过模型 ​​ 类访问,而不能通过模型 ​​ 实例访问。

模型方法

在模型上定义自定义方法,以向对象添加自定义“行级”功能。虽然Manager方法旨在执行“表格范围”的事情,但模型方法应该作用于特定的模型实例。
这是将业务逻辑保存在一个地方的有价值的技术 - 模型
例如,此模型有一些自定义方法:

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()

    def baby_boomer_status(self):
        "Returns the person's baby-boomer status."
        import datetime
        if self.birth_date < datetime.date(1945, 8, 1):
            return "Pre-boomer"
        elif self.birth_date < datetime.date(1965, 1, 1):
            return "Baby boomer"
        else:
            return "Post-boomer"

    @property
    def full_name(self):
        "Returns the person's full name."
        return '%s %s' % (self.first_name, self.last_name)

此示例中的最后一个方法是属性
该模型实例参考具有的完整列表,自动给每个模型的方法。您可以覆盖其中的大多数 - 请参阅下面的覆盖预定义模型方法 - 但有几个您几乎总是想要定义:
__str__()

Python“魔术方法”,返回任何对象的字符串表示形式。这是 Python 和 Django 在模型实例需要被强制并显示为纯字符串时将使用的内容。最值得注意的是,当您在交互式控制台或管理员中显示对象时会发生这种情况。
你总是想要定义这个方法; 默认情况下根本没有用。
get_absolute_url()
这告诉 Django 如何计算对象的 URL。Django 在其管理界面中使用它,并且只要它需要找出对象的 URL。
具有唯一标识它的 URL 的任何对象都应定义此方法。

继承模型

模型继承在 Django 中与普通类继承在 Python 中的工作方式几乎完全相同,但也仍有遵循本页开头的内容。这意味着其基类应该继承自 django.db.models.Model。
您必须做出的唯一决定是您是希望父模型本身是模型(使用自己的数据库表),还是父母只是通过子模型可见的公共信息的持有者。
Django 中有三种可能的继承方式。
1.通常,您只想使用父类来保存您不希望为每个子模型键入的信息。这个类不会被孤立使用,所以抽象基类就是你所追求的。
2.如果你是现有模型的子类(可能是完全来自另一个应用程序的东西),并希望每个模型都有自己的数据库表,那么 多表继承是最佳选择。
3.最后,如果您只想修改模型的 Python 级行为,而不以任何方式更改模型字段,则可以使用 代理模型

抽象基类

当您想要将一些公共信息放入许多其他模型时,抽象基类非常有用。你写你的基类,并把abstract=True 类。然后,此模型将不用于创建任何数据库表。相反,当它用作其他模型的基类时,其字段将添加到子类的字段中。
一个例子:

from django.db import models

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True

class Student(CommonInfo):
    home_group = models.CharField(max_length=5)

Student模型将有三个领域:nameagehome_group。该CommonInfo模型不能用作普通的 Django 模型,因为它是一个抽象基类。它不生成数据库表或具有管理器,并且无法直接实例化或保存。
从抽象基类继承的字段可以使用其他字段或值覆盖,也可以使用删除 None。
对于许多用途,这种类型的模型继承将完全符合您的要求。它提供了一种在 Python 级别分解公共信息的方法,同时仍然只在数据库级别为每个子模型创建一个数据库表。

Meta 继承

当创建抽象基类时,Django 使 您在基类中声明的任何 Meta 内部类可用作属性。如果子类没有声明自己的 Meta 类,它将继承父类的 Meta。如果孩子想要扩展父类的 Meta 类,它可以将其子类化。例如:

from django.db import models

class CommonInfo(models.Model):
    # ...
    class Meta:
        abstract = True
        ordering = ['name']

class Student(CommonInfo):
    # ...
    class Meta(CommonInfo.Meta):
        db_table = 'student_info'

Django 确实对抽象基类的 Meta 类进行了一次调整:在安装 Meta 属性之前,它设置了abstract=False。这意味着抽象基类的子项本身不会自动成为抽象类。当然,您可以创建一个继承自另一个抽象基类的抽象基类。您只需要记住abstract=True每次都明确设置。
在抽象基类的 Meta 类中包含一些属性是没有意义的。例如,包含db_table意味着所有子类(未指定自己的 Meta)将使用相同的数据库表,这几乎肯定不是您想要的。

多表继承

Django 支持的第二种模型继承是当层次结构中的每个模型都是模型本身时。每个模型对应于自己的数据库表,可以单独查询和创建。继承关系引入子模型与其每个父模型之间的链接(通过自动创建 OneToOneField)。例如:

from django.db import models

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

尽管数据将驻留在不同的数据库表 PlaceRestaurant,但所有字段都将可用。所以这些都是可能的:

Place.objects.filter(name="Bob's Cafe")
Restaurant.objects.filter(name="Bob's Cafe")

如果你的 a Place 也是 a Restaurant,你可以使用模型名称的小写版本从 Place对象到Restaurant对象: 但是,如果 p 在上面的示例中不是 a Restaurant(它已直接创建为 Place 对象或是其他类的父级),则引用 p.restaurant 会引发 Restaurant.DoesNotExist 异常。
自动创建OneToOneFieldRestaurant,它链接到 Place 看起来像这样:

place_ptr = models.OneToOneField(
    Place, on_delete=models.CASCADE,
    parent_link=True,
)

Meta 和多表继承

在多表继承情况下,子类从其父类的 Meta 类继承是没有意义的。所有的 Meta 选项都已经应用于父类,并且再次应用它们通常只会导致矛盾的行为(这与基类本身不存在的抽象基类情况形成对比)。
因此,子模型无法访问其父级的 Meta 类。但是,有一些有限的情况,子进程从父进程继承行为:如果子进程没有指定 ordering属性或get_latest_by属性,它将从其父进程继承它们。
如果父级有一个排序而你不希望孩子有任何自然顺序,你可以明确地禁用它:

class ChildModel(ParentModel):
    # ...
    class Meta:
        # Remove parent's ordering effect
        ordering = []

代理模型

使用多表继承时,会为模型的每个子类创建一个新的数据库表。这通常是所需的行为,因为子类需要一个位置来存储基类上不存在的任何其他数据字段。但是,有时您只想更改模型的 Python 行为 - 可能更改默认管理器或添加新方法。
这就是代理模型继承的用途:为原始模型创建代理。您可以创建,删除和更新代理模型的实例,并且将保存所有数据,就像使用原始(非代理)模型一样。不同之处在于您可以更改代理中的默认模型排序或默认管理器等内容,而无需更改原始内容。
代理模型声明为普通模型。你通过设置类的 proxy 属性告诉 Django 它是一个代理模型。
例如,假设您要向 Person 模型添加方法。你可以这样做:

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

class MyPerson(Person):
    class Meta:
        proxy = True

    def do_something(self):
        # ...
        pass

该 MyPerson 班在同一个数据库表作为它的父工作 Person 类。特别是,任何新的实例 Person 也可以通过 MyPerson,反之亦然:
你仍然可以使用一个代理模型来定义模型的默认排序方法,你也许不会想一直对“Person”进行排序,但是通常情况下用代理模型根据“姓氏”属性进行排序这很简单。:

class OrderedPerson(Person):
    class Meta:
        ordering = ["last_name"]
        proxy = True

基类限制

一个代理模型必须仅能继承一个非抽象模型类。你不能继承多个非抽象模型类,因为代理模型无法提供不同数据表的任何行间连接。一个代理模型可以继承任意数量的抽象模型类,假如他们没有定义任何的模型字段。一个代理模型也可以继承任意数量的代理模型,只需他们共享同一个非抽象父类

模型代理管理器

如果未在代理模型上指定任何模型管理器,它将从其模型父项继承管理器。如果您在代理模型上定义管理器,它将成为默认管理器,尽管在父类上定义的任何管理器仍然可用。
继续上面的示例,您可以更改查询 Person 模型时使用的默认管理器,如下所示:

from django.db import models

class NewManager(models.Manager):
    # ...
    pass

class MyPerson(Person):
    objects = NewManager()

    class Meta:
        proxy = True

如果要在不更换现有默认值的情况下向代理添加新管理器,可以使用自定义管理器文档中描述的技术:创建包含新管理器的基类,并在主基类之后继承:

# Create an abstract class for the new manager.
class ExtraManagers(models.Model):
    secondary = NewManager()

    class Meta:
        abstract = True

class MyPerson(Person, ExtraManagers):
    class Meta:
        proxy = True

多重继承

正如 Python 的子类化一样,Django 模型可以从多个父模型继承。请记住,正常的 Python 名称解析规则适用。特定名称(例如 Meta)出现的第一个基类将是使用的基类; 例如,这意味着如果多个父类包含一个 Meta 类,则只会使用第一个类,而将忽略所有其他类。
通常,您不需要从多个父级继承。这有用的主要用例是“混入”类:向每个继承混合的类添加特定的额外字段或方法。尽量使您的继承层次结构尽可能简单明了,这样您就不必费力去找出特定信息来自哪里。
请注意,从具有公共 id 主键字段的多个模型继承将引发错误。要正确使用多重继承,可以AutoField在基本模型中使用explici

class Article(models.Model):
    article_id = models.AutoField(primary_key=True)
    ...

class Book(models.Model):
    book_id = models.AutoField(primary_key=True)
    ...

class BookReview(Book, Article):
    pass