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

  • 相关阅读:
    STL map
    HDU1372 Knight Moves BFS
    HDU1072 Nightmare BFS
    discuz论坛发帖添加字段
    gridview自定义button事件 ,无法触发 onrowcommand
    discuz 怎么开启评分!!!
    discuz学习网站收集
    discuz扩展工具集合
    童话世界整理“说说”
    asp.net中Literal与label的区别
  • 原文地址:https://www.cnblogs.com/JeromeLong/p/9875171.html
Copyright © 2011-2022 走看看