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

posted @ 2020-07-20 18:20  Fallin  阅读(494)  评论(0)    收藏  举报