12 - 模型

参考文档

  点我跳转

篇首吐槽

  又一次快写完博客没有保存

  恕我直言, 博客园的自动保存真的是辣鸡

  再不完善本地编辑器博客园吃枣药丸

  你瞅瞅别人CSDN

1 模型

  模型是关于数据信息的单一明确的资源, 模型包含了数据的必要的基本字段和行为

  一个模型对应数据库中的一张表

  其中

    1) 所有的模型类都是django.db.models.Model的子类

    2) 模型的每个字段( fileds )也就是类的属性( attribute )代表着数据库中表的一列( colum )

    3) Django提供了操作数据库的API

2 简单的示例

  定义一个含有 first_name 和 last_name Person

from django.db import models

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

  上述模型相当于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 , 这是因为派生自模型的元数据(model metadata)

    - id列时自动添加的

    - 这只是一个PostgreSQL的示例, 实际具体会根据设置为什么样的数据库语言

3 使用模型

  定义好的模型需要告知Django这个模型需要生效

  因此需要配置INSTALLED_APPS

  如果我们的模型模块myapp.models

INSTALLED_APPS  =  [ 
    #... 
    “myapp” ,
    #... 
]

  每当配置INSTALLED_APPS的时候, 需要对数据库操作

python manage.py makemigrations
python manage.py migrate

4 字段(Fields)  

  字段用类中的属性定义

  字段是模型中最重要的部分

  注意命名的时候字段的名字不要和模型的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()

4.1 字段类型

  每个字段都应该是Field类的对象

  不同的Field对象有以下区分:

    - 根据不同的Field类来设置不同的数据类型(INTEGER, VARCHAR, TEXT等)

    - 同时不同的Field类型在HTML的组件也是不一样的

    - 最后不同的数据类型有一些针对性的验证, 这个在后台管理和一些自动生成的表单中会体现

  具体类型参考

  自定义数据类型

4.2 字段选项

  每个字段类型都会传入一些特定的参数选项

    例如CharField就会传入max_length来限定字符的长度

  除了特定的参数还是有一些公共的参数:

  1) null

  默认为False, 设置为True表示空值的时候设置为NULL

  2) blank

  默认为False, 设置为True该字段可以为空

nullblank的不同

  null是数据库的要求, blank是验证性要求(主要应用于后台管理时)

  也就是说如果blank=True那么该地段可以不用传值

  3) choices

  它指定的数据类型是一个可迭代对象(列表或者元组), 其中的值必须是一个含有两个值的元组, 形式为

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

  如果设定为choice, 那么相对应的HTML空间就不是一个文本框(默认字段是)而是一个下拉菜单(select)

  关于它的值

    - 元组中的第一个元素是用于存放在数据库中的值, 因此查询出来的值也就是一个元素的值

    - 元组中的第二个元素是表单组件或者是ModelChoiceField的显示, 取得它的值可以使用方法 get_字段名字_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)

  使用为

>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'

  4) default

  字段的默认值

  值可以有两种

    - 一般的值

    - 可调用对象, 每次创建一个模型的对象的时候都会调用它

  5) help_text

  用于在表单组件的帮组信息

  6) primary_key

  如果设置为True, 表示这是模型的主键

  如果在模型中不特地的指定主键, 那么就会默认的创建一个自增的IntegerField的主键

  主键是只读的, 因此如果修改一个已经存在的对象的主键为另一个值, 并且保存, 那么得到的结果是再创建一个新的对象

  如创建一个主键为name的水果模型

from django.db import models

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

  对主键进行修改得到的结果

>>> fruit = Fruit.objects.create(name='Apple')
# 对主键进行了修改并保存
>>> fruit.name = 'Pear'
>>> fruit.save()
>>> Fruit.objects.values_list('name', flat=True)
['Apple', 'Pear']

  7) unique

  如果设置为true, 那么整个表中该字段中的每个值应该是唯一的

  还有更多的公共选项

