zoukankan      html  css  js  c++  java
  • django ORM的总结

    1.django分表的方案:   https://mp.weixin.qq.com/s?__biz=MjM5NjA3Nzk3Ng==&mid=2648154502&idx=1&sn=db51de56a47c2a6a2d0e758a92856331&chksm=becdc97189ba4067dd8995b97a2ed29284bd43369a4c7dd4da19d35d491595bfda856d8c6d7f%23rd

    由来

    知乎上的一个问题:Django 分表 怎么实现?

    这个问题戳到了Django ORM的痛点,对于多数据库/分库的问题,Django提供了很好的支持,通过using和db router可以很好的完成多数据库的操作。但是说到分表的问题,就有点不那么友好了。但也不是那么难处理,只是处理起来不太优雅。

    解析

    在Django中,数据库访问的逻辑基本上是在Queryset中完成的,一个查询请求,比如:User.objects.filter(group_id=10)

    其中的objects其实就是models.Manager,而Manager又是对QuerySet的一个包装。而QuerySet又是最终要转换为sql的一个中间层(就是ORM种,把Model操作转换为SQL语句的部分)。所以当我们写下User.objects的时候,就已经确定了要访问的是哪个表了,这是由class Meta中的db_table决定的。

    class User(models.Model):
    
        username = models.CharField(max_length=255)
    
    
        class Meta:
    
            db_table = 'user'

    理论上讲,我们可以通过在运行时修改db_table来完成分表CRUD的逻辑,但是the5fire在看了又看源码之后,还是没找到如何下手。还是上面的问题,当执行到User.objects的时候,表已经确定了,当执行到User.objects.filter(group=10)的时候只不过是在已经生成好的sql语句中增加了一个where部分语句。所以并没有办法在执行filter的时候来动态设置db_table。

    对于问题中说的get也是一样,因为get本身就是在执行完filter之后从_result_cache列表中获取的数据(_result_cache[0])。

    方案一

    根据the5fire上面的分析,要想在执行具体查询时修改db_table已经是不可能了(当然,如果你打算去重写Model中Meta部分的逻辑以及Queryset部分的逻辑,就当我没说,我只能表示佩服)。

    所以只能从定义层面下手了。也就是我需要定义多个Model,同样的字段,不同的db_table。大概是这样。

    class User(models.Model):
    
        username = models.CharField(max_length=255)
    
    
        class Meta:
    
            abstract = True
    
    
    class User1(User):
    
    
        class Meta:
    
            db_table = 'user_1'  # 默认情况下不设置db_table属性时,Django会使用``<app>_<model_name>``.lower()来作为表名
    
    
    
    class User2(User):
    
    
        class Meta:
    
            db_table = 'user_2'

    这样在User.objects.get(id=3)的时候,如果按照模2计算,那就是User01.objects.get(id=3),笨点的方法就是写一个dict:

    user_sharding_map = {
    
        1: User1,
    
        2: User2
    
    }
    
    
    def get_sharding_model(id):
    
        key = id % 2 + 1
    
        return user_sharding_map[key]
    
    
    
    ShardingModel = get_sharding_model(3)
    
    ShardingModel.objects.get(id=3)

    如果真的这么写那Python作为动态语言,还有啥用,你分128张表试试。我们应该动态创建出User01,User02,....UserN这样的表。

    class User(models.Model):
    
    
        @classmethod
    
        def get_sharding_model(cls, id=None):
    
            piece = id % 2 + 1
    
    
            class Meta:
    
                db_table = 'user_%s' % piece
    
    
            attrs = {
    
                '__module__': cls.__module__,
    
                'Meta': Meta,
    
            }
    
    
            return type(str('User%s' % piece), (cls, ), attrs)
    
    
    
        username = models.CharField(max_length=255, verbose_name="the5fire blog username")
    
    
        class Meta:
    
            abstract = True
    
    
    
    ShardingUser = User.get_sharding_model(id=3)
    
    user = ShardingUser.objects.get(id=3)

      嗯,这样看起来似乎好了一下,但是还有问题,id=3需要传两次,如果两次不一致,那就麻烦了。Model层要为上层提供统一的入口才行。

    class MyUser(models.Model):
    
        # 增加方法 BY the5fire
    
        @classmethod
    
        def sharding_get(cls, id=None, **kwargs):
    
            assert id, 'id is required!'
    
            Model = cls.get_sharding_model(id=id)
    
            return Model.objects.get(id=id, **kwargs)

    对上层来书,只需要执行MyUser.sharding_get(id=10)即可。不过这改变了之前的调用习惯 objects.get 。

    不管怎么说吧,这也是个方案,更完美的方法就不继续探究了,在Django的ORM中钻来钻去寻找可以hook的点实在憋屈。

    我们来看方案二吧

    方案二

    ORM的过程是这样的,Model——> SQL ——> Model,在方案一中我们一直在处理Model——> SQL的部分。其实我们可以抛开这一步,直接使用raw sql。

    QuerySet提供了raw这样的接口,用来让你忽略第一层转换,但是有可以使用从SQL到Model的转换。只针对SELECT的案例:

    class MyUser(models.Model):
    
        id = models.IntegerField(primary_key=True, verbose_name='ID')
    
        username = models.CharField(max_length=255)
    
    
        @classmethod
    
        def get_sharding_table(cls, id=None):
    
            piece = id % 2 + 1
    
            return cls._meta.db_table + str(piece)
    
    
        @classmethod
    
        def sharding_get(cls, id=None, **kwargs):
    
            assert isinstance(id, int), 'id must be integer!'
    
            table = cls.get_sharding_table(id)
    
            sql = "SELECT * FROM %s" % table
    
            kwargs['id'] = id
    
            condition = ' AND '.join([k + '=%s' for k in kwargs])
    
            params = [str(v) for v in kwargs.values()]
    
            where = " WHERE " + condition
    
            try:
    
                return cls.objects.raw(sql + where, params=params)[0]  # the5fire:这里应该模仿Queryset中get的处理方式
    
            except IndexError:
    
                # the5fire:其实应该抛Django的那个DoesNotExist异常
    
                return None
    
    
        class Meta:
    
            db_table = 'user_'

    大概这么个意思吧,代码可以再严谨些。

    总结

    单纯看方案一的话,可能会觉得这么大量数据的项目,就别用Django了。其实the5fire第一次尝试找一个优雅的方式hack db_table时,也是一头灰。但是,所有的项目都是由小到大的,随着数据/业务的变大,技术人员应该也会更加了解Django,等到一定阶段之后,可能发现,用其他更灵活的框架,跟直接定制Django成本差不多。

    2.type动态创建类https://blog.csdn.net/wangbowj123/article/details/77162828

    type还有一种完全不同的功能,动态的创建类。

    type可以接受一个类的描述作为参数,然后返回一个类。(要知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)

    type可以像这样工作:

    type(类名,由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值)) 
    代码如下:

    #运用type创建类、添加属性
    Test = type("Test",(),{'age':13,'name':"wangbo"})
    test = Test()
    print(test.age)
    #利用type添加方法
    @classmethod #类方法
    def testClass(cls):
        print(cls.name)
    
    @staticmethod #静态方法
    def testStatic():
        print("static method.....")
    
    def echo_age(self):
        print(self.age)
    Test2 = type("Test2",(Test,),{'echo_age':echo_age,'testStatic':testStatic,'testClass':testClass})
    test2 = Test2()
    test2.echo_age()
    test2.testStatic()
    test2.testClass()

    元类就是用来创建类的“东西”。你创建类就是为了创建类的实例对象,不是吗?但是我们已经学习到了Python中的类也是对象。

    元类就是用来创建这些类(对象)的,元类就是类的类,你可以这样理解为:

    MyClass = MetaClass() #使用元类创建出一个对象,这个对象称为“类” 
    MyObject = MyClass() #使用“类”来创建出实例对象 
    你已经看到了type可以让你像这样做:

    MyClass = type(‘MyClass’, (), {})

    函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。现在你想知道那为什么type会全部采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。type就是创建类对象的类。你可以通过检查class属性来看到这一点。Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来,这个类就是type。

    >>> age = 35
    >>> age.__class__
    <type 'int'>
    >>> name = 'bob'
    >>> name.__class__
    <type 'str'>
    >>> def foo(): pass
    >>>foo.__class__
    <type 'function'>
    >>> class Bar(object): pass
    >>> b = Bar()
    >>> b.__class__
    <class '__main__.Bar'>

    现在,对于任何一个class的class属性又是什么呢?

    >>> a.__class__.__class__
    <type 'type'>
    >>> age.__class__.__class__
    <type 'type'>
    >>> foo.__class__.__class__
    <type 'type'>
    >>> b.__class__.__class__
    <type 'type'>

    因此,元类就是创建类这种对象的东西。type就是Python的内建元类,当然了,你也可以创建自己的元类。

    3.queryset对象的合并  https://www.cnblogs.com/haoshine/p/6134242.html           http://www.yihaomen.com/article/python/533.htm

     在用python或者django写一些小工具应用的时候,有可能会遇到合并多个list到一个 list 的情况。单纯从技术角度来说,处理起来没什么难度,能想到的办法很多,但我觉得有一个很简单而且效率比较高的方法是我以前没注意到的。那就是利用 chain 方法来合并多个list. 同样也可以用来合并django 的 QuerySet. 

    1. python用chain 来合并多个list
    chain 是用C实现的,自然性能上比较可靠。下面看下基本用法:

    #coding:utf-8
    
    from itertools import chain
    
    a = [1,2,"aaa",{"name":"roy","age":100}]
    b = [3,4]
    c = [5,6]
    
    #items = a + b + c
    items = chain(a,b,c)
    for item in items:
        print item

    输出结果如下:

    1
    2
    aaa
    {'age': 100, 'name': 'roy'}
    3
    4
    5
    6

    由此可见可以很好的合并成功。

    2. 在Django 总用 chain 合并多个QuerySet.
    本身如果在Django中如果要合并同一个model的多个QuerySet 的话,是可以采用这种方式的.

    #coding:utf-8
    
    from itertools import chain
    from yihaomen.common.models import Article
    
    articles1 = Article.objects.order_by("autoid").filter(autoid__lt = 16).values('autoid','title')
    articles2 = Article.objects.filter(autoid = 30).values('autoid','title')
    
    articles = articles1 | articles2 # 注意这里采用的方式。如果 Model相同,而且没有用切片,并且字段一样时可以这样用
    print articles1
    print articles2
    print articles

    这样能很好的工作,但有些局限性,对于Django 来说很多情况下也够用了,合并到一个 QuerySet 中,然后返回到模板引擎中去处理。

    当然也可以用chain 来实现,用chain 来实现会更方便,也没那么多限制条件,即使是不同的MODEL中查询出来的数据,都可以很方便的合并到一个 list 中去.

    #coding:utf-8
    
    from itertools import chain
    from yihaomen.common.models import Article, UserID
    
    articles1 = Article.objects.order_by("autoid").filter(autoid__lt = 16).values('autoid','title')
    users = UserID.objects.all()
    
    items = chain(articles1, users)
    for item in items:
        print item

    这样做更方便,也很实用, 对于处理某些需要合并的list 然后再传输到某一个地方去的情况下,这样做很方便。 

    4.django多数据库的使用:https://code.ziqiangxuetang.com/django/django-multi-database.html

         本文讲述在一个 django project 中使用多个数据库的方法, 多个数据库的联用 以及多数据库时数据导入导出的方法。

       直接给出一种简单的方法吧,想了解更多的到官方教程,点击此处

        代码文件下载:project_name.zip(2017年05月01日更新)

    1. 每个app都可以单独设置一个数据库

    settings.py中有数据库的相关设置,有一个默认的数据库 default,我们可以再加一些其它的,比如:

    # Database
    # https://docs.djangoproject.com/en/1.8/ref/settings/#databases
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        },
        'db1': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'dbname1',
            'USER': 'your_db_user_name',
            'PASSWORD': 'yourpassword',
            "HOST": "localhost",
        },
        'db2': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'dbname2',
            'USER': 'your_db_user_name',
            'PASSWORD': 'yourpassword',
            "HOST": "localhost",
        },
    }
     
    # use multi-database in django
    # add by WeizhongTu
    DATABASE_ROUTERS = ['project_name.database_router.DatabaseAppsRouter']
    DATABASE_APPS_MAPPING = {
        # example:
        #'app_name':'database_name',
        'app1': 'db1',
        'app2': 'db2',
    }

    在project_name文件夹中存放 database_router.py 文件,内容如下:

    # -*- coding: utf-8 -*-
    from django.conf import settings
     
    DATABASE_MAPPING = settings.DATABASE_APPS_MAPPING
     
     
    class DatabaseAppsRouter(object):
        """
        A router to control all database operations on models for different
        databases.
     
        In case an app is not set in settings.DATABASE_APPS_MAPPING, the router
        will fallback to the `default` database.
     
        Settings example:
     
        DATABASE_APPS_MAPPING = {'app1': 'db1', 'app2': 'db2'}
        """
     
        def db_for_read(self, model, **hints):
            """"Point all read operations to the specific database."""
            if model._meta.app_label in DATABASE_MAPPING:
                return DATABASE_MAPPING[model._meta.app_label]
            return None
     
        def db_for_write(self, model, **hints):
            """Point all write operations to the specific database."""
            if model._meta.app_label in DATABASE_MAPPING:
                return DATABASE_MAPPING[model._meta.app_label]
            return None
     
        def allow_relation(self, obj1, obj2, **hints):
            """Allow any relation between apps that use the same database."""
            db_obj1 = DATABASE_MAPPING.get(obj1._meta.app_label)
            db_obj2 = DATABASE_MAPPING.get(obj2._meta.app_label)
            if db_obj1 and db_obj2:
                if db_obj1 == db_obj2:
                    return True
                else:
                    return False
            return None
     
        # for Django 1.4 - Django 1.6
        def allow_syncdb(self, db, model):
            """Make sure that apps only appear in the related database."""
     
            if db in DATABASE_MAPPING.values():
                return DATABASE_MAPPING.get(model._meta.app_label) == db
            elif model._meta.app_label in DATABASE_MAPPING:
                return False
            return None
     
        # Django 1.7 - Django 1.11
        def allow_migrate(self, db, app_label, model_name=None, **hints):
            print db, app_label, model_name, hints
            if db in DATABASE_MAPPING.values():
                return DATABASE_MAPPING.get(app_label) == db
            elif app_label in DATABASE_MAPPING:
                return False
            return None

    这样就实现了指定的 app 使用指定的数据库了,当然你也可以多个sqlite3一起使用,相当于可以给每个app都可以单独设置一个数据库!如果不设置或者没有设置的app就会自动使用默认的数据库。

    2.使用指定的数据库来执行操作

    在查询的语句后面用 using(dbname) 来指定要操作的数据库即可

    # 查询
    YourModel.objects.using('db1').all() 
    或者 YourModel.objects.using('db2').all()
     
    # 保存 或 删除
    user_obj.save(using='new_users')
    user_obj.delete(using='legacy_users')

    3.多个数据库联用时数据导入导出

    使用的时候和一个数据库的区别是:

    如果不是defalut(默认数据库)要在命令后边加 --database=数据库对应的settings.py中的名称  如: --database=db1  或 --database=db2

    数据库同步(创建表)

    # Django 1.6及以下版本
    python manage.py syncdb #同步默认的数据库,和原来的没有区别
     
    # 同步数据库 db1 (注意:不是数据库名是db1,是settings.py中的那个db1,不过你可以使这两个名称相同,容易使用)
    python manage.py syncdb --database=db1
     
    # Django 1.7 及以上版本
    python manage.py migrate --database=db1

    数据导出

    python manage.py dumpdata app1 --database=db1 > app1_fixture.json
    python manage.py dumpdata app2 --database=db2 > app2_fixture.json
    python manage.py dumpdata auth > auth_fixture.json

    数据库导入

    python manage.py loaddata app1_fixture.json --database=db1
    python manage.py loaddata app2_fixture.json --database=db2
  • 相关阅读:
    May 1 2017 Week 18 Monday
    April 30 2017 Week 18 Sunday
    April 29 2017 Week 17 Saturday
    April 28 2017 Week 17 Friday
    April 27 2017 Week 17 Thursday
    April 26 2017 Week 17 Wednesday
    【2017-07-04】Qt信号与槽深入理解之一:信号与槽的连接方式
    April 25 2017 Week 17 Tuesday
    April 24 2017 Week 17 Monday
    为什么丑陋的UI界面却能创造良好的用户体验?
  • 原文地址:https://www.cnblogs.com/1a2a/p/8690147.html
Copyright © 2011-2022 走看看