Metadata-Version: 2.4
Name: django-micboard
Version: 26.7.15
Summary: Real-time multi-manufacturer wireless microphone monitoring for Django
Author: Django Micboard Contributors
License-Expression: AGPL-3.0-or-later
Project-URL: Homepage, https://github.com/justprosound/django-micboard
Project-URL: Documentation, https://django-micboard.readthedocs.io
Project-URL: Repository, https://github.com/justprosound/django-micboard.git
Project-URL: Bug Tracker, https://github.com/justprosound/django-micboard/issues
Project-URL: Changelog, https://github.com/justprosound/django-micboard/blob/main/CHANGELOG.md
Keywords: django,wireless,microphone,monitoring,shure,sennheiser,real-time
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 5.1
Classifier: Framework :: Django :: 5.2
Classifier: Framework :: Django :: 6.0
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.13
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Django<6.1,>=5.1
Requires-Dist: Pillow>=10.0.0
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.0
Requires-Dist: python-dateutil>=2.8
Provides-Extra: standard
Requires-Dist: djangorestframework; extra == "standard"
Requires-Dist: drf-spectacular; extra == "standard"
Requires-Dist: djangorestframework-camel-case; extra == "standard"
Requires-Dist: django-rest-knox; extra == "standard"
Requires-Dist: django-auth-adfs; extra == "standard"
Requires-Dist: django-guardian; extra == "standard"
Requires-Dist: django-unfold; extra == "standard"
Requires-Dist: django-htmx; extra == "standard"
Requires-Dist: pydantic; extra == "standard"
Requires-Dist: tenacity; extra == "standard"
Requires-Dist: django-environ; extra == "standard"
Requires-Dist: django-lifecycle; extra == "standard"
Requires-Dist: django-split-settings; extra == "standard"
Requires-Dist: django-csp; extra == "standard"
Requires-Dist: django-safemigrate; extra == "standard"
Requires-Dist: django-nh3; extra == "standard"
Requires-Dist: cryptography; extra == "standard"
Requires-Dist: httpx; extra == "standard"
Requires-Dist: huey<4,>=3.2; extra == "standard"
Requires-Dist: redis>=5; extra == "standard"
Requires-Dist: django-filter; extra == "standard"
Requires-Dist: django-constance; extra == "standard"
Requires-Dist: django-extensions; extra == "standard"
Requires-Dist: django-watchman; extra == "standard"
Requires-Dist: django-template-partials; extra == "standard"
Requires-Dist: drf-standardized-errors; extra == "standard"
Requires-Dist: sentry-sdk; extra == "standard"
Provides-Extra: tasks
Requires-Dist: huey<4,>=3.2; extra == "tasks"
Requires-Dist: redis>=5; extra == "tasks"
Provides-Extra: realtime
Requires-Dist: channels>=4.0; extra == "realtime"
Requires-Dist: channels-redis>=4.0; extra == "realtime"
Provides-Extra: shure
Requires-Dist: websockets>=12.0.0; extra == "shure"
Provides-Extra: api
Requires-Dist: django-filter>=23.0; extra == "api"
Provides-Extra: admin-ui
Requires-Dist: django-admin-rangefilter>=0.13.0; extra == "admin-ui"
Requires-Dist: django-admin-sortable2>=2.2.0; extra == "admin-ui"
Provides-Extra: import-export
Requires-Dist: django-import-export>=4.0; extra == "import-export"
Provides-Extra: admin-theme
Requires-Dist: django-unfold>=0.30.0; extra == "admin-theme"
Provides-Extra: audit
Requires-Dist: django-simple-history>=3.5.0; extra == "audit"
Provides-Extra: performance
Requires-Dist: django-redis>=5.4.0; extra == "performance"
Requires-Dist: hiredis>=3.0.0; extra == "performance"
Provides-Extra: security
Requires-Dist: django-cryptography>=1.1; extra == "security"
Provides-Extra: fixtures
Requires-Dist: PyYAML>=6.0.0; extra == "fixtures"
Provides-Extra: media
Requires-Dist: Pillow>=10.0.0; extra == "media"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-django>=4.5; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: pytest-timeout>=2.3; extra == "dev"
Requires-Dist: factory-boy>=3.2; extra == "dev"
Requires-Dist: pre-commit>=3.0; extra == "dev"
Requires-Dist: pre-commit-hooks>=6.0; extra == "dev"
Requires-Dist: django-safemigrate>=6.0; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Requires-Dist: mypy>=1.0; extra == "dev"
Requires-Dist: pathspec<1.2; extra == "dev"
Requires-Dist: django-stubs[compatible-mypy]; extra == "dev"
Requires-Dist: django-debug-toolbar; extra == "dev"
Requires-Dist: django-extensions>=3.2.0; extra == "dev"
Requires-Dist: types-channels>=4.0; extra == "dev"
Requires-Dist: types-redis>=4.3; extra == "dev"
Requires-Dist: types-python-dateutil>=2.8; extra == "dev"
Requires-Dist: bandit>=1.7; extra == "dev"
Requires-Dist: interrogate>=1.5; extra == "dev"
Requires-Dist: coverage>=7.0; extra == "dev"
Provides-Extra: docs
Requires-Dist: mkdocs>=1.6; extra == "docs"
Provides-Extra: all
Requires-Dist: djangorestframework; extra == "all"
Requires-Dist: drf-spectacular; extra == "all"
Requires-Dist: djangorestframework-camel-case; extra == "all"
Requires-Dist: django-rest-knox; extra == "all"
Requires-Dist: django-auth-adfs; extra == "all"
Requires-Dist: django-guardian; extra == "all"
Requires-Dist: django-htmx; extra == "all"
Requires-Dist: tenacity; extra == "all"
Requires-Dist: django-environ; extra == "all"
Requires-Dist: django-lifecycle; extra == "all"
Requires-Dist: django-split-settings; extra == "all"
Requires-Dist: django-csp; extra == "all"
Requires-Dist: django-nh3; extra == "all"
Requires-Dist: cryptography; extra == "all"
Requires-Dist: django-constance; extra == "all"
Requires-Dist: django-watchman; extra == "all"
Requires-Dist: django-template-partials; extra == "all"
Requires-Dist: drf-standardized-errors; extra == "all"
Requires-Dist: sentry-sdk; extra == "all"
Requires-Dist: huey<4,>=3.2; extra == "all"
Requires-Dist: redis>=5; extra == "all"
Requires-Dist: channels>=4.0; extra == "all"
Requires-Dist: channels-redis>=4.0; extra == "all"
Requires-Dist: websockets>=12.0.0; extra == "all"
Requires-Dist: django-filter>=23.0; extra == "all"
Requires-Dist: django-admin-rangefilter>=0.13.0; extra == "all"
Requires-Dist: django-admin-sortable2>=2.2.0; extra == "all"
Requires-Dist: django-import-export>=4.0; extra == "all"
Requires-Dist: django-unfold>=0.30.0; extra == "all"
Requires-Dist: django-simple-history>=3.5.0; extra == "all"
Requires-Dist: django-redis>=5.4.0; extra == "all"
Requires-Dist: hiredis>=3.0.0; extra == "all"
Requires-Dist: django-cryptography>=1.1; extra == "all"
Requires-Dist: PyYAML>=6.0.0; extra == "all"
Requires-Dist: Pillow>=10.0.0; extra == "all"
Requires-Dist: pytest>=7.0; extra == "all"
Requires-Dist: pytest-django>=4.5; extra == "all"
Requires-Dist: pytest-cov>=4.0; extra == "all"
Requires-Dist: pytest-timeout>=2.3; extra == "all"
Requires-Dist: factory-boy>=3.2; extra == "all"
Requires-Dist: pre-commit>=3.0; extra == "all"
Requires-Dist: pre-commit-hooks>=6.0; extra == "all"
Requires-Dist: django-safemigrate>=6.0; extra == "all"
Requires-Dist: ruff>=0.1.0; extra == "all"
Requires-Dist: mypy>=1.0; extra == "all"
Requires-Dist: pathspec<1.2; extra == "all"
Requires-Dist: django-stubs[compatible-mypy]; extra == "all"
Requires-Dist: django-debug-toolbar; extra == "all"
Requires-Dist: django-extensions>=3.2.0; extra == "all"
Requires-Dist: types-channels>=4.0; extra == "all"
Requires-Dist: types-redis>=4.3; extra == "all"
Requires-Dist: types-python-dateutil>=2.8; extra == "all"
Requires-Dist: bandit>=1.7; extra == "all"
Requires-Dist: interrogate>=1.5; extra == "all"
Requires-Dist: coverage>=7.0; extra == "all"
Requires-Dist: mkdocs>=1.6; extra == "all"
Dynamic: license-file

