在这里get_tenant_db是我们实现的根据租户别名获取租户数据连接的方法
def get_tenant_db(alias: str) -> Dict[str,str]: Tenant = get_tenant_model() try: # 租户信息全部保存在default数据库连接里面 tenant = Tenant.objects.using(\'default\').filter(is_active=True).get(code=alias) return tenant.get_db_config() except Tenant.DoesNotExist: logger.warning(f\'db alias [{alias}] dont exists\') pass 执行对于新租户执行数据库迁移当一个租户被创建的时候,采用django的post_save信号触发对应的创建数据库连接和执行迁移的动作
@receiver(post_save, sender=Tenant) def create_data_handler(sender, signal, instance, created, **kwargs): # 如果租户被创建 if created: try: # 创建数据库 instance.create_database() logger.info(f\'create database : [{instance.db_name}] successfuly for {instance.code}\') # 在线程中执行migrate 命令 thread = Thread(target=migrate,args=[instance.code]) thread.start() except Exception as e: logger.error(e) instance.delete(force=True) def migrate(database: str): try: from django.core.management import execute_from_command_line except ImportError as exc: logger.error(\'migrate fail\') raise ImportError( "Couldn\'t import Django. Are you sure it\'s installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc execute_from_command_line([\'manage.py\', \'migrate\', f\'--database={database}\']) logger.info(\'migrate successfuly!\') 创建数据库因为django目前只支持SQLite、Posgres、MySQL和Oracle四种关系型数据库,因为我们的租户model,根据这四种数据库模型实现对应的create_daatabase方法
class AbstractTenant(models.Model): Mysql, SQLite, Postgres, Oracle = (\'Mysql\', \'SQLite3\', \'Postgres\', \'Oracle\') ... def create_database(self) -> bool: from multi_tenant.tenant.utils.db import MutlTenantOriginConnection # 创建数据原生连接 if self.engine.lower() == self.SQLite.lower(): connection = MutlTenantOriginConnection().create_connection(tentant=self, popname=False) return True elif self.engine.lower() == self.Postgres.lower(): connection = MutlTenantOriginConnection().create_connection(tentant=self, popname=True, **{\'NAME\':\'postgres\'}) else: connection = MutlTenantOriginConnection().create_connection(tentant=self, popname=True) create_database_sql = self.create_database_sql if create_database_sql: with connection.cursor() as cursor: # 执行创建数据库SQL语句 cursor.execute(create_database_sql) return True def _create_sqlite3_database(self) -> str: pass def _create_mysql_database(self) -> str: return f"CREATE DATABASE IF NOT EXISTS {self.db_name} character set utf8;" def _create_postgres_database(self) -> str: return f"CREATE DATABASE \"{self.db_name}\" encoding \'UTF8\';" def _create_oracle_database(self) -> str: return f"CREATE DATABASE {self.db_name} DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;" Auth 模块适配django的settings里面的AUTH_USER_MODEL配置项目为django自定义全局User的配置项,为了便于在租户模块完整的使用django.contrib.auth模块,我们将AUTH_USER_MODEL指定为GlobalUser,但是通常这里的AUTH_USER_MODEL必须继承django.contrib.auth.models.AbstractUser对象,为了保证这一点,django.contrib.auth模块,通常在app初始化的是会检查Usermodel
# django.contrib.auth.apps class AuthConfig(AppConfig): ... def ready(self): ... if isinstance(last_login_field, DeferredAttribute): from .models import update_last_login user_logged_in.connect(update_last_login, dispatch_uid=\'update_last_login\') checks.register(check_user_model, checks.Tags.models) checks.register(check_models_permissions, checks.Tags.models)但是对于GlobalUser而言我们没必要使用完整的django.contrib.auth功能,因此不能简单指定GlobalUser
,必须保证GlobalUser通过check_user_model检查,因此我们必须实现例如normalize_username、check_password、set_password、USERNAME_FIELD、PASSWORD_FIELD等常见的属性和方法
然后我们将django.contrib.auth.models.AbstractUser.Meta.swappable属性改为AUTH_TENANT_USER_MODEL,即租户级别的用户
from django.contrib.auth.models import AbstractUser, User class Meta(AbstractUser.Meta): swappable = \'AUTH_TENANT_USER_MODEL\' User.Meta = Meta这样我们就可以愉快地租户模型中完整的使用django.contrib.auth模块了
Admin模块适配Admin模块即要在公共app中使用,又要在租户模块使用,我们只需要保证根据登陆的用户不同加载不同的app下的admin即可,
在这里我们需要让GlobalUser实现两个方法has_module_perms和has_perm