Package: django-easy-tenants
easy-tenantsThis is a Django app for managing multiple tenants on the same project
instance using a shared approach.BackgroundThere are typically three solutions for solving the multitenancy problem:Isolated Approach: Separate Databases. Each tenant has it’s own database.Semi Isolated Approach: Shared Database, Separate Schemas.
One database for all tenants, but one schema per tenant.Shared Approach: Shared Database, Shared Schema. All tenants share
the same database and schema. There is a main tenant-table, where all
other tables have a foreign key pointing to.This application implements the third approach,  which in our opinion,
is the best solution for a large amount of tenants.For more information:Building Multi Tenant Applications with DjangoBelow is a demonstration of the features in each approach for an application
with 5000 tenants.ApproachNumber of DBNumber of SchemasDjango migration timePublic accessIsolated50005000slow (1/DB)NoSemi Isolated15000slow (1/Schema)YesShared11fast (1)YesInstallationAssuming you have django installed, the first step is to installdjango-easy-tenants.python-mpipinstalldjango-easy-tenantsNow you can import the tenancy module in your Django project.SetupIt is recommended to install this app at the beginning of a project.
In an existing project, depending on the structure of the models,
the data migration can be hard.Addeasy_tenantsto yourINSTALLED_APPSonsettings.py.settings.pyINSTALLED_APPS=[...,'easy_tenants',]Create a model which will be the tenant of the application.yourapp/models.pyfromdjango.dbimportmodelsclassCustomer(models.Model):...settings.pyEASY_TENANTS_TENANT_MODEL="yourapp.Customer"Your models, which must have isolated data per tenant, we need to add the foreign field from the Customer model.
and objects need to be replaced withTenantManager().fromdjango.dbimportmodelsfromeasy_tenants.modelsimportTenantManagerclassProduct(models.Model):tenant=models.ForeignKey(Customer,on_delete=models.CASCADE,editable=False)name=models.CharField(max_length=10)objects=TenantManager()If you prefer you can useTenantAwareAbstractto implement the save method for you,
so when saving an object the tenant will be automatically defined.classProduct(TenantAwareAbstract):tenant=models.ForeignKey(Customer,on_delete=models.CASCADE,editable=False)name=models.CharField(max_length=10)objects=TenantManager()If your foreign field has a name other thantenantyou can change it with a settings. (default is"tenant")# models.pyclassProduct(TenantAwareAbstract):customer=models.ForeignKey(Customer,on_delete=models.CASCADE,editable=False)name=models.CharField(max_length=10)objects=TenantManager()# settings.pyEASY_TENANTS_TENANT_FIELD="customer"To obtain the data for each tenant, it is necessary to define which tenant will be used:fromeasy_tenantsimporttenant_contextwithtenant_context(customer):Product.objects.all()# filter by customerTo define the tenant to be used, this will depend on the business rule used. Here is an example for creating middleware that defines a tenant:fromdjango.httpimportHttpResponsefromeasy_tenantsimporttenant_contextclassTenantMiddleware:def__init__(self,get_response):self.get_response=get_responsedef__call__(self,request):customer=get_customer_by_request(request)ifnotcustomer:returnHttpResponse("Select tenant")withtenant_context(customer):returnself.get_response(request)If you want to separate the upload files by tenant, you need to change theDEFAULT_FILE_STORAGEconfiguration (only available for local files).DEFAULT_FILE_STORAGE='easy_tenants.storage.TenantFileSystemStorage'Running the example projectpythonmanage.pymigrate
pythonmanage.pycreatesuperuser
pythonmanage.pyrunserverAccess the page/admin/, create aCustomer.Motivationdjango-tenant-schemasdjango-tenantsdjango-scopes