在上一篇博客《【Django 015】Django2.2数据模型关系之一对一(OneToOneField)》中我们了解了一对一关系,这一篇我们来看一对多。
我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。
一对多使用场景
算是三种关系中最常见的了,两个表中其中一个表的某列是指向另一个表的外键。
一对多SQL原理
直接用外键就可以到到目的,不需要加任何额外约束。在下面的实例中我们可以通过数据模型的DDL来进行验证。
一对多实例操作
建立两个模型如下,分别是员工表和公司表,其中一个员工只能任职于一个公司,但是一个公司可以有很多员工。所以在员工表中建立指向公司表的外键
class Company(models.Model):
c_name = models.CharField(max_length=32, unique=True)
class Employee(models.Model):
e_name = models.CharField(max_length=16)
e_comp = models.ForeignKey(Company,on_delete=models.CASCADE)
这里通过models.ForeignKey()
来声明一对多关系,两个参数和一对一时候一样,分别是另一个表的表名和on_delete
选项。声明的表叫从表,也就是一对多的那个多数,被声明的表叫主表,也就是一对多的那个唯一。这里从表就是Employee
,主表就是Company
。查看Employee
的DDL如下
create table Three_employee
(
id int auto_increment
primary key,
e_name varchar(16) not null,
e_comp_id int not null,
constraint Three_employee_e_comp_id_71fe82e0_fk_Three_company_id
foreign key (e_comp_id) references Three_company (id)
);
这里也是将模型定义时候表示实例的e_comp
变为了表示实例中的id名e_comp_id
,是一个指向Company
的id
列的外键。
添加数据
创建路由规则和view函数如下
path('add_employee/', views.add_employee, name='add_employee'),
def add_employee(request):
name = request.GET.get('name')
company_name = request.GET.get('company')
company = Company.objects.filter(c_name=company_name)
if company.exists():
employee = Employee()
employee.e_name = name
employee.e_comp = company.first()
employee.save()
else:
company = Company()
company.c_name = company_name
company.save()
employee = Employee()
employee.e_name = name
employee.e_comp = company
employee.save()
return HttpResponse('Add employee successfully')
这里是通过url的查询参数来传递员工名字和对应的公司名字。如果公司已经存在就查询以后添加到员工的公司信息中,如果公司不存在就首先添加公司,然后将公司信息添加到员工信息中。
先后访问http://127.0.0.1:8000/three/add_employee/?name=xiaofu&company=apple
和http://127.0.0.1:8000/three/add_employee/?name=cook&company=apple
的结果如下
Company
表:
Employee
表:
访问数据
和一对一关系一样,根据从表访问主表使用显性属性,根据主表访问从表使用隐性属性
根据从表访问主表
创建路由和view函数
path('get_company_from_employee/', views.get_company_from_employee, name='get_company_from_employee'),
def get_company_from_employee(request):
name = request.GET.get('name')
employee_list = Employee.objects.filter(e_name=name)
if employee_list.exists():
employee = employee_list.first()
company = employee.e_comp
return HttpResponse('Employee {} works for {}'.format(name,company.c_name))
else:
return HttpResponse('Did not find this employee')
因为直接调用显性属性,所以没有啥好说的。
如果访问http://127.0.0.1:8000/three/get_company_from_employee/?name=xiaofu
就会显示
Employee xiaofu works for apple
如果访问http://127.0.0.1:8000/three/get_company_from_employee/?name=james
就会显示
Did not find this employee
不过这里获取c_name
还有另一种更简便的方法,我们在下面删除数据中会看到。
根据主表访问从表
为了便于查询,我又添加了一些数据到两个表中,如下
创建路由和view函数
path('get_employees_from_company/', views.get_employees_from_company, name='get_employees_from_company'),
def get_employees_from_company(request):
company_name = request.GET.get('company')
company = Company.objects.filter(c_name=company_name)
if company.exists():
employee = company.first().employee_set # employee is a Manager instance, similar to objects
employee_names = employee.all()
context = {
'names': employee_names
}
return render(request, 'three_employees.html', context=context)
else:
return HttpResponse('Did not find the company')
这个view函数是重点,在获取到Company
实例后,也就是company.first()
,调用其employee_set
属性获得一个Manager
实例。这里的属性名是从表的类名小写加上set。之后就可以用这个Manager
实例进行类似于objects
的操作了,像filter
,get
等等,不过这里的全部集合是满足一对多关系的从表数据,所以直接用all方法获取所有记录即可。
随后传入模板文件three_employees.html
中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Eployees</title>
</head>
<body>
<h2>Employees:</h2>
<ul>
{% for name in names %}
<li>{{ name.e_name }}</li>
{% endfor %}
</ul>
</body>
</html>
访问三个公司的数据分别如下
删除数据
和一对一关系一样,我们先看看on_delete=models.CASCADE
的表现
删除从表数据
同样是根据显性属性来操作即可,不过这里使用了双下划线来获取主表数据,需要注意
path('delete_employee/', views.delete_employee, name='delete_employee'),
def delete_employee(request):
name = request.GET.get('name')
company_name = request.GET.get('company')
employee = Employee.objects.filter(e_comp__c_name=company_name).filter(e_name=name)
if employee.exists():
employee.first().delete()
return HttpResponse('Delete successfully')
else:
return HttpResponse('Did not find the employee')
这里直接用e_comp__c_name
来获取主表的c_name
值,格式为显性属性__字段名
。
此时访问http://127.0.0.1:8000/three/delete_employee/?name=xiaofu&company=xiaomi
就成功将xiaomi公司的xiaofu删除了,但是主表没有收到任何影响,这个和一对一中的结论是一样的。
删除主表数据
如果和一对一中现象一致的话,删除主表中的数据,从表中的级联数据也会被删除,我们来试试
path('delete_company/', views.delete_company, name='delete_company'),
def delete_company(request):
company_name = request.GET.get('company')
company = Company.objects.filter(c_name=company_name)
if company.exists():
company.first().delete()
return HttpResponse('Delete successfully')
else:
return HttpResponse('Did not find the company')
此时访问http://127.0.0.1:8000/three/delete_company/?company=xiaomi
发现不光主表中的xiaomi公司被删除了,从表中的Davis员工也被删除,符合预期。
和一对一中提到的多种on_delete
设置一样,这里也对应的有一样的设置,这里就不一一演示了。
总结
一对多和一对一在使用上还是很类似的,稍有不同的是这里又引入了双下划线获取主表字段的方法。下一节我们再看看第三类关系也是最复杂的,多对多。