django-multi-manifest-loader 0.1.3
Standalone template tag for loading webpack manifest files from multiple Django packages/apps
Requires Python: >=3.10
Keywords: django, webpack, manifest, cache-busting, static-files

# Django Multi-Manifest Loader

**A simple, standalone template tag for loading webpack manifest files from multiple Django packages/apps. Zero dependencies on unmaintained packages.**

## Problem

When using webpack with multiple Django packages (like `righttowork-check`, `criminalrecords-check`, etc.), each package generates its own `manifest.json` file with hashed asset filenames for cache busting. However, most manifest loaders only read a single manifest file, making it impossible to reference assets from installed packages.

## Solution

`django-multi-manifest-loader` provides a standalone template tag that:
1. Loads the main webpack manifest (from your dashboard/main app)
2. Automatically discovers and loads manifest files from all installed Django packages
3. **Namespaces manifests by app** to prevent naming collisions
4. **Auto-detects the current app** from template context for seamless usage
5. Makes all assets available via the `{% manifest %}` template tag
6. **Zero dependencies** - doesn't rely on unmaintained packages like `django-manifest-loader`

### Key Features

- **Automatic Namespacing**: Each app's manifest is namespaced, so `config.js` in `package1` doesn't conflict with `config.js` in `package2`
- **Smart Auto-Detection**: Automatically uses the correct app's assets based on which template is rendering
- **Explicit App Parameter**: Use `app='appname'` parameter when you need assets from a specific app
- **Fallback to Main**: If an asset isn't found in the current app, falls back to the main app's manifest

## Installation

```bash
pip install django-multi-manifest-loader
```

Or install from source:

```bash
cd django-multi-manifest-loader
pip install -e .
```

## Usage

### 1. Add to INSTALLED_APPS

In your `settings.py`:

```python
INSTALLED_APPS = [
    # ...
    'django_multi_manifest_loader',
    # ...
]
```

### 2. Configure (Optional)

In your `settings.py`:

```python
DJANGO_MULTI_MANIFEST_LOADER = {
    'cache': True,  # Enable caching (default: True in production, False in DEBUG)
    'main_app_name': 'dashboard',  # Name for main app manifests (default: 'main')
}
```

### 3. Use in Templates

The manifest tag automatically detects which app's template is rendering and loads the correct assets:

```django
{% load manifest %}

{# In package1/templates/package1/form.html #}
{# Automatically uses package1's manifest #}
<script src="{% manifest 'config.js' %}"></script>
{# Returns: package1/js/config.abc123.js #}

{# In package2/templates/package2/wizard.html #}
{# Automatically uses package2's manifest #}
<script src="{% manifest 'config.js' %}"></script>
{# Returns: package2/js/config.xyz789.js #}

{# Explicit app parameter - use assets from another app #}
<script src="{% manifest 'main.js' app='dashboard' %}"></script>
{# Always returns dashboard's main.js #}

{# If not found in current app, falls back to main app #}
<link href="{% manifest 'shared-styles.css' %}" rel="stylesheet">
```

## How It Works

The loader searches for manifest files in all installed Django apps and namespaces them by app:

1. **Discovery**: Finds `manifest.json` files in all Django apps using staticfiles finders
2. **Namespacing**: Each app's manifest is stored under its app name (e.g., `package1`, `package2`)
3. **Auto-Detection**: When rendering a template, extracts the app name from the template path (e.g., `package1/form.html` → `package1`)
4. **Resolution**:
   - First tries the current app's manifest
   - Falls back to the main app if not found
   - Supports explicit `app='appname'` parameter for cross-app asset references

**Result**: No naming collisions! Each app's `config.js` is kept separate.

## Package Manifest Example

In your Django package (e.g., `righttowork-check`), generate a manifest using webpack:

**webpack.config.js:**
```javascript
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');

module.exports = {
    output: {
        path: path.resolve(__dirname, 'static/righttoworkcheck/js/'),
        filename: '[name].[contenthash:8].js',
        publicPath: 'righttoworkcheck/js/'
    },
    plugins: [
        new WebpackManifestPlugin({
            fileName: '../manifest.json',
            publicPath: 'righttoworkcheck/js/',
        }),
    ],
};
```

