Django Relationship fields 一对一(OneToOneField)
数据模型
Django中数据模型的关系可以用SQL中的表的关系来进行类比,共分为三类:
- 1:1
一对一的关系,例如一个学生对应一个学号,一个学号也只能对应一个学生。 - 1:N
一对多的关系,例如一个班级可以有多个学生,而一个学生只能在一个班级。 - M:N
多对多的关系,例如一个学生有多个课程,而每个课程也有多个学生。
一对一使用场景
通常见于对复杂表进行字段拆分,或者是在不修改现有表的情况下进行功能扩充。
一对一SQL原理
表A的主键是表B的外键,同时在表B的外键上加上UNIQUE限制,就形成了一对一关系。在下面的实例中我们可以通过数据模型的DDL来进行验证。
一对一实例操作
建立两个模型
建立两个数据模型如下,分别是员工信息和工号信息
# Employee:员工
class Employee(models.Model):
e_name = models.CharField(max_length=16)
e_sex = models.BooleanField(default=False) # False 代表女
class EmployeeID(models.Model):
id_num = models.CharField(max_length=25, unique=True)
id_emp = models.OneToOneField(Employee, on_delete=models.CASCADE)
Django中通过models.OnetoOneField()来声明一个一对一关系。第一个参数是另一个模型的名字,这里表EmployeeID的id_emp字段与Employee表的主键是一一对应的。通过查看EmployeeID的DDL可以看到
# pythonORM数据库操作记录
create table Two_employeeid
(
id int auto_increment
primary key,
id_num varchar(25) not null,
id_emp_id int not null,
constraint id_emp_id
unique (id_emp_id),
constraint Two_employeeid_id_emp_id_f50ad424_fk_Two_employee_id
foreign key (id_emp_id) references Two_employee (id)
);
第二个参数on_delete是删除另一张表数据时候本表的动作,在下面的删除数据部分会详细说明。
添加数据
然后建立一个路由和view函数来添加数据
path('add_employee/', views.add_employee, name='add_employee'),
# 添加员工
def add_employee(request):
name = request.GET.get('name')
emp_id = request.GET.get('emp_id')
sex = request.GET.get('sex')
employee = Employee() # 如果要让ID自动增加,请不要在init中添加参数
employee.e_name = name
employee.e_sex = sex
employee.save()
employee_id = EmployeeID()
employee_id.id_num = emp_id
employee_id.id_emp = employee
employee_id.save()
return HttpResponse('成功添加员工')
这里是用一个view函数同时添加两个表的数据。url会带进来三个查询参数,分别是name,emp_id和sex,分别赋值给单独定义的三个字段,声明一对一关系的第四个字段,需要赋值另一个模型的一个实例,也就是某一条记录。
这里额外提一嘴的就是最好将模型实例以后再对每个字段单独赋值,不要直接在初始化的时候带入值,因为有一个自带的id字段需要进行自动递增。
例如添加一条信息http://127.0.0.1:8000/two/add_employee/?name=xiaofu&emp_id=120120&sex=True之后两个表的内容如下
Employee表:

EmployeeID表:

针对声明一对一的第四个字段,我们定义的是id_emp,但是到这里却变成了id_emp_id,也就是一条记录被这条记录的id值取代了。注意,id_emp并没有消失,还是可以被调用,参考下面的访问数据部分。这里使用id_emp_id是为了在界面上直观展示。
访问数据
确定了一对一关系以后,主动声明关系的就是从表,而被声明关系的就是主表。在我们这里Employee就是主表,而EmployeeID就是从表。在生产环境中将更为重要的表设置为主表,后面的删除部分会有所体现。
根据从表获取主表
这个比较好理解,因为从表中已经有个对应的主表中的那条记录,所以可以直接调用
path('get_person/', views.get_person, name='get_person'),
# 访问员工
def get_person(request):
emp_id = request.GET.get('emp_id')
employeeid = EmployeeID.objects.get(id_num=emp_id)
employee = employeeid.id_emp
return HttpResponse('The name for {} is {}'.format(emp_id, employee.e_name))
根据传递进去的id值获取对应的EmployeeID记录实例,然后获取到对应的Employee记录实例,最后获取到对应的e_name字段的内容。
当然上面的方法也可以用下面的这种方法来理解,就是稍微麻烦点
ef get_person(request):
emp_id = request.GET.get('emp_id')
employeeid = EmployeeID.objects.get(id_num=emp_id)
id = employeeid.id_emp_id
employee = Employee.objects.get(id=id)
return HttpResponse('The name for {} is {}'.format(emp_id, employee.e_name))
此时访问http://127.0.0.1:8000/two/get_person/?emp_id=120120结果如下

