4 - django-orm基本使用

1 数据库与ORM

对象关系映射(英语:(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序技术,用于实现面向对象编程语言里不一样类型系统的数据之间的转换 。从效果上说,它实际上是建立了一个可在编程语言里使用的--“虚拟对象数据库”。前端

简单一句话来讲:就是把数据库的表映射为一个个对象,对对象的操做会被映射成SQL语句,在数据库执行。
django-ormpython

2 orm的配置

django默认支持sqlite,mysql, oracle,postgresql数据库。mysql

  • sqllit: django默认使用sqlite的数据库,默认自带sqlite的数据库驱动 , 引擎名称:django.db.backends.sqlite3
  • mysql: 引擎名称:django.db.backends.mysql

2.1 引擎和配置

在django的项目中会默认使用sqlite数据库,在settings里有以下设置:git

DATABASES = {
    'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

下面是MySQL的配置sql

DATABASES = {
    'default': {
- 'ENGINE': 'django.db.backends.mysql', 
- 'NAME': 'books',    # 你的数据库名称
- 'USER': 'root',     # 你的数据库用户名
- 'PASSWORD': '',     # 你的数据库密码
- 'HOST': '',-  # 你的数据库主机,留空默认为localhost
- 'PORT': '3306',     # 你的数据库端口
    }
}

参数以下:数据库

  • NAME即数据库的名字,在mysql链接前该数据库必须已经建立,而上面的sqlite数据库下的db.sqlite3则是项目自动建立
  • USER和PASSWORD分别是数据库的用户名和密码。

2.2 mysql驱动程序

django可使用以下mysql的驱动程序:django

MySQLdb(不完美支持Python3) 
mysqlclient (官方建议)
PyMySQL(纯python的mysql驱动程序)

建议使用:编程

  • mysqlclient
  • PyMySQL

django基础配置部分已经描述了mysqlclient的安装,这里介绍如何使用pymysql,首先须要安装缓存

pip install pymysql

下一步只须要找到项目名文件下的__init__,在里面写入:

import pymysql
pymysql.install_as_MySQLdb()

便可完成配置

3 orm 表模型

咱们所说的ORM主要分为两种:

  • DB First 数据库里先建立数据库表结构,根据表结构生成类,根据类操做数据库
  • Code First 先写代码,执行代码建立数据库表结构

主流的orm都是code first。django 的orm也是code first,因此本质上分为两部分:

  • 根据类自动建立数据库表
  • 根据类对数据库表中的数据进行各类操做

3.1 建立表对象

如今有一张表,主要字段以下:
orm-table
对应的models中的映射类为:

from django.db import models     # 导入models,django提供的对象包含不少建表的方法
 
# Create your models here.
 
class UserInfo(models.Model):    # 须要继承models.Model
    class Meta:
        db_table = 'userinfo'  # 在数据库中生成的代表

    id = models.AutoField(primary_key=True,null=False,verbose_name='ID')
    name = models.CharField(max_length=4,null=False,verbose_name='用户名')
    password = models.CharField(max_length=64,null=False,verbose_name='密码')
    email = models.EmailField(null=False,verbose_name='邮箱')
 
 
# AutoField : 自增字段,相似于mysql的int字段加auto_increment属性
# CharField:可变长字段,相似于mysql的varchar类型,须要指定长度
# EmailField:邮件字段,仅仅提供给 django admin进行约束使用,映射到MySQL上,根本上也是字符串类型
# null:是否为空,通用参数,默认为否。
# verbose_name:django admin上对表操做时,显示的字段名称
# primary_key:主键
# max_length:针对于CharField字段,标示其长度

3.2 Django字段类型

部分字段类型及说明以下:

字段名称 含义
AutoField(Field) int自增列,必须填入参数 primary_key=True
BigAutoField(AutoField) bigint自增列,必须填入参数 primary_key=True
注:当model中若是没有自增列,则自动会建立一个列名为id的列
SmallIntegerField(IntegerField) 小整数 -32768 ~ 32767
PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) 正小整数 0 ~ 32767
IntegerField(Field) 整数列(有符号的) -2147483648 ~ 2147483647
PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) 正整数 0 ~ 2147483647
BigIntegerField(IntegerField) 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807
BooleanField(Field) 布尔值类型
NullBooleanField(Field) 能够为空的布尔值
CharField(Field) 字符类型,必须提供max_length参数, max_length表示字符长度
TextField(Field) 文本类型
EmailField(CharField) 字符串类型,Django Admin以及ModelForm中提供验证机制
IPAddressField(Field) 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制
GenericIPAddressField(Field) 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
URLField(CharField) 字符串类型,Django Admin以及ModelForm中提供验证 URL
SlugField(CharField) 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、链接符(减号)
CommaSeparatedIntegerField(CharField) 字符串类型,格式必须为逗号分割的数字
UUIDField(Field) 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证
FilePathField(Field) 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功
FileField(Field) 字符串,路径保存在数据库,文件上传到指定目录
ImageField(FileField) 字符串,路径保存在数据库,文件上传到指定目录
DateTimeField(DateField) 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
DateField(DateTimeCheckMixin, Field) 日期格式 YYYY-MM-DD
TimeField(DateTimeCheckMixin, Field) 时间格式 HH:MM[:ss[.uuuuuu]]
DurationField(Field) 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型
FloatField(Field) 浮点型
DecimalField(Field) 10进制小数
BinaryField(Field) 二进制类型

3.3 经常使用字段参数说明

django中提供了不少参数对字段进行控制

通用类:

属性 含义
null 是否能够为空
default 默认值
primary_key 主键
db_column 列名
db_index 索引(bool)
unique 惟一索引

时间日期类:

属性 含义
unique_for_date 对日期字段来讲,表示只对时间作索引
unique_for_month 对日期字段来讲,表示只对月份作索引
unique_for_year 对日期字段来讲,表示只对年作索引
auto_now 不管是你添加仍是修改对象,时间为你添加或者修改的时间。
auto_now_add 为添加时的时间,更新对象时不会有变更。

django admin相关:

属性 含义
choices 在django admin中显示下拉框,避免连表查询(好比用户类型,能够在存在内存中)
blank 在django admin中是否能够为空
verbose_name 字段在django admin中显示的名称
editable 在django admin中是否能够进行编辑,默认是true
error_message 当在django admin中输入的信息不匹配时,字段的提示信息
help_text 在django admin中输入框旁边进行提示
validators 在django admin中自定义规则限制

django中提供了不少的字段类型,大部分都是提供给django admin 来作验证的,实际体如今数据库中的,大部分都是字符串类型。

3.4 特殊类型字段参数说明

GenericIPAddressField(Field):字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
参数:

  • protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
  • unpack_ipv4, 若是指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,须要protocol="both"

FilePathField(Field):字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
参数:

  • path:文件夹路径
  • match=None:正则匹配
  • recursive=False:递归下面的文件夹
  • allow_files=True:容许文件
  • allow_folders=False:容许文件夹

FileField(Field):字符串,路径保存在数据库,文件上传到指定目录
参数:

  • upload_to = "":上传文件的保存路径
  • storage = None:存储组件,默认django.core.files.storage.FileSystemStorage

ImageField(FileField):字符串,路径保存在数据库,文件上传到指定目录
参数:

  • upload_to = "" 上传文件的保存路径
  • storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
  • width_field=None, 上传图片的高度保存的数据库字段名(字符串)
  • height_field=None 上传图片的宽度保存的数据库字段名(字符串)

DecimalField(Field):10进制小数
参数:

  • max_digits,小数总长度
  • decimal_places,小数位长度

3.5 Meta信息

在类内部定义内部类Meta,因为设置映射的表的元数据信息,

class User(models.Model):
    class Meta:
        db_name = 'user'
    
    id...
    name...

这里的Meta的类属性db_name,表示生成的数据库表名称为'user',更多的参数有:

属性信息 含义
db_tablespace 有些数据库有数据库表空间,好比Oracle。你能够经过db_tablespace来指定这个模型对应的数据库表放在哪一个数据库表空间。
managed 默认值为True,Django能够对数据库表进行 migrate或migrations、删除等操做,若是为False的时候,不会对数据库表进行建立、删除等操做。能够用于现有表、数据库视图等,其余操做是同样的。
ordering 告诉Django模型对象返回的记录结果集是按照哪一个字段排序的
unique_together 设置两个字段保持惟一性时使用
verbose_name 给模型类起一个更可读的名字

经常使用的就是db_table,用于指定生成的表的名称

3.6 生成表

        前面咱们已经编写了对应数据库表的类,这里咱们将进行实例化(建立数据库对应的表)。利用django提供的命令进行数据库的初始化工做.(以及其余对数据库表进行修改的动做,好比修改表结构,字段属性等,都须要执行以下步骤)

# 进入项目目录下执行
python manage.py makemigrations
# 大体含义是:把类转换成对应格式的sql语句。
 
# 建立表
python manage.py migrate
# 在数据库中执行生成的sql语句

注意:

  • 执行makemigrations后生成的文件,存放在应用目录下的migrations目录下,通常以0001_initial.py为文件名而且依次递增。
  • 0001_initial.py存放的是 django 根据咱们写的 class 生成的相关代码,执行migrate后,会根据这些代码生成对应的数据库表。
  • 若是咱们执行migrate后没有生成咱们建立的表格,那么须要确认在django配置文件中,是否加载了咱们的应用(由于django会加载setting里面的install_app中查找对应的modules.py)
  • 表名默认是:应用名_class类名,能够在映射类内部定义内部类Meta,设置db_table='表名',来修改在数据库中生存的表名

3.6.1 注册app

若是没有生成对应的表文件,那么须要在settings.py中,注册你的app

# settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
    '你的app名称'       
]

