Django 组件之 Model

1. 字段

1.1 自定义无符号整数

class UnsignedIntegerField(models.IntegerField):
    def db_type(self, connection):
        return 'integer UNSIGNED'

PS: 返回值为字段在数据库中的属性,Django字段默认的值为:
    'AutoField': 'integer AUTO_INCREMENT',
    'BigAutoField': 'bigint AUTO_INCREMENT',
    'BinaryField': 'longblob',
    'BooleanField': 'bool',
    'CharField': 'varchar(%(max_length)s)',
    'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
    'DateField': 'date',
    'DateTimeField': 'datetime',
    'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
    'DurationField': 'bigint',
    'FileField': 'varchar(%(max_length)s)',
    'FilePathField': 'varchar(%(max_length)s)',
    'FloatField': 'double precision',
    'IntegerField': 'integer',
    'BigIntegerField': 'bigint',
    'IPAddressField': 'char(15)',
    'GenericIPAddressField': 'char(39)',
    'NullBooleanField': 'bool',
    'OneToOneField': 'integer',
    'PositiveIntegerField': 'integer UNSIGNED',
    'PositiveSmallIntegerField': 'smallint UNSIGNED',
    'SlugField': 'varchar(%(max_length)s)',
    'SmallIntegerField': 'smallint',
    'TextField': 'longtext',
    'TimeField': 'time',
    'UUIDField': 'char(32)',

1.2 Model 验证和错误提示

Model 中验证和错误提示有两种方式:

  • Admin 内部的 ModelForm:优先
  • 验证 Model 字段:上述验证通过,才验证
  1. 通过 ModelForm 验证:
# admin.py
from django.contrib import admin
from app01 import models
from django import forms

class UserInfoForm(forms.ModelForm):
    age = forms.IntegerField(initial=1, error_messages={'required': '请输入数值', 'invalid': '年龄必须为数值'})
    class Meta:
        model = models.UserInfo
        fields = '__all__'
        exclude = ['title']
        labels = {'name': 'Writer',}
        help_texts = {'name': 'some useful help text',}		# 提示信息
        error_messages = {'name': {'max_length': 'this writer name is too long'}}	# 错误类型,字典
        # widgets = {'name': Textarea(attrs={'cols': 80, 'rows': 20})}


class UserInfoAdmin(admin.ModelAdmin):
    form = UserInfoForm

admin.site.register(models.UserInfo, UserInfoAdmin)
  1. Model 字段验证:
# models.py
from django.db import models

class UserInfo(models.Model):
    nid = models.AutoField(primary_key=True)
    username = models.CharField(max_length=32)
    email = models.EmailField(error_messages={'invalid': '格式错误'})

    def __str__(self):
        return self.username
    
# views.py
from app01 import models
from django.shortcuts import render, HttpResponse

def index(request):
    obj = models.UserInfo(username='12', email='12')
    try:
        print(obj.clean_fields())		# Model 的 clean 方法是一个钩子函数,可用于定制操作
    except Exception as e:
        print(e)		# {'email': ['格式错误']}
    return HttpResponse('ok')

# 访问 http://127.0.0.1:8000/app01/index/

3. 多表关系及参数

  • 一对一:Foreignkey 基础上加一个唯一索引 unique
  • 一对多:ForeignKey
  • 多对多:ManyToMany(两个一对多,两个 ForeignKey 相连)

3.1 一对多模型

一对多模型,如:员工部门表,一个部门可以有多个员工。那么 Django 怎么建立这种关系呢?

其实就是通过外键 ForeignKey 进行关联,在多的一方,字段指定外键即可

ForeignKey 字段参数

ForeignKey(ForeignObject) # ForeignObject(RelatedField)
        to,                         # 要进行关联的表名
        to_field=None,              # 要关联的表中的字段名称
        on_delete=None,             # 当删除关联表中的数据时,当前表与其关联的行的行为
        	    - models.CASCADE,删除关联数据,与之关联也删除
                - models.DO_NOTHING,删除关联数据,引发错误IntegrityError
                - models.PROTECT,删除关联数据,引发错误ProtectedError
                - models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
                - models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
                - models.SET,删除关联数据,
                                a. 与之关联的值设置为指定值,设置:models.SET()
                                b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)

                        def func():
                            return 10

                        class MyModel(models.Model):
                            user = models.ForeignKey(
                                to="User",		# 关联 User 表
                                to_field="id"	# 关联 User 表 的  id 字段
                                on_delete=models.SET(func),)

        related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
        related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
        
        limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
                # 如:
                - limit_choices_to={'nid__gt': 5}
                - limit_choices_to=lambda : {'nid__gt': 5}

                from django.db.models import Q
                - limit_choices_to=Q(nid__gt=10)
                - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
                
        db_constraint=True          # 是否在数据库中创建外键约束
        parent_link=False           # 在Admin中是否显示关联数据

示例:

class Department(models.Model):
    """部门表"""
    name = models.CharField(max_length=32)
    create_data = models.DateField(auto_now_add=True)	# 创建时间
    id_delete = models.BooleanField(default=False)		# 是否可删除
    
    class Meta:
        db_table = 'department'
    
    