# django-micboard

**Real-time multi-manufacturer wireless microphone monitoring for Django.**

django-micboard is a community-driven, pre-production Django reusable app for monitoring wireless audio systems (Shure, Sennheiser, etc.) in real-time. It provides device discovery, telemetry, alerting, performer assignment, and multi-tenant/multi-location support with a manufacturer-agnostic plugin architecture.

- **License**: AGPL-3.0-or-later
- **Status**: Beta (pre-production)
- **Python**: 3.13+
- **Django**: 5.1 through 6.0

## Features

- **Multi-Manufacturer Support**: Plugin architecture for Shure System API, Sennheiser SSCv2, and more
- **Real-Time Updates**: Live device telemetry via WebSockets (Channels) or SSE polls
- **Device Lifecycle**: Automated discovery, registration, tracking, and movement auditing
- **Wireless Monitoring**: Battery levels, RF signal strength, audio levels, charging status
- **Location Management**: Buildings, rooms, zones with multi-site/multi-location support
- **Performer Assignment**: Map performers to devices with activity history
- **Alert System**: User-specific notification rules for battery, signal, offline events
- **Regulatory Compliance**: Frequency band coordination and domain auditing
- **Multi-Tenant Safe**: Optional MSP (Managed Service Provider) mode with organization isolation
- **Settings Registry**: Typed configuration at each definition's exact declared scope
- **Admin Interface**: Beautiful Unfold admin theme with advanced filtering and history tracking

