{"id":6065,"date":"2023-05-10T07:00:05","date_gmt":"2023-05-10T14:00:05","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/cosmosdb\/?p=6065"},"modified":"2023-05-09T11:56:34","modified_gmt":"2023-05-09T18:56:34","slug":"evolving-django-multitenant-to-build-scalable-saas-apps-on-postgres-citus","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/cosmosdb\/evolving-django-multitenant-to-build-scalable-saas-apps-on-postgres-citus\/","title":{"rendered":"Evolving django-multitenant to build scalable SaaS apps on Postgres &#038; Citus"},"content":{"rendered":"<p><span style=\"font-size: 10pt;\"><em><span class=\"TextRun SCXW79785518 BCX0\" lang=\"EN-US\" xml:lang=\"EN-US\" data-contrast=\"auto\"><span class=\"NormalTextRun SCXW79785518 BCX0\">This post by <\/span><span class=\"NormalTextRun SCXW79785518 BCX0\">G<\/span><span class=\"NormalTextRun SCXW79785518 BCX0\">u<\/span><span class=\"NormalTextRun SCXW79785518 BCX0\">rkan<\/span> <span class=\"NormalTextRun SCXW79785518 BCX0\">I<\/span><span class=\"NormalTextRun SCXW79785518 BCX0\">ndibay<\/span><span class=\"NormalTextRun SCXW79785518 BCX0\"> about the <\/span><span class=\"NormalTextRun SpellingErrorV2Themed SCXW79785518 BCX0\">django<\/span><span class=\"NormalTextRun SCXW79785518 BCX0\">-multitenant library was <\/span><\/span><a class=\"Hyperlink SCXW79785518 BCX0\" href=\"https:\/\/www.citusdata.com\/blog\/2023\/05\/09\/evolving-django-multitenant-to-build-scalable-saas-apps-on-postgres-and-citus\/\" target=\"_blank\" rel=\"noreferrer noopener\"><span class=\"TextRun Underlined SCXW79785518 BCX0\" lang=\"EN-US\" xml:lang=\"EN-US\" data-contrast=\"none\"><span class=\"NormalTextRun SCXW79785518 BCX0\" data-ccp-charstyle=\"Hyperlink\">originally published on the Citus Open Source Blog<\/span><\/span><\/a><span class=\"TextRun SCXW79785518 BCX0\" lang=\"EN-US\" xml:lang=\"EN-US\" data-contrast=\"auto\"><span class=\"NormalTextRun SCXW79785518 BCX0\">.\u00a0<\/span><\/span><span class=\"EOP SCXW79785518 BCX0\" data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/em><\/span><\/p>\n<p class=\"pg-section\">If you\u2019re building a software application that serves multiple tenants, you may have already encountered the challenges of managing and isolating tenant-specific data. That\u2019s where the\u00a0<a href=\"https:\/\/github.com\/citusdata\/django-multitenant\">django-multitenant library<\/a>\u00a0comes in. This library, actively used since 2017 and now downloaded more than 10K times per month, offers a simple and flexible solution for building multi-tenant Django applications.<\/p>\n<p class=\"pg-section\">In this blog post, we\u2019ll dive deeper into the concept of multi-tenancy and explore how Django-multitenant can help you build scalable, secure, and maintainable multi-tenant applications on top of PostgreSQL and the\u00a0<a href=\"https:\/\/github.com\/citusdata\/citus\">Citus database extension<\/a>. We\u2019ll also provide a practical example of how to use Django-multitenant in a real-world scenario. So, if you\u2019re looking to simplify your multi-tenant development process, keep reading.<\/p>\n<p class=\"pg-section\">If you already have a good understanding of multi-tenancy concepts, you can skip this multi-tenancy part and proceed to the sections below:<\/p>\n<ul class=\"pg-section\">\n<li><a href=\"#django-multi\">Automating Django queries with django-multitenant<\/a><\/li>\n<li><a href=\"#whats-new\">What\u2019s new in django-multitenant version 3.2<\/a><\/li>\n<\/ul>\n<h2>What is multi-tenancy?<\/h2>\n<p class=\"pg-section\">Multitenancy is an architecture where a single instance of a software application serves multiple customers (tenants). Each tenant has its own data, configuration, and user interface, but shares the same codebase and infrastructure. It\u2019s beneficial for SaaS applications as it allows for efficient resource usage, cost savings, and easier maintenance through simultaneous updates for all tenants.<\/p>\n<h3>Multitenancy challenges and approaches<\/h3>\n<p class=\"pg-section\">When dealing with multitenancy in your application, you must design with a few database issues:<\/p>\n<ol>\n<li><strong>Tenant Isolation<\/strong>\u00a0: Ensure data and resource separation, route requests correctly, and manage tenant-specific configuration.<\/li>\n<li><strong>Scalability<\/strong>\u00a0: Design a scalable database architecture, partition data, and manage concurrent access to handle increasing tenant load.<\/li>\n<li><strong>Security<\/strong>\u00a0: Implementing additional security measures to protect tenant data, secure data in transit and at rest, and manage access control.<\/li>\n<li><strong>Customization<\/strong>\u00a0: Allow easy customization of the application for each tenant, including configuration, branding, and workflows.<\/li>\n<li><strong>Performance<\/strong>\u00a0: Optimize application performance through efficient database schema design, caching, and data access mechanisms.<\/li>\n<\/ol>\n<p class=\"pg-section\">There are three approaches to managing multi-tenancy in applications.<\/p>\n<ol>\n<li><strong>Separate Databases<\/strong>\u00a0&#8211; Each tenant has their database.<\/li>\n<li><strong>Shared Database, Separate Schemas<\/strong>. Tenants have their own schemas but can reside in the same database.<\/li>\n<li><strong>Shared Database, Shared Schema<\/strong>\u00a0&#8211; All tenants share the same schema and data objects should include tenant_id to denote ownership to a tenant.<\/li>\n<\/ol>\n<p class=\"pg-section\">The first 3 approaches above (separate databases, and separate schemas) are out of scope for this blogpost.<\/p>\n<p class=\"pg-section\">We will focus on the 3rd approach, the \u201c<strong>Shared Database, Shared Schema<\/strong>\u201d approach.<\/p>\n<h3>Multitenancy with a shared database, shared schema<\/h3>\n<p class=\"pg-section\">Django-multitenant supports sharing a schema between tenants, which offers advantages such as easier maintenance, reduced complexity, better resource utilization, improved scalability, and enhanced data security. However, this approach has two challenges: scaling Postgres databases for multiple tenants and dealing with complexity in application development.<\/p>\n<p class=\"pg-section\">If you are currently in the process of developing a multi-tenant application using Django ORM on PostgreSQL using the shared schema approach, there are 2 problems needs to be addressed:<\/p>\n<ol>\n<li>Application complexity due to tenant ID addition into application<\/li>\n<li>Scaling the application while adding more tenants<\/li>\n<\/ol>\n<p class=\"pg-section\">The django-multitenant library helps with the first problem above\u2014application complexity\u2014 while the\u00a0<a href=\"https:\/\/www.citusdata.com\/download\/\">Citus extension to Postgres<\/a>\u00a0can be used to enable distributed scale.<\/p>\n<blockquote><p><a href=\"https:\/\/github.com\/citusdata\/citus\">Citus is<\/a>\u00a0available as open source, and as managed service as part of\u00a0<a href=\"https:\/\/learn.microsoft.com\/azure\/cosmos-db\/postgresql\/\">Azure Cosmos DB for PostgreSQL<\/a><\/p><\/blockquote>\n<p class=\"pg-section\">You can use the django-multitenant library with Citus seamlessly whether using Citus open source or on the Azure Cosmos DB for PostgreSQL managed service. django-multitenant helps with data isolation in application development. Data isolation requires careful development process, including filtering queries by tenant_id, including tenant column in join clauses, and recording correct tenant data when saving user input which django-multitenant easy for you.<\/p>\n<h2 id=\"automating-django-queries\"><a id=\"django-multi\"><\/a>Automating Django queries with django-multitenant<\/h2>\n<p class=\"pg-section\">The django-multitenant library extends the Django ORM to automatically and uniformly scope queries by tenant. It adds the\u00a0<code>tenant_id<\/code>\u00a0in both fetching and manipulating data. It\u2019s a mature library which is downloaded over 10,000 times each month.<\/p>\n<p class=\"pg-section\">Features of django-multitenant:<\/p>\n<ol>\n<li>You can set the tenant with one method call, and existing application code will access the proper tenant without extensive changes.<\/li>\n<li>With a tenant set, the library will seamlessly include the tenant ID in join operations, eliminating the need to manually add tenant IDs.<\/li>\n<li>Supports standard Django and the Django Rest Framework to streamline integration.<\/li>\n<li>Implements helper classes for Citus, a distributed Postgres extension\u2014and facilitates table distribution during the database migration process.<\/li>\n<\/ol>\n<h2 id=\"whats-new-in-django-multitenant\"><a id=\"whats-new\"><\/a>What\u2019s new in django-multitenant version 3.2<\/h2>\n<p class=\"pg-section section-active\">With the start of 2023, the django-multitenant project has been reinvigorated with active development and new releases. The latest release, version 3.2, includes the following critical changes and new features:<\/p>\n<ul class=\"pg-section section-active\">\n<li>Added DjangoRestFramework support, enabling you to easily create RESTful APIs that can handle multi-tenant data.<\/li>\n<li>Improved model migration guidelines, which can help you migrate tenant-specific data seamlessly.<\/li>\n<li>Support for the latest version of Django (version 4.1), allowing you to take advantage of its latest features and improvements.<\/li>\n<li>Ability to automatically set the tenant for ManyToMany related models.<\/li>\n<li>Fixed invalid error messages in case of invalid field names.<\/li>\n<li>Support for getting models using apps.get_model.<\/li>\n<li>Removed reserved tenant_id limitation by introducing TenantMeta usage.<\/li>\n<li>Introduced ReadTheDocs\u00a0<a href=\"https:\/\/django-multitenant.readthedocs.io\/en\/stable\/\">documentation<\/a>.<\/li>\n<li>Fixed issue with ManyToMany Non tenant model saves.<\/li>\n<\/ul>\n<h2>Quickstart on how to get started with django-multitenant<\/h2>\n<p class=\"pg-section section-active\">Let\u2019s learn how to use\u00a0<code>django-multitenant<\/code>\u00a0in a sample application which includes newly added features introduced with version 3.2. This example provides a web interface for a fictitious project management application. (The example uses the Django Rest Framework for developer efficiency, but the underlying Django-multitenant library works with vanilla Django too.)<\/p>\n<ol>\n<li>To get started, first install the Python packages:\n<pre class=\"prettyprint language-default\"><code class=\"language-default\">pip install django-multitenant\r\npip install Django\r\npip install djangorestframework<\/code><\/pre>\n<p>&nbsp;<\/li>\n<li>Add a TenantMeta class in your models and specify the field which identifies tenants.\n<div class=\"highlight\">\n<pre class=\"highlight python\" tabindex=\"0\"><code><span class=\"k\">class<\/span> <span class=\"nc\">Country<\/span><span class=\"p\">(<\/span><span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">Model<\/span><span class=\"p\">):<\/span>\r\n    <span class=\"n\">name<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">CharField<\/span><span class=\"p\">(<\/span><span class=\"n\">max_length<\/span><span class=\"o\">=<\/span><span class=\"mi\">255<\/span><span class=\"p\">)<\/span>\r\n\r\n<span class=\"k\">class<\/span> <span class=\"nc\">Account<\/span><span class=\"p\">(<\/span><span class=\"n\">TenantModel<\/span><span class=\"p\">):<\/span>\r\n    <span class=\"n\">user<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">ForeignKey<\/span><span class=\"p\">(<\/span><span class=\"n\">User<\/span><span class=\"p\">,<\/span> <span class=\"n\">on_delete<\/span><span class=\"o\">=<\/span><span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">CASCADE<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"n\">name<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">CharField<\/span><span class=\"p\">(<\/span><span class=\"n\">max_length<\/span><span class=\"o\">=<\/span><span class=\"mi\">255<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"n\">domain<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">CharField<\/span><span class=\"p\">(<\/span><span class=\"n\">max_length<\/span><span class=\"o\">=<\/span><span class=\"mi\">255<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"n\">subdomain<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">CharField<\/span><span class=\"p\">(<\/span><span class=\"n\">max_length<\/span><span class=\"o\">=<\/span><span class=\"mi\">255<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"n\">country<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">ForeignKey<\/span><span class=\"p\">(<\/span><span class=\"n\">Country<\/span><span class=\"p\">,<\/span> <span class=\"n\">on_delete<\/span><span class=\"o\">=<\/span><span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">CASCADE<\/span><span class=\"p\">)<\/span>\r\n\r\n    <span class=\"k\">class<\/span> <span class=\"nc\">TenantMeta<\/span><span class=\"p\">:<\/span>\r\n        <span class=\"n\">tenant_field_name<\/span> <span class=\"o\">=<\/span> <span class=\"s\">'id'<\/span>\r\n\r\n<span class=\"k\">class<\/span> <span class=\"nc\">Manager<\/span><span class=\"p\">(<\/span><span class=\"n\">TenantModel<\/span><span class=\"p\">):<\/span>\r\n    <span class=\"n\">name<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">CharField<\/span><span class=\"p\">(<\/span><span class=\"n\">max_length<\/span><span class=\"o\">=<\/span><span class=\"mi\">255<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"n\">account<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">ForeignKey<\/span><span class=\"p\">(<\/span>\r\n        <span class=\"n\">Account<\/span><span class=\"p\">,<\/span> <span class=\"n\">on_delete<\/span><span class=\"o\">=<\/span><span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">CASCADE<\/span><span class=\"p\">,<\/span> <span class=\"n\">related_name<\/span><span class=\"o\">=<\/span><span class=\"s\">\"managers\"<\/span>\r\n    <span class=\"p\">)<\/span>\r\n    <span class=\"k\">class<\/span> <span class=\"nc\">TenantMeta<\/span><span class=\"p\">:<\/span>\r\n        <span class=\"n\">tenant_field_name<\/span> <span class=\"o\">=<\/span> <span class=\"s\">'account_id'<\/span>\r\n    <span class=\"k\">class<\/span> <span class=\"nc\">Meta<\/span><span class=\"p\">:<\/span>\r\n        <span class=\"n\">constraints<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span>\r\n            <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">UniqueConstraint<\/span><span class=\"p\">(<\/span><span class=\"n\">fields<\/span><span class=\"o\">=<\/span><span class=\"p\">[<\/span><span class=\"s\">'id'<\/span><span class=\"p\">,<\/span> <span class=\"s\">'account_id'<\/span><span class=\"p\">],<\/span> <span class=\"n\">name<\/span><span class=\"o\">=<\/span><span class=\"s\">'unique_manager_account'<\/span><span class=\"p\">)<\/span>\r\n        <span class=\"p\">]<\/span>\r\n\r\n<span class=\"k\">class<\/span> <span class=\"nc\">Project<\/span><span class=\"p\">(<\/span><span class=\"n\">TenantModel<\/span><span class=\"p\">):<\/span>\r\n    <span class=\"n\">name<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">CharField<\/span><span class=\"p\">(<\/span><span class=\"n\">max_length<\/span><span class=\"o\">=<\/span><span class=\"mi\">255<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"n\">account<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">ForeignKey<\/span><span class=\"p\">(<\/span>\r\n        <span class=\"n\">Account<\/span><span class=\"p\">,<\/span> <span class=\"n\">related_name<\/span><span class=\"o\">=<\/span><span class=\"s\">\"projects\"<\/span><span class=\"p\">,<\/span> <span class=\"n\">on_delete<\/span><span class=\"o\">=<\/span><span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">CASCADE<\/span>\r\n    <span class=\"p\">)<\/span>\r\n    <span class=\"n\">managers<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">ManyToManyField<\/span><span class=\"p\">(<\/span><span class=\"n\">Manager<\/span><span class=\"p\">,<\/span> <span class=\"n\">through<\/span><span class=\"o\">=<\/span><span class=\"s\">\"ProjectManager\"<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"k\">class<\/span> <span class=\"nc\">TenantMeta<\/span><span class=\"p\">:<\/span>\r\n        <span class=\"n\">tenant_field_name<\/span> <span class=\"o\">=<\/span> <span class=\"s\">'account_id'<\/span>\r\n\r\n    <span class=\"k\">class<\/span> <span class=\"nc\">Meta<\/span><span class=\"p\">:<\/span>\r\n        <span class=\"n\">constraints<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span>\r\n            <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">UniqueConstraint<\/span><span class=\"p\">(<\/span><span class=\"n\">fields<\/span><span class=\"o\">=<\/span><span class=\"p\">[<\/span><span class=\"s\">'id'<\/span><span class=\"p\">,<\/span> <span class=\"s\">'account_id'<\/span><span class=\"p\">],<\/span> <span class=\"n\">name<\/span><span class=\"o\">=<\/span><span class=\"s\">'unique_project_account'<\/span><span class=\"p\">)<\/span>\r\n        <span class=\"p\">]<\/span>\r\n\r\n<span class=\"k\">class<\/span> <span class=\"nc\">ProjectManager<\/span><span class=\"p\">(<\/span><span class=\"n\">TenantModel<\/span><span class=\"p\">):<\/span>\r\n    <span class=\"n\">project<\/span> <span class=\"o\">=<\/span> <span class=\"n\">TenantForeignKey<\/span><span class=\"p\">(<\/span>\r\n        <span class=\"n\">Project<\/span><span class=\"p\">,<\/span> <span class=\"n\">on_delete<\/span><span class=\"o\">=<\/span><span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">CASCADE<\/span><span class=\"p\">,<\/span> <span class=\"n\">related_name<\/span><span class=\"o\">=<\/span><span class=\"s\">\"projectmanagers\"<\/span>\r\n    <span class=\"p\">)<\/span>\r\n    <span class=\"n\">manager<\/span> <span class=\"o\">=<\/span> <span class=\"n\">TenantForeignKey<\/span><span class=\"p\">(<\/span><span class=\"n\">Manager<\/span><span class=\"p\">,<\/span> <span class=\"n\">on_delete<\/span><span class=\"o\">=<\/span><span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">CASCADE<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"n\">account<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">ForeignKey<\/span><span class=\"p\">(<\/span><span class=\"n\">Account<\/span><span class=\"p\">,<\/span> <span class=\"n\">on_delete<\/span><span class=\"o\">=<\/span><span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">CASCADE<\/span><span class=\"p\">)<\/span>\r\n\r\n    <span class=\"k\">class<\/span> <span class=\"nc\">TenantMeta<\/span><span class=\"p\">:<\/span>\r\n        <span class=\"n\">tenant_field_name<\/span> <span class=\"o\">=<\/span> <span class=\"s\">'account_id'<\/span>\r\n<\/code><\/pre>\n<\/div>\n<\/li>\n<li>Distribute tables by their tenant columns. You need to remove constraints before distribution, distribute tables via tenant_migrations.Distribute(), and reinstate the constraints.\n<div class=\"highlight\">\n<pre class=\"highlight python\" tabindex=\"0\"><code><span class=\"kn\">from<\/span> <span class=\"nn\">django_multitenant.db<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">migrations<\/span> <span class=\"k\">as<\/span> <span class=\"n\">tenant_migrations<\/span>\r\n\r\n<span class=\"k\">def<\/span> <span class=\"nf\">get_operations<\/span><span class=\"p\">():<\/span>\r\n    <span class=\"n\">operations<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[]<\/span>\r\n    <span class=\"n\">operations<\/span> <span class=\"o\">+=<\/span> <span class=\"p\">[<\/span>\r\n        <span class=\"n\">migrations<\/span><span class=\"p\">.<\/span><span class=\"n\">RunSQL<\/span><span class=\"p\">(<\/span>\r\n            <span class=\"s\">\"ALTER TABLE tests_country DROP CONSTRAINT tests_country_pkey CASCADE;\"<\/span>\r\n        <span class=\"p\">),<\/span>\r\n        <span class=\"n\">migrations<\/span><span class=\"p\">.<\/span><span class=\"n\">RunSQL<\/span><span class=\"p\">(<\/span>\r\n            <span class=\"s\">\"ALTER TABLE tests_manager DROP CONSTRAINT tests_manager_pkey CASCADE;\"<\/span>\r\n        <span class=\"p\">),<\/span>\r\n        <span class=\"n\">migrations<\/span><span class=\"p\">.<\/span><span class=\"n\">RunSQL<\/span><span class=\"p\">(<\/span>\r\n            <span class=\"s\">\"ALTER TABLE tests_project DROP CONSTRAINT tests_project_pkey CASCADE;\"<\/span>\r\n        <span class=\"p\">),<\/span>\r\n        <span class=\"n\">migrations<\/span><span class=\"p\">.<\/span><span class=\"n\">RunSQL<\/span><span class=\"p\">(<\/span>\r\n            <span class=\"s\">\"ALTER TABLE tests_projectmanager DROP CONSTRAINT tests_projectmanager_pkey CASCADE;\"<\/span>\r\n        <span class=\"p\">)<\/span>\r\n    <span class=\"p\">]<\/span>\r\n\r\n    <span class=\"n\">operations<\/span> <span class=\"o\">+=<\/span> <span class=\"p\">[<\/span>\r\n        <span class=\"n\">tenant_migrations<\/span><span class=\"p\">.<\/span><span class=\"n\">Distribute<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Country\"<\/span><span class=\"p\">,<\/span> <span class=\"n\">reference<\/span><span class=\"o\">=<\/span><span class=\"bp\">True<\/span><span class=\"p\">),<\/span>\r\n        <span class=\"n\">tenant_migrations<\/span><span class=\"p\">.<\/span><span class=\"n\">Distribute<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Account\"<\/span><span class=\"p\">),<\/span>\r\n        <span class=\"n\">tenant_migrations<\/span><span class=\"p\">.<\/span><span class=\"n\">Distribute<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Manager\"<\/span><span class=\"p\">),<\/span>\r\n        <span class=\"n\">tenant_migrations<\/span><span class=\"p\">.<\/span><span class=\"n\">Distribute<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Project\"<\/span><span class=\"p\">),<\/span>\r\n        <span class=\"n\">tenant_migrations<\/span><span class=\"p\">.<\/span><span class=\"n\">Distribute<\/span><span class=\"p\">(<\/span><span class=\"s\">\"ProjectManager\"<\/span><span class=\"p\">),<\/span>\r\n    <span class=\"p\">]<\/span>\r\n\r\n    <span class=\"n\">operations<\/span> <span class=\"o\">+=<\/span> <span class=\"p\">[<\/span>\r\n        <span class=\"n\">migrations<\/span><span class=\"p\">.<\/span><span class=\"n\">RunSQL<\/span><span class=\"p\">(<\/span>\r\n            <span class=\"s\">\"ALTER TABLE tests_country ADD CONSTRAINT tests_country_pkey PRIMARY KEY (id);\"<\/span>\r\n        <span class=\"p\">),<\/span>\r\n        <span class=\"n\">migrations<\/span><span class=\"p\">.<\/span><span class=\"n\">RunSQL<\/span><span class=\"p\">(<\/span>\r\n            <span class=\"s\">\"ALTER TABLE tests_project ADD CONSTRAINT tests_project_pkey PRIMARY KEY (account_id, id);\"<\/span>\r\n        <span class=\"p\">),<\/span>\r\n        <span class=\"n\">migrations<\/span><span class=\"p\">.<\/span><span class=\"n\">RunSQL<\/span><span class=\"p\">(<\/span>\r\n            <span class=\"s\">\"ALTER TABLE tests_manager ADD CONSTRAINT tests_manager_pkey PRIMARY KEY (account_id, id);\"<\/span>\r\n        <span class=\"p\">),<\/span>\r\n        <span class=\"n\">migrations<\/span><span class=\"p\">.<\/span><span class=\"n\">RunSQL<\/span><span class=\"p\">(<\/span>\r\n            <span class=\"s\">\"ALTER TABLE tests_projectmanager ADD CONSTRAINT tests_projectmanager_pkey PRIMARY KEY (account_id, id);\"<\/span>\r\n        <span class=\"p\">),<\/span>\r\n    <span class=\"p\">]<\/span>\r\n<\/code><\/pre>\n<\/div>\n<\/li>\n<\/ol>\n<p class=\"pg-section\">That\u2019s all the setup needed for models and migrations. Once the initial setup is done, you must call \u201cset_current_tenant\u201d somewhere in the code.<\/p>\n<p class=\"pg-section\">In the case of Django Rest Framework, our library interoperates to ensure effective calling of \u201cset_current_tenant\u201d and prevent any possibility of inter-tenant data leakage.<\/p>\n<p class=\"pg-section\">To complete our REST application, let\u2019s complete the integration steps:<\/p>\n<div id=\"continue-list\"><\/div>\n<ol start=\"4\">\n<li>Define a \u201cget_tenant\u201d method in a suitable location in the code. In this example, we use the views file\n<div class=\"highlight\">\n<pre class=\"highlight python\"><code><span class=\"kn\">from<\/span> <span class=\"nn\">django_multitenant<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">views<\/span>\r\n<span class=\"k\">def<\/span> <span class=\"nf\">tenant_func<\/span><span class=\"p\">(<\/span><span class=\"n\">request<\/span><span class=\"p\">):<\/span>\r\n    <span class=\"k\">return<\/span> <span class=\"n\">Account<\/span><span class=\"p\">.<\/span><span class=\"n\">objects<\/span><span class=\"p\">.<\/span><span class=\"nb\">filter<\/span><span class=\"p\">(<\/span><span class=\"n\">user<\/span><span class=\"o\">=<\/span><span class=\"n\">request<\/span><span class=\"p\">.<\/span><span class=\"n\">user<\/span><span class=\"p\">).<\/span><span class=\"n\">first<\/span><span class=\"p\">()<\/span>\r\n\r\n<span class=\"n\">views<\/span><span class=\"p\">.<\/span><span class=\"n\">get_tenant<\/span> <span class=\"o\">=<\/span> <span class=\"n\">tenant_func<\/span>\r\n<\/code><\/pre>\n<\/div>\n<\/li>\n<li>Construct the viewsets for the multi-tenant models using \u201cTenantModelViewSet.\n<div class=\"highlight\">\n<pre class=\"highlight python\"><code><span class=\"c1\"># views.py\r\n<\/span><span class=\"kn\">from<\/span> <span class=\"nn\">django_multitenant.views<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">TenantModelViewSet<\/span>\r\n\r\n<span class=\"k\">class<\/span> <span class=\"nc\">AccountViewSet<\/span><span class=\"p\">(<\/span><span class=\"n\">TenantModelViewSet<\/span><span class=\"p\">):<\/span>\r\n    <span class=\"s\">\"\"\"\r\n    API endpoint that allows groups to be viewed or edited.\r\n    \"\"\"<\/span>\r\n\r\n    <span class=\"n\">model_class<\/span> <span class=\"o\">=<\/span> <span class=\"n\">Account<\/span>\r\n    <span class=\"n\">serializer_class<\/span> <span class=\"o\">=<\/span> <span class=\"n\">AccountSerializer<\/span>\r\n    <span class=\"n\">permission_classes<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span><span class=\"n\">permissions<\/span><span class=\"p\">.<\/span><span class=\"n\">IsAuthenticated<\/span><span class=\"p\">]<\/span>\r\n<\/code><\/pre>\n<\/div>\n<\/li>\n<li>Add MultitenantMiddleware into the Middleware list in your settings file\n<div class=\"highlight\">\n<pre class=\"highlight shell\"><code><span class=\"c\"># settings.py<\/span>\r\n\r\nMIDDLEWARE <span class=\"o\">=<\/span> <span class=\"o\">[<\/span>\r\n    ..\r\n    <span class=\"s2\">\"django_multitenant.middlewares.MultitenantMiddleware\"<\/span>,\r\n<span class=\"o\">]<\/span>\r\n<\/code><\/pre>\n<\/div>\n<\/li>\n<\/ol>\n<p class=\"pg-section\">Thats it. Now you have a running multitenant application. Here\u2019s the link to the fully functional sample application using django-multitenant. You can download the application and test all its features:<\/p>\n<p class=\"pg-section\"><a href=\"https:\/\/github.com\/gurkanindibay\/django-mt-examples\">https:\/\/github.com\/gurkanindibay\/django-mt-examples<\/a><\/p>\n<p class=\"pg-section\">For comprehensive guidance on how to use\u00a0<a href=\"https:\/\/github.com\/citusdata\/django-multitenant\">django-multitenant<\/a>, you can refer to its documentation available at the following link:\u00a0<a href=\"https:\/\/django-multitenant.readthedocs.io\/en\/stable\/\">https:\/\/django-multitenant.readthedocs.io\/en\/stable\/<\/a><\/p>\n<p class=\"pg-section\">It\u2019s important to note that this library is responsible for handling tenancy functions in the ORM. However, you will need to set the tenant by calling &#8220;set_current_tenant\u201d throughout your application controllers. If you fail to call it correctly, you may end up getting all tenant results in queries, and tenants won\u2019t be properly set in your insert\/update operations.<\/p>\n<p class=\"pg-section\">For more information on Multi-tenancy and Django-Multitenant, this\u00a0<a href=\"https:\/\/youtu.be\/RKSwjaZKXL0\">PyCon Canada presentation<\/a>\u00a0about \u201cScaling multi-tenant apps using the Django ORM and Postgres\u201d gives a good overview.<\/p>\n<h2>django-multitenant can simplify your multi-tenant SaaS app<\/h2>\n<p class=\"pg-section section-active\">In conclusion, building multi-tenant applications is a challenging task that requires careful planning and implementation. With the Django-multitenant library, you can simplify your multi-tenant development process by providing a flexible and scalable solution to manage and isolate tenant-specific data.<\/p>\n<p class=\"pg-section section-active\">Finally, we provided a practical example of how to use Django-multitenant in a real-world scenario, demonstrating its ease of use and flexibility.<\/p>\n<p class=\"pg-section section-active\">Overall, with its active development and support, django-multitenant is a valuable tool if you are looking to build scalable, secure, and maintainable multi-tenant applications on Postgres (and with Citus).<\/p>\n<h3>Further reading<\/h3>\n<p class=\"pg-section section-active\">Multi-tenancy is a very broad concept. If you need more information, please refer to the below resources. If you want to learn more about django-Multitenant , you can refer to the links below:<\/p>\n<ul class=\"pg-section section-active\">\n<li><a href=\"https:\/\/django-multitenant.readthedocs.io\/en\/stable\/\">Docs for django-multitenant<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/citusdata\/django-multitenant\">GitHub repo for django-multitenant<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/citusdata\/citus\">GitHub repo for Citus database aka distributed PostgreSQL<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>This post by Gurkan Indibay about the django-multitenant library was originally published on the Citus Open Source Blog.\u00a0\u00a0 If you\u2019re building a software application that serves multiple tenants, you may have already encountered the challenges of managing and isolating tenant-specific data. That\u2019s where the\u00a0django-multitenant library\u00a0comes in. This library, actively used since 2017 and now downloaded [&hellip;]<\/p>\n","protected":false},"author":118189,"featured_media":6067,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1838],"tags":[],"class_list":["post-6065","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-postgresql"],"acf":[],"blog_post_summary":"<p>This post by Gurkan Indibay about the django-multitenant library was originally published on the Citus Open Source Blog.\u00a0\u00a0 If you\u2019re building a software application that serves multiple tenants, you may have already encountered the challenges of managing and isolating tenant-specific data. That\u2019s where the\u00a0django-multitenant library\u00a0comes in. This library, actively used since 2017 and now downloaded [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/posts\/6065","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/users\/118189"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/comments?post=6065"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/posts\/6065\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/media\/6067"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/media?parent=6065"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/categories?post=6065"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/tags?post=6065"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}