3.6.2 修改表结构遇到的问题

当咱们对已生成的表添加新字段时,会出现以下状况

ou are trying to add a non-nullable field 'code' to business without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
Select an option:

        因为咱们添加字段会影响以前的数据,因此这时django提醒咱们,不能只是单纯的添加一个字段,还须要为该字段指定对应的值,固然有几种解决办法:

  • 设置新增字段default属性,表示其默认值
  • 设置新增字段null属性,表示其默承认觉得空
  • 根据上面的提示,选择1,手动输入默认值便可(注意默认值须要用引号引发来,否则django认为输入的数据是个变量)

4 利用orm完成数据库的增删改查

接下来就须要利用orm来对数据库的表数据进行增删改查等基本操做了。

4.1 orm之增长

        根据django的MTV架构,业务的处理是在views中完成的,那么对数据库的查询逻辑也应该在views中编写,而咱们定义的数据库对象在model.py中,那么在导入以后,才能进行表操做。

django提供了两种增长数据的方式:一、create,二、save

#------------------------------------- create -------------------------------------
from app01 import models      # 从本身的应用中导入models模块
 
username = 'daxin'
password = 'abc.123'
email = 'daixin@qq.com'
models.UserInfo.objects.create(name=username,password=password,email=email)
 
# 表对象.objects.create() 用来新增数据
# name,password,email 表示表的字段
# 等号右边的表示数据
# 传递的key也能够是字典利用**dic进行传递,在后面利用form的时候会常用这种方式。
dict = {'usernane':'daxin','password':'abc.123',email='daxin@qq.com'}
models.UserInfo.objects.create(**dict)
 


