django-easy-tenants


Namedjango-easy-tenants JSON
Version 0.9.6 PyPI version JSON
download
home_pageNone
SummaryEasy to create applications that use tenants in django
upload_time2025-07-17 19:37:32
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseNone
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage
            # easy-tenants

![Tests](https://github.com/CleitonDeLima/django-easy-tenants/workflows/Tests/badge.svg)
[![codecov](https://codecov.io/gh/CleitonDeLima/django-easy-tenants/branch/master/graph/badge.svg)](https://codecov.io/gh/CleitonDeLima/django-easy-tenants)
[![PyPI Version](https://img.shields.io/pypi/v/django-easy-tenants.svg)](https://pypi.org/project/django-easy-tenants/)
[![PyPI downloads](https://img.shields.io/pypi/dm/django-easy-tenants.svg)](https://img.shields.io/pypi/dm/django-easy-tenants.svg)


This is a Django app for managing multiple tenants on the same project
instance using a shared approach.


## Background

There are typically three solutions for solving the multitenancy problem:

1. Isolated Approach: Separate Databases. Each tenant has it’s own database.
2. Semi Isolated Approach: Shared Database, Separate Schemas.
One database for all tenants, but one schema per tenant.
3. 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 Django
](https://books.agiliq.com/projects/django-multi-tenant/en/latest/)

Below is a demonstration of the features in each approach for an application
with 5000 tenants.

Approach       | Number of DB | Number of Schemas | Django migration time | Public access
-------------- | ------------ | ----------------- | --------------------- | ---------------
Isolated       | 5000         | 5000              | slow (1/DB)           | No
Semi Isolated  | 1            | 5000              | slow (1/Schema)       | Yes
Shared         | 1            | 1                 | fast (1)              | Yes


## Installation
Assuming you have django installed, the first step is to install `django-easy-tenants`.
```bash
python -m pip install django-easy-tenants
```
Now you can import the tenancy module in your Django project.


## Setup
It 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.

Add `easy_tenants` to your `INSTALLED_APPS` on `settings.py`.

`settings.py`
```python
INSTALLED_APPS = [
    ...,
    'easy_tenants',
]
```

Create a model which will be the tenant of the application.

`yourapp/models.py`
```python
from django.db import models

class Customer(models.Model):
    ...
```

`settings.py`
```python
EASY_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 with `TenantManager()`.


```python
from django.db import models
from easy_tenants.models import TenantManager

class Product(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 use `TenantAwareAbstract` to implement the save method for you,
so when saving an object the tenant will be automatically defined.

```python
class Product(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 than `tenant` you can change it with a settings. (default is `"tenant"`)

```python
# models.py
class Product(TenantAwareAbstract):
    customer = models.ForeignKey(Customer, on_delete=models.CASCADE, editable=False)
    name = models.CharField(max_length=10)

    objects = TenantManager()

# settings.py
EASY_TENANTS_TENANT_FIELD = "customer"
```

To obtain the data for each tenant, it is necessary to define which tenant will be used:

```python
from easy_tenants import tenant_context

with tenant_context(customer):
    Product.objects.all()  # filter by customer
```

To 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:

```python
from django.http import HttpResponse
from easy_tenants import tenant_context

class TenantMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        customer = get_customer_by_request(request)

        if not customer:
            return HttpResponse("Select tenant")

        with tenant_context(customer):
            return self.get_response(request)
```

If you want to separate the upload files by tenant, you need to change the `DEFAULT_FILE_STORAGE`
configuration (only available for local files).

```python
DEFAULT_FILE_STORAGE = 'easy_tenants.storage.TenantFileSystemStorage'
```

## UniqueTenantConstraint
`UniqueTenantConstraint` is a custom Django constraint that ensures uniqueness of fields within the context of the current tenant. This is especially useful for multi-tenant applications, where you want to allow the same values to exist across different tenants, but enforce uniqueness within each tenant.

### How it works
This constraint automatically adds the tenant field to the list of fields being checked for uniqueness. That means, for example, two tenants can have products with the same name and SKU, but a single tenant cannot have duplicate products with the same name and SKU.

### Usage Example
Suppose you have a `Product` model and a `Tenant` model. You want to make sure that each product's `name` and `sku` combination is unique per tenant.

```python
from django.db import models

from easy_tenants.models import TenantAwareAbstract, TenantManager, UniqueTenantConstraint


class Product(TenantAwareAbstract):
    name = models.CharField(max_length=100)
    sku = models.CharField(max_length=50)
    price = models.DecimalField(max_digits=10, decimal_places=2)

    objects = TenantManager()

    class Meta:
        constraints = [
            UniqueTenantConstraint(
                fields=["name", "sku"],
                name="unique_product_name_sku_per_tenant"
            )
        ]
```

With this constraint, the following is possible:
- Tenant A can have a product with name "Shirt" and SKU "123".
- Tenant B can also have a product with name "Shirt" and SKU "123".
- But Tenant A cannot have two products with the same name "Shirt" and SKU "123".

### Notes
- The tenant field is automatically added to the uniqueness check, so you don't need to include it in the `fields` list.
- If the uniqueness constraint is violated within a tenant, a `ValidationError` will be raised.

## Running the example project
```bash
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
```
Access the page `/admin/`, create a `Customer`.

## Motivation
[django-tenant-schemas](https://github.com/bernardopires/django-tenant-schemas)

[django-tenants](https://github.com/tomturner/django-tenants)

[django-scopes](https://github.com/raphaelm/django-scopes)

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "django-easy-tenants",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": null,
    "author": null,
    "author_email": "Cleiton Lima <cleiton.limapin@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/3b/f4/12f54e482552539ea174def6f98cd1ffef29e5b90124cacdbb4016b0f27f/django_easy_tenants-0.9.6.tar.gz",
    "platform": null,
    "description": "# easy-tenants\n\n![Tests](https://github.com/CleitonDeLima/django-easy-tenants/workflows/Tests/badge.svg)\n[![codecov](https://codecov.io/gh/CleitonDeLima/django-easy-tenants/branch/master/graph/badge.svg)](https://codecov.io/gh/CleitonDeLima/django-easy-tenants)\n[![PyPI Version](https://img.shields.io/pypi/v/django-easy-tenants.svg)](https://pypi.org/project/django-easy-tenants/)\n[![PyPI downloads](https://img.shields.io/pypi/dm/django-easy-tenants.svg)](https://img.shields.io/pypi/dm/django-easy-tenants.svg)\n\n\nThis is a Django app for managing multiple tenants on the same project\ninstance using a shared approach.\n\n\n## Background\n\nThere are typically three solutions for solving the multitenancy problem:\n\n1. Isolated Approach: Separate Databases. Each tenant has it\u2019s own database.\n2. Semi Isolated Approach: Shared Database, Separate Schemas.\nOne database for all tenants, but one schema per tenant.\n3. Shared Approach: Shared Database, Shared Schema. All tenants share\nthe same database and schema. There is a main tenant-table, where all\nother tables have a foreign key pointing to.\n\nThis application implements the third approach,  which in our opinion,\nis the best solution for a large amount of tenants.\n\nFor more information: [Building Multi Tenant Applications with Django\n](https://books.agiliq.com/projects/django-multi-tenant/en/latest/)\n\nBelow is a demonstration of the features in each approach for an application\nwith 5000 tenants.\n\nApproach       | Number of DB | Number of Schemas | Django migration time | Public access\n-------------- | ------------ | ----------------- | --------------------- | ---------------\nIsolated       | 5000         | 5000              | slow (1/DB)           | No\nSemi Isolated  | 1            | 5000              | slow (1/Schema)       | Yes\nShared         | 1            | 1                 | fast (1)              | Yes\n\n\n## Installation\nAssuming you have django installed, the first step is to install `django-easy-tenants`.\n```bash\npython -m pip install django-easy-tenants\n```\nNow you can import the tenancy module in your Django project.\n\n\n## Setup\nIt is recommended to install this app at the beginning of a project.\nIn an existing project, depending on the structure of the models,\nthe data migration can be hard.\n\nAdd `easy_tenants` to your `INSTALLED_APPS` on `settings.py`.\n\n`settings.py`\n```python\nINSTALLED_APPS = [\n    ...,\n    'easy_tenants',\n]\n```\n\nCreate a model which will be the tenant of the application.\n\n`yourapp/models.py`\n```python\nfrom django.db import models\n\nclass Customer(models.Model):\n    ...\n```\n\n`settings.py`\n```python\nEASY_TENANTS_TENANT_MODEL = \"yourapp.Customer\"\n```\n\nYour models, which must have isolated data per tenant, we need to add the foreign field from the Customer model.\nand objects need to be replaced with `TenantManager()`.\n\n\n```python\nfrom django.db import models\nfrom easy_tenants.models import TenantManager\n\nclass Product(models.Model):\n    tenant = models.ForeignKey(Customer, on_delete=models.CASCADE, editable=False)\n    name = models.CharField(max_length=10)\n\n    objects = TenantManager()\n```\n\nIf you prefer you can use `TenantAwareAbstract` to implement the save method for you,\nso when saving an object the tenant will be automatically defined.\n\n```python\nclass Product(TenantAwareAbstract):\n    tenant = models.ForeignKey(Customer, on_delete=models.CASCADE, editable=False)\n    name = models.CharField(max_length=10)\n\n    objects = TenantManager()\n```\n\n\nIf your foreign field has a name other than `tenant` you can change it with a settings. (default is `\"tenant\"`)\n\n```python\n# models.py\nclass Product(TenantAwareAbstract):\n    customer = models.ForeignKey(Customer, on_delete=models.CASCADE, editable=False)\n    name = models.CharField(max_length=10)\n\n    objects = TenantManager()\n\n# settings.py\nEASY_TENANTS_TENANT_FIELD = \"customer\"\n```\n\nTo obtain the data for each tenant, it is necessary to define which tenant will be used:\n\n```python\nfrom easy_tenants import tenant_context\n\nwith tenant_context(customer):\n    Product.objects.all()  # filter by customer\n```\n\nTo 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:\n\n```python\nfrom django.http import HttpResponse\nfrom easy_tenants import tenant_context\n\nclass TenantMiddleware:\n    def __init__(self, get_response):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        customer = get_customer_by_request(request)\n\n        if not customer:\n            return HttpResponse(\"Select tenant\")\n\n        with tenant_context(customer):\n            return self.get_response(request)\n```\n\nIf you want to separate the upload files by tenant, you need to change the `DEFAULT_FILE_STORAGE`\nconfiguration (only available for local files).\n\n```python\nDEFAULT_FILE_STORAGE = 'easy_tenants.storage.TenantFileSystemStorage'\n```\n\n## UniqueTenantConstraint\n`UniqueTenantConstraint` is a custom Django constraint that ensures uniqueness of fields within the context of the current tenant. This is especially useful for multi-tenant applications, where you want to allow the same values to exist across different tenants, but enforce uniqueness within each tenant.\n\n### How it works\nThis constraint automatically adds the tenant field to the list of fields being checked for uniqueness. That means, for example, two tenants can have products with the same name and SKU, but a single tenant cannot have duplicate products with the same name and SKU.\n\n### Usage Example\nSuppose you have a `Product` model and a `Tenant` model. You want to make sure that each product's `name` and `sku` combination is unique per tenant.\n\n```python\nfrom django.db import models\n\nfrom easy_tenants.models import TenantAwareAbstract, TenantManager, UniqueTenantConstraint\n\n\nclass Product(TenantAwareAbstract):\n    name = models.CharField(max_length=100)\n    sku = models.CharField(max_length=50)\n    price = models.DecimalField(max_digits=10, decimal_places=2)\n\n    objects = TenantManager()\n\n    class Meta:\n        constraints = [\n            UniqueTenantConstraint(\n                fields=[\"name\", \"sku\"],\n                name=\"unique_product_name_sku_per_tenant\"\n            )\n        ]\n```\n\nWith this constraint, the following is possible:\n- Tenant A can have a product with name \"Shirt\" and SKU \"123\".\n- Tenant B can also have a product with name \"Shirt\" and SKU \"123\".\n- But Tenant A cannot have two products with the same name \"Shirt\" and SKU \"123\".\n\n### Notes\n- The tenant field is automatically added to the uniqueness check, so you don't need to include it in the `fields` list.\n- If the uniqueness constraint is violated within a tenant, a `ValidationError` will be raised.\n\n## Running the example project\n```bash\npython manage.py migrate\npython manage.py createsuperuser\npython manage.py runserver\n```\nAccess the page `/admin/`, create a `Customer`.\n\n## Motivation\n[django-tenant-schemas](https://github.com/bernardopires/django-tenant-schemas)\n\n[django-tenants](https://github.com/tomturner/django-tenants)\n\n[django-scopes](https://github.com/raphaelm/django-scopes)\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Easy to create applications that use tenants in django",
    "version": "0.9.6",
    "project_urls": {
        "Homepage": "https://github.com/CleitonDeLima/django-easy-tenants",
        "Repository": "https://github.com/CleitonDeLima/django-easy-tenants"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "8c1181703cd7b5a6b94c2b226c1a242e2028be85a291a750cb80ecc5e6b5c0c4",
                "md5": "77c377715b3e902172a615d4d7797260",
                "sha256": "f68359d240f4a97351d32ce5b6777dc5519c1570cfac81941b104db87f70852b"
            },
            "downloads": -1,
            "filename": "django_easy_tenants-0.9.6-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "77c377715b3e902172a615d4d7797260",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 9518,
            "upload_time": "2025-07-17T19:37:31",
            "upload_time_iso_8601": "2025-07-17T19:37:31.581486Z",
            "url": "https://files.pythonhosted.org/packages/8c/11/81703cd7b5a6b94c2b226c1a242e2028be85a291a750cb80ecc5e6b5c0c4/django_easy_tenants-0.9.6-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "3bf412f54e482552539ea174def6f98cd1ffef29e5b90124cacdbb4016b0f27f",
                "md5": "52c996b0c64c3fb16bac601bff013f53",
                "sha256": "57b8b4e50b8fe398f7dd739e5ee62700d5761c7fc31b25c9fe1f120de19314b3"
            },
            "downloads": -1,
            "filename": "django_easy_tenants-0.9.6.tar.gz",
            "has_sig": false,
            "md5_digest": "52c996b0c64c3fb16bac601bff013f53",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 10028,
            "upload_time": "2025-07-17T19:37:32",
            "upload_time_iso_8601": "2025-07-17T19:37:32.402196Z",
            "url": "https://files.pythonhosted.org/packages/3b/f4/12f54e482552539ea174def6f98cd1ffef29e5b90124cacdbb4016b0f27f/django_easy_tenants-0.9.6.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-17 19:37:32",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "CleitonDeLima",
    "github_project": "django-easy-tenants",
    "travis_ci": false,
    "coveralls": true,
    "github_actions": true,
    "tox": true,
    "lcname": "django-easy-tenants"
}
        
Elapsed time: 1.09698s