class Employee(models.Model):
    """员工表"""
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    gender = models.IntegerField(default=0)
    salary = models.DecimalField(max_digits=8, decimal_places=2)	# max_digits 表示八个数字,包括两位小数,decimal_places 表示保留两位小数
    
    # null=True 表示可以为空, blank=True 表示 Django admin 后台可以输入空
    comment = models.CharField(max_length=300, null=True, blank=True)	
    hire_data = models.DateField(auto_now_add=True)
    department = models.ForeignKey('Department')		# 外键字段
    
    class Meta:
        db_table = 'employee'		# 数据表名字

Tips

在设置外键时,需要给子表(有外键的表)指定当主表(被连接的表)删除数据时,从表该如何处理。Django 通过设置 on_delete 属性来控制,它有三种值:

  • 删除时报错:DO_NOTHING/PROTECT
  • 级联删除:主表删除数据,从表与之关联的数据也将被删除:CASCADE
  • 设置默认值:SET_NULL/SET_DEFAULTset_null 仅在字段可以是 null 时才能使用

3.2 一对一模型

OneToOneField(ForeignKey)
    to,                         # 要进行关联的表名
    to_field=None               # 要关联的表中的字段名称
    on_delete=None,             # 当删除关联表中的数据时,当前表与其关联的行的行为
###### 对于一对一 ######
# 1. 一对一其实就是 一对多 + 唯一索引
# 2.当两个类之间有继承关系时,默认会创建一个一对一字段
# 如下会在A表中额外增加一个c_ptr_id列且唯一:
class C(models.Model):
    nid = models.AutoField(primary_key=True)
    part = models.CharField(max_length=12)

class A(C):
    id = models.AutoField(primary_key=True)
    code = models.CharField(max_length=1)
    
# 使用 OneToOneField 字段创建一对一模型
class Person(models.Model):
    name = models.CharField(max_length=32)
    o2o = models.OneToOneField(to='Person_detail', to_field='id', on_delete=models.CASCADE)
    
class Person_detal(models.Model):
    age = models.IntegerField()
    gender_choices = [(0, '男'), (1, '女')]
    gender = models.IntegerField(
        choices=gender_choices,
        default=0,
    )
    height = models.PositiveIntegerField()      # 正整数
    email = models.EmailField(max_length=64)

3.3 多对多模型

多对多其实就是两个一对多,通过两个外键将三张表相连,其中第三张表存储了前面两张表的对应关系。例如:名字和爱好表,一个人可以有多个爱好,一个爱好也可以是多个人有。

如何创建三张表:

  • 自动创建:ManyToMangField 字段自动创建,不能新增列
  • 手动创建:元 Meta,可以新增列
  • 手动加自动:元 Meta + ManyToMangField

多对多 ManyToManyField 字段参数:

ManyToManyField(RelatedField)
        to,                         # 要进行关联的表名
        related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
        related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
        limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
                                    # 如:
                                            - limit_choices_to={'nid__gt': 5}
                                            - limit_choices_to=lambda : {'nid__gt': 5}

                                            from django.db.models import Q
                                            - limit_choices_to=Q(nid__gt=10)
                                            - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                                            - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
                                    
        symmetrical=None,           # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段
                                    # 做如下操作时,不同的symmetrical会有不同的可选字段
                                        models.BB.objects.filter(...)

                                        # 可选字段有:code, id, m1
                                            class BB(models.Model):

                                            code = models.CharField(max_length=12)
                                            m1 = models.ManyToManyField('self',symmetrical=True)

                                        # 可选字段有: bb, code, id, m1
                                            class BB(models.Model):

                                            code = models.CharField(max_length=12)
                                            m1 = models.ManyToManyField('self',symmetrical=False)

        through=None,               # 自定义第三张表时,使用字段用于指定关系表
        through_fields=None,        # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表
                                        from django.db import models

                                        class Person(models.Model):
                                            name = models.CharField(max_length=50)

                                        class Group(models.Model):
                                            name = models.CharField(max_length=128)
                                            members = models.ManyToManyField(
                                                Person,
                                                through='Membership',
                                                through_fields=('group', 'person'),
                                            )

                                        class Membership(models.Model):
                                            group = models.ForeignKey(Group, on_delete=models.CASCADE)
                                            person = models.ForeignKey(Person, on_delete=models.CASCADE)
                                            inviter = models.ForeignKey(
                                                Person,
                                                on_delete=models.CASCADE,
                                                related_name="membership_invites",
                                            )
                                            invite_reason = models.CharField(max_length=64)
        db_constraint=True,         # 是否在数据库中创建外键约束
        db_table=None,              # 默认创建第三张表时,数据库中表的名称

自动创建

class UserInfo(models.Model):
    username = models.CharField(max_length=32, verbose_name='用户名')
    m = models.ManyToManyField(
    	to = 'Tag',
        related_name = 'bb',
    )
    
class Tag(models.Model):
    title = models.CharField(max_length=32)

