我正在构建一个类似于属性维护应用程序的SaaS。物业维护公司将使用我的应用程序来管理多个建筑物和一大堆租户。所以我们的建筑物和租户都由一家管理公司管理。但是,管理公司可能有多个管理这些建筑物/租户的员工(用户)。
由于这是一个SaaS,我想让一大堆管理公司都使用我的应用程序来管理他们自己的建筑物/租户。因此,每个管理公司只需要能够查看其数据并且无法访问另一个管理公司的数据。
我不知道如何在Django中实现这一点,尽管我有一个想法:
也许有一个名为ManagementCompany
的模型,所有建筑物和租户都有ForeignKey
字段,指向ManagementCompany
模型。该管理公司的员工将通过ManagementCompany
与ForeignKey
记录相关联。这样,用户只能看到ManagementCompany
与用户匹配的建筑物/租户。
这是一种合理的方法,还是有更好的方法?
答案 0 :(得分:1)
您是否想过子域名?
自定义子域是为SaaS产品的客户提供自定义并在不使用长URL路径的情况下区分内容的好方法。
因此,当公司注册时,您可以使用公司名称调用to_lower()方法,或者提示他们选择他们喜欢的方法,就像Slack那样,或者Jira。我建议你阅读这篇文章Using Subdomains in Django Applications
答案 1 :(得分:0)
您一定可以使用django-tenant-schemas
修改强>
鉴于您在我的原始答案的评论中提到正在使用的数据库是MySQL,django-tenant-schemas在您的情况下是无用的。如何将多个数据库与数据库路由器一起使用,可以为每个公司提供单独的数据库,并使用数据库路由器,您可以通过它来路由数据库请求。
可能会过度劳累,但你可能会想出一个光滑的方法来做到这一点。
答案 2 :(得分:0)
这取决于您希望如何在SaaS应用程序中存储数据 - 所有实例的单个数据库或多个数据库(每个实例都有单独的数据库)。当您想要添加新功能,迁移等时,多数据库方法很痛苦。单个数据库更易于管理,但您需要为每个模型添加一堆ForeignKey
。
对于单个数据库,您需要:
这就是全部。 Django将读/写不同的数据库。
对于多个数据库,您需要:
因为您可能不想手动为每个模型添加ForeignKey
并手动过滤它:
ForeignKey
的抽象模型和自定义保存方法自动设置ForeignKey
。get_queryset
方法的自定义模型管理器,该方法将使用当前SaaS实例过滤所有ORM查询。对于这样的查询,此经理应该覆盖create
方法自动设置ForeignKey
:Foo.objects.create(**data)
每个适合SaaS实例的模型都应该继承该抽象模型,您需要将此模型管理器设置为该自定义模型管理器。
这就是全部。 Django将过滤您对当前SaaS实例的ORM查询。
示例中间件(使用域模型检查域是否存在,如果不存在,您将获得HTTP404):
try:
from threading import local
except ImportError:
from django.utils._threading_local import local
_thread_locals = local()
def get_current_saas_instance():
return getattr(_thread_locals, 'current_instance', None)
class SaaSSubdomainMiddleware(object):
def process_request(self, request):
_thread_locals.current_instance = None
host = request.get_host()
try:
domain = Domain.objects.get(name=host)
_thread_locals.current_instance = domain.company
except:
logger.error('Error when checking SaaS domain', exc_info=True)
raise Http404
抽象模型示例:
class SaaSModelAbstract(Model):
SAAS_FIELD_NAME = 'company'
company = ForeignKey(Company, null=True, blank=True)
class Meta:
abstract = True
def save(self, *args, **kwargs):
from .middleware import get_current_saas_instance
self.company = get_current_saas_instance()
super(SaaSModelAbstract, self).save(*args, **kwargs)
示例模型管理器:
class CurrentSaaSInstanceManager(models.Manager):
def get_current_saas_instance(self):
from .middleware import get_current_saas_instance
return get_current_saas_instance()
def get_queryset(self):
current_instance = self.get_current_saas_instance()
if current_instance is not None:
return super(CurrentSaaSInstanceManager, self).get_queryset().filter(
**{self.model.SAAS_FIELD_NAME: current_instance})
return super(CurrentSaaSInstanceManager, self).get_queryset()
def create(self, **kwargs):
current_instance = self.get_current_saas_instance()
if current_instance is not None:
kwargs[self.model.SAAS_FIELD_NAME] = current_instance
instance = self.model(**kwargs)
self._for_write = True
instance.save(force_insert=True, using=self.db)
return instance
示例模型:
class FooModel(SaaSModelAbstract):
# model fields, methods
objects = CurrentSaaSInstanceManager()
class BarModel(models.Model):
# model fields, methods
pass
示例查询:
FooModel.objects.all() # will return query with all objects for current SaaS instance
BarModel.objects.all() # will return all objects withoout SaaS filtering
# Create objects for SaaS instance:
FooModel.objects.create(**data)
# or:
foo = FooModel()
foo.save()
在这两种情况下(单个/多个数据库)django admin将正常工作。
我没有发布db router,因为实现很简单,所有你需要的都可以在django docs中找到。