1👍
Have you thought of subdomains?
Custom subdomains are a great way to provide customization for customers of SaaS products and to differentiate content without resorting to long URL paths.
So when a company registers, you either take the company name call to_lower() method, or prompt them to choose the one they like, just like Slack does, or Jira. I suggest you read this article Using Subdomains in Django Applications
1👍
It depends on how you want to store data in you SaaS application – single db for all instances or multiple db (each instance have separate database). Multiple db approach is pain when you want add new features, migrations etc. Single db is easier to manage but you need to add bunch of ForeignKey
for each model.
For single db you will need:
- Middleware that will detect SaaS instance (by subdomain, domain, port, custom url etc.).
- Database router that will return database name for read/write depending on SaaS instance.
That’s all. Django will read/write to separate databases.
For multiple db you will need:
- Middleware that will detect SaaS instance (by subdomain, domain, port, custom url etc.).
Because you probably don’t want to add ForeignKey
to each model manually and filter it manually:
- Abstract model with
ForeignKey
and custom save method to auto set thatForeignKey
. - Custom model manager with custom
get_queryset
method that will filter all ORM queries with current SaaS instance. This manager should overidecreate
method to auto setForeignKey
for queries like this:Foo.objects.create(**data)
Each model that will be fitlered for SaaS instance should inherit from that abstract model and you will need to set this model manager to that custom model manager.
That’s all. Django will filter you ORM queries for current SaaS instance.
Example middleware (uses Domain model to check if domain exists, if not you will get 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
Example abstract model:
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)
Example model manager:
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
Example models:
class FooModel(SaaSModelAbstract):
# model fields, methods
objects = CurrentSaaSInstanceManager()
class BarModel(models.Model):
# model fields, methods
pass
Example queries:
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()
In both cases (single/multiple db) django admin will be working properly.
I’m not posted db router because implementation is trivial and all you need can be found in django docs.
- [Answered ]-Django define default view with IP address
- [Answered ]-Django Rest Framework: How to get value of the ForeignKey
- [Answered ]-Schedule table updates in Django
0👍
You can surely use django-tenant-schemas
Edit:
Given that you mentioned in the comment to my original answer that the database being used is MySQL, django-tenant-schemas is useless in your case. How about using multiple databases with database routers, that ways there can be separate databases for every company and using database routers you can route request for your databases through it.
It might be overwork but you probably can figure out a slick way to do it.
- [Answered ]-Django model A can has only one instance of model B
- [Answered ]-Dynamic choices in Foreignkey Field in Django Rest Framework
- [Answered ]-Django app has a no ImportError: No module named 'django.core.context_processors'