# django-markdownfield

Django package providing a MarkdownField with automatic HTML rendering and sanitization.

## Architecture

**Dual-field pattern**: `MarkdownField` (editable source) + `RenderedMarkdownField` (read-only pre-rendered HTML).
Rendering happens in `MarkdownField.pre_save()` — markdown → HTML via `markdown` lib → sanitized via `bleach` → stored.

**Key files:**
- [markdownfield/models.py](markdownfield/models.py) — `MarkdownField`, `RenderedMarkdownField`
- [markdownfield/validators.py](markdownfield/validators.py) — `Validator` dataclass; presets: `VALIDATOR_STANDARD`, `VALIDATOR_CLASSY`, `VALIDATOR_NULL`
- [markdownfield/widgets.py](markdownfield/widgets.py) — `MDEWidget`, `MDEAdminWidget` (EasyMDE v2.14.0)
- [markdownfield/forms.py](markdownfield/forms.py) — `MarkdownFormField`
- [markdownfield/util.py](markdownfield/util.py) — external link processing (`format_link`, `blacklist_link`)
- [markdownfield/templates/markdownfield/widget.html](markdownfield/templates/markdownfield/widget.html) — EasyMDE widget init

**Settings used:**
- `SITE_URL` — for internal/external link detection
- `MARKDOWN_EXTENSIONS` / `MARKDOWN_EXTENSION_CONFIGS`
- `MARKDOWN_LINKIFY_BLACKLIST`

## Stack

- Python ≥3.10, Django ≥3.2
- Build: hatchling + uv; package name `django-markdownfield`, module `markdownfield`
- Version: set in `markdownfield/__init__.py` as `__version__`
- Key deps: `bleach[css]>=5.0.1`, `markdown`, `shortuuid`

## Release workflow

1. Bump `__version__` in [markdownfield/__init__.py](markdownfield/__init__.py)
2. Commit: `git commit -S -m "bump version to X.Y.Z"`
3. Tag: `git tag -s vX.Y.Z -m "vX.Y.Z"` (signed)
4. Push: `git push && git push --tags`
5. Build: `uv build` — produces `dist/django_markdownfield-X.Y.Z-*.whl` and `.tar.gz`
6. Publish: `uv publish` — uploads to PyPI (requires PyPI token in `UV_PUBLISH_TOKEN` or `~/.pypirc`)

## Known Issues

| Issue | Severity |
|-------|----------|
| Widget broken in formsets/inlines (#13, #38) — JS var names use hyphens | **High** |
| Django 5 admin CSS variable names changed (#37) | Medium |
| django-csp integration assumes specific library (#26) | Medium |
| `MDEAdminWidget` loads both `md.css` and `md_admin.css` (duplicate) | Medium |
| `bleach` unmaintained since 2023 (#25) | Medium |
| EasyMDE v2.14.0 bundled; v2.18.0 available (#27) | Low |
| Images stripped by linkify interaction (#21) — missing width/height | Low |

## Testing

No test suite exists. Quality enforced via pre-commit: ruff lint + ruff format.
Linting: max line length 120, ruff with Django/isort profile, single quotes.

## Code Style

- Single quotes in Python (ruff/flake8 convention)
- Minimal comments
- Max line length: 120