#------------------------------------- save -------------------------------------
from app01 import models      # 从本身的应用中导入models模块
 
username = 'daxin'
password = 'abc.123'
email = 'daixin@qq.com'
userobj = models.UserInfo(name=username,password=password,email=email)
userobj.save()
 
# save的方式是利用对象实例化的传递参数后,调用save方法进行保存的
# 利用对象,咱们能够方便的进行数据修改后,再进行save
# 好比 userobj.name = 'dahuaidan'
# userobj.save()

注意:利用create新增数据时,会默认返回新增长的数据对象,咱们能够接受该对象来进行其余的操做,好比添加多对多关系等

比较经常使用的方式是 利用create进行数据的增长

4.2 orm之删除

想要对数据进行删除,首先须要匹配到数据,而后执行删除操做,那么就涉及两部分:查找,删除

models.UserInfo.objects.filter(id=1).delete()
 
# 利用filter对数据惟一的字段进行过滤,匹配到删除的行,也能够用all()匹配全部数据,进行删除
# 利用delete方法进行删除操做

4.3 orm之修改

总体思路和删除是相同的,首先查找到数据,而后对字段进行修改

models.UserInfo.objects.filter(id=1).update(name='dachenzi')
 
# 利用filter过滤到要修改的数据,而后更新它的name字段的值

