• ORM之字段属性与表关系


    关系类型字段

    除了普通类型字段,Django还定义了一组关系类型字段,用来表示模型与模型之间的关系

    一对一(OneToOneField)

    一对一关系类型的定义如下:

    class OneToOneField(to, on_delete, parent_link=False, **options)[source]

    一对一关系非常类似具有unique=True属性的外键关系,但是反向关联对象只有一个。这种关系类型多数用于当一个模型需要从别的模型扩展而来的情况。该关系的第一位置参数为关联的模型。

    一对一的这个关系字段写在两个表的任意一个表里面都可以。

    如果没有给一对一关系设置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_delete=models.CASCADE, # 就是foreignkey+unique,并且orm会自动给这个字段名字拼上一个_id,数据库中字段名称为user_id
        )
        supervisor = models.OneToOneField(
            settings.AUTH_USER_MODEL,
            on_delete=models.CASCADE,
            related_name='supervisor_of',
        )

    这样下来,User模型将拥有下面的属性:

    >>> user = User.objects.get(pk=1)
    >>> hasattr(user, 'myspecialuser')
    True
    >>> hasattr(user, 'supervisor_of')
    True

    OneToOneField一对一关系拥有和多对一外键关系一样的额外可选参数,只是多了一个parent_link参数。

    跨模块的模型

    如果关联的模型并不在当前模型的文件内,就可以像导入第三方库一样的从别的模块内导入进来,如下例所示:

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

    多对一(ForeignKey)

    多对一的关系,通常被称为外键。外键字段类的定义如下:

    class ForeignKey(to, on_delete, **options)[source]

    外键需要两个位置参数,一个是关联的模型,另一个是on_delete选项。

    外键要定义在‘多’的一方

    from django.db import models

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

    class Manufacturers(models.Model):
        # ...
        pass
    # 每辆车都会有一个生产工厂,一个工厂可以生产N辆车,于是用一个外键字段manufacturer表示,并放在Car模型中。

    假设Manufacturers模型存在于production这个app中,则Car模型的定义如下:

    class Car(models.Model):
        manufacturer = models.ForeignKey(
            'production.Manufacturers',   # 如果要关联的对象在另外一个app中,可以显式的指出
            on_delete=models.CASCADE,
        )

    如果要创建一个递归的外键,也就是自己关联自己的的外键,使用下面的方法:

    models.ForeignKey('self', on_delete=models.CASCADE) # 核心在于‘self’这个引用

    自己引用自己的外键典型的例子就是评论系统,一条评论可以被很多人继续评论,如下所示:

    class Comment(models.Model):
        title = models.CharField(max_length=128)
        text = models.TextField()
        parent_comment = models.ForeignKey('self', on_delete=models.CASCADE)
        # .....

    注意上面的外键字段定义的是父评论,而不是子评论。因为外键要放在‘多’的一方。

    在实际的数据库后台,Django会为每一个外键添加_id后缀,并以此创建数据表里的一列。在上面的工厂与车的例子中,Car模型对应的数据表中,会有一列叫做manufacturer_id。但在Django代码中并不需要使用这个列名,一般都直接使用字段名manufacturer,只有书写原生的SQL语句才会用到。

    verbose_name参数用于设置字段的别名。很多情况下,为了方便,我们都会设置这么个值,并且作为字段的第一位置参数。但是对于关系字段,其第一位置参数永远是关系对象,不能是verbose_name

      注意事项:

    • 表的名称myapp_modelName,是根据 模型中的元数据自动生成的,也可以覆写为别的名称  
    • id 字段是自动添加的
    • 对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名。
    • Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句。
    • 外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。

    参数说明:

    on_delete
    # 例:有一个可为空的外键,并且它在关联的对象被删除时,自动设为null,可以如下定义:
    user = models.ForeignKey(
        User,
        models.SET_NULL,
        blank=True,
        null=True,
    )

    这个参数在Django2.0之后,不可以省略,需要显式的指定。

    该参数可选的值都内置在django.db.models中,包括:

    • CASCADE:模拟SQL语言中的ON DELETE CASCADE约束,删除关联数据时,将定义有外键的模型对象同时删除。
    • PROTECT:阻止删除关联数据操作,引发ProtectedError异常
    • SET_NULL:删除关联数据,与之关联的值设置为null。只有当字段设置了null=True时,方可使用该值(前提FK字段需要设置为可空)。
    • SET_DEFAULT:删除关联数据,与之关联的值设置为默认值。只有当字段设置了default参数时,方可使用。
    • DO_NOTHING:什么也不做。
    • SET():设置为一个传递给SET()的值或者一个回调函数的返回值。
      • 删除关联数据,与之关联的值设置为指定值,设置:models.SET(值)
      • 删除关联数据,与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
    from django.conf import settings
    from django.contrib.auth import get_user_model
    from django.db import models

    def get_sentinel_user():
        return get_user_model().objects.get_or_create(username='deleted')[0]

    class MyModel(models.Model):
        user = models.ForeignKey(
            settings.AUTH_USER_MODEL,
            on_delete=models.SET(get_sentinel_user),
        )
    limit_choices_to

    该参数用于限制外键所能关联的对象,只能用于Django的ModelForm(Django的表单模块)和admin后台,对其它场合无限制功能。其值可以是一个字典、Q对象或者一个返回字典或Q对象的函数调用,如下例所示:

    staff_member = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        limit_choices_to={'is_staff'True},
    )

    这样定义,则ModelForm的staff_member字段列表中,只会出现那些is_staff=True的Users对象。。

    可以参考下面的方式,使用函数调用:

    def limit_pub_date_choices():
        return {'pub_date__lte': datetime.date.utcnow()}

    # ...
    limit_choices_to = limit_pub_date_choices
    # ...
    related_name

    用于关联对象反向引用模型的名称。反向操作时,使用的字段名,用于代替原反向查询时的表名_set

    通常情况下,这个参数可以不设置,Django会默认以模型的小写加上_set作为反向关联名。示例:

    class Car(models.Model):
        manufacturer = models.ForeignKey(
            'production.Manufacturer',      
            on_delete=models.CASCADE,
            related_name='car_producted_by_this_manufacturer'
        )

    以后从工厂对象反向关联到它所生产的汽车,就必须使用maufacturer.car_producted_by_this_manufacturer

    related_query_name

    反向关联查询名,反向查询操作时,使用的连接前缀,用于替换表名。用于从目标模型反向过滤模型对象的名称。

    class Tag(models.Model):
        article = models.ForeignKey(
            Article,
            on_delete=models.CASCADE,
            related_name="tags",
            related_query_name="tag",       # 注意这一行
        )
        name = models.CharField(max_length=255)

    # 现在可以使用‘tag’作为查询名了
    Article.objects.filter(tag__name="important")
    to_field

    默认情况下,外键都是关联到被关联对象的主键上(一般为id)。如果指定这个参数,可以关联到指定的字段上,但是该字段必须具有unique=True属性,也就是具有唯一属性。

    db_constraint

    默认情况下,这个参数被设为True,表示遵循数据库约束。如果设为False,那么将无法保证数据的完整性和合法性。在下面的场景中,可能需要将它设置为False:

    • 有历史遗留的不合法数据,没办法的选择
    • 你正在分割数据表

    当它为False,并且你试图访问一个不存在的关系对象时,会抛出DoesNotExist 异常。

    ForeignKey的db_contraint参数

    关系和约束:不加外键依然能表示两个表之间的关系。但就不能使用ORM外键相关的方法了。

    db_constraint=False只加两者的关系,没有强制约束的效果,并且ORM外键相关的接口(方法)还能使用,所以如果建立外键,并且不能有强制的约束关系,那么就可以将这个参数改为False
        customer = models.ForeignKey(verbose_name='关联客户', to='Customer',db_constraint=False)
    swappable

    控制迁移框架的动作,如果当前外键指向一个可交换的模型。使用场景非常稀少,通常请将该参数保持默认的True。

    多对多(ManyToManyField)

    class ManyToManyField(to, **options)[source]

    例:
    class Book(models.Model):

        title = models.CharField( max_length=32)
        publishDate=models.DateField()
        authors=models.ManyToManyField(to='Author'# 不管是一对多还是多对多,to参数的值是个字符串,如果是变量,就需要将要关联的那个表放到这个表的上面(从上往下加载)。

    多对多关系需要一个位置参数:关联的对象模型。

    在数据库后台,Django实际上会额外创建一张用于体现多对多关系的中间表。默认情况下,该表的名称是“多对多字段名+关联对象模型名+一个独一无二的哈希码”,例如‘author_books_9cdf4’,可以通过db_table选项,自定义表名。

    多对多表的三种创建方式:

    方式一:自行创建第三张表,但无法使用orm提供的set、add、remove、clear方法来管理多对多的关系。

    class Book(models.Model):
        title = models.CharField(max_length=32, verbose_name="书名")


    class Author(models.Model):
        name = models.CharField(max_length=32, verbose_name="作者姓名")


    # 自己创建第三张表,分别通过外键关联书和作者
    class Author2Book(models.Model):
        author = models.ForeignKey(to="Author")
        book = models.ForeignKey(to="Book")

        class Meta:
            unique_together = ("author""book")

    方式二:通过ManyToManyField自动创建第三张表,但自动生成的第三张表我们是没有办法添加其他字段。在中间表中,并不是将两张表的数据都保存在一起,而是通过id的关联进行映射。

    通常情况下,多对多第三张表在数据库内的结构是:

    中间表的id列....模型对象的id列.....被关联对象的id列
    # 各行数据

    方式三:设置ManyTomanyField并指定自行创建的第三张表(称为中介模型),可以在第三张关系表中存储额外的字段,可以使用多对多关联关系操作的接口(all、add、clear等等)

    class Book(models.Model):
        title = models.CharField(max_length=32, verbose_name="书名")


    # 自己创建第三张表,并通过ManyToManyField指定关联
    class Author(models.Model):
        name = models.CharField(max_length=32, verbose_name="作者姓名")
        books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author""book"))
        # through_fields接受一个2元组('field1','field2'):
        # 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。


    class Author2Book(models.Model):
        author = models.ForeignKey(to="Author")
        book = models.ForeignKey(to="Book")
        #可以扩展其他的字段了
        class Meta:
            unique_together = ("author""book")

    自定义中间表结构:自定义中间表并添加时间字段,则在数据库内的表结构如下

    中间表的id列....模型对象的id列.....被关联对象的id列.....时间对象列
    # 各行数据

    参数说明:

    related_name、related_query_name

    limit_choices_to

    对于使用through参数自定义中间表的多对多字段无效。

    symmetrical

    默认情况下,Django中的多对多关系是对称的。:

    from django.db import models

    class Person(models.Model):
        friends = models.ManyToManyField("self")

    Django不会为Person模型添加person_set属性用于反向关联。如果不想使用这种对称关系,可以将symmetrical设置为False,这将强制Django为反向关联添加描述符。

    through(定义中间表)

    自定义多对多关系的那张额外的关联表,参数的值为一个中间模型。

    例子:

    from django.db import models

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

    class Group(models.Model):
        name = models.CharField(max_length=128)
        members = models.ManyToManyField(
            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)
        inviter = models.ForeignKey(
            Person,
            on_delete=models.CASCADE,
            related_name="membership_invites",
        )
        invite_reason = models.CharField(max_length=64)
    through_fields

    through_fields参数接收一个二元元组('field1', 'field2'),field1是指向定义有多对多关系的模型的外键字段的名称,另外一个则是指向目标模型的外键字段的名称,through_fields参数指定从中间表模型中选择哪两个字段,作为关系连接字段。

    db_table

    设置中间表的名称。不指定的话,则使用默认值。

    db_constraint

    参考外键的相同参数。

    swappable

    参考外键的相同参数。

    ManyToManyField多对多字段不支持Django内置的validators验证功能。

    null参数对ManyToManyField多对多字段无效,设置null=True毫无意义

    >># 无效
    >>> beatles.members.add(john)
    >># 无效
    >>> beatles.members.create(name="George Harrison")
    >># 无效
    >>> beatles.members.set([john, paul, ringo, george])

    字段的参数

    所有的模型字段都可以接收一定数量的参数,比如CharField至少需要一个max_length参数。下面的这些参数是所有字段都可以使用的可选的参数。

    null

    该值为True时,Django在数据库用NULL保存空值。默认null=False。对于保存字符串类型数据的字段,应尽量避免将此参数设为True,那样会导致两种‘没有数据’的情况,一种是NULL,另一种是‘空字符串’。

    blank

    True时,字段可以为空。默认False。和null参数不同的是,null是纯数据库层面的,而blank是验证相关的,它与表单验证是否允许输入框内为空有关,与数据库无关。要小心一个null为False,blank为True的字段接收到一个空值可能会出bug或异常。

    default

    字段的默认值,可以是值或者一个可调用对象。如果是可调用对象,那么每次创建新对象时都会调用。设置的默认值不能是一个可变对象,比如列表、集合等等。lambda匿名函数也不可用于default的调用对象,因为匿名函数不能被migrations序列化。

    注意:在某种原因不明的情况下将default设置为None,可能会引发intergyerror:not null constraint failed,即非空约束失败异常,导致python manage.py migrate失败,此时可将None改为False或其它的值,只要不是None就行。

    unique

    设为True时,在整个数据表内该字段的数据不可重复。

    注意:

    1. 对于ManyToManyField和OneToOneField关系类型,该参数无效。

    2. 当unique=True时,db_index参数无须设置,因为unqiue隐含了索引。

    3. 自1.11版本后,unique参数可以用于FileField字段。

    primary_key

    如果没有给模型的任何字段设置这个参数为True,Django将自动创建一个AutoField自增字段,名为‘id’,并设置为主键。也就是id = models.AutoField(primary_key=True)

    如果为某个字段设置了primary_key=True,则当前字段变为主键,并关闭Django自动生成id主键的功能。

    primary_key=True隐含null=Falseunique=True的意思。一个模型中只能有一个主键字段

    另外,主键字段不可修改,如果你给某个对象的主键赋个新值实际上是创建一个新对象,并不会修改原来的对象。

    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']
    choices

    用于页面上的选择框标签,需要先提供一个二维的二元元组,第一个元素表示存在数据库内真实的值,第二个表示页面上显示的具体内容。在浏览器页面上将显示第二个元素的值。例如:

        YEAR_IN_SCHOOL_CHOICES = (
            ('FR''Freshman'),
            ('SO''Sophomore'),
        )

    一般来说,最好将选项定义在类里,并取一个直观的名字,如下所示:

    from django.db import models

    class Student(models.Model):
        FRESHMAN = 'FR'
        SOPHOMORE = 'SO'
        YEAR_IN_SCHOOL_CHOICES = (
            (FRESHMAN, 'Freshman'),
            (SOPHOMORE, 'Sophomore'),
        )
        year_in_school = models.CharField(
            max_length=2,
            choices=YEAR_IN_SCHOOL_CHOICES,
            default=FRESHMAN,
        )

        def is_upperclass(self):
            return self.year_in_school in (self.JUNIOR, self.SENIOR)

    要获取一个choices的第二元素的值,可以使用get_FOO_display()方法,其中的FOO用字段名代替。

    db_index

    该参数接收布尔值。如果为True,数据库将为该字段创建索引。

    db_column

    该参数用于定义当前字段在数据表内的列名。如果未指定,Django将使用字段名作为列名。

    db_tablespace

    用于字段索引的数据库表空间的名字,前提是当前字段设置了索引。默认值为工程的DEFAULT_INDEX_TABLESPACE设置。如果使用的数据库不支持表空间,该参数会被忽略。

    editable

    如果设为False,那么当前字段将不会在admin后台或者其它的ModelForm表单中显示,同时还会被模型验证功能跳过。参数默认值为True。

    error_messages

    用于自定义错误信息。参数接收字典类型的值。字典的键可以是nullblankinvalidinvalid_choiceuniqueunique_for_date其中的一个。

    help_text

    额外显示在表单部件上的帮助文本。使用时请注意转义为纯文本,防止脚本攻击。

    unique_for_date

    日期唯一。例:如果你有一个名叫title的字段,并设置了参数unique_for_date="pub_date",那么Django将不允许有两个模型对象具备同样的title和pub_date。有点类似联合约束。

    unique_for_month

    同上,只是月份唯一。

    unique_for_year

    同上,只是年份唯一。

    verbose_name

    为字段设置一个人类可读,更加直观的别名。

    对于每一个字段类型,除了ForeignKeyManyToManyFieldOneToOneField这三个特殊的关系类型,其第一可选位置参数都是verbose_name。如果没指定这个参数,Django会利用字段的属性名自动创建它,并将下划线转换为空格。

    下面这个例子的verbose name是"person’s first name":

    first_name = models.CharField("person's first name", max_length=30)

    下面这个例子的verbose name是"first name":

    first_name = models.CharField(max_length=30)

    对于外键、多对多和一对一字字段,由于第一个参数需要用来指定关联的模型,因此必须用关键字参数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",
    )

    另外,你无须大写verbose_name的首字母,Django自动为你完成这一工作。

    validators

    运行在该字段上的验证器的列表。

    其他参数

    DatetimeField、DateField、TimeField这个三个时间字段,都可以设置如下属性:

    auto_now_add :配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。

    auto_now配置上auto_now=True,每次更新数据记录的时候会更新该字段,标识这条记录最后一次的修改时间。

    关于auto_now:

    当需要更新时间的时候,我们尽量通过datetime模块来创建当前时间,并保存或者更新到数据库里面,看下面的分析:
    假如我们的表结构是这样的

    class User(models.Model):
        username = models.CharField(max_length=255, unique=True, verbose_name='用户名')
        is_active = models.BooleanField(default=False, verbose_name='激活状态')

    那么我们修改用户名和状态可以使用如下两种方法:

    方法一:

    User.objects.filter(id=1).update(username='nick',is_active=True)

    方法二:

    _t = User.objects.get(id=1)
    _t.username='nick'
    _t.is_active=True
    _t.save()

    方法一适合更新一批数据,类似于mysql语句update user set username='nick' where id = 1

    方法二适合更新一条数据,也只能更新一条数据,当只有一条数据更新时推荐使用此方法,另外此方法还有一个好处,我们接着往下看

    具有auto_now属性字段的更新
    我们通常会给表添加三个默认字段 
    - 自增ID,这个django已经默认加了,就像上边的建表语句,虽然只写了username和is_active两个字段,但表建好后也会有一个默认的自增id字段 
    - 创建时间,用来标识这条记录的创建时间,具有auto_now_add属性,创建记录时会自动填充当前时间到此字段 
    - 修改时间,用来标识这条记录最后一次的修改时间,具有auto_now属性,当记录发生变化时填充当前时间到此字段

    就像下边这样的表结构

    class User(models.Model):
        create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
        update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
        username = models.CharField(max_length=255, unique=True, verbose_name='用户名')
        is_active = models.BooleanField(default=False, verbose_name='激活状态')

    当表有字段具有auto_now属性且你希望他能自动更新时,必须使用上边方法二的更新,不然auto_now字段不会更新,也就是:

    _t = User.objects.get(id=1)
    _t.username='nick'
    _t.is_active=True
    _t.save()

    json/dict类型数据更新字段
    目前主流的web开放方式都讲究前后端分离,分离之后前后端交互的数据格式大都用通用的jason型,那么如何用最少的代码方便的更新json格式数据到数据库呢?同样可以使用如下两种方法:

    方法一:

    data = {'username':'nick','is_active':'0'}
    User.objects.filter(id=1).update(**data)

    同样这种方法不能自动更新具有auto_now属性字段的值
    通常我们再变量前加一个星号(*)表示这个变量是元组/列表,加两个星号表示这个参数是字典
    方法二:

    data = {'username':'nick','is_active':'0'}
    _t = User.objects.get(id=1)
    _t.__dict__.update(**data)
    _t.save()

    方法二和方法一同样无法自动更新auto_now字段的值
    注意这里使用到了一个__dict__方法
    方法三:

    _t = User.objects.get(id=1)
    _t.role=Role.objects.get(id=3)
    _t.save()

    #想让auto_now更新数据时自动更新时间,必须使用save方法来更新数据,所以很不方便,所以这个创建时自动添加时间或者更新时间的auto_now方法我们最好就别用了,比较恶心,并且支持我们自己来给这个字段更新时间:
    models.py:
    class Book(models.Model):
        name = models.CharField(max_length=32)
        date1 = models.DateTimeField(auto_now=True,null=True)
        date2 = models.DateTimeField(auto_now_add=True,null=True)

    views.py:
            import datetime
            models.Book.objects.filter(id=1).update(
                name='chao',
                date1=datetime.datetime.now(),
                date2=datetime.datetime.now(),
            )
  • 相关阅读:
    用代码初始化AE控件许可
    打开shpfile,mdb,sde工作空间
    UESTC_Tournament CDOJ 124
    UESTC_Little Deer and Blue Cat CDOJ 1025
    UESTC_Judgment Day CDOJ 11
    UESTC_One Step Two Steps CDOJ 1027
    UESTC_In Galgame We Trust CDOJ 10
    UESTC_贪吃蛇 CDOJ 709
    UESTC_冰雪奇缘 CDOJ 843
    java 常用API 时间
  • 原文地址:https://www.cnblogs.com/wby-110/p/13394054.html
Copyright © 2020-2023  润新知