# django-multitenant-middleware

`django-multitenant-middleware` is a Django package that provides multi-tenant support for both **HTTP** and **WebSocket (Channels)** requests. It resolves tenants dynamically based on subdomains, hostnames, or custom headers.

---

## Installation

```bash
pip install django-multitenant-middleware
```

---

## Settings Configuration (HTTP)

1. Define your tenant model in `settings.py`:

```python
CHANNELS_MULTITENANT_TENANT_MODEL = "tenants.Client"

# Optional: Custom resolver class and args
CHANNELS_MULTITENANT_RESOLVER_CLASS = None  # defaults to FlexibleSubdomainTenantResolver
CHANNELS_MULTITENANT_RESOLVER_ARGS = {"base_domain": BASE_DOMAIN}
```

2. Add the middleware to your Django `MIDDLEWARE` list:

```python
MIDDLEWARE = [
    # Other middleware...
    "django_multitenant_middleware.http_middleware.TenantHTTPMiddleware",
]
```

> The middleware automatically sets `request.tenant` and `request.tenant_context`.

---

## WebSocket Integration (ASGI)

1. In `asgi.py`:

```python
import os
import django
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application

from django_multitenant_middleware.ws_middleware import TenantWebSocketMiddleware
from django_multitenant_middleware.resolvers import FlexibleSubdomainTenantResolver
from tenants.models import Client
from core.websocket.routing import websocket_urlpatterns
```

2. Setup ASGI:

```python
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
django.setup()
django_asgi_app = get_asgi_application()
```

3. Configure tenant resolver:

```python
resolver = FlexibleSubdomainTenantResolver(
    base_domain="app.example.com",  # your base domain
    separators=("-", ".")
)
```

4. Wrap WebSocket routes:

```python
application = ProtocolTypeRouter({
    "http": django_asgi_app,
    "websocket": TenantWebSocketMiddleware(
        AuthMiddlewareStack(
            URLRouter(websocket_urlpatterns)
        ),
        tenant_model=Client,  # your tenant model
        resolver=resolver,
    ),
})
```

---

## Tenant Resolvers

* **FlexibleSubdomainTenantResolver**: Extracts tenant from subdomain prefixes
  Example: `tenant1-app.example.com → tenant1`

* **HeaderTenantResolver**: Extracts tenant from a custom HTTP header
  Example: `X-Tenant-ID: tenant1 → tenant1`

* **Custom Resolver**: Subclass `BaseTenantResolver` to implement your logic

```python
from channels_multitenant.resolvers import BaseTenantResolver

class MyCustomResolver(BaseTenantResolver):
    def resolve(self, scope) -> str | None:
        # Implement your custom logic
        return "my_tenant"
```

---

## TenantFetcher (Optional Helper)

`TenantFetcher` provides async caching for WebSocket tenants:

```python
from channels_multitenant.tenant_fetcher import TenantFetcher
from tenants.models import Client

fetcher = TenantFetcher(Client)
tenant = await fetcher.get_tenant("tenant1")
```

* Caches tenants (default 5 minutes)
* Async-ready for Channels

---

## Notes

* Ensure `CHANNELS_MULTITENANT_TENANT_MODEL` points to your tenant model.
* Missing or invalid settings will raise `ImproperlyConfigured`.
* Compatible with Django 4.x+ and Channels 3.x+.
