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
相關文章
相關標籤/搜索