一种Django多租户解决方案 (3)

在这里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

class AbstractGlobalUser(models.Model): ... def has_module_perms(self, app_label:str) -> bool: # 是否有模块权限 common_applist = get_common_apps() # 如果是租户用户 if self.tenant: # 租户用户不能访问公共app if app_label in common_applist: return False else: return True else: # 只有非租户用并且是超级用户的才能访问公共app if app_label in common_applist and self.is_super: return True else: return False def has_perm(self, permission:str) -> bool: # 用户是否有权限(permission表中的权限) TenantUser = get_tenant_user_model() # 如果是租户用户 if self.tenant: # 检查租户用户的权限 try: tenant_user = TenantUser.objects.using(self.tenant.code).get(username=self.username) all_permissions = tenant_user.get_all_permissions() if permission in all_permissions: result = tenant_user.has_perm(permission) return result else: return False except Exception as e: print(e) return False else: # 非租户用户因为只有超级用户可以登陆,因此可以拥有公共app的所有权限 True return True migrate适配

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgywjf.html