4.3 自增主键字段

   如果在整个模型中没有指定任何字段为主键, 那么Django会自动创建一个主键字段

id = models.AutoField(primary_key=True)

  注意在模型中, 明确添加的主键字段应该是只有一个

 4.4 verbose name

  verbose name是用于展示的名字, 如在后台管理中的名字

  设定verbose name分为两种字段

  1) 一般字段

  设置的时候传入一个字符串就可以设置了

  如果不设置verbose name, Django会将该字段的名字处理(下换线替换成空格)一下得到

first_name = models.CharField(max_length=30)
# 此时的verbose name为first name

first_name = models.CharField("person's first name", max_length=30)
# 此时的verbose name为person's first name

  2) 特殊字段

  特殊字段有ForeignKey, ManyToManyFieldOneToOneField 

   创建方法是创建的时候添加一个关键字参数verbose_name

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",
)

  另外不用处理首字母大写的问题, Django会在合适的时候自己转换首字母大写

4.5 关系

  关系型数据库的优势就是各个表之间的相互关系, Django支持常见的表的关系

4.5.1 一对多(Many-to-one) 

   要定义一对多关系, 需要使用 django.db.models.ForeignKey , 用它来创建一个字段

  ForeignKey需要传入一个位置参数: 相关联的模型

  创建范例如下

from django.db import models

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

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

  另外还可以创建递归关系(自己与自己是一对多关系)

  另外, 官方建议:

    一对多的字段名字推荐命名为: 关联表的小写形式

4.5.2 多对多(Many-to-many) 

  要定义一对多关系, 需要使用 ManyToManyField , 用它来创建一个字段

  ManyToManyField 同样需要传入一个位置参数: 相关联的模型

  现在定义披萨和配料的模型为

from django.db import models

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

class Pizza(models.Model):
    # ...
    toppings = models.ManyToManyField(Topping)

  ManyToManyField在哪一张表定义都是可以的, 只要定义一个就可以了

  但是这或许还是有一些习惯的问题, 针对上面的例子, 一般来说我们都是说披萨有多个配料(而不是配料有多个披萨)

    而且一般也是在披萨中选择配料, 因此将多对多的关系设定到了披萨的模型中

  同样可以创建递归关系

  官方也建议:

    多对多的字段名字推荐命名为: 关联表小写的复数形式

4.5.3 多对多额外的字段

  标准的多对多关系只需要一个ManyToManyField字段就可以完成

  但是在大多数情况下, 多对多的关系可能需要保存一些额外的信息, 这个时候就可以需要使用第三张表来存储数据了

  例如音乐家与音乐团体是多对多的关系, 但是可能我们不仅需要了解他们之间的关系, 还需要知道音乐家在什么时候加入该团队的, 邀请的理由又是什么

  对此我们可以创建如下模型  

from django.db import models

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

    def __str__(self):              # __unicode__ on Python 2
        return self.name

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

    def __str__(self):              # __unicode__ on Python 2
        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)

  为了定义第三张表, 我们必须明确指定关联的那张表表, 为此有两个外键字段

  注意事项:

    1) 针对多对多的两方, 每一方必须在中间表中指定一个且唯一一个外键, 如果不指定外键, 那就需要在自己表中定义ManyToManyField.through_fields

    2) 如果是自己与自己形成的多对多关系, 运行表中这个表的两个外键, 但是会被看成多对多的两个方面, 如果出现了两个以上的外键, 则需要指定through_fields否则会校验错误

    3) 对于自己与自己多对多, 需要指定symmetrical=False

  使用多对多API操作数据库

