Python全栈技术

Django中的模型与数据库

2018-07-29  本文已影响80人  da_yu

文章内容大部分参考官方文档,以作者理解叙述
Django中模型是你的数据的唯一的,确定的信息源.它包含你所存储数据所必须的字段和行为.通常,每个模型对应数据库中唯一的一张表.

基础

使用模型

  1. 如下代码,在一个app(database)中创建一个记录人姓名的数据表
from django.db import models

# Create your models here.


class Person(models.Model):
    first_name = models.CharField(verbose_name='姓名1', max_length=30)
    last_name = models.CharField('姓氏', max_length=30)
  1. 然后使用 manage.py makemigrations 生成迁移脚本,控制台输入如下:
Operations to perform:   
  Apply all migrations: admin, auth, contenttypes, database, sessions
Running migrations:
  Applying database.0001_initial... OK

# 翻译
执行的操作:
  
应用所有迁移:admin,auth,contenttypes,数据库,会话

正在运行迁移:
  
应用database.0001_initial ...确定

  1. 生成的迁移脚本内容如下:
    迁移脚本在你的app下的migrations文件夹下.
# Generated by Django 2.0.7 on 2018-07-27 10:32

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Person',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('first_name', models.CharField(max_length=30)),
                ('last_name', models.CharField(max_length=30)),
            ],
        ),
    ]

除了我们命名的first_name和last_name以外,多出一个id的字段.
当我们创建模型类时,如果我们没有在模型类中显式的设置了主键,django会自动的添加id列,作为主键.
怎样显式的设置主键,在字段讲解中阐述.

  1. 然后使用manage.py mirgate 创建数据库,控制台输出如下:
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, database, sessions
Running migrations:
  Applying database.0001_initial... OK

# 翻译
执行的操作:
  
应用所有迁移:admin,auth,contenttypes,数据库,会话

正在运行迁移:
  
应用database.0001_initial ...确定

到此,数据库已经完成创建.

字段

上一小节,遗留了如何显式设置主键的问题,要解决他,了解下字段.

了解字段

根据数据表结构来说:

first_name = models.CharField(max_length=50)
 字段名             字段类型     字段属性

了解了数据表和模型类的对应关系后,只需要了解有哪些可选项供我们使用,就能简单,快速的创建模型类(数据表).

字段类型

这是字段类型的作用,在了解django admin前,只需关注一点,字段的类型是'告诉数据库要存储那种数据,并设置列类型'.
了解django的所有内置类型[https://yiyibooks.cn/xx/Django_1.11.6/ref/models/fields.html#model-field-types]
也可以编写自定义的模型字段,暂且不说.

字段属性

不同的字段类型其字段属性也有不同,可以在上面的字段类型链接后中详细了解,下面列举一些通用的字段属性.

关系

django提供了三种数据库关系:

  1. 多对一
  2. 多对多
  3. 一对一

多对一关系

django使用ForeignKey(to, on_delete, **options)来定义多对一关系.

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


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

on_delete表示删除时的行为或者理解为如何执行删除动作,models.CASCADE表示级联删除.

ER图 一对多.PNG

如果需要创建递归关联关系,应该这样写

models.ForeignKey('self', on_delete=models.CASCADE)

ForeignKey会主动创建索引,如果不需要,通过db_index=False显式的取消.
如果想要在两个app之间创建一对多关系,应该在源模型(被ForeignKey的模型类)的models中,引入目标模型(定义ForeignKey的模型类)的名称.

多对多关系

dajngo使用ManyTOManyField来定义多对多关系

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


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

ER图

捕获.PNG
从ER图可以清楚的了解,当使用多对对模型时,django会自动创建一个中间表,这个表维护了topping与pizza两张表之间的关系.
如果想要在中间表添加额外的字段,那就要使用django的中介模型
django允许指定一个中介模型来定义多对多关系,你可以将其他字段放在中介模型里面,源模型的ManyToMany字段将使用through参数/属性指向中介模型,在中介模型中需要显式的定义连个模型是如何关联的,就如上面没有指定中介模型的多对多关系中的pizza_id和topping_id.
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) # 其他字段
ER图 多对多中介模型.PNG

与上面没有使用中介模型的多对多关系相比,中间表多了额外的字段信息.
使用中介模型的限制:

一对一关系

django使用OneToOneField字段定义一对一关系,工作方式与ForeignKey完全一致,接受一个可选的的参数/属性 parent_link

Meta选项

模型元数据,是指任何不适字段的数据,比如排序选项, 数据库表名等,使用class Meta来定义

from django.db import models

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

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

模型属性

模型方法

可以在模型上定义自定义的方法给对象的添加增加一些功能.Manage方法用于'表范围'的事务,模型的方法也应该着眼于特定的模型实例.
也可以覆盖模型的遇定义方法:

执行自定义SQL

执行自定义SQL需要使用管理器(manage)的raw()方法,该方法返回模型的实例.
Manager.raw(raw_query, params=None, translations=None)

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

这样来使用,objects是默认的管理器(manage)名称,前文有提到.

person = Person.objects.raw('SELECT * FROM myapp_person')

模型的继承

模型继承方式与pytho类的继承使用相同,有三种形式

抽象基类

