django ORM的总结

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

由来python

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

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

解析

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

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

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。json

对于问题中说的get也是同样,由于get自己就是在执行完filter以后从_result_cache列表中获取的数据(_result_cache[0])。app

方案一

根据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对象的合并  http://www.javashuo.com/article/p-atkdpqpj-bs.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
相关文章
相关标签/搜索