手动创建

class UserInfo(models.Model):
    username = models.CharField(max_length=32, verbose_name='用户名')
    
class Tag(models.Model):
    title = models.CharField(max_length=32)
    
# 手动创建第三张表
class UserToTag(models.Model):
    tid = models.AutoField(primary_key=True)
    u = models.ForeignKey('UserInfo', on_delete=models.CASCADE)		# 通过外键相连
    t = models.ForeignKey('Tag', on_delete=models.CASCADE)

    #  联合唯一索引
    class Meta:
        unique_together = [
            ('u', 't'),
        ]

手动加自动

class UserInfo(models.Model):
    username = models.CharField(max_length=32, verbose_name='用户名')
    m = models.ManyToManyField(
    	through = 'UserToTag',		# 指定第三张表,不自动创建
        through_fields = ['u', 't']		# 指定第三张表的字段
    )
    
class Tag(models.Model):
    title = models.CharField(max_length=32)
    
# 手动创建第三张表
class UserToTag(models.Model):
    tid = models.AutoField(primary_key=True)
    u = models.ForeignKey('UserInfo', on_delete=models.CASCADE)		# 通过外键相连
    t = models.ForeignKey('Tag', on_delete=models.CASCADE)

    #  联合唯一索引
    class Meta:
        unique_together = [
            ('u', 't'),
        ]

3.4 自关联模型

自关联模型,即表中的某列关联表中另一列。最典型的自关联模型就是地区表(省市县三级联动)省的 pid 为 null,市的 pid 为省的 id,县的 pid 为市的 id。具体如下:

自关联表现形式

  • 省的 parent_id 为 null
  • 市的 parent_id 为对应省的 id
  • 区的 parent_id 为对应市的 id
  1. 数据库设计 models.py
class Area(models.Model):
    name = models.CharField(max_length=32, verbose_name='名称')
    parent = models.ForeignKey('self',          # 自关联字段的外键指向自身,也可以是 Area
                               verbose_name='上级行政区划',
                               on_delete=models.SET_NULL,
                               related_name='subs',
                               null=True,
                               blank=True
                               )

    class Meta:
        db_table = 'tb_areas'       # 自定义表名
        verbose_name = '行政区划'   # admin 中显示的名称
        verbose_name_plural = '行政区划'

    def __str__(self):
        return self.name

自关联模型,最核心的地方就是自己关联自己 self/Area,用一张表做两张表才能做的事。

  1. 路由系统 app/urls.py
from django.urls import path
from app import views

urlpatterns = [
    path('area/', views.area, name='area'),
    path('getPro/', views.getPro, name='getArea'),
    path('getCity/', views.getCity, name='getCity'),
    path('getDis/', views.getDis, name='getDis'),
]
  1. 视图函数 views.py
from django.shortcuts import render, HttpResponse
from app import models
from django.http import JsonResponse

# 访问 http://127.0.0.1:8000/app/area/,返回 area.html
def area(request):
    return render(request, 'area.html')


def getPro(request):
    """获取省份信息"""
    pro_list = models.Area.objects.filter(parent_id=None)		# 省份的 parent_id 为 None
    res = []
    for i in pro_list:
        res.append([i.id, i.name])
    print(res)      # [[1, '广东省'], [7, '湖南省']]
    return JsonResponse({'pro_list': res})		# JsonResponse 打包成 json 格式字符串


def getCity(request):
    """获取市信息"""
    city_id = request.GET.get('city_id')
    city_list = models.Area.objects.filter(parent_id=int(city_id))
    res = []
    for i in city_list:
        res.append([i.id, i.name])

    print('city', res)      #  [[2, '深圳市'], [3, '广州市'], [6, '湛江市']]
    return JsonResponse({'city_list': res})


def getDis(request):
    """获取区信息"""
    dis_id = request.GET.get('dis_id')
    dis_list = models.Area.objects.filter(parent_id=int(dis_id))
    res = []
    for i in dis_list:
        res.append([i.id, i.name])
    return JsonResponse({'dis_list': res})
  1. 模板 area.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <select id="pro">
        <option value="">请选择省份</option>
    </select>

    <select id="city">
        <option value="">请选择城市</option>
    </select>

    <select id="dis">
        <option value="">请选择区</option>
    </select>

