Contributions

All contributions and third party integrations live inside django_pgschemas.contrib.

If you want to implement an integration with other Django packages, please submit a pull request containing:

  • The code for your integration.

  • The tests for your integration.

  • The docs for your integration in this section of the documentation.

We’re striving to maintain/increase our code coverage, but please, make sure your integration is properly tested. Proper tests will always beat meaningless 100% coverage.

Caching

In order to generate tenant aware cache keys, we provide django_pgschemas.contrib.cache.make_key which can be used as KEY_FUNCTION:

CACHES = {
    "default": {
        # ...
        "KEY_FUNCTION": "django_pgschemas.contrib.cache.make_key",
    }
}

Tenant aware file system storage

We provide a tenant aware file system storage at django_pgschemas.contrib.files.TenantFileSystemStorage. It subclasses django.core.storage.FileSystemStorage and behaves like it in every aspect, except that it prepends a tenant identifier to the path and URL of all files.

By default, the tenant identifier is the schema name of the current tenant. In order to override this behavior, it is possible to provide a different identifier. The storage will consider these options when looking for an identifier:

  • A method called schema_pathname in the current tenant. This method must accept no arguments and return an identifier.

  • A function specified in a setting called PGSCHEMAS_PATHNAME_FUNCTION. This function must accept a schema descriptor and return an identifier.

  • Finally, the identifier will default to the schema name of the current tenant.

In the case of the URL returned from the storage, if the storage detects that the current schema has been routed via subfolder, it won’t prepend the schema identifier, because it considers that the path is properly disambiguated as is. This means that instead of something like:

/tenant1/static/tenant1/path/to/file.txt

It will generate:

/tenant1/static/path/to/file.txt

This storage class is a convenient way of storing media files in a folder structure organized at the top by tenants, as well as providing a perceived tenant centric organization in the URLs that are generated. However, this storage class does NOT provide any form of security, such as controlling that from one tenant, files from another tenant are not accessible. Such security requirements have other implications that fall out of the scope of this basic utility.

Tip

In a project that requires airtight security, you might want to use and customize django-private-storage.

Channels (websockets)

We provide a tenant aware protocol router for using with channels. You can use it as follows:

# routing.py

from django_pgschemas.contrib.channels2 import TenantProtocolRouter

application = TenantProtocolRouter()

# settings.py

ASGI_APPLICATION = "routing.application"

It requires that you also route the websockets requests, at least for the dynamic tenants. If you don’t route websocket requests for static tenants, the dynamic route will be used:

TENANTS = {
    # ...
    "default": {
        # ...
        "URLCONF": "tenant_app.urls",
        "WS_URLCONF": "tenant_app.ws_urls",
    }
}

You still need to name your channel groups appropriately, taking the current tenant into account if you want to keep your groups tenant-specific. You will get the current tenant in scope["tenant"].

For Channels 3 to use with Django 3+, you can use django_pgschemas.contrib.channels3.

Attention

This module is NOT included in the test battery of the package. Please, create a GitHub issue for any errors you may find.