>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(person=ringo, group=beatles,
...     date_joined=date(1962, 8, 16),
...     invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
]>
>>> ringo.group_set.all()
]>
>>> m2 = Membership.objects.create(person=paul, group=beatles,
...     date_joined=date(1960, 8, 1),
...     invite_reason="Wanted to form a band.")
>>> beatles.members.all()
, ]>

  带有额外字段的不能使用add(), create(), or set()来创建关系

  原因是:

    由于第三张表有附带的额外信息, 简单的添加, 创建和调用是无法得到这些详细信息的

  因此要创建只能通过第三张表的对象来实现了

  remove()被禁止的原因, 是因为指定的对象可能有多个

>>> Membership.objects.create(person=ringo, group=beatles,
...     date_joined=date(1968, 9, 4),
...     invite_reason="You've been gone for a month and we miss you.")
>>> beatles.members.all()
, , ]>
>>> # This will not work because it cannot tell which membership to remove
>>> beatles.members.remove(ringo)

  然而clear()是可以被使用的

>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()

  同样的可以执行一般的多对多查询

# Find all the groups with a member whose name starts with 'Paul'
>>> Group.objects.filter(members__name__startswith='Paul')
]>

  也可以加入中间件的属性

# Find all the members of the Beatles that joined after 1 Jan 1961
>>> Person.objects.filter(
...     group__name='The Beatles',
...     membership__date_joined__gt=date(1961,1,1))

  也可以直接查询第三方表

>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'

  还可以通过外键查询到第三方表获得信息

>>> ringos_membership = ringo.membership_set.get(group=beatles)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'

4.5.4 一对一

  要定义一对一关系, 可以使用OneToOneField来创建一个字段

  OneToOneField同样需要一个位置参数传递关联表

4.6 引用模型

  如果需要使用别的应用的模型, 需要导入该模型

  例如

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,
    )

4.7 字段名字的限制

  Django对名字的限制有两个

  1) 名字不能是Python的关键字

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

  2) 名字不能连续多个下划线

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

  3) 名字可以是SQL中的保留字, 因为Django会对名字进行转义处理, 而且也会使用特定的数据库引擎使用语法

4.8 自定义字段类型

  参看详细介绍  

5 元选项(Meta options)

  通过使用内部类Meta可以使用模型的元数据(model metadata) 

from django.db import models

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

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

  元数据(model metadata)也就是除字段以外的其他数据

  例如排序(ordering), 数据库表名(db_table), 单数形式(verbose_name ), 复数形式(verbose_name_plural)

  更多参见

6 模型属性

  模型最重要的属性是Manager

  它是Django提供用于查询数据库操作的接口

  如果不自定义Manager, 那么默认的名字就是objects

  只有通过模型类来调用Manager, 不能通过模型的对象来调用

7 模型方法  

  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)

  常用的需要被重写的函数

  1) __str__()

    在python2中是__unicode__() 

    这是Python的魔法函数, 用于修改对象输出的时候的输出信息

  2) get_absolute_url()

    该方法告诉Django如何为对象计算URL

    每个有唯一定义的URL的对象都应该定义这个方法

7.1 重写预定义的方法

  模型中还有一些封装了一系列数据库操作的函数, 如save()和delete()

  自定义这些函数也是十分有益的

  例如重写save()

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, *args, **kwargs):
        do_something()
        super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.
        do_something_else()

  还可以

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, *args, **kwargs):
        if self.name == "Yoko Ono's blog":
            return # Yoko shall never have her own blog!
        else:
            super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.

  在重写save()函数的时候, 一定要记得调用原来的函数以便于完成正常的功能

  同时使用万能参数也是必不可少的

7.2 执行自定义SQL

  使用raw SQL

  更多信息  

8 模型继承

  Django中的模型继承和正常的Python继承没有差别

  但是仍要注意, 模型始终要是django.db.models.Model的子类

  在Django中有三种继承方式

    1) 抽象基类

    2) 多表继承

    3) 代理模型

8.1 抽象基类

  当你想把一些公共的数据分发给其他模型的时候, 就可以使用抽象基类

  编写基类的时候需要在Meta类中设置 abstract=True 

  且抽象基类不能作为正常的模型使用, 不能生成表和Manager, 也不能直接实例化和保存

  但是要注意, 子类中不要出现和父类中同名的字段

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)

