zoukankan      html  css  js  c++  java
  • Python学习笔记Day20

    ORM介绍

    ORM的两种方式

    db first    先连接数据库    -> ...
    code first  先创建类        -> sqlachemy、Django、大多数都是
    

    Django ORM

    ORM:Object Relational Mapping(关系对象映射)

    类名             ->>   数据库中的表名
    
    类属性           ->>   数据库里的字段
    
    类实例           ->>   数据库表里的一行数据
    
    obj.name.....    ->>   类实例对象的属性
    

    Django orm的优势:Django的orm操作本质上会根据对接的数据库引擎,翻译成对应的sql语句;所有使用Django开发的项目无需关心程序底层使用的是MySQL、Oracle、sqlite....,如果数据库迁移,只需要更换Django的数据库引擎即可

    QuerySet数据类型介绍

    QuerySet特点:

    • 可迭代的

    • 可切片

    • 惰性计算:等于一个生成器,.objects.all()或者.filter()等都只是返回了一个QuerySet的查询结果集对象,它并不会马上执行sql,而是当调用QuerySet的时候才执行。

    • 缓存机制:每一次数据库查询结果QuerySet都会对应一块缓存,再次使用该QuerySet时,不会发生新的SQL操作

    这样减小了频繁操作数据库给数据库带来的压力

    但是有时候取出来的数据量太大会撑爆缓存,可以使用迭代器解决这个问题:

    models.Publish.objects.all().iterator()
    

    创建ORM类

    1. 在models里创建表的类

    /app/models.py
    
    from django.db import models
    # 表名为app01_userinfo
    class UserInfo(models.Model):
        # 自动创建id列,自增,主键
        # 列名,字符串类型,指定长度
        username = models.CharField(max_length=32)
        password = models.CharField(max_length=64)
        email = models.EmailField(max_length=19)
    

    类的字段和参数详见字段和参数

    2. 注册APP

    /./settings.py
    
    INSTALLED_APPS = [
        ...,
        'app01',
    ]
    

    3. 执行命令,每次更改表结构都要重复一遍

    python manage.py  makemigrations    ->  生成表结构的缓存
    python manage.py  migrate           ->  创建表结构
    

    4. 默认使用sqlite3数据库,可修改为mysql

    /./settings.py      ->  DATABASES
    
    ********** 注意 ***********
    Django默认使用MySQLdb模块链接MySQL
    主动修改为pymysql,在project同名文件夹下的__init__文件中添加如下代码即可:
        import pymysql
        pymysql.install_as_MySQLdb()
    

    增删改查

    1.增

    /app/views.py
    from app01 import models
    def orm(request):
        # 直接传入参数
        models.UserInfo.objects.create(username='root',password='123')
        # 传入字典
        dic = {'username': 'eric', 'password': '666'}
        models.UserInfo.objects.create(**dic)
        # 另一种增加方式
        obj = models.UserInfo(username='alex',password='123')
        obj.save()
        # 判断是否重复添加(速度慢)
        models.UserInfo.objects.get_or_create(...)
        新建时返回 True, 已经存在时返回 False
        # 批量增加,提高速度
        models.UserInfo.objects.bulk_create(data_list)
        # 更新或创建
        update_or_create(defaults=None, **kwargs)
    
        执行规则: filter kwargs,create/update defaults
        返回值为元组: (object, created)
            object为新建或者更新的对象,
            created为一个布尔值,表示是新建还是更新,True为新建
    

    2.查

    result = models.UserInfo.objects.all()
    result = models.UserInfo.objects.filter(user='root',psd='123') -> filter传入字典也可 **dic
            => QuerySet, Django的一种列表, [], 内部元素是.obj => [obj(id,username),obj]
        
    # 转化为字典输出                
        .all().values('id','caption')       -> [{'id:1,'username':'alex'},{},{}]
    # 转化为tuple输出            
        .all().values_list('id','caption')  -> [(1,'alex'),(),()]
    # 取第一个obj
        .filter(xxx).first()                -> 不存在返回None
                => 用get取单条数据,如果不存在,直接报错
                => models.UserInfo.objects.get(id=nid)
        obj.XXX取对象的某一数据
    
    # 计数
        .filter(name='seven').count()
    
    # 切片
        .all()[10:20]
        .all()[::2]
        .all()[6]    # 索引
    
    # 去重
        .distinct('name')   # 查询姓名不重复的人
    
    # 排序
        .filter(name='seven').order_by('id')    ->  asc
        .filter(name='seven').order_by('-id')   ->  desc
    
    # 通过字典更新数据
        mymodel=MyModel.objects.get(pk=pk)#....找到唯一的一个,自行修改
        mymodel.__dict__.update(data_dict )
        mymodel.save()
        当然也有用如下方式实现更新的:
    
        MyModel.objects.filter(pk=pk).update(**data_dict )
    

    3.删

    models.UserInfo.objects.filter(username="alex").delete()
    

    4.改

    models.UserInfo.objects.filter(id=3).update(password="69")  # 可添加**kwargs形式
    # 或者先查找对象再修改保存
        obj = models.tb.objects.get(id=1)
        obj.c1 = '111'
        obj.save()     # 修改单条数据
    # Django2.2以上支持批量更新
        objs = list()
        for i in range(6):
            obj = App.objects.get(pk=i)
            obj.app_name = "xx"
            objs.append(obj)
    
        # fields 要求是 可迭代对象,比如list, fields需要包含你想要更新的所有字段
        # 按id更新,需要id存在(即unique=True那一项)
        App.objects.bulk_update(objs, fields=["app_name"],batch_size=100)
    

    特殊的判断语句(神奇的双下划线1)

    # 大于小于
        .filter(id__gt=1)              ->      > 1
        .filter(id=1)                  ->      = 1
        .filter(id__lt=1)              ->      < 1
        .filter(id__lte=1)             ->      <= 1
        .filter(id__gte=1)             ->      >= 1
        .exclude(id__gt=1)             ->      != 1  exclude 除了...与filter相反
        .filter(id__gt=1, id__lt=10)   ->      1< x <10
    
    # 范围range
        .filter(id__range=[1,3])       ->      [1~3]   bettwen + and
    
    # 范围in
        .filter(id__in=[1,2,3])        ->      in [1,2,3]
        .exclude(id__in=[1,2,3])       ->      in [1,2,3]
    
    # 是否为空
        .filter(name__isnull=True)
    
    # 包含、开头、结尾 __startswith, istartswith, endswith, iendswith
        .filter(name__contains="ven")
        .filter(name__icontains="ven")    # i 忽略大小写
    
    # regex正则匹配,iregex 不区分大小写
        .get(title__regex=r'^(An?|The) +')
        .get(title__iregex=r'^(an?|the) +')
    
    # date
        .filter(pub_date__date=datetime.date(2005, 1, 1))
        .filter(pub_date__date__gt=datetime.date(2005, 1, 1))
    
    # year、month、day、week_day
        .filter(pub_date__year=2005)
        .filter(pub_date__year__gte=2005)
    
    # hour、minute、second
        .filter(timestamp__hour=23)
        .filter(time__hour=5)
        .filter(timestamp__hour__gte=12)
    

    进阶查询

    F模块

    用于获取对象中的某一字段(列)的值,并且对其进行操作;

    from django.db.models import F      # 首先导入F模块
    models.Book.objects.all().update(price=F('price')+1)   # 每一本书的价格上调1块钱
    

    Q模块

    用于构造复杂的查询条件,使用逻辑关系(&与、|或、~非)组合进行多条件查询。

    虽然filter中可以使用 , 隔开表示关系与,但没法表示或非的关系

    from django.db.models import Q      # 导入Q模块
    # 方式一:
        .filter( Q(id__gt=10) )                 -> 
        .filter( Q(id=8) | Q(id__gt=10) )       -> or
        .filter( Q( Q(id=8) | Q(id__gt=10) ) & Q(caption='root') )  -> and, or
    # 方式二:
    # 可以组合嵌套
        # q1里面的条件都是or的关系
        q1 = Q()
        q1.connector = 'OR'
        q1.children.append(('id', 1))
        q1.children.append(('id', 10))
        q1.children.append(('id', 9))
        # q2里面的条件都是or的关系
        q2 = Q()
        q2.connector = 'OR'
        q2.children.append(('c1', 1))
        q2.children.append(('c1', 10))
        q2.children.append(('c1', 9))
        # con通过and的条件把q1和q2联系到一块
        con  = Q()
        con.add(q1, 'AND')
        con.add(q2, 'AND')
        models.tb.objects.filter(con)
    

    实例:查询作者姓名中包含 方/少/伟/3字,书名不包含伟,并且出版社地址以山西开头的书

    book=models.Book.objects.filter(
                                    Q(
                                        Q(author__name__contains='方') |
                                        Q(author__name__contains='少') |
                                        Q(author__name__contains='伟') |
                                        Q(title__icontains='伟')
                                    ) & 
                                    Q(publish__addr__contains='山西')
                                ).values('title')
    

    注意:Q查询和非Q查询混合使用,非Q查询一定要放在Q查询后面

    extra方法

    对不同的数据库引擎可能存在移植问题(因为你在显式的书写SQL语句),尽量避免使用extra

    a.映射
        - select={'new_id':select count(1) from app01_usertype where id>%s'}
        - select_params=[1,]
        # 例:
            models.UserInfo.objects.all().extra(
                select={
                    'n':"select count(1) from app01_utype WHERE id=%s or id=%s",
                    'm':"select count(1) from app01_uinfo WHERE id=%s or id=%s",
                },
                select_params=[1,2,3,4]
            )
    
    b.条件
        - where=["foo='a' OR bar = 'a'", "baz = '%s'"],
        - params=['Lennon',]
    
    c.表
        - tables=["app01_usertype"]
    
    d.排序
        - order_by = ['-id']
    
    # 例1:
        models.UserInfo.objects.extra(
            select={'new_id':select count(1) from app01_usertype where id>%s'},
            select_params=[1,],
            where=['age>%s'],
            params=[18,],
            order_by=['-age'],
            tables=["app01_usertype']
        )
        -> 相当于:
            '''
            select
                app01_userinfo.id,
                (select count(1) from app01_usertype where id>1) as new_id
            from
                app01_userinfo,
                app01_usertype
            where 
                app01_userinfo.age>18
            order by 
                app01_userinfo.age desc
            '''
    
    # 例2:
        current_user = models.UserInfo.objects.filter(username=username).first()   # 当前用户
    
        1、models.Article.objects.all()      # 查出每一篇文章
        2、models.Article.objects.all().filter(user=current_user)  # 查出当前用户的所有文章
        3、models.Article.objects.all().filter(user=current_user).extra(select={"filter_create_date":"strftime(‘%%Y/%%m‘,create_time)"}).values_list("filter_create_date")
            # 查出当前用户的所有文章的create_time,并且只取出年份和月份
    

    执行原生SQL的三种方式

    1. 使用extra方法

       结果集修改器,一种提供额外查询参数的机制  
       依赖model模型
      
    2. 使用raw方法

       执行原始sql并返回模型  
       依赖model多用于查询
      
       book = Book.objects.raw("select * from hello_book")
       for item in book:
           print(item.title)
      
    3. 使用cursor游标

       不依赖model
      
       from django.db import connection, connections
       cursor = connection.cursor()  
       # 或cursor = connections['default'].cursor() 
       # 其中'default'是django数据库配置的default,也可取别的值
       cursor.execute("""SELECT * from auth_user where id = %s""", [1])
       row = cursor.fetchone()
      

    类的字段和参数

    字段:字符串、数字、时间、二进制

    AutoField(Field)        ->  自定义自增列(必须加primary_key=True)
    IntegerField(Field)     ->  整数列
    BooleanField(Field)     ->  布尔
    GenericIPAddressField(Field)    ->  IP验证(仅限django admin)
    URLField(CharField)     ->  url验证(仅限django admin)
    # Django里有很多的字段类型在数据库中都是Char类型,只是用于django admin便于区分
    

    更多详见:武沛齐的博客 - Django

    字段的参数:

    null                -> db中是否可以为空
    default=''          -> 默认值
    primary_key         -> 是否主键
    db_column           -> 列名
    
    db_index            -> 是否可索引
    unique              -> 是否可唯一索引
    unique_for_date     -> 【日期】部分是否可索引
    unique_for_month    -> 【月】部分是否可索引
    unique_for_year     -> 【年】部分是否可索引
    
    auto_now_add        -> 创建时,自动生成时间
    auto_now            -> 更新时,自动更新为当前时间
            # update方式不生效,先获取再更改才生效
            ctime = models.DateTimeField(auto_now_add=True)
            UserGroup.objects.filter(id=1).update(caption='CEO')    -> 不生效
            obj = UserGroup.objects.filter(id=1).first()
            obj.caption = "CEO"             -> 生效,自动更新更改时间
            obj.save()
    
    # django admin中才生效的字段
    blank               -> django admin是否可以为空
    verbose_name=''     -> django admin显示字段中文
    editable            -> django admin是否可以被编辑
    help_text           -> django admin帮助提示
    choices=[]          -> django admin中显示下拉框
            # 可避免连表查询,提高效率,一般用于基本不变的选项
            user_type_choices = (
                (1, '超级用户'),
                (2, '普通用户'),
                (3, '普普通用户'),
            )
            user_type_id = models.IntegerField(choices=user_type_choices,default=1)
    
    error_messages      -> 自定义错误信息(字典类型)
            # 字典的键:null, blank, invalid, invalid_choice, unique, unique_for_date
            # 例:error_messages = {'null': "不能为空", 'invalid': '格式错误'}
    
    validators          -> django form ,自定义错误信息(列表类型)
            # 例:
            from django.core.validators import RegexValidator
            from django.core.validators import EmailValidator,URLValidator,DecimalValidator,
                    MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
            error_messages={
                'c1': '优先错信息1',
                'c2': '优先错信息2',
                'c3': '优先错信息3',
            },
            validators=[
                RegexValidator(regex='root_d+', message='错误了', code='c1'),
                RegexValidator(regex='root_112233d+', message='又错误了', code='c2'),
                EmailValidator(message='又错误了', code='c3'), ]
    

    更多错误信息的使用方法参考武沛齐 - FORM

    创建 Django admin用户: python manage.py createsuperuser

    Meta元信息

    class UserInfo(models.Model):
        ...
        class Meta:
            # 定义数据库中生成的表名称 默认 app名称 + 下划线 + 类名
            db_table = "table_name"
    
            # 联合索引
            index_together = [("pub_date", "deadline"),]
    
            # 联合唯一索引,一旦三者都相同,则会被Django拒绝创建。
            可以同时设置多组约束。为了方便,对于只有一组约束的情况下,可以简单地使用一维元素
            unique_together = (("driver", "restaurant"),)
    
            # admin后台中显示的表名称
            verbose_name = '用户信息'
    
            # verbose_name加s,复数形式,不指定自动加s
            verbose_name_plural = 
    
            # 默认排序
            ordering=['-order_date'] # 按订单降序排列,-表示降序,不加升序,加?表示随机
            ordering=['-pub_date','author'] # 以pub_date为降序,再以author升序排列
    

    更多:meta帮助文档

    Admin拓展知识

    1. 触发Model中的验证和错误提示有两种方式:

       a. Django Admin中的错误信息会优先根据Admin内部的ModelForm错误信息提示,如果都成功,才来检查Model的字段并显示指定错误信息
       b. 调用Model对象的 clean_fields 方法,如:
           # models.py
           class UserInfo(models.Model):
               username = models.CharField(max_length=32)
               email = models.EmailField(error_messages={'invalid': '格式错了.'})
      
           # views.py
           def index(request):
               obj = models.UserInfo(username='11234', email='uu')
               try:
                   print(obj.clean_fields())
               except Exception as e:
                   print(e)
               return HttpResponse('ok')
      
          # Model的clean方法是一个钩子,可用于定制操作,如:上述的异常处理。
      
    2. Admin中修改错误提示

       # admin.py
       from django.contrib import admin
       from model_club import models
       from django import forms
      
       class UserInfoForm(forms.ModelForm):
           username = forms.CharField(error_messages={'required': '用户名不能为空.'})
           email = forms.EmailField(error_messages={'invalid': '邮箱格式错误.'})
           age = forms.IntegerField(initial=1, error_messages={'required': '请输入数值.', 'invalid': '年龄必须为数值.'})
      
           class Meta:
               model = models.UserInfo
               # fields = ('username',)
               fields = "__all__"
      
       class UserInfoAdmin(admin.ModelAdmin):
           form = UserInfoForm
      
       admin.site.register(models.UserInfo, UserInfoAdmin)
      

    ORM连表的几种类型

    ORM一对多

    当一张表中创建一行数据时,有一个单选的下拉框(可以被重复选择)

    • 创建表结构时关联外键

        user_group = models.ForeignKey("UserGroup",to_field='uid')  ->>   obj(UserGroup)
        # 自动创建user_group_id列,存的是数字(关联主键)
      
    • 添加数据时关联id或对象

        方式一:创建数据时添加id关联
            models.UserInfo.object.create(name='root', user_group_id=1)
      
        方式二:查询obj对象进行关联
            user_group = models.UserGroup.objects.filter(id=1).first()
      

    一对多自关联

    由原来的2张表,变成一张表!

    # 例:回复评论
    class Comment(models.Model):
        news_id = models.IntegerField()                 -> 新闻ID
        content = models.CharField(max_length=32)       -> 评论内容
        user = models.CharField(max_length=32)          -> 评论者
        reply = models.ForeignKey('Comment',null=True,blank=True,related_name='xxxx') -> 回复ID
    # 注意:回复的id必须是已经存在的评论的id
    

    ORM多对多

    在某表中创建一行数据是,有一个可以多选的下拉框

    • 两种创建方式

      以下两种创建方式建议都用,自动创建的只能关联两个表,自定义的可以不断关联

      • 方式一:自定义关系表

        可以直接操作第三张表,但无法通过字段跨表查询,查询麻烦

          class UserInfo(models.Model):
              ...
          class UserGroup(models.Model):
              ...
          # 创建中间表
          class UserInfoToUserGroup(models.Model):
              user_info_obj = models.ForeignKey(to='UserInfo',to_field='nid')
              group_obj = models.ForeignKey(to='UserGroup',to_field='id')
        
          # 添加关联数据:  
          UserInfoToApp.objects.create(user_info_obj_id=1,group_obj_id=2)
        
      • 方式二:Django自动创建关系表

        可以使用字段跨表查询,但无法直接操作第三张表

          class UserInfo(models.Model):
              ...
          # ManyToManyField字段
          class UserGroup(models.Model):
              user_info = models.ManyToManyField("UserInfo")
        
      • 方式三:既自定义第三张关系表 也使用ManyToManyField字段(杂交类型)

        既可以使用字段跨表查询,也可以直接操作第3张关系表

        注意:obj.m.all() 只有查询和清空方法

          # 例:博主粉丝关系
          class UserInfo(AbstractUser):
              ...
              fans = models.ManyToManyField(to='UserInfo',
                                            through='UserFans',                   -> 指定关系表表名
                                            through_fields=('user', 'follower'))  -> 指定关系表字段
          class UserFans(models.Model):
              ...
              user = models.ForeignKey(to='UserInfo', to_field='nid', related_name='users')
              follower = models.ForeignKey(to='UserInfo', to_field='nid', related_name='followers')
              class Meta:
                  unique_together = [('user', 'follower'),]
        

    多对多自关联

    (由原来的3张表,变成只有2张表)
    把两张表通过 choices 字段合并为一张表
    使用ManyToManyField字段

    1、查询第三张关系表前面那一列:obj.m

    2、查询第三张关系表后面那一列:obj.userinfo_set

    class Userinfo(models.Model):
        sex=((1,'男'),(2,'女'))
        gender=models.IntegerField(choices=sex)
        m=models.ManyToManyField('Userinfo')
    
    # 通过男士查询女生
        boy_obj=models.Userinfo.objects.filter(id=4).first()
        res=boy_obj.m.all()
    # 通过女士查询男生
        girl_obj=models.Userinfo.objects.filter(id=4).first()
        res=girl_obj.userinfo_set.all()
    

    ORM一对一

    在某表中创建一行数据时,有一个单选的下拉框(下拉框中的内容被用过一次就消失了)

    例如:原有含10列数据的一张表保存相关信息,经过一段时间之后,10列无法满足需求,需要为原来的表再添加5列数据

    r = models.OneToOneField(...)
    
    # 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)
    

    ORM连表操作

    字段参数

    • 一对多ForeignKey()

        to                          ->  要关联的表名
        to_field='uid',             ->  要关联的字段,不写默认关联主键
        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(func)
                        def func():
                            return 10
                        class MyModel(models.Model):
                            user = models.ForeignKey(...,on_delete=models.SET(func))
      
        related_name=None,          ->  反向操作时,使用的字段名,用于替换【表名_set】
                                        如: obj.表名_set.all()
        related_query_name=None,    ->  反向操作时,使用的连接前缀,用于替换【表名】
                            如: ...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中是否显示关联数据
      
    • 多对多ManyToManyField()

        symmetrical=None,       -> 仅用于多对多自关联时,指定内部是否创建反向操作的字段
            => 做如下操作时,不同的symmetrical会有不同的可选字段
                models.BB.objects.filter(...)
      
            => 可选字段有:code, id, m1
                class BB(models.Model):
                    code = models.CharField(max_length=12)
                    m1 = models.ManyToManyField('self',symmetrical=True)
      
            => 可选字段有: code, id, m1, bb
                class BB(models.Model):
                    code = models.CharField(max_length=12)
                    m1 = models.ManyToManyField('self',symmetrical=False)
      
        through=None,           -> 自定义第三张表时,用于指定关系表
        through_fields=None,    -> 自定义第三张表时,用于指定关系表中哪些字段做多对多关系表
      
        db_constraint=True,         -> 是否在数据库中创建外键约束
        db_table=None,              -> 默认创建第三张表时,数据库中表的名称
      
    • 一对一OneToOneField()

        to                          ->  要关联的表名
        to_field='uid',             ->  要关联的字段,不写默认关联主键
        on_delete=None,             ->  删除关联表中的数据时,当前表与其关联的行的行为
      

    跨表查询(神奇的双下划线2)

    • 获取值时使用 . 连接

        group_obj = models.UserGroup.objects.filter(id=1).first()   # orm连表必须取单个对象
        # 增
        group_obj.user_info.add(1)                -> 添加一个
        group_obj.user_info.add(2,3,4)            -> 添加多个
        group_obj.user_info.add(*[1,2,3,4])       -> 添加*列表
        # 删
        group_obj.user_info.remove(1)
        group_obj.user_info.remove(2,4)
        group_obj.user_info.remove(*[1,2,3])
        group_obj.user_info.clear()           -> 清除当前对象关联的多对多数据
        # 改
        group_obj.user_info.set([3,5,7])      -> (不加*)只保留1-3,1-5,1-7,其它删除
        # 查
        group_obj.user_info.all()             -> 获取所有相关的主机obj 的QuerySet
        group_obj.user_info.filter()
        ......
      
    • 搜索条件使用 __ 连接 (value、value_list、fifter)

        obj = models.UserGroup.objects.filter(id=1).value('name','user_info__name').first()
        在html里也用obj.user_group__name
      
    • 反查

        # . 操作,获取对象的QuerySet,表名小写_set
        user_info_obj.usergroup_set.add(group_obj)
        user_info_obj.usergroup_set.remove(group_obj)
        user_info_obj.usergroup_set.all()
        user_info_obj.usergroup_set.filter()
        ......
        {{ article_obj.comment_set.count }}         # 反查关联的对象总数
      
        # 反查
        {% for article in article_obj.comment_set.all %}    # 需要加.all 获取所有对象的QuerySet集合
      
        print(article_obj.comment_set.all())
      
        # __操作,搜索属性,表名小写__属性
        obj = models.UserInfo.objects.filter('usergruop__name').first()
      

    设置反向查找别名

    related_query_name      -> 反向查找时用 obj.别名_set.all(),保留了_set
    relatedname             -> 反向查找时用 obj.别名.all()  
    
    
    # 例如:
    '''把男女表混合在一起,在代码层面控制第三张关系表的外键关系'''
    
        # models.py
        class UserInfo(models.Model):
            ...
            sex=((1,'男'),(2,'女'))
            gender=models.IntegerField(choices=sex)
        class U2U(models.Model):
            b=models.ForeignKey(Userinfo,related_name='boy')
            g=models.ForeignKey(Userinfo,related_name='girl')
    
           # 写到此处问题就来了,原来两个外键 对应2张表 2个主键,可以识别男女
           # 现在两个外键对应1张表,反向查找,无法区分男女了了
           # object对象女.U2U.Userinfo.set  object对象男.U2U.Userinfo.set
           # 所以要加related_name设置反向查找命名 对表中主键加以区分
           # 查找方法
           # 男:obj.a.all()
           # 女:obj.b.all()
    
        # views.py
        def index(request):
           #查找 ID为1男孩 相关的女孩
           boy_obj=models.UserInfo.objects.filter(id=1).first()
           res = boy_obj.boy.all()   # 得到U2U的对象再正向跨表           
           for obj in res:
               print(obj.girl.name)
           return HttpResponse('OK')
    

    分组和聚合查询

    1. aggregate() 聚合函数

      通过对QuerySet进行计算,返回一个聚合值的字典。
      aggregate()中每一个参数都指定一个包含在字典中的返回值。即在查询集上生成聚合。

       from django.db.models import Avg,Sum,Max,Min
      
       # 求书籍的平均价
       ret = models.Book.objects.all().aggregate(Avg('price'))
       # {'price__avg': 145.23076923076923}
      
       # 参与西游记著作的作者中最老的一位作者
       ret = models.Book.objects.filter(title__icontains='西游记').values('author__age').aggregate(Max('author__age'))
       # {'author__age__max': 518}
      
    2. annotate() 分组函数

       # 查看每一位作者出过的书中最贵的一本  
       # (按作者名分组 values(),然后 annotate() 分别取每人出过的书价格最高的)
       ret=models.Book.objects.values('author__name').annotate(Max('price'))
       # < QuerySet[
       # {'author__name': '吴承恩', 'price__max': Decimal('234.000')},
       # {'author__name': '吕不韦','price__max': Decimal('234.000')},
       # {'author__name': '姜子牙', 'price__max': Decimal('123.000')},
       # ] >
      

    浅谈ORM查询性能

    普通跨表查询

    原理:第一次发送SQL查询请求,每for循环一次也会发送SQL查询请求

    obj_list=models.Love.objects.all() 
    for row in obj_list:
        print(row.b.name)
    

    结果为对象,query_set类型的对象都有该方法

    原理:select_related查询时进行join连表操作,一次性获取关联的数据,形成一张大表,for循环时不用额外发请求。

    缺点:select_related虽好,但是连表查询也会消耗一部分时间(连表查询性能低)

    试用场景:节省硬盘空间,数据量少的时候适用,相当于做了一次数据库查询

    obj_list=models.Love.objects.all().select_related('b')      # b => ForeignKey、m2m、o2o等参数
        for row in obj_list:
            print(row.b.name)
    

    结果为对象,query_set类型的对象都有该方法

    原理:prefetch_related不做连表,多次SQL单表查询外键表(1次主表查询+N次单表查询),分别在多个表中查询得到结果,然后重组。将相关联的数据放入内存,for循环时不需要再sql操作。

    过程:

    1. 先去person表          select * from person where ...  
    2. 再去关联表中取数据    select * from usertype where id in [persion_list中的id列表]  
    3. Django将两者数据数据相结合,然后返回给用户
    

    此方法不使用连表,也是一次获取所有数据,但是高效

    适用场景:效率高,数据量大的时候使用

    obj_list=models.Love.objects.all().prefetch_related('b')
        for obj in obj_list:
            print(obj.b.name)
    

    update()和obj.save()性能PK

    • update()方式

        models.Book.objects.filter(id=1).update(price=3)
        # 执行结果
            (0.000) BEGIN; args=None
            (0.000) UPDATE "app01_book" SET "price" = '3.000' WHERE "app01_book"."id" = 1; args=('3.000', 1)
      
    • obj.save()方式

        book_obj=models.Book.objects.get(id=1)
        book_obj.price=5
        book_obj.save()
        # 执行结果
            (0.000) SELECT "app01_book"."id", "app01_book"."title", "app01_book"."price", "app01_book"."date", "app01_book"."publish_id", "app01_book"."classify_id" FROM "app01_book" WHERE "app01_book"."id" = 1; args=(1,)
            (0.000) BEGIN; args=None
            (0.000) UPDATE "app01_book" SET "title" = '我的奋斗', "price" = '5.000', "date" = '1370-09-09', "publish_id" = 4, "classify_id" = 3 WHERE "app01_book"."id" = 1; args=('我的奋斗', '5.000', '1370-09-09', 4, 3, 1)
      
    • 结论:

        update() 比 obj.save()性能好
      

    Django自带ContentType表

    Django程序启动后自带的一张表,记录了Django程序的所有APP下model中的表名和所在app的名称

    1. 通过ContentType中的app名和表名,查找到Django model中所有表;

       from django.contrib.contenttypes.models import ContentType
       def test(request):
           c = ContentType.objects.get(app_label='app01',model='boy')
           print(c)                    -> boy
           print(c.model_class())      -> app01.models.Boy
      
    2. 解决 1张表 同时与其他N张表建立外键,并且多个外键中只能选择1个的复杂问题

      场景1:现有N种优惠券,每1种优惠券分别对应N门课程中的一门课程,怎么设计表结构呢?
      场景2:学生的学习成绩如何奖惩、 作业如何奖惩、学习进度如何奖惩...

       # 例:场景1
           from django.db import models
           from django.contrib.contenttypes.models import ContentType
           from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
      
           class DegreeCourse(models.Model):
               name = models.CharField(max_length=128, unique=True)
               # GenericRelation 自动连表查询
               xxx = GenericRelation('Coupon')
      
           class Course(models.Model):
               name = models.CharField(max_length=128, unique=True)
      
           class Coupon(models.Model):
               """优惠券生成规则
                   ID     优惠券名称         content_type_id(表)         object_id(表中数据ID)
                    1       通用                 null                    null
                    2       满100-10               8                      1
                    3       满200-30               8                      2
                    4       满200-30               9                      1
               """
               name = models.CharField(max_length=64, verbose_name="活动名称")
               # course_type 代指哪张表 注意该字段必须为 content_type
               content_type = models.ForeignKey(ContentType,blank=True,null=True)
               # 代指对象ID 该字段必须为 object_id
               object_id = models.PositiveIntegerField(blank=True, null=True, help_text="可以把优惠券跟课程绑定")
               # GenericForeignKey 通过 content_type 直接创建外键关系,不会生成额外的列
               content_object = GenericForeignKey('content_type','object_id')
      
       # 给学位课1,创建优惠券100
       # 方式1:
       # 1、在学位课表中 ,找到学位课1
           d1 = models.DegreeCourse.objects.get(id=1)
       # 2、在ContentType找到学位课表
           c1 = ContentType.objects.get(app_label='app01',model='degreecourse')
       # 3、给学位课1,创建优惠券100
           models.Coupon.objects.create(name='优惠券',brief='100',content_type=c1,object_id=d1.id)
      
       # 方式2:
           d1 = models.DegreeCourse.objects.get(id=1)
           models.Coupon.objects.create(name='优惠券',brief='100',content_object=d1)
      
       # 查询关联的所有优惠券
           d1 = models.DegreeCourse.objects.get(id=1)
           print(d1.xxx.all())
           v = models.DegreeCourse.objects.values('name','xxx__brief','xxx__name')
           print(v)
      

    其他小技巧

    数据库表删除重建:

    1. 先到数据库把表删掉:drop table

    2. 注释django中对应的Model

    3. 执行以下命令:

       python manage.py makemigrations   
       python manage.py migrate --fake     ->  只记录变化,不提交数据库操作
      
    4. 去掉注释重新迁移

       python manage.py makemigrations   
       python manage.py migrate
      

    字典key替换

    # 把value传给新key并同时删除旧key
    row['delivery'] = [row.pop('投递')]
    

    获取字段名和verbose_name

    fields_data = Group._meta.fields
    for key in data:
        # 这里是将当前的数据转换成数据字典,方便后面修改后提交
        data_dict = Group.__dict__
        for field in fields_data:
            # 这样或输出这条记录的所有字段名,需要的话还可以输出verbose_name
            print(field.name)
            if field.name == key:
                #进行匹配,将前端传来的字段匹配到,然后修改数据库里面的数据
                data_dict[key] = data[key]
    # 保存数据到数据库,这样的好处就是提高效率,避免过多重复操作
    

    obj转换为dict

    new_item = {c.name: getattr(item, c.name) for c in item.__table__.columns}
    

    判断重复

    obj_list = []
    for item in data:
        if not models.AuctionInfos.objects.filter(nid=item['id']).exists():
            item['nid'] = item.pop('id')
            print(item['nid'], 'append!')
            obj_list.append(models.AuctionInfos(**item))
    models.AuctionInfos.objects.bulk_create(obj_list)
    

    参考博客

    ORM详细讲解
    武沛齐的博客 - Django

  • 相关阅读:
    Balanced Binary Tree
    Convert Sorted List to Binary Search Tree
    Convert Sorted Array to Binary Search Tree
    Binary Tree Zigzag Level Order Traversal
    Validate Binary Search Tree
    Binary Tree Level Order Traversal II
    Binary Tree Level Order Traversal
    Maximum Depth of Binary Tree
    如何把U盘的两个盘或者多个盘合成一个
    bugku 想蹭网先解开密码
  • 原文地址:https://www.cnblogs.com/JeromeLong/p/9875171.html
Copyright © 2011-2022 走看看