Skip to main content

Django Shared Schema Multi Tenant

Project description

Multi Tenant App for Django

Shared schema multi tenant system for Django.

Setup

pip install accrete

  • Add 'accrete' to INSTALLED_APPS

    # settings.py
    
    INSTALLED_APPS = [
        ...
        'accrete',
        'django...',
    ]
    
  • Add accrete.middleware.TenantMiddleware after the auth middleware

    # settings.py
    
    MIDDLEWARE = [
        ...
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'accrete.middleware.TenantMiddleware',
        ...
    ]
    
  • Run migrations

Overview

Tenants and their Members are separated by a ForeignKey to accrete.models.Tenant and authenticated by request parameters and headers. On Models inheriting from accrete.modeles.TenantModel and Forms/ModelForms inheriting from accrete.form.(Model)Form, querysets are filtered automatically. The authenticated tenant and member are stored in a contextvar and can be retrieved and set from anywhere in the code.

Models

accrete.models.TenantModel

Abstract Base Model that links to accrete.models.Tenant.
Sets the objects attribute to accrete.managers.TenantManager.
Adds safety and integrity checks to the save method.

Inherit form this model to enable tenant separation.

# models.py

from accrete.models import TenantModel

class MyModel(TenantModel):
    ...

accrete.models.Tenant

Model representing a tenant. The active tenant for a request/process is stored in a contexvar and can be retrieved and set by accrete.tenant.get_tenant and accrete.tenant.set_tenant respectively.

accrete.models.Member

This model links the user model to the tenant model, allowing users to be associated with multiple tenants. The member, like the tenant, is authenticated by the accrete.middleware.TenantMiddleware and stored in a contextvar.
The active member can be retived by accrete.tenant.get_mamber and set by accrete.tenant.set_member. Using set_member also sets the appropriate tenant.

accret.models.AccessGroup

Arbitrary authorization groups that can be set on tenant or members.
These groups are checked on views decorated by accrete.decorator.tenant_reuired or subclassed from accrete.mixins.TenantRequiredMixin.

Manager

accrete.managers.TenantManager

Filters QuerySets by the active Tenant.
Ensures the active tenant is set on the instances on bulk operations.

Middleware

tenant.middleware.TenantMiddleware

This Middleware adds the Tenant(request.tenant) and Member(request.member) attributes to the request object.
On responses the X-TENANT-ID header and tenant_id url parameter is added with the active Tenant-ID as the value.

If the user is a member of multiple tenants, the request is parsed for a tenant_id in this order.

  • The "tenant_id" URL Parameter in the GET data
  • The Header X-TENANT-ID

If no tenant could be assigned the two attributes are set to None.
Additionally, the user is checked for membership of the found tenant. If the user has is_staff set to True, the tenant is set regardless of membership.

The Middleware must be added to the MIDDLEWARE setting after your authentication Middleware as it needs access to request.user.is_authenticated().

Views

tenant.views.TenantRequiredMixin

Adds tenant and optionally access right checks to the dispatch method.
This Mixin is meant as a substitute to django.contrib.auth.mixins.LoginRequiredMixin as TenantRequiredMixin inherits LoginRequiredMixin.

tenant.decorator.tenant_required

Substitute for django.contrib.auth.decorators.login_required
Checks if a tenant is set on the request and redirects to the TENANT_NOT_SET_URL specified in the settings.

The decorator itself is wrapped by login_required and can pass the arguments redirect_field_name and login_url to login_required.

Forms

accrete.forms.Form

Form class that filters the queryset of every field with a queryset attribute.

accrete.forms.ModelForm

Same behavior as tenant.forms.Form. Additionally, sets the tenant on the instance on save if needed.

Fields

accrete.fields.TranslatedCharField

Extends JsonField to store strings in different languages with the language code as the key.
By default, the language specified in settings.LANGUAGE_CODE is used to store values.

from django.db import models
from django.utils import translation
from accrete.utils.models import translated_db_value


class MyModel(models.Model):
    name = TranslatedCharField(...)

    