根据主表获取从表
从表里面有个主表的实例,其实主表里面也有个从表模型名的隐性属性可供调用。
path('get_id/', views.get_id, name='get_id'),
def get_id(request):
name = request.GET.get('name')
employee = Employee.objects.get(e_name=name)
employeeid = employee.employeeid
return HttpResponse('The id for {} is {}'.format(name, employeeid.id_num))
此时访问http://127.0.0.1:8000/two/get_id/?name=xiaofu结果如下

删除数据
和查询数据一样,也对于主表和从表的操作有很大差别
删除从表数据
还是先从从表说起,删除从表的一条数据不会影响主表相关联的数据
path('delete_employee_by_id/', views.delete_employee_by_id, name='delete_employee_by_id'),
# 删除员工
def delete_employee_by_id(request):
emp_id = request.GET.get('emp_id')
if emp_id:
employee = EmployeeID.objects.filter(id_num=emp_id)
if employee.exists():
to_delete = employee.last()
to_delete.delete()
return HttpResponse('删除成功')
else:
return HttpResponse('找不到指定的ID')
else:
return HttpResponse('请指定一个ID')
直接通过传进去的id找到对应的记录删除即可,这里加入了对用户输入的一些判断,用filter的好处就是没找到记录也不会报错,不过需要额外用exists()来判断一下
此时访问http://127.0.0.1:8000/two/delete_employee_by_id/?emp_id=120120就会把上面EmployeeID中的记录删除了,但是Employee中的记录没有受影响
删除主表数据
但是删除主表数据就没那么简单了。上面创建表的时候我们提到从表中有个参数叫on_delete,我们设置的是models.CASCADE。这个表示删除主表数据的时候,从表中对应的数据也会级联删除
path('delete_employee_by_name/', views.delete_employee_by_name, name='delete_employee_by_name'),
def delete_employee_by_name(request):
name = request.GET.get('name')
if name:
employee = Employee.objects.filter(e_name=name)
if employee.exists():
to_delete = employee.last()
to_delete.delete()
return HttpResponse('Delete successfully')
else:
return HttpResponse('Did not find the name specified')
else:
return HttpResponse('Please specify a name')
此时访问http://127.0.0.1:8000/two/delete_employee_by_name/?name=xiaofu会把两个表中的记录同时删除。
需要注明的是,在Django的数据库图形界面中删除主表的数据因为没有触发级联操作,所以会报错
CASCADE虽然是默认的方式,但是并不是唯一的方式
几种on_delete方式对比
-
CASCADE
这个上面已经说到了,主表删除从表跟着删除 -
PROTECT
这种模式下删除主表记录会失败,只能先删除从表记录再删除主表记录,可以防止误删主表记录 -
SET_NULL
这种模式下删除主表记录会使得从表中对应记录的地方为空,后续可以绑定别的主表记录。此时从表中声明一对一的字段NULL=True要记得声明 -
SET_DEFAULT
这种模式下会用一个默认值进行替代,但是似乎比较矛盾,因为每条记录的值都是不一样的。 -
SET
这种模式下可以认为定义要赋什么值给字段
总结
一对一的数据模型关系就了解的差不多了,下一节我们一起来看看一对多的模型关系。
版权声明:本文为CSDN博主「T型人小付」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Victor2code/java/article/details/105150630

浙公网安备 33010602011771号