Django - 具有唯一数据的多个用户

时间:2016-05-27 01:54:39

标签: django

我正在构建一个类似于属性维护应用程序的SaaS。物业维护公司将使用我的应用程序来管理多个建筑物和一大堆租户。所以我们的建筑物和租户都由一家管理公司管理。但是,管理公司可能有多个管理这些建筑物/租户的员工(用户)。

由于这是一个SaaS,我想让一大堆管理公司都使用我的应用程序来管理他们自己的建筑物/租户。因此,每个管理公司只需要能够查看其数据并且无法访问另一个管理公司的数据。

我不知道如何在Django中实现这一点,尽管我有一个想法:

也许有一个名为ManagementCompany的模型,所有建筑物和租户都有ForeignKey字段,指向ManagementCompany模型。该管理公司的员工将通过ManagementCompanyForeignKey记录相关联。这样,用户只能看到ManagementCompany与用户匹配的建筑物/租户。

这是一种合理的方法,还是有更好的方法?

3 个答案:

答案 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

对于单个数据库,您需要:

  1. 将检测SaaS实例的中间件(通过子域,域,端口,自定义URL等)。
  2. 数据库路由器,它将根据SaaS实例返回读/写数据库名称。
  3. 这就是全部。 Django将读/写不同的数据库。

    对于多个数据库,您需要:

    1. 将检测SaaS实例的中间件(通过子域,域,端口,自定义URL等)。
    2. 因为您可能不想手动为每个模型添加ForeignKey并手动过滤它:

      1. 使用ForeignKey的抽象模型和自定义保存方法自动设置ForeignKey
      2. 具有自定义get_queryset方法的自定义模型管理器,该方法将使用当前SaaS实例过滤所有ORM查询。对于这样的查询,此经理应该覆盖create方法自动设置ForeignKeyFoo.objects.create(**data)
      3. 每个适合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中找到。