<script src="{% static 'jquery-3.1.1.js' %}"></script>
<script>
    $(function () {
        var pro = $('#pro');
        var city = $('#city');
        var dis = $('#dis');

        // 查询省信息,$.get() 为 $.ajax 的简化版
        // 向 /app/getPro/ 发送 ajax 请求,arg 为请求成功返回的信息
        $.get('/app/getPro/', function (arg) {			
            console.log(arg);   // {'pro_list': [[1, '广东省'],[7, '湖南省']]}

            $.each(arg.pro_list, function (index, item) {
                console.log(item);      // [1, '广东省'] 、[7, '湖南省']
                console.log(index);     // 0、1
                
                // 将从数据库中取出的数据添加到 option 标签中,其中 value 为 id,文本值为 name
                var $new = $("<option value="+ item[0] +">" + item[1] + "</option>");
                pro.append($new);
            });
            
{# 或者使用 JavaScript for 循环#}
{#            console.log(arg.pro_list);#}
{#            for (var i = 0, len=arg.pro_list.length; i<len; i++){#}
{#                var $new = $('<option value='+ arg.pro_list[i][0] +'>' + arg.pro_list[i][1] + '</option>')#}
{#                pro.append($new);#}
{#            }#}

        });

        // 根据省的变化查询市,改变省时,清空市和区
        pro.change(function () {
            city.empty().append('<option value="">请选择市</option>');
            dis.empty().append('<option value="">请选择市</option>');
            $.ajax({
                url: '/app/getCity/',
                type: 'get',
                data: {'city_id': $(this).val()},
                success: function (arg) {
                    $.each(arg.city_list, function (index, item) {
                        var $new = $('<option value='+ item[0]+'>' + item[1] + '</value>');
                        city.append($new);
                    })
                }
            })
        });

        // 根据市变化查询区,改变市,清空区
        city.change(function () {
            dis.empty().append('<option value="">请选择市</option>');
            $.ajax({
                url: '/app/getDis/',
                type: 'get',
                data: {'dis_id': $(this).val()},
                success: function (arg) {
                    console.log('区', arg.dis_list);     // [[4, '宝安区'], ]
                        $.each(arg.dis_list, function (index, item) {
                            console.log(item[1]);
                            var $new = $('<option value='+ item[0] +'>' + item[1] + '</value>');
                            dis.append($new);
                    })
                }
            })
        })

    })
</script>
</body>
</html>

当访问 http://127.0.0.1:8000/app/area/ 时,会向 /app/getPro/ 发送 ajax 请求,请求成功获得包含省份 id、name 的信息 {'pro_list': [[1, '广东省'],[7, '湖南省']]}。取出数据遍历循环,添加到 option 标签中,从而获得省份信息。

当我们改变省份时,将上一次省份的市、区信息清空,避免添加重复。获取当前省份的 id,并向 /app/getDis/ 发送 ajax 请求,从而获得相应市的信息,同理区也是一样。

  1. 添加数据 views.py

添加数据,可以用 pycharm 自带的数据库添加,也可以手动添加:

def add(request):
    models.Area.objects.create(id=7, name='湖南省', parent_id=None)
    models.Area.objects.create(id=8, name='长沙市', parent_id=7)
    models.Area.objects.create(id=9, name='雨花区', parent_id=8)

    return HttpResponse('添加成功')

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wdJ5AtQO-1679749350500)(https://raw.githubusercontent.com/hj1933/img/master/iimg/%E7%BA%A7%E8%81%94%E8%8F%9C%E5%8D%95.gif)]

参考博客:

3.5 知识点补充

Ajax 的 get() 方法,它可以向后台发送 ajax 请求,请求成功可调用回调函数。如果需要在出错时执行函数,请使用 $.ajax,它是 $.ajax 简化版本。

语法

$(selector).get(url, data, success(response, status, xhr), dataType);	// 除了 url 必需,其他都可选,status 为请求状态,xhr - 包含 XMLHttpRequest 对象
$('button').click(function(){
    get('/app/getdata/', function(arg){});
});

3.6 Model 常用设计模型示例及方法

3.6.1 Model 设计

总共四张表,分别是班级、学生、学生个人信息以及老师表,之间的关系如下:

class Class(models.Model):
    """班级表"""
    class_name = models.CharField(max_length=32)
    create_data = models.DateField()

    def __str__(self):
        return self.class_name

    class Meta:
        verbose_name = '班级'


class Student(models.Model):
    """
    学生表
    一每个学生都有对应的个人信息(一对一),一个班级可以有多个学生(一对多)
    """
    student_name = models.CharField(max_length=32)

    # 一对多,与班级关联
    sc = models.ForeignKey(to='Class', to_field='id', related_name='stu', on_delete=models.CASCADE)

    # 一对一,与学生个人信息关联
    detail = models.OneToOneField('StudentDetail', to_field='id', on_delete=models.CASCADE)

    class Meta:
        verbose_name = '学生'

    def __str__(self):
        return self.student_name


class StudentDetail(models.Model):
    """学生个人信息表"""
    age = models.IntegerField()
    gender_choices = [(0, '男'), (1, '女')]
    gender = models.IntegerField(
        choices=gender_choices,
        default=0,
    )
    height = models.PositiveIntegerField()      # 正整数
    email = models.EmailField(max_length=64)


class Teacher(models.Model):
    """
    老师表
    一个老师可以教多个班,一个班也可以有多个老师
    """
    teacher_name = models.CharField(max_length=32)
    tc = models.ManyToManyField(to='Class', related_name='b')

    class Meta:
        verbose_name = '老师'

    def __str__(self):
        return self.teacher_name
  1. 老师表 app_teacher

  1. 学生个人信息表 app_student_detail

  1. 学生表 app_student

  1. 班级表 app_class

  1. 老师、班级关系表(第三张表)app_teacher_tc

3.6.2 查询操作

def query(request):
    ########### 一对一 #####################
    # 正向查询(根据外键字段)
    # 根据学生名字查询其个人信息
    # obj = models.Student.objects.filter(student_name='rose')[0]
    # print(obj)
    # print(obj.detail.age, obj.detail.gender, obj.detail.height, obj.detail.email)
    # # 17 1 170 456@qq.com


    # 反向查询(根据要查询的数据表名)
    # 根据邮箱查询学生名字
    # obj2 = models.StudentDetail.objects.filter(email='456@qq.com')[0]
    # print(obj2)         # <QuerySet [<StudentDetail: StudentDetail object (2)>]>
    #
    # print(obj2.student.student_name)        # rose


    ############## 一对多(班级表和学生表) ##################
    # 正向查询
     # 根据学生名查询所属班级
    # obj3 = models.Student.objects.get(student_name='rose')
    # print(obj3.sc_id)   # 1
    # print(type(obj3.sc))      # <class 'app.models.Class'>
    # print(obj3.sc.class_name)   # 一班


    # 反向查询
    # 二班有哪些学生
    # obj4 = models.Class.objects.filter(class_name='二班')[0]
    # print(obj4)
    # # res = obj4.student_set.all()     # 如果外键字段没有设置 related_name 就用 表名_set
    # res = obj4.stu.all()
    # print(res)       # <QuerySet [<Student: john>, <Student: lila>]>
    # for i in res:
    #     print(i.student_name)   # john、lila

    # 方法二
    # ret = models.Student.objects.filter(sc=obj4).values('student_name')   # 字典形式
    # print(ret)      # <QuerySet [{'student_name': 'john'}, {'student_name': 'lila'}]>
    # for i in ret:
    #     print(i['student_name'])    # john、lila

    # # 双下划线
    # 正向
    obj4 = models.Class.objects.filter(class_name='二班').values('stu__student_name')

    # 反向
    obj5 = models.Student.objects.filter(sc__class_name='二班').values('student_name')



############################### 多对多(老师表与班级表) ############################################
    # 查看 孙老师教哪几个班
    # 正向查询(通过多对多字段)

    # obj5 = models.Teacher.objects.filter(teacher_name='孙老师')[0]
    # ret = obj5.tc.all()
    # print(ret)      # <QuerySet [<Class: 一班>, <Class: 二班>]>
    # for i in ret:
    #     print(i.class_name)

    # 查看一班有哪几个老师
    # 反向查询
    # obj5 = models.Class.objects.filter(class_name='一班')[0]
    # ret = obj5.b.all()
    # print(ret)          # < QuerySet[ < Teacher: 孙老师 >, < Teacher: 刘老师 >] >
    # for i in ret:
    #     print(i.teacher_name)       # 孙老师、刘老师

    # # 双下划线
    # # 如果没有设置 related_name = b,那么就是 values('teacher__name`) 即要查的表的表名__要查询的字段
    # obj6 = models.Class.objects.filter(class_name='一班').values('b__teacher_name')
    # print(obj6)     # <QuerySet [{'b__teacher_name': '孙老师'}, {'b__teacher_name': '刘老师'}]>

    # # 正向  tc = models.ManyToManyField(to='Class', related_name='b')
    # obj6 = models.Teacher.objects.filter(tc__class_name='一班').values('teacher_name')

    return HttpResponse('查询成功!')

3.6.3 总结

一对一、一对多、多对多,最好都设置好 related_name 参数,没有设置时,反向查找用 要查询的命名.查询字段 或 _set.all()

一对一

  • 正向查询:根据 OneToOneField 字段查询
  • 反向查询:根据 要查询的命名.查询字段查询
# detail = models.OneToOneField('StudentDetail', to_field='id', on_delete=models.CASCADE)
# 正向
obj = models.Student.objects.filter(student_name='rose')[0]
print(obj.detail.age)

# 反向
obj2 = models.StudentDetail.objects.filter(email='456@qq.com')[0]
print(obj2.student.student_name)	# 这里没设置 related_name 因此用表名

一对多

  • 正向查询:根据外键字段查询
  • 反向查询
    • 设置了related_name,就用这个名字查询
    • 没有设置,用表名_set 方式查询
  • 双下划线查询
    • 正向:filter(条件).values(要查询的表名__要查询字段)
    • 反向:filter(外键字段__条件).values(要查询字段)
# sc = models.ForeignKey(to='Class', to_field='id', related_name='stu', on_delete=models.CASCADE)
# 正向
obj3 = models.Student.objects.get(student_name='rose')
print(obj3.sc.class_name)	# 根据外键字段 sc 查询

# 反向
obj4 = models.Class.objects.filter(class_name='二班')[0]
# res = obj4.student_set.all()	# 没设置 related_name,用表名_set
res = obj4.stu.all()		# 用 related_name 名字

# 双下划线
# 正向
obj5 = models.Class.objects.filter(class_name='二班').values('stu__student_name')

# 反向
obj6 = models.Student.objects.filter(sc__class_name='二班').values('student_name')

多对多

  • 正向查询:ManyToManyField 字段查询
  • 反向查询:表名_set、related_name名字查询
  • 双下划线与一对多一样
# tc = models.ManyToManyField(to='Class', related_name='b')
# 正向
obj5 = models.Teacher.objects.filter(teacher_name='孙老师')[0]
print(obj5.tc.all())

# 反向
obj5 = models.Class.objects.filter(class_name='一班')[0]
print(obj5.b.all())

参考博客:django 一对一、一对多、多对多操作、常用方法

4. ORM 操作

4.1 基本操作

添加数据

# 方法一
models.Tb.objects.create(id=1, name='rose')	# 增加一条数据,可接受字典类型数据 **kwargs
    
# 方法二
obj = models.Tb(id=1, name='rose')
obj.save()

查询数据

models.Tb.objects.get(id=123)		# 获取单条数据,不存在报错(不建议)
models.Tb.objects.all()				# 获取全部,QuerySet 集合
obj = models.Tb.objects.filter(name='rose')	# 获取指定条件数据,QuerySet 集合,取第一个 obj[0]
models.Tb.objects.exclude(name='rose')	# 排除指定条件数据

删除数据

models.Tb.objects.filter(name='rose').delete()

修改数据

models.Tb.objects.filter(name='rose').update(age=19)

# 方法二
obj = models.Tb.objects.get(id=1)
obj.age = 19
obj.save()

4.2 进阶操作

以下为 QuerySet 对象方法,因此只有 all()、filter()、exclude()方法才有:

# 获取个数
models.Tb.objects.filter(name='rose').count()

# 大于、小于
models.Tb1.objects.filter(id__gt=1)              # 获取id大于1的值
models.Tb1.objects.filter(id__gte=1)              # 获取id大于等于1的值
models.Tb1.objects.filter(id__lt=10)             # 获取id小于10的值
models.Tb1.objects.filter(id__lte=10)             # 获取id小于10的值
models.Tb1.objects.filter(id__lt=10, id__gt=1)   # 获取id大于1 且 小于10的值

# in
models.Tb1.objects.filter(id__in=[11, 22, 33])   # 获取id等于11、22、33的数据
models.Tb1.objects.exclude(id__in=[11, 22, 33])  # not in

# isnull
models.Tb1.objects.filter(pub_date__isnull=True)

# contains
models.Tb1.objects.filter(name__contains="ven")
models.Tb1.objects.filter(name__icontains="ven") 	# icontains大小写不敏感
models.Tb1.objects.exclude(name__icontains="ven")

# range
models.Tb1.objects.filter(id__range=[1, 2])   # 范围 bettwen and

# 其他类似,以什么开头、结尾,大小写不敏感等
models.Tb1.objects.filter(name__startswith='r')

# 排序 order by
models.Tb1.objects.filter(name='seven').order_by('id')    # asc
models.Tb1.objects.filter(name='seven').order_by('-id')   # desc 降序

##############################  聚合查询和分组查询		###########################################
# 分组 group by
# annotate() 可以为 QuerySet 中每个对象生成一个独立的摘要,输出结果仍然是一个 QuerySet 对象,能调用 filter()、order by() 甚至是 annotate()
from django.db.models import Count, Min, Max, Sum, Avg
# 分组条件为 id
obj = models.Student.objects.filter(student_name='rose').values('id').annotate(c=Count('sc_id'))
print(obj)      # <QuerySet [{'id': 1, 'c': 1}]>


# obj = models.Student.objects.annotate(Count('sc_id'))
# print(obj)		# <QuerySet [<Student: rose>, <Student: john>, <Student: tom>, <Student: lila>, <Student: david>]>
# print(obj[0].student_name)	# rose

# 执行的 SQL 语句
SELECT "app_student"."id", COUNT("app_student"."sc_id") AS "c" FROM "app_student" WHERE "app_student"."student_name" = 'rose' GROUP BY "app_student"."id"  LIMIT 21; args=('rose',)

# aggregate() 用于 QuerySet 对象汇总值,返回一个聚合值得字典
obj1 = models.Student.objects.all().aggregate(id_avg=Avg('sc_id'))
print(obj1)     # 没取别名:{'sc_id__avg': 1.8}   {'id_avg': 1.8}
# models.Student.objects.aggregate(Avg('sc_id'))

# 相当于
SELECT AVG("app_student"."sc_id") AS "id_avg" FROM "app_student"; args=()

##################################################################################################
# limit、offset 限制
obj = models.Student.objects.all()[1:3]     # 索引:[1,3)

# regex 正则匹配,iregex 不区分大小写,有时候 Django 查询 API 不能方便设置查询条件,我们可以自己定制
obj = models.Student.objects.filter(student_name__regex=r'^r|t')		# 以 r、t开头

# 日期:date、year、month、day、weekday、hour、minute、second
# create_date = models.DateTimeField()
models.Tb1.objects.filter(create_date__date=datetime.date(2019, 2, 15))
models.Tb1.objects.filter(create_date__date__gt=datetime.date(2005, 1, 1))

models.Tb1.objects.filter(create_date__year=2019)		# 过滤条件 create_date 等于 2019 年
models.Tb1.objects.filter(create_date__year__gte=2019)		# 大于等于

# 其余的月、日、时间等用法类似

# F、Q
# F 在原来基础上进行操作
obj = models.StudentDetail.objects.all().update(age=F('age')+1)		# 所有用户年龄加 1 岁
print(obj)  # obj: 5 影响的条数
# 原生SQL: UPDATE "app_studentdetail" SET "age" = ("app_studentdetail"."age" + 1); 

# Q 构建搜索条件(在这里解决了或、与的问题)
# 1、 Q 对象可以对关键字参数进行封装,从而更好地应用多个查询
obj1 = models.StudentDetail.objects.filter(Q(age=18))		# 构建过滤条件

# 2、组合使用 &、| 、~ 操作符
obj2 = models.StudentDetail.objects.filter(Q(age=18) | Q(age=19))	# 解决了 Django API 中没有或的问题
obj3 = models.StudentDetail.objects.filter(Q(age=18) & Q(age=19))	# 解决了 Django API 中没有与的问题
obj3 = models.StudentDetail.objects.filter(~ Q(age=19))	# 解决了 Django API 中没有非的问题

# 方法二:手动创建逻辑关系
    q1 = Q()		# 创建 Q 对象
    q1.connector = 'OR'		# q1 内部为 or 关系,即 age=18 或 age =19
    q1.children.append(('age', 18))
    q1.children.append(('age', 19))
	
    q2 = Q()		# 创建 Q 对象
    q2.connector = 'AND'	# q2 内部为 and 关系
    q2.children.append(('gender', 0))

    q3 = Q()			# 创建 Q 对象
    q3.add(q1, 'AND')		# 将 q1、q2 都添加到 q3 中,创建总的过滤条件
    q3.add(q2, 'AND')
    obj = models.StudentDetail.objects.filter(q3)
    print(obj)  # <QuerySet [<StudentDetail: StudentDetail object (1)>]>

    # 原生 SQL
SELECT "app_studentdetail"."id", "app_studentdetail"."age", "app_studentdetail"."gender", "app_studentdetail"."height", "app_studentdetail"."email" FROM "app_studentdetail" WHERE (("app_studentdetail"."age" = 18 OR "app_studentdetail"."age" = 19) AND "app_studentdetail"."gender" = 0)  LIMIT 21;
        
# 3、Q 对象 可以和关键字参数一起查询使用,不管一定要在关键字参数前面
models.StudentDetail.filter(Q(age=18), email__startswith='123')

# 4、应用
import datetime
obj = models.Class.objects.filter(Q(create_data=datetime.date(2019,2,15)))
print(obj)

4.3 公共方法

用于查询数据,并返回一个 QuerySet 对象集合。

def all(self)
    # 获取所有的数据对象

def filter(self, *args, **kwargs)
    # 条件查询
    # 条件可以是:参数,字典,Q
    
def exclude(self, *args, **kwargs)
    # 条件查询
    # 条件可以是:参数,字典,Q

def select_related(self, *fields)
     性能相关:表之间进行join连表操作,一次性获取关联的数据。
     model.tb.objects.all().select_related()
     model.tb.objects.all().select_related('外键字段')
     model.tb.objects.all().select_related('外键字段__外键字段')

def prefetch_related(self, *lookups)
    性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。
            # 获取所有用户表
            # 获取用户类型表where id in (用户表中的查到的所有用户ID)
            models.UserInfo.objects.prefetch_related('外键字段')



            from django.db.models import Count, Case, When, IntegerField
            Article.objects.annotate(
                numviews=Count(Case(
                    When(readership__what_time__lt=treshold, then=1),
                    output_field=CharField(),
                ))
            )

            students = Student.objects.all().annotate(num_excused_absences=models.Sum(
                models.Case(
                    models.When(absence__type='Excused', then=1),
                default=0,
                output_field=models.IntegerField()
            )))

def annotate(self, *args, **kwargs)
    # 用于实现聚合group by查询,values() 是谁,就对谁 group by	

    from django.db.models import Count, Avg, Max, Min, Sum

    v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id'))
    # SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id

    v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1)
    # SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1

    v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)
    # SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1

def distinct(self, *field_names)
    # 用于distinct去重
    models.UserInfo.objects.values('nid').distinct()		# 对 nid 去重
    # select distinct nid from userinfo

    注:只有在PostgreSQL中才能使用distinct进行去重

def order_by(self, *field_names)
    # 用于排序,reverse() 只对 order by 后的结果反转,其他无效
    models.UserInfo.objects.all().order_by('-id','age')
    models.UserInfo.objects.all().order_by('-id','age').reverse()

def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
    # 构造额外的查询条件或者映射,如:子查询,自定义 SQL 语句

    Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
    # 原生 SQL:
    # select id, (select col from sometable where othercol > %s ) as new_id from Entry
    	
    Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
    Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])	# 逗号为 and,相当于 (foo='a' or bar = 'a') and baz = 'a'
    Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])

 def reverse(self):
    # 倒序
    models.UserInfo.objects.all().order_by('-nid').reverse()
    # 注:如果存在order_by,reverse则是倒序,如果多个排序则一一倒序


 def defer(self, *fields):
    # 映射中排除某列数据,若有 20列,取 18列,不需要全写出来,可以排除剩余两列
    models.UserInfo.objects.defer('username','id')
    或
    models.UserInfo.objects.filter(...).defer('username','id')
    

 def only(self, *fields):
    #仅取某个表中的某几列,返回的还是 QuerySet 对象
     models.UserInfo.objects.only('username','id')	
     或
     models.UserInfo.objects.filter(...).only('username','id')
     
    # select id, username from userinfo where ...

 def using(self, alias):
     # 指定使用的数据库,参数为别名(setting中的设置),实质是用来连接别的数据库
        DATABASES = {
    'default2': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
        models.Tb1.objects.all().order_by('id', 'name').using('default2')


##################################################
# 返回 QuerySet 子类 #
##################################################

def raw(self, raw_query, params=None, translations=None, using=None):
    # 执行原生SQL
    models.UserInfo.objects.raw('select * from userinfo')

    # 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名
    models.UserInfo.objects.raw('select id as nid from 其他表')

    # 为原生SQL设置参数
    models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])

    # 将获取的到列名转换为指定列名
    name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
    Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)

    # 指定数据库
    models.UserInfo.objects.raw('select * from userinfo', using="default")

    ################### 原生SQL ###################
    from django.db import connection, connections
    cursor = connection.cursor()  # cursor = connections['default'].cursor()
    cursor.execute("""SELECT * from auth_user where id = %s""", [1])
    row = cursor.fetchone() # fetchall()/fetchmany(..)