抽象基类是把模型类的共有信息,抽取出来,作为一个基类存在.这样避免在多个模型类中重复定义相同的字段.
第一步,定义基类
基类除了需要定义模型类的共有信息外,还需要在Meta类中声明属性 abstract=True,这个属性的作用是,阻止django对基类生成数据表.

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

    class Meta:
        abstract = True

第二步,继承基类
只需把基类名作为父类给子类即可,与python的继承相同.

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

注意,如果子类定义了与基类相同的字段,django将抛出异常.也就是说,子类定义的字段名不能与基类重复.
基类除了定义的字段可以继承,meta类也可以被继承,如下

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

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

不需要担心继承抽象基类的meta类后,子类也会变成抽象基类,原因是django在meta类继承时,会自动把abstract设置为False.
如果你需要抽象基类继承另一个抽象基类的场景,就需要在meta类中显式设置abstract=True.
抽象基类有效的避免代码不必要的重复代码.
注意,通过继承抽象基类的方式定义的模型类,在使用ForeignKey和ManyToMany时,将会遇到麻烦,因为继承自同一抽象基类,所以这些模型类的信息将完全相同.要解决此问题,应该是指定唯一反向名称和查询名称.

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

或者阻止该模型进行反向关联,将related_name设置为 +

class Base(models.Model):
    m2m = models.ManyToManyField(
        OtherModel,
        related_name='+',
    )

    class Meta:
        abstract = True

class ChildA(Base):
    pass

class ChildB(Base):
    pass

多表继承

多表继承使得每个模型类都有自己的数据表,都能够执行全部的model方法(完整性),多表继承是django通过隐式创建一对一关系的OneToOneField来实现的

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的全部数据,但Restaurant的数据表并没有Place的任何字段,Place也一样没有Restaurant的数据.
还可以通过多表继承的model对象,来获取对应的model对象.格式是:model对象.与之对应的模型类 类名(小写)
也可以在继承的模型类中显式声明OneToOneField,并设置parent_link=True来覆盖隐式声明的字段,覆盖后,自己定义的OneToOneField字段就是,访问父类的字段.
多表继承的meta也是可以继承的,但不能访问父类的meta类.
比如,子类没有定义ordering排序,就会继承父类的排序规则,这些都是隐式完成的.
如果不想继承弗雷德meta类属性,那么可以在子类显式声明这些属性.
注意,如果两个父类相同的模型类(子类),发生了ManyToManyField或者ForeignKey,会因为related_name相同,而引发一个一场.所以我们在同一父类的多个子类间发生多对多(ManyToManyField)或者一对多(ForeignKey)关系时,指定一个唯一的related_name,来解决这个问题.
例如: models.ManyToManyField(Place, related_name='provider')

代理模型

代理模型可以理解为,继承了模型类,创建了一个新的入口类,在这个类中可以更改默认的mange,增加需要的方法.并且这个类具备源模型类的所有方法.
设置代理模型,只需要在meta类中设置proxy=True,其余与声明一个普通模型类完全一样.

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

meta类继承方式同多表继承的meta类继承方式相同.

代理模型限制

代理模型管理器

如果没有在代理模型中定义管理器(manage),代理模型会继承父类的管理器
如果想要替换管理器,需要在代理模型中,对默认管理器重新赋值为新的管理器对象.这样做不影响父类的管理器.

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

class MyPerson(Person):
    objects = NewManager()

    class Meta:
        proxy = True

如果不想替换,而是想添加一个新的管理器,也就是说,想要代理模型中有一个以上的管理器.
需要定义一个含有新管理器的抽象基类,然后在代理模型中,把他放在主基类的后面.这样代理模型就有了两个管理器,一个objects,另一个secondary.

class ExtraManagers(models.Model):
    secondary = NewManager()

    class Meta:
        abstract = True

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

代理继承与非托管之间的差异

代理继承就是我们的代理模型的使用方式,什么是非托管?
非托管是当我们定义了一个模型类,但我们不需要由django来创建和删除这个数据表,这时,需要设置meta类中的managed=False.这样这个模型类,在数据库迁移时,就不会被创建.这就是非托管,不让django管理了.
代理继承是继承父类模型,并允许新增和重定义父类方法.根据他们的差异,应用场景如下:

多重继承

同python子类相同,django模型类也可以继承自多个父类模型,特定名称(例如meta)将继承自第一个基类,其他的被忽略.
多重继承的目的,应该是为了使子类拥有不同的属性和方法.
注意,多重继承遇到多个隐式声明主键id的父类时,和引发错误.

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
class Piece(models.Model):
    pass

class Article(Piece):
    article_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
    ...

class Book(Piece):
    book_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
    ...

class BookReview(Book, Article):
    pass

字段名称不允许覆盖

django模型里的继承,不允许子类覆盖父类的字段名称.继承的是抽象基类,不适用此规则.
抽象基类字段可以被子类覆盖护着通过 字段名=None的方式删除.(django1.10更改,早期版本不支持)

使用包(模块)组织模型(模型类)

如果有很多模型文件,可以使用包来组织模型,方法是,把需要组织的app下的models.py删除,创建app/models/目录,目录下创建init.py文件和你的模型文件.,每创建一个模型文件,就在init中,导入他们.
例如:
myapp/models/__init__.py

from .organic import Person
from .synthetic import Robot
上一篇 下一篇

猜你喜欢

热点阅读