Django 多数据库联用(同一个APP的models里不同class用不同数据库)

很多网站有多数据库联用的文章,如自强学堂http://code.ziqiangxuetang.com/django/django-multi-database.html 大都只讲解如何让不同的app对应不同的数据库,而没有谈到如何让同一个app里的不同class对应不同的数据库。 经过N多次试验,历经好几天时间,终于找出如下的简便易行的途径,而不需要自己造轮子,现总结如下:

方式一:通过数据库路由自动分发Model,无需手动using

settings配置如下:

1、增加了DATABASE_ROUTERS,用于指定路由路径 2、增加了DATABASE_APPS_MAPPING,指定app_label对应的数据库

DBNAME = 'multi_test'
DATABASES = {
  'default': {
      'ENGINE': 'django.db.backends.sqlite3',
      'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
  },
  'db1': {
      'ENGINE': 'django.db.backends.mysql',
      'NAME': DBNAME,
      'USER': 'root',
      'PASSWORD': 'xxx',
      'HOST': '127.0.0.1',
      'PORT': 3306,
      'OPTIONS': {
          'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
      },
  },
  'db2': {
      'ENGINE': 'django.db.backends.postgresql_psycopg2',
      'NAME': DBNAME,
      'USER': 'root',
      'PASSWORD': 'xxx',
      'HOST': '127.0.0.1',
      'PORT': 5432,
  },
  'db3': {
      'ENGINE': 'django.db.backends.dummy',
  },
}
connect(DBNAME) #connect to mongodb
SUB_DIR = os.path.basename(os.path.dirname(__file__))
DATABASE_ROUTERS = [
  '{}.database_router.DatabaseAppsRouter'.format(SUB_DIR)
]
DATABASE_APPS_MAPPING = {
      # example:
      #'app_label':'database_name',
      'mysql': 'db1',
      'postgres': 'db2',
}
# 'mysql', 'postgres'需加到INSTALLED_APPS中,它们是通过startapp创建的两个空app

在settings同目录下增加database_router.py 文件,内容如下: (直接从自强学堂拷贝,只注释掉print语句,无需做其他更改)

# -*- 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):
      """
      Make sure that apps only appear in the related database.
      根据app_label的值只在相应的数据库中创建一个表,如果删除该def或
      不指定过滤条件,则一个Model会在每个数据库里都创建一个表。
      """
      if db in DATABASE_MAPPING.values():
          return DATABASE_MAPPING.get(app_label) == db
      elif app_label in DATABASE_MAPPING:
          return False
      return None

models里在class Meta中指定app_label就可以指定它要使用的数据库

例如:settings的DATABASE_APPS_MAPPING中指定app1对应db1、app2对应db2, 那么app1中的Model默认使用db1,而当指定Meta中的app_label为app2时,则改用db2。 注:app_label指定的值须包含在settings的INSTALLED_APPS中,否则makemigrations不会自动创建表。 app1/models.py内容如下:

#coding=utf-8
from django.db import models
from django.utils.encoding import python_2_unicode_compatible

@python_2_unicode_compatible
class Animal(models.Model):
  name = models.CharField(max_length=20)

  def __str__(self):
      return self.name

  class Meta:
      # 通过app_label来指定要使用的数据库
      # 需指定db_table,否则该class的表名会是mysql_animal
      # 如果不指定Meta的app_label,会使用默认数据库
      app_label = 'mysql'
      db_table = 'app1_animal'

@python_2_unicode_compatible
class Plant(models.Model):
  name = models.CharField(max_length=20)

  def __str__(self):
      return self.name

  class Meta:
      app_label = 'postgres'
      db_table = 'app1_plant'

app2/models.py

#coding=utf-8
from django.db import models
from django.utils.encoding import python_2_unicode_compatible

@python_2_unicode_compatible
class Fruit(models.Model):
  name = models.CharField(max_length=20)

  def __str__(self):
      return self.name

  class Meta:
      app_label = 'mysql' #使用mysql数据库
      db_table = 'app2_fruit' #指定表名为app2_fruit