# Create instance with the language set in settings.LANGUAGE_CODE
instance = MyModel(name='Name in default language')
instance.save()

# Switch to german
translation.activate('de-de')
instance.name = 'Name auf Deutsch'
instance.save()

# Switch back to english
translation.activate('en-us')
instance.refresh_from_db()
print(instance.name)
>>> 'Name in default language'

# Utility to get the saved json as a dict
print(translated_db_value(instance, 'name'))
>>> {'en-us': 'Name in default language', 'de-de': 'Name auf Deutsch'}

# Create an instance with multiple translations at once
translation.activate('de-de')
instance = MyModel(name={'en-us': 'Name in default language', 'de-de': 'Name auf Deutsch'})
instance.save()
print(instance.name)
>>> 'Name auf Deutsch'

Channels

accrete.channels.TenantMiddleware

Support for django-channels. Sets the tenant and member contextvar.
Must be inside auth middleware.

# asgi.py

application = ProtocolTypeRouter({
    "http": django_asgi_app,
    "websocket": AllowedHostsOriginValidator(
        AuthMiddlewareStack(
            TenantMiddleware(
                URLRouter([
                    path('path/to/consumer/', Consumer.as_asgi()),
                ])
            )
        )
    ),
})

Consumer

accrete.consumer.WebsocketTenantConsumer

Checks on connect if a tenant is set.

accrete.consumer.JsonWebsocketTenantConsumer

Checks on connect if a tenant is set.

Settings

  • ACCRETE_TENANT_NOT_SET_URL Redirect to this URL when no tenant could be set for an authenticated user.

  • ACCRETE_GROUP_NOT_SET_URL
    Redirect to this URL when a tenant or member tries to access a URL without having the needed access rights.

Tenant Utils

Tenant

accrete.tenant.set_tenant(tenant: accrete.models.Tenant)

Stores the tenant in contextvar.

accrete.tenant.get_tenant

Retrieves the tenant from contextvar.

accrete.tenant.set_member(member: accrete.models.Member)

Stores the member and associated tenant contextvar.

accrete.tenant.set_member(member: accrete.models.Member)

Retrieves the member from contextvar.

accrete.tenant.unscoped()

Context Manager to temporally disable tenant isolation.

accrete.tenant.unscope()

Decorator to disable tenant isolation.

accrete.tenant.per_tenant(include: Q = None, exclude: Q = None)

Decorator to run the decorated function per tenant. Tenant to run on can be filtered by the include or exclude arguments.

Contrib

Additional Apps

App Description
country Countries with translatable names
log Global auditlog, runs at post_save signal
sequence Configurable sequences with support for subsequences per year.
system_mail Celery mail queue
ui User Interface based on HTMX, Alpine.js and Bulma
user Custom User model
user_registration User registration using system_mail to send confirmation mails

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

accrete-0.1.3.tar.gz (1.5 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

accrete-0.1.3-py3-none-any.whl (1.6 MB view details)

Uploaded Python 3

File details

Details for the file accrete-0.1.3.tar.gz.

File metadata

  • Download URL: accrete-0.1.3.tar.gz
  • Upload date:
  • Size: 1.5 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.0.1 CPython/3.12.3

File hashes

Hashes for accrete-0.1.3.tar.gz
Algorithm Hash digest
SHA256 b2b70fc2b02fa0e33e3dcfa62516691fc8f34b6a9ad9585911ea423c415e1192
MD5 2e72b9b22142a70a6a0af0f80d26a2eb
BLAKE2b-256 6e91722a0e28ddb74378bc692b29d26e4904e48b52908f92eb40d6289575b885

See more details on using hashes here.

File details

Details for the file accrete-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: accrete-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 1.6 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.0.1 CPython/3.12.3

File hashes

Hashes for accrete-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 e9c83c62f41be2525c1ebcd86b4a2cf233d888280346410983c424955c331513
MD5 90984d966e7d174f67df65d151f8f069
BLAKE2b-256 b10b74baeefb45d79b7dc9bcd2c2ee68011d5fd22988b72b76da3aebd570d4b2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page