4.4 orm之查询

查询会返回结果的集,它是django.db.models.query.QuerySet类型。是惰性求值,和sqlalchemy同样。结果就是查询的集。同时它也是一个可迭代对象。

  • 建立查询集不会带来任何数据库的访问,直到调用方法使用数据时,才会访问数据库。在迭代、序列化、if语句中
    都会当即求值。
  • 每个查询集都包含一个缓存,来最小化对数据库的访问。

4.4.1 查询过滤方法

返回结果集的方法:

名称 说明
all() 获取全部
filter() 过滤,返回知足条件的数据
exclude() 排除,排除知足条件的数据
order_by() 以什么字段排序(在字段前面加减号,表示倒序)
values() 返回一个对象字典的列表,列表的元素是字典,字典内是字段和值的键值对
values_list() 返回一个对象字典的列表,列表的元素是元组,字典内是字段和值的键值对
models.Userinfo.object.all().values('id','username')
# 表示只取指定的id和username列
 
# 结果依旧返回的是QuerySet,可是数据不一样,体现的形式是一个字典
QuerySet({'id':1,'username':'daxin'},{'id':2,'username':'dachenzi'}......)
 
# 使用value_list('id','username'),体现的形式是一个元组
QuerySet((1,'daxin'),(2,'dachenzi')......)

关于filter:

  • filter(k1=v1).filter(k2=v2) 等价于 filter(k1=v1, k2=v2)
  • filter(pk=10) 这里pk指的就是主键, 不用关心主键字段名,固然也可使用使用主键名filter(std_id=10)

获取结果集的方法经过适当的组合能够链式编写。

返回单个值的方法:

名称 说明
get() 仅返回单个知足条件的对象
若是未能返回对象则抛出DoesNotExist异常;
若是能返回多条抛出MultipleObjectsReturned异常
count() 返回当前查询的总条数
first() 返回第一个对象
last() 返回最后一个对象
exist() 判断查询集中是否有数据,若是有则返回True
# 获取表里全部的对象
models.UserInfo.objects.all()    # 结果为一个QuerySet对象(django提供的),能够理解为一个列表
 
# 获取id为1的对象
models.UserInfo.objects.filter(id=1)   # 相似于sql中的 where查询条件,结果也是一个QuerySet
 
# 获取name字段包含da的记录
models.UserInfo.objects.filter(name__contains='da') # 这里涉及了万能的双下划线,在后续会进行说明
 
# get获取一条数据
models.UserInfo.objects.get(id=1)    # 注意get不到数据,会直接抛出异常
 
# 除了id等于1的其余数据
models.UserInfo.objects.exclude(id=1)

注意:

  • 查找到的不是想sql语句那样直接列出一行的各个字段及对应的值,而已把改行表示为一个行对象。匹配到一行,就表示1个表中的行对象。
  • 每一个对象内才包含了对应的字段和值。
  • 经过 行对象.字段名,来获取对应的数据。
  • filter中包含多个条件,那么它们是and的关系。

PS:因为filter、all取到的数据默认是 QuerySet 格式,在某些场景下咱们只须要验证是否取到,咱们能够直接获取结果中的第一个数据便可,即在最后添加.first()便可,表示取第一个数据,或者使用.count()来统计匹配到的数据的个数。

module.UserInfo.filter(username='daxin').first()

4.4.2 限制查询集(切片)