## Installation

### For End Users (Using the App)

Add to your Django project:

```bash
uv add "django-micboard[standard,audit]"
```

Use `uv add "django-micboard[standard]"` without the optional history app, or
`uv add django-micboard` when only the core reusable app is needed.

In `settings.py`:

```python
import os

DEBUG = os.environ.get("DJANGO_DEBUG", "False").lower() == "true"

INSTALLED_APPS = [
    # ... Django core apps ...
    "micboard",
]

# Optionally, add these for enhanced features
INSTALLED_APPS += [
    "django.contrib.sites",  # For multi-site support
    "unfold",  # Modern admin theme
    "unfold.contrib.filters",  # Unfold date and datetime range filters
    "simple_history",  # Model change tracking
    "huey.contrib.djhuey",  # Native Huey Django integration
]

HUEY = {
    "huey_class": "huey.RedisHuey",
    "name": "micboard",
    "connection": {
        "url": os.environ.get("REDIS_URL", "redis://localhost:6379/1"),
    },
    "immediate": DEBUG,
}

# Configure Micboard
MICBOARD_CONFIG = {
    "SHURE_API_BASE_URL": os.environ.get(
        "MICBOARD_SHURE_API_BASE_URL", "https://localhost:10000"
    ),
    "SHURE_API_SHARED_KEY": os.environ.get("MICBOARD_SHURE_API_SHARED_KEY"),
    "SHURE_API_TIMEOUT": int(os.environ.get("MICBOARD_SHURE_API_TIMEOUT", "10")),
    "POLL_INTERVAL": 5,  # seconds
}

# Exact hostnames allowed for credential-bearing admin API-server checks.
MICBOARD_API_SERVER_ALLOWED_HOSTS = [
    host.strip()
    for host in os.environ.get("MICBOARD_API_SERVER_ALLOWED_HOSTS", "localhost").split(",")
    if host.strip()
]

# Optional: Enable multi-tenancy
MICBOARD_MULTI_SITE_MODE = True
MICBOARD_MSP_ENABLED = False  # or True for full MSP mode
MICBOARD_SITE_ISOLATION = "site"  # or "organization", "campus"
```

Micboard intentionally disables generic admin import and export. A host may install the
`import-export` extra only after defining request-aware resources that validate tenant ownership
for every transferred row and explicitly opting its own admin classes into those resources.

In MSP mode, Django model permissions are necessary but do not override membership roles:
`viewer` is read-only, `operator` changes performer assignments through the service-backed
assignment workflow, and `admin`/`owner` may mutate rows only in their exact organization and
campus scopes. Host-wide catalogs remain platform-superuser surfaces. Multi-site creation of an
unassigned performer is deliberately disabled until onboarding can bind the performer and first
tenant assignment atomically.