8.1.1 元继承

  继承抽象基类的模型, 同时还会继承抽象基类的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'

  注意一个细节, 如果子类把抽象基类的Meta都继承了, 那么abstract=True 也被继承了那岂不是无限的抽象基类了?

  为此, Django做了一个隐性的调整, 在被继承之前, 会把抽象基类的abstract=False

  还要注意不要在抽象基类的Meta中设置一些无效的属性, 例如设置 db_table , 这样如果子类不重写, 那岂不是每个表名都一样了?

8.1.2 related_name和related_query_name  

  当你在ForeignKey 或者 ManyToManyField 中使用 related_name 或者 related_query_name的时候一定要注意一件事情: 

    你必须为该字段设置唯一的 反向名字(reverse name)和查询名字(query name)

  为了解决这个问题, 需要在基类使用 related_name 或者related_query_name的时候使用 '%(app_label)s' 和 '%(class)s' 

    - '%(class)s' 表示字子类类名的小写

    - '%(app_label)s' 表示子类所在应用名的小写 

  由于应用名不应该相同, 同一个应用下模型名不相同, 这就会保证始终不相同

  例如

  common/models.py

from django.db import models

class 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 = True

class ChildA(Base):
    pass

class ChildB(Base):
    pass

  rare/models.py

from common.models import Base

class ChildB(Base):
    pass

  common.ChildA.m2m的反向名为: common_childa_related

  common.ChildA.m2m的查询名为: common_childas

  common.ChildB.m2m的反向名为: common_childb_related

  common.ChildA.m2m的查询名为: common_childbs

  rare.ChildB.m2m的反向名为: rare_childb_related

  rare.ChildB.m2m的查询名为: rare_childbs

  注意, 如果不在抽象基类中定义 related_name, 那么默认的反向名字为 小写类名_set

8.2 多表继承

  示例

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)

  Restaurant继承了Place表, Place表中的所有字段Restaurant都可以使用, 尽管数据存在于不同的表当中, 因而可以进行如下查询

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

  同时, 父表也可以查询到子表, 通过 点小写的字表名 来取得

  但是前提条件是父表的该对象实际是一个子表对象

>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant

  但是如果刚才的那个p不是一个餐馆对象, 而是一个直接用place创建的对象或者是其他类的基类锁创建的对象, 再使用p.restaurant会报错

  在餐馆表中自动创建与place一对一关系类似于

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

  如果你希望能够重写这个字段, 那么你可以选择自己创建一个 OneToOneField 字段并且在子表当中设置parent_link=True

8.2.1 多表继承和元  

  在多表继承的情况下, 子类继承父类的Meta是没有用的

  因此子类是无法访问到父类的Meta的  

  但是如果有两个属性除外, 就是 ordering和get_latest_by

  子类如果想消除父类对子类的影响, 就可以自定义自己的Meta类

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

8.2.2 继承和逆向关系

  因为多表继承隐藏的使用的一对一的关系, 因此从父类中获取子类的信息是可以的

  但是这将要使用related_name来标记外键, 如果这样设置的话, 每个子类都要设置related_name, 不设置的话就会校验出错

  对于上述的place, 在创建一个子类拥有多对多的字段

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 就可以解决了

models.ManyToManyField(Place, related_name='provider')

8.2.3 指定父链接字段

  正如之前所说, Django会自动创建一个 OneToOneField来链接子类与父类

  如果你想控制这个属性的话

  你可以自己创建一个 OneToOneField字段并且设置 parent_link=True  

8.3 代理模式 

8.4 多重继承

8.5 字段名字不能为Hiding

9 组织包中的模型           

 

转载于:https://www.cnblogs.com/weihuchao/p/7089796.html

你可能感兴趣的:(12 - 模型)