查询集对象能够直接使用索引下标的方式(不支持负索引),至关于SQL语句中的limit和offset子句。
注意使用索引返回的新的结果集,依然是惰性求值,不会当即查询。

qs = User.objects.all()[20:40]
# LIMIT 20 OFFSET 20
qs = User.objects.all()[20:30]
# LIMIT 10 OFFSET 20

4.4.2 字段查询(双下划线)

字段查询表达式能够做为filter()、exclude()、get()的参数,实现where子句。
语法:字段名称__比较运算符=值,属性名和运算符之间使用双下划线

比较运算符以下

名称 举例 说明
exact
filter(isdeleted=False)
filter(isdeleted__exact=False) 严格等于,可省略不写
contains exclude(title__contains='天') 是否包含,大小写敏感,等价于like '%天%'
statswith
endswith
filter(title__startswith='天') 以什么开头或结尾,大小写敏感
isnull
isnotnull
filter(title__isnull=False) 是否为null
iexact
icontains
istartswith
iendswith
i的意思是忽略大小写
in filter(pk__in=[1,2,3,100]) 是否在指定范围数据中
gt、gte
lt、lte
filter(id__gt=3)
filter(pk__lte=6)
filter(pub_date__gt=date(2000,1,1))
year、month、day
week_day、hour
minute、second
filter(pub_date__year=2000) 对日期类型属性处理

4.4.3 Q对象

        虽然Django提供传入条件的方式,可是不方便,它还提供了Q对象来解决。Q对象是django.db.models.Q,可使用&(and)、|(or)操做符来组成逻辑表达式。 ~ 表示not。

from django.db.models import Q
User.objects.filter(Q(pk__lt=6))           # 不如直接写User.objects.filter(pk<6)
User.objects.filter(pk__gt=6).filter(pk_lt=10)  # 与
User.objects.filter(Q(pk_gt=6) & Q(pk_lt=10))   # 与
User.objects.filter(Q(pk=6) | Q(pk=10))         # 或
User.objects.filter(~Q(pk__lt=6))               # 非

可以使用&|和Q对象来构造复杂的逻辑表达式

  • 过滤器函数可使用一个或多个Q对象
  • 若是混用关键字参数和Q对象,那么Q对象必须位于关键字参数的前面。全部参数都将and在一块儿

5 表与表之间的关系

咱们常说的表与表之间的关系有:一对1、一对多、多对多,下面分别说明

5.1 一对多

        表示当前表的某个字段的值,来自于其余表,好比人员表和部门表,在人员表中利用一对多外键关系标明该员工属于哪一个部门。

user_type = models.ForeignKey('表名',to_field='字段')       # 默认会自动关联对方表的ID字段(主键),手动指定的话必须是惟一列才行。

注意:

  1. 默认状况下,外键字段在数据库中存储的名称为:字段名_id (上面的例子的话,在数据库中的字段名:user_type_id)
  2. 外键字段(user_type_id)存放的是所关连表的id信息
  3. 同时还存在一个字段名(上面的例子就是user_type)对象,这个对象指代的就是所关联的表中的数据对象
  4. 咱们能够经过user_type,来跨表获取其关联的信息的某写字段的值(经过.字段名,便可访问)

5.1.1 正向查询

        什么叫正向查询?仍是拿人员表和部门表举例,外键关系存在人员表中,那么咱们经过人员表利用表中的外键字段就能够查询到该人员的部门信息,我通常称之为正向查询。

一对多跨表查询例子:

# -------------------- models.py --------------------

from django.db import models

class Business(models.Model):
    class Meta:
        db_table='business'
    caption = models.CharField(max_length=32)
    code = models.CharField(max_length=16,default='SA')

class Host(models.Model):
    class Meta:
        db_table='host'
    nid = models.AutoField(primary_key=True)
    hostname = models.CharField(max_length=16)
    ip = models.GenericIPAddressField(protocol='ipv4',db_index=True)
    port = models.IntegerField()
    b = models.ForeignKey(to='Business',to_field='id')    # 外键关联Business表

#  -------------------- views.py --------------------