The built-in DisplayWall page renders one typed snapshot for its initial response, periodic HTML
refreshes, JSON consumers, and section fragments. Micboard ships a pinned local HTMX runtime for
offline and restrictive-CSP deployments. The base template still loads Bootstrap from
`cdn.jsdelivr.net`; hosts that block that origin can override the template with a local asset.

Add to your `urls.py`:

```python
from django.urls import include, path

urlpatterns = [
    path("micboard/", include("micboard.urls", namespace="micboard")),
    # ... other patterns ...
]
```

Run migrations:

```bash
uv run --no-sync python manage.py migrate
```

Host projects should commit their own app migrations and apply django-micboard's shipped
migrations through Django's normal `migrate` command.

Run the native Huey consumer with:

```bash
uv run --no-sync python manage.py run_huey
```

### For Development

> **CRITICAL: Environment & Dependency Management Policy**
>
> This project **strictly forbids** the use of `pip`, `pipx`, `poetry`, or Python's built-in `venv` for all environment and package management. **ALL** environments and dependencies must be set up and managed using [`uv`](https://github.com/astral-sh/uv) exclusively. Any documentation, code, or CI which references or suggests non-`uv` patterns must be updated or escalated to project maintainers for correction. See [CONTRIBUTING.md](CONTRIBUTING.md) for full enforcement and escalation procedure.
>
> Agents and automation are expected to enforce this pattern in all workflows.

1. **Clone the repository**:
   ```bash
   git clone https://github.com/justprosound/django-micboard.git
   cd django-micboard
   ```

2. **Create the uv-managed environment and install every supported extra**:
   ```bash
   uv sync --locked --all-extras
   ```

3. **Configure the shell environment** (the example project does not load `.env` files
   implicitly):
   ```bash
   export DJANGO_SECRET_KEY="local-development-only"
   export MICBOARD_SHURE_API_BASE_URL="https://localhost:10000"
   export MICBOARD_SHURE_API_SHARED_KEY="your-shared-key"
   ```

4. **Run the example project**:
   ```bash
   uv run --no-sync python manage.py migrate
   uv run --no-sync python manage.py createsuperuser
   uv run --no-sync python manage.py runserver
   ```

5. **Access the admin**:
   - http://localhost:8000/admin
   - Login with your superuser credentials

## Configuration

### Environment Variables

The package reads Django settings, not process environment variables directly. The host-settings
example above maps these variables into `MICBOARD_CONFIG`:

```bash
# Shure API
MICBOARD_SHURE_API_BASE_URL=https://shure-api.example.com:10000
MICBOARD_SHURE_API_SHARED_KEY=your-secret-key
MICBOARD_SHURE_API_TIMEOUT=10

# Restrict credential-bearing API server requests to explicit hostnames
MICBOARD_API_SERVER_ALLOWED_HOSTS=localhost,shure-api.example.com
```

Host projects may choose different variable names; set `MICBOARD_CONFIG` and the
`MICBOARD_*` Django feature flags explicitly in their settings module.

Authenticated manufacturer connections require HTTPS or WSS, and certificate verification is
mandatory. For an internal certificate authority, set `SSL_CERT_FILE` or `SSL_CERT_DIR` to the
trusted CA bundle before starting Django or Huey.

### Using the Configuration API

```python
from micboard.services.settings.settings_service import settings as micboard_settings

# Feature flags
if micboard_settings.msp_enabled:
    ...

# Get custom settings
timeout = micboard_settings.get("SHURE_API_TIMEOUT", default=10)

# Scoped settings use the same service
value = micboard_settings.get(
    "CUSTOM_KEY",
    organization=org,
    site=site,
    default="fallback",
)
```

See [micboard/ARCHITECTURE.md](micboard/ARCHITECTURE.md) for detailed architecture documentation.

## Plugin Architecture

Extend Micboard with manufacturer-specific plugins. Put each plugin in
`micboard/integrations/<code>/plugin.py`; `PluginRegistry` discovers it by module and class name.
For example, `micboard/integrations/acme/plugin.py` can contain:

```python
from typing import Any

from micboard.services.common.base.plugin import ManufacturerPlugin


class AcmePlugin(ManufacturerPlugin):
    @property
    def name(self) -> str:
        return "Acme"

    @property
    def code(self) -> str:
        return "acme"

    def get_client(self) -> object:
        return object()

    def get_devices(self) -> list[dict[str, Any]]:
        return []

    def get_device(self, device_id: str) -> dict[str, Any] | None:
        return None

    def get_device_channels(self, device_id: str) -> list[dict[str, Any]]:
        return []

    def transform_device_data(self, api_data: dict[str, Any]) -> dict[str, Any] | None:
        return dict(api_data)

    def is_healthy(self) -> bool:
        return True

    def check_health(self) -> dict[str, Any]:
        return {"status": "healthy"}
```

Load the class or an instance through the registry; no central registration file is required:

```python
from micboard.services.manufacturer.plugin_registry import PluginRegistry

plugin_class = PluginRegistry.get_plugin_class("acme")
plugin = PluginRegistry.get_plugin("acme", manufacturer=manufacturer)
```

## Testing

Run the test suite:

```bash
# All tests
uv run --no-sync pytest

# Specific test file
uv run --no-sync pytest tests/test_settings_diff_admin.py -v

# With coverage
just coverage

# Specific markers
uv run --no-sync pytest -m unit
uv run --no-sync pytest -m integration
uv run --no-sync pytest -m django_db
```

## Linting & Pre-commit

Use ruff and pre-commit to keep code quality consistent:

```bash
uv run --no-sync ruff check .
uv run --no-sync ruff format .
uv run --no-sync pre-commit run --all-files
```

## Release Notes

- Update CHANGELOG.md under [Unreleased] with notable changes.
- Run the **Prepare Release PR** workflow from `main`. Leave the version blank to use the current
  UTC calendar date automatically, or enter a `YY.MM.DD` override for a backfill.
- Release metadata reaches `main` through a protected pull request and required checks.
- The publication workflow builds the protected merge commit once, signs Sigstore provenance and
  SPDX SBOM attestations, verifies the sealed files through TestPyPI, and publishes with
  environment-bound PEP 740 attestations.
- Stable publication pauses for production-environment approval before PyPI. GitHub receives the
  exact registry-signed wheel, source archive, SPDX SBOM, publish attestations, and checksums in a
  draft-first release suitable for immutable-release enforcement.

## Development Workflow

> **Agent & Research Workflow Policy**
>
> - When you need to search programming documentation, always use the `context7` tools (see AGENTS.md Quick Reference).
> - If you are unsure how to implement or use a library, use `gh_grep` to search for up-to-date code examples from GitHub.

1. **Install pre-commit hooks**:
   ```bash
   uv run --no-sync pre-commit install
   ```

2. **Run linting/formatting**:
   ```bash
   uv run --no-sync ruff check . --fix
   uv run --no-sync ruff format .
   ```

3. **Type checking**:
   ```bash
   uv run --no-sync python -m mypy micboard
   ```

4. **Run tests before committing**:
   ```bash
   uv run --no-sync pytest
   uv run --no-sync pre-commit run --all-files
   ```

5. **Security checks**:
   ```bash
   uv run --no-sync bandit -r micboard -ll
   ```

## Important Notes on Migrations

⚠️ **CRITICAL**: This is a pre-production reusable app, but migration history is protected:

- **DO NOT** manually edit files in `micboard/migrations/`
- **DO NOT** run `makemigrations` carelessly
- **ONLY** create new migrations when schema changes are approved
- **NEVER** delete or modify existing migrations
- **ALWAYS** test migrations thoroughly before production deployment
- **USE** `uv run --no-sync python manage.py safemigrate` in production hosts configured with
  `django_safemigrate`

See [CONTRIBUTING.md](CONTRIBUTING.md) for details.

## Documentation

- [Architecture Guide](micboard/ARCHITECTURE.md) - System design and patterns
- [Contributing Guide](CONTRIBUTING.md) - Development process
- [Changelog](CHANGELOG.md) - Release notes
- [API Documentation](docs/) - Full reference

## Support & Contributing

- **Issues**: [GitHub Issues](https://github.com/justprosound/django-micboard/issues)
- **Discussions**: [GitHub Discussions](https://github.com/justprosound/django-micboard/discussions)
- **Contributing**: See [CONTRIBUTING.md](CONTRIBUTING.md)

## License

AGPL-3.0-or-later – This program is free software. See [LICENSE](LICENSE) for details.

**Note**: If you use this software in production, you may need to comply with AGPL licensing requirements, including making source code available to users.
