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.5.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.5-py3-none-any.whl (1.6 MB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: accrete-0.1.5.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.5.tar.gz
Algorithm Hash digest
SHA256 be989dd89b6aeaffe0f1f12145e2e6ce5f7f86e5ce21e2b978dc1eb4cbbb5174
MD5 0bc7f6b6df334c72ae503fe0e33dc87a
BLAKE2b-256 1aa715b00866864a2dc84c2ac8e338a5199435125d45efce54756b6803784a9b

See more details on using hashes here.

File details

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

File metadata

  • Download URL: accrete-0.1.5-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.5-py3-none-any.whl
Algorithm Hash digest
SHA256 8c409dda373140d48b39254c2cb65eb19f21a0d3a7b5e7d293660b98cc5d4636
MD5 59df7d4a91e2bcd9969309c630747bcf
BLAKE2b-256 4d35e8604e180fb42b6165dda12527b9aac2051587ced21e9ef1fd5f0f5631f8

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