def values(self, *fields):
    # 获取每行数据为字典格式

def values_list(self, *fields, **kwargs):
    # 获取每行数据为元祖

def dates(self, field_name, kind, order='ASC'):
    # 根据时间进行某一部分进行去重查找并截取指定内容
    # kind只能是:"year"(年), "month"(年-月), "day"(年-月-日)
    # order只能是:"ASC"  "DESC"
    # 并获取转换后的时间
        - year :-01-01
        - month:--01
        - day  :--日

    models.DatePlus.objects.dates('ctime','day','DESC')

def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
    # 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间
    # kind只能是 "year", "month", "day", "hour", "minute", "second"
    # order只能是:"ASC"  "DESC"
    # tzinfo时区对象
    models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC)
    models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai'))

    """
    pip3 install pytz
    import pytz
    pytz.all_timezones
    pytz.timezone(‘Asia/Shanghai’)
    """

def none(self):
    # 空QuerySet对象


####################################
# 数据库查询 #
####################################

def aggregate(self, *args, **kwargs):
   # 聚合函数,获取字典类型聚合结果
   from django.db.models import Count, Avg, Max, Min, Sum
   result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid'))
   ===> {'k': 3, 'n': 4}

def count(self):
   # 获取个数

def get(self, *args, **kwargs):
   # 获取单个对象