This generates `static/righttoworkcheck/manifest.json`:
```json
{
  "custom/candidate/wizard/multiple-upload.js": "righttoworkcheck/js/custom/candidate/wizard/multiple-upload.dd215078.js"
}
```

## Configuration

All configuration is optional. Add to your `settings.py`:

```python
DJANGO_MULTI_MANIFEST_LOADER = {
    # Enable/disable manifest caching
    # Default: True in production (not DEBUG), False in DEBUG mode
    'cache': True,

    # Name for main app manifests (manifests not tied to a specific app)
    # Default: 'main'
    'main_app_name': 'dashboard',

    # Enable debug logging to see which manifests are loaded
    # Default: False
    'debug': False,
}
```

### Configuration Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `cache` | `bool` | `not DEBUG` | Cache merged manifests in memory. Disable in development for hot reloading. |
| `main_app_name` | `str` | `'main'` | Name used for main/shared manifests that aren't tied to a specific app. |
| `debug` | `bool` | `False` | Enable detailed logging of manifest loading process. |

## Development

### Setup Development Environment

```bash
# Clone the repository
cd django-multi-manifest-loader

# Create virtual environment
python3 -m venv .venv

# Activate virtual environment
source .venv/bin/activate  # Linux/Mac
# or
.venv\Scripts\activate  # Windows

# Install package with dev dependencies
pip install -e ".[dev]"
```

### Running Tests

```bash
# Run all tests
pytest

# Run with coverage report
pytest --cov=django_multi_manifest_loader --cov-report=term-missing

# Run specific test file
pytest tests/test_manifest_loader.py -v
```

### Code Quality

```bash
# Run all quality checks
flake8 django_multi_manifest_loader/ tests/
black --check django_multi_manifest_loader/ tests/
isort --check-only django_multi_manifest_loader/ tests/
ruff check django_multi_manifest_loader/ tests/

# Auto-format code
black django_multi_manifest_loader/ tests/
isort django_multi_manifest_loader/ tests/
ruff check --fix django_multi_manifest_loader/ tests/
```

### Clear Cache Programmatically

During development, you can clear the manifest cache:

```python
from django_multi_manifest_loader import ManifestLoader
ManifestLoader.clear_cache()
```

### Debug Mode

Enable debug mode to see detailed logging:

```python
# settings.py
DJANGO_MULTI_MANIFEST_LOADER = {
    'debug': True,
}
```

This will log:
- Number of manifest files found
- Path to each manifest being loaded
- Number of entries in each manifest
- Total merged entries

### Hot Reloading in Development

By default, caching is disabled in DEBUG mode (`cache=not DEBUG`). This means:
- **Production**: Manifests are cached for performance
- **Development**: Manifests are reloaded on each request for hot reloading

## Requirements

- Python >= 3.10
- Django >= 4.0

**No other dependencies!** This package is completely standalone.

## How It Works

The loader uses Django's `staticfiles` finders to discover manifest files:

1. **Main manifest**: Searches for `manifest.json` in static directories
2. **Package manifests**: Iterates through all `INSTALLED_APPS` and checks each for `<app_name>/manifest.json`
3. **Merging**: All found manifests are merged into a single dictionary (later entries override earlier ones)
4. **Caching**: Merged manifest is cached in memory (unless disabled)

## Publishing

### Automated Publishing to PyPI

The package automatically publishes to PyPI when you push a version tag:

```bash
# Update version in __init__.py
# Commit changes
git add django_multi_manifest_loader/__init__.py
git commit -m "Bump version to 0.2.0"

# Create and push tag
git tag v0.2.0
git push origin main --tags
```

This triggers the GitHub Actions workflow which:
1. Runs all tests across Python 3.10-3.12 and Django 4.2-5.1
2. Runs all linters (flake8, black, isort, ruff)
3. Builds the package
4. Publishes to PyPI using trusted publishing (no API tokens needed)

### Manual Publishing

```bash
# Build package
python -m build

# Check package
twine check dist/*

# Upload to PyPI (requires PyPI credentials)
twine upload dist/*
```

## License

MIT