【Django 016】Django2.2数据模型关系之外键一对多(ForeignKey)

本文深入探讨Django框架中的一对多数据模型关系,包括使用场景、SQL原理及实例操作,如添加、访问和删除数据的具体方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在上一篇博客《【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,是一个指向Companyid列的外键。

添加数据

创建路由规则和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=applehttp://127.0.0.1:8000/three/add_employee/?name=cook&company=apple的结果如下

Company表:
1-company.png

Employee表:
2-employee.png

访问数据

和一对一关系一样,根据从表访问主表使用显性属性,根据主表访问从表使用隐性属性

根据从表访问主表

创建路由和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还有另一种更简便的方法,我们在下面删除数据中会看到。

根据主表访问从表

为了便于查询,我又添加了一些数据到两个表中,如下
3-company.png
4-employee.png

创建路由和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的操作了,像filterget等等,不过这里的全部集合是满足一对多关系的从表数据,所以直接用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>

访问三个公司的数据分别如下
5-apple.png
6-xiaomi.png
7-dami.png

删除数据

和一对一关系一样,我们先看看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设置一样,这里也对应的有一样的设置,这里就不一一演示了。

总结

一对多和一对一在使用上还是很类似的,稍有不同的是这里又引入了双下划线获取主表字段的方法。下一节我们再看看第三类关系也是最复杂的,多对多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值