def create(self, **kwargs):
   # 创建对象

def bulk_create(self, objs, batch_size=None):
    # 批量插入
    # batch_size表示一次插入的个数
    objs = [
        models.DDD(name='r11'),
        models.DDD(name='r22')
    ]
    models.DDD.objects.bulk_create(objs, 10)

def get_or_create(self, defaults=None, **kwargs):
    # 如果存在,则获取,否则,创建
    # defaults 指定创建时,其他字段的值
    obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2})

def update_or_create(self, defaults=None, **kwargs):
    # 如果存在,则更新,否则,创建
    # defaults 指定创建时或更新时的其他字段
    obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1})

def first(self):
   # 获取第一个

def last(self):
   # 获取最后一个

def in_bulk(self, id_list=None):
   # 根据主键ID进行查找
   id_list = [11,21,31]
   models.DDD.objects.in_bulk(id_list)

def delete(self):
   # 删除

def update(self, **kwargs):
    # 更新

def exists(self):
   # 是否有结果

性能相关

数据库操作越少,程序运行性能越高,因此我们要尽可能地减少对数据库的操作。

select_related(self, *fields) 方法用于连接数据表:

# models.py
class Department(models.Model):
    name = models.CharField(max_length=32)
    
class Employee(models.Model):
    name = models.CharField(max_length=32)
    fk = models.ForeignKey('Department') 
# views.py
obj_list = models.Employee.objects.all()	# select * from employee
for item in obj_list:
    print(item.name)
    print(item.fk.name)		# 跨表查询部门表的名字

上面当在跨表进行查询时还会对 department 查询一次,总共要查询两次。连表查询:

# 加上 select_related() 后,只需一次
obj_list = models.Employee.objects.all().select_related('fk')		# 外键字段
# 相当于 select * from employee left join department on employee.fk_id = department.id

Tips

  • 没有跨表查询,没必要连表
  • 支持多张表连接:select_related('fk', 'fk2')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风老魔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值