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
原文地址:https://www.cnblogs.com/1a2a/p/8690147.html