def host(request):
    v1 = models.Host.objects.all()
    return render(request,'host.html',{'v1':v1})

# -------------------- host.html --------------------

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div>
        <p>主机信息</p>
        {% for row in v1 %}
            <p>{{ row.nid }} - {{ row.hostname }} - {{ row.ip }} - {{ row.port }} - {{ row.b_id }} - {{ row.b.caption }} - {{ row.b.code }}</p>   # 经过b对象,进行跨表查询主机业务线相关信息
        {% endfor %}
    </div>
</body>
</html>

这里经过row.b_id 和经过b对象来获取 row.b.id 结果是相同的,区别在于使用b对象,会多一次sql查询

PS:当多个表进行及联跨表,那么均可以经过表的外键字段使用点来进行跨表访问(或者使用双下划线)
双下划线和点跨表的不一样之处在于:

  • 双下划线通常用于条件查询(values,values_list)
  • 点则通常用于在结果中利用外键对象进行跨表查询
# -------------- 利用点进行跨表查询 --------------
 
# 若是我只想获取主机地址和所属业务线使用点跨表的话
v2 = models.Host.objects.all()
return render(request,'host.html',{'v2':v2})
# 前端代码
{% for row in v2 %}
    <p>{{ row.hostname }} - {{ row.b.caption }}</p>
{% endfor %}
# 咱们只用了两个字段,却取出了相关的全部数据。
 
# 是否能够在查询阶段只取出所需字段便可?使用双下划线__便可
 
 
# -------------- 利用双下划线跨表查询 --------------
 
v3 = models.Host.object.all().values('hostname','b__caption')
# 咱们能够看到在做为value的条件语句,经过b对象配合双下划线就进行了一次跨表查询
 
# 前端代码
<div>
    {%  for row in v2 %}
        <p>{{ row.hostname }} - {{ row.b__caption }}</p>    # 只须要向字典同样便可。
    {% endfor  %}
</div>

在django内部,它之因此能识别__,是由于,它在处理values条件时,会使用__做为分隔符,分隔后再进行相关处理

5.1.2 反向查询

        不少时候咱们会有另外一种需求,查询一个部门下的全部员工,这时部门表中没有外间字段关联人员表啊,该怎么查?实际上是能够的,我把这个查询方式称之为反向查询。
        在创建外键关系时,不只会在外键所在表中产生外键所关联表的对象,在所关联表中也会产生一个关联表的对象(可能有点绕,没找到更好的表达方式),这个对象通常有两种体现方式:

  1. 关联个人表(小写):通常用在values,values_list看成条件作字段过滤
  2. 关联个人表名(小写)_set:通常看成对象,看成字段处理
def index(request):
    users = models.User.objects.all()
    departments = models.Department.objects.values('user__name')   # 跨到管理本表的user表中,查找对应的user的名称,利用的是join 格式
    for dep in departments:
        print(dep)

        # {'user__name': 'daxin'}
        # {'user__name': 'dachenzi'}
        # {'user__name': 'hello'}
        # {'user__name': 'xiaoming'}
        # {'user__name': 'xiaochen'}
        
    return HttpResponse('ok')

对应的sql语句为:

  • SELECT"user"."name"FROM"department"LEFTOUTERJOIN"user"ON("department"."dept_id" = "user"."dept_id");
def index(request):
    departments = models.Department.objects.filter(pk=1)
    for dep in departments:
        for username in dep.user_set.values('name'):  # user_set指代的是关联的user表对象
            print(username)

            # {'name': 'daxin'}
            # {'name': 'dachenzi'}
            # {'name': 'hello'}

    return HttpResponse('ok')

对应的sql语句为:

  • SELECT "department"."dept_id", "department"."dept_name" FROM "department" WHERE "department"."dept_id" = 1;
  • SELECT "user"."name" FROM "user" WHERE "user"."dept_id" = 1;

5.2 多对多

        前面说了一对多的状况,这里还有一种状况叫多对对,好比一个主机能够关联多个业务线,不一样的业务线能够关联多个主机,因此这里,业务线和主机的关系为多对多,在多对多的状况下,有须要一张额外的表来表示对应关系,这里有两种状况来建立这张关系表。

5.2.1 手动建立

手动建立,故名思议,咱们须要手动的建立一张关系表,而后建立两个ForeignKey字段(一对多),关联两张表便可。

# 业务线表
class Business(models.Model):
    caption = models.CharField(max_length=32)
    code = models.CharField(max_length=16,default='SA')
 
# 主机表
class Host(models.Model):
    nid = models.AutoField(primary_key=True)
    hostname = models.CharField(max_length=16)
    ip = models.GenericIPAddressField(protocol='ipv4',db_index=True)
    port = models.IntegerField()
 
# 多对多关系表
class Application(models.Model):
    h = models.ForeignKey(to='Host',to_field='nid')          # 关联主机id,字段名为h_id,同时存在对象h,存放对应的Host信息
    b = models.ForeignKey(to='Business',to_field='id')       # 关联业务线id,字段名为b_id,同时存在对象b,存放对应的Business信息

PS:一共手动建立三张表,能够利用建立的Application关系表来直接操做多对多关系。

5.2.2 自动建立

在Django中,还存在一种方式为自动建立, 经过django提供的ManyToMany关键字建立。

# 业务线表
class Business(models.Model):
    caption = models.CharField(max_length=32)
    code = models.CharField(max_length=16,default='SA')
 
# 主机表
class Host(models.Model):
    nid = models.AutoField(primary_key=True)
    hostname = models.CharField(max_length=16)
    ip = models.GenericIPAddressField(protocol='ipv4',db_index=True)
    port = models.IntegerField()
    business = models.ManyToManyField('Business')      # 经过manytomany字段建立多对多关系

注意:

  1. 虽然咱们建立了两张表,可是因为咱们使用了manytomany关键字建立多对对关系,django还会为咱们建立一张表名为当前表名加当前字段名的表(上面的例子就是:app01_host_business),看成关系表,不会产生business字段。
  2. 因为咱们没有关系表这个class类对象,因此咱们不能直接操做关系表,须要经过Host对象间接经过business字段来操做关系表。
  3. 虽然利用manytomany关键字,能帮咱们建立关系表,节省了不少代码,可是自动生成的表中只会生成两个字段(两张表的主键id字段),不能定制其余字段,相反手动生成的表能够定制,平时能够根据状况两者混用。

5.2.3 操做关系表

手动建立关系表的状况下,因为含有第三张表对应的class,那么咱们能够直接使用这个class对关系表进行操做,可是多对多的状况下没有关系表的class,因此咱们须要经过其余办法来操做。

方法 含义
add() 添加关系
remove() 删除关系
clear() 清除全部关系  
all() 获取对应的全部关系对象(在查询时这里可使用all,filter,get等,就像查找过滤其余数据同样)
models.Host.objects.filter(nid=1).first().business.add(1,2)   # 添加两条多对多关系 1(host) --> 1(business),1(host) --> 2(business)
models.Host.objects.filter(nid=1).first().business.remove(2)  # 删除一条多对多关系 1 -x-> 2
models.Host.objects.filter(nid=1).first().business.clear()    # 清除nid为1的全部的多对多关系

下面是一个小栗子:

# models.py
class Bussiness(models.Model):
    class Meta:
        db_table = 'bussiness'
    bus_id = models.AutoField(primary_key=True)
    bus_name = models.CharField(max_length=64, null=False)

class Host(models.Model):
    class Meta:
        db_table = 'host'
    host_id = models.AutoField(primary_key=True)
    host_name = models.CharField(max_length=64, null=False)
    host_ip = models.GenericIPAddressField(protocol='both')
    bus = models.ManyToManyField(Bussiness)

# views.py
def index(request):
    hosts = models.Host.objects.all()
    for host in hosts:
        for bus in host.bus.all():   # 经过bus对象获取它关联的全部bussiness实例记录
            print(host.host_name, host.host_ip, bus.bus_name)
    return HttpResponse('ok')
相关文章
相关标签/搜索