@python_2_unicode_compatible
class Nut(models.Model):
  name = models.CharField(max_length=20)

  def __str__(self):
      return self.name

  class Meta:
      app_label = 'postgres' #使用postgres数据库
      db_table = 'app2_nut' #指定表名为app2_fruit

app1/test.py

from django.test import TestCase

from app1.models import Animal, Plant
from app2.models import Fruit, Nut

class ModelsTestCase(TestCase):
  # db1 -- mysql
  # db2 -- postgres
  def setUp(self):
      Animal.objects.create(name='db1')
      Plant.objects.create(name='db2')
      Fruit.objects.create(name='mysql_fruit')
      Nut.objects.create(name='post_nut')

  def test_methods_auto_choose_db(self):
      self.assertEqual(Animal.objects.all()[0],
              Animal.objects.using('db1').all()[0])
      self.assertEqual(Plant.objects.all()[0],
              Plant.objects.using('db2').all()[0])
      self.assertEqual(Fruit.objects.all()[0],
              Fruit.objects.using('db1').all()[0])
      self.assertEqual(Nut.objects.all()[0],
              Nut.objects.using('db2').all()[0])

      self.assertNotEqual(Animal.objects.all()[0],
              Fruit.objects.using('db1').all()[0])
      self.assertNotEqual(Plant.objects.all()[0],
              Nut.objects.using('db2').all()[0])
      self.assertNotEqual(Fruit.objects.all()[0],
              Plant.objects.using('db2').all()[0])

然后在命令行中运行

python manage.py makemigrations
python manage.py migrate --database=db1
python manage.py migrate --database=db2
python manage.py migrate
python manage.py test

完整代码放在coding上:

git clone https://git.coding.net/zhengwenjie/multi_dbs.git
cd multi_dbs
virtualenv venv
source venv/bin/activate
pip install -r requirements.txt
vi multi_dbs/settings.py #修改数据库名和密码等
alias mg='python manage.py'
mg makemigrations
mg migrate --database=db1
mg migrate --database=db2
mg migrate
mg test

方式二:通过using手动指定数据库

例如settings中的数据库配置如下:

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

import pymysql
pymysql.install_as_MySQLdb()
from mongoengine import connect
DBNAME = 'multi_test'
DATABASES = {
  'default': {
      'ENGINE': 'django.db.backends.sqlite3',
      'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
  },
  'mysql': {
      'ENGINE': 'django.db.backends.mysql',
      'NAME': DBNAME,
      'USER': 'root',
      'PASSWORD': 'xxx',
      'HOST': '127.0.0.1',
      'PORT': 3306,
      'OPTIONS': {
          'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
      },
  },
  'postgres': {
      'ENGINE': 'django.db.backends.postgresql_psycopg2',
      'NAME': DBNAME,
      'USER': 'root',
      'PASSWORD': 'xxx',
      'HOST': '127.0.0.1',
      'PORT': 5432,
  },
  'mongo': {
      'ENGINE': 'django.db.backends.dummy',
  },
}

则查询和创建数据语句如下(不加using为采用default数据库)

from app1.models import House
House.objects.using('mysql').create(name='Tom')
House.objects.using('mysql').get(name='Tom')
House.objects.using('postgres').get_or_create(name='Jim')

 


DJANGO 使用多个数据库

在django项目中, 一个工程中存在多个APP应用很常见. 有时候希望不同的APP连接不同的数据库,这个时候需要建立多个数据库连接。

参考:http://blog.csdn.net/songfreeman/article/details/70229839

 

\1. 修改项目的 settings 配置 settings.py 中配置需要连接的多个数据库连接串

DATABASES = {
  'default': {
      'ENGINE': 'django.db.backends.sqlite3',
      'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
  },
  'ora1': {   # 配置第二个数据库节点名称
      'ENGINE': 'django.db.backends.oracle',
      'NAME': 'devdb',
      'USER': 'hysh',
      'PASSWORD': 'hysh',
      'HOST': '192.168.191.3',
      'PORT': '1521',
  },
}

  

\2. 设置数据库的路由规则方法

settings.py 中配置 DATABASE_ROUTERS

DATABASE_ROUTERS ``=` `[``'Prject.database_router.DatabaseAppsRouter'``]

Project: 建立的django项目名称(project_name) database_router: 定义路由规则database_router.py 文件名称, 这个文件名可以自己定义 DatabaseAppsRouter: 路由规则的类名称,这个类是在database_router.py 文件中定义

\3. 设置APP对应的数据库路由表

每个APP要连接哪个数据库,需要在做匹配设置,在 settings.py 文件中做如下配置:

DATABASE_APPS_MAPPING = {
  # example:
  # 'app_name':'database_name',
  'report': 'ora1',
  'admin': 'defualt',
  'regs': 'defualt',
}

以上的report, regs是项目中的 APP名,分别指定到 ora1, default的数据库。

为了使django自己的表也创建到你自己定义的数据库中,你可以指定 : admin, auth, contenttypes, sessions 到设定的数据库中,如果不指定则会自动创建到默认(default)的数据库中.

4. 创建数据库路由规则 在项目工程根路径下(与 settings.py 文件一级)创建 database_router.py 文件:

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

  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

  def allow_migrate(self, db, app_label, model=None, **hints):
      """
      Make sure the auth app only appears in the 'auth_db'
      database.
      """
      if db in DATABASE_MAPPING.values():
          return DATABASE_MAPPING.get(app_label) == db
      elif app_label in DATABASE_MAPPING:
          return False
      return None

  

5.原生sql 的使用:

def exc_sql(sql):
  cursor = connections['ora1'].cursor()
  # cursor = connection.cursor()
  cursor.execute(sql)
  result = cursor.fetchall()
  return result

参考:https://docs.djangoproject.com/en/2.0/topics/db/sql/

 

\6. Models创建样例 在各自的 APP 中创建数据表的models时,必须要指定表的 app_label 名字,如果不指定则会创建到 default 中配置的数据库名下, 如下:

app01下创建models

class Users(models.Model):
  name = models.CharField(max_length=50)
  passwd = models.CharField(max_length=100)

  def __str__(self):
      return "app01 %s " % self.name

  class Meta:
      app_label = "app01"

app02下创建models

class Users(models.Model):
  username = models.CharField(max_length=100)
  password = models.CharField(max_length=50)
  age = models.IntegerField()

  def __str__(self):
      return "app02 %s" % self.username

  class Meta:
      app_label = "app02"

class Book(models.Model):
  user = models.ForeignKey("Users", on_delete=models.CASCADE)
  bookname = models.CharField(max_length=100)

  def __str__(self):
      return "%s: %s" % (self.user.username, self.bookname)

  class Meta:
      app_label = "app02"

  

\7. 生成数据表 在使用django的 migrate 创建生成表的时候,需要加上 –database 参数,如果不加则将 未 指定 app_label 的 APP的models中的表创建到default指定的数据库中,如:

将app01下models中的表创建到db01的数据库”db_01”中

./ manage.py  migrate  --database=db01                                

将app02下models中的表创建到db02的数据库”db_02”中

./ manage.py  migrate  --database=db02

将app03下models中的表创建到default的数据库”sqlite3”中

./ manage.py  migrate

以上创建完成后,其它所有的创建、查询、删除等操作就和普通一样操作就可以了,无需再使用类似 models.User.objects.using(dbname).all() 这样的方式来操作


 

OK, 简单介绍我项目的情况, 项目名称: djangoMy, 里面放了两个 app: vote 和 restaurant

vote 连默认数据库, restaurant 连另一个数据库:restaurant(数据库名)

第一步: 在你setting中设置

 

就是把你其它的数据库扔进去, 千万不要忘记在你的数据库中创建这个库啊, 不然迁移肯定失败

第二步, 我觉得把你的models 写好比较重要, 写好models后,

在元数据中指明这个数据库要用啥 :就是app_label='数据库名', 然后使用默认数据库的就不用管他了,如下示例

 

第三步就是写一个数据库调用规则,就是你写一个文件, 然后 在setting中把这个路由指出

这里这个文件怎么写, 我本人也不是很懂, 放上别人大佬写的吧,给两个地址

https://www.cnblogs.com/fu-yong/p/9889423.html

https://blog.csdn.net/weixin_42282496/article/details/80795261

然后setting中写上 :DATABASE_ROUTERS ``= [``'project_name.database_router.DatabaseAppsRouter'``]

project_name 就是你这个项目的名字, 然后database_router就是你刚写的数据库使用规则, 从这可以看出来, 没错, 我把这个文件放在了这个项目下, 既跟setting文件平级, 然后DatabaseAppsRouter 就你定义数据库规则文件中的类名

补充说明, 我看到文章中, 有人如下图所示的写法, 我没写, 我给注释掉了, 一样不影响, 所以, 你如果没有成功的话,要自己检查好错误

 

第五步 迁移

python manage.py makemigrations

python manage.py migrate --database=数据库名

 

其实我坐这里的时候, 并没有这么顺利成功, 在我删了N多次 迁移文件和数据库的情况下, 才迁移成功的

最后成功了我也不清楚到底中间让我不成功的因素是什么, 但是这里记录几个我粗心大意的点

\1. 你不需要去写明另一个默认数据库中models的元数据, 去指明这个默认数据库, 当然可以写, 但没必要

2.你必须要有这个数据库才能迁移成功的,估计除了我不会有人犯这样低级错误

3.就是setting中, 不要写错了路由地址, 其它的应该就还好

 


 

1.编辑settings.py,添加多个数据库

DATABASES = {
    'default': {
    'ENGINE': 'django.db.backends.mysql',
    'NAME': 'blog',
    'USER':'root',
    'PASSWORD':'123456',
    'HOST':'127.0.0.1',
    'PORT':'3306',
},
    'appinfo': {
    'ENGINE': 'django.db.backends.mysql',
    'NAME': 'appinfo',
    'USER': 'root',
    'PASSWORD': '123456',
    'HOST': '127.0.0.1',
    'PORT': '3306',
}
}

2.添加数据库路由表 在工程同名文件下创建:db_router.py (文件名可变)

 

class appinfoRouter(object):
    def db_for_read(self,model,**hints):
        if model._meta.app_label == 'appinfo':
            return 'appinfo'
        else:
            return None

    def db_for_write(self,model,**hints):
        if model._meta.app_label == 'appinfo':
            return 'appinfo'
        else:
            return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label == 'appinfo':
            return db == 'appinfo'
        else:
            return None

3.编辑settings.py,添加路由:

DATABASE_ROUTERS = [xxx.db_router.appinfoRouter]   xxxx 为工程名

4.为每个app的model分别指定所需要连接的数据库:

class vivo_package(models.Model):
    appName = models.CharField(max_length=40)
    packageName = models.CharField(max_length=40)
    appVersion = models.CharField(max_length=20,null=True)
    apkUrlRelativePath = models.CharField(max_length=100)

    class Meta:
        db_table = 'vivo_package'         
        app_label = 'appinfo'             //指定使用的数据库名称
python manage.py makemigrations
python manage.pymigrate --database=appinfo   更新appinfo 数据库

python manage.py makemigrations
python manage.pymigrate             更新默认数据库

6.操作数据库:

models.xxx.objects.using('appinfo').all()            // using('appinfo')表示使用appinfo 这个数据库

 

 

 


 

posted on 2022-08-10 08:30  root-123  阅读(686)  评论(0编辑  收藏  举报