Giter VIP home page Giter VIP logo

django-easy-tenants's Introduction

easy-tenants

Tests codecov PyPI Version PyPI downloads

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

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.

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

INSTALLED_APPS = [
    ...,
    'easy_tenants',
]

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

yourapp/models.py

from django.db import models

class Customer(models.Model):
    ...

settings.py

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().

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.

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")

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

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:

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).

DEFAULT_FILE_STORAGE = 'easy_tenants.storage.TenantFileSystemStorage'

Running the example project

python manage.py migrate
python manage.py createsuperuser
python manage.py runserver

Access the page /admin/, create a Customer.

Motivation

django-tenant-schemas

django-tenants

django-scopes

django-easy-tenants's People

Contributors

cleitondelima avatar dependabot[bot] avatar eliasfank avatar joaogehlen91 avatar jramnai avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

django-easy-tenants's Issues

Only set tenant in TenantManager.bulk_create if tenant exists

Change from:

    def bulk_create(self, objs, *args, **kwargs):
        tenant = get_current_tenant()

        for obj in objs:
            setattr(obj, field_name, tenant)

        return super().bulk_create(objs, *args, **kwargs)

to

    def bulk_create(self, objs, *args, **kwargs):
        tenant = get_current_tenant()

        if tenant:
            for obj in objs:
                setattr(obj, field_name, tenant)

        return models.Manager.bulk_create(self, objs, *args, **kwargs)

My use-case is the use of bulk_create with tenant_context_disabled() where I control the tenant myself with the objects parsed to bulk_create.

Django Rest Support

Hello,
Please any example on how to use this package in Django Rest project?

Make TenantManager.bulk_create forward arguments

Change TenantManager.bulk_create from:

def bulk_create(self, objs):
    ...
    return super().bulk_create(objs)

to

def bulk_create(self, objs, *args, **kwargs):
    ...
    return super().bulk_create(objs, *args, **kwargs)

so all QuerySet.bulk_create arguments are forwarded.

In Django 3.2.3 a warning from the use of default_app_config

default_app_config is being removed in Django 4.1 and a new warning has shown up

RemovedInDjango41Warning: 'easy_tenants' defines default_app_config = 'easy_tenants.apps.EasyTenantsConfig'. Django now detects this configuration automatically. You can remove default_app_config.

https://github.com/CleitonDeLima/django-easy-tenants/blob/master/easy_tenants/__init__.py#L15

https://docs.djangoproject.com/en/dev/internals/deprecation/#deprecation-removed-in-4-1

Thanks for the great library.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.