Metadata-Version: 2.4
Name: django-blog-core
Version: 0.1.0
Summary: Reusable multi-blog Django app: Blog, Section, Article with draft/schedule/publish, feeds, hookset, and optional Markdown/sanitize extras.
Author-email: DLRSP <dlrsp.dev@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/DLRSP/django-blog-core
Project-URL: Documentation, https://dlrsp.github.io/django-blog-core/
Project-URL: Repository, https://github.com/DLRSP/django-blog-core
Project-URL: Issues, https://github.com/DLRSP/django-blog-core/issues
Project-URL: Changelog, https://github.com/DLRSP/django-blog-core/blob/main/CHANGELOG.rst
Project-URL: Funding, https://github.com/sponsors/DLRSP
Keywords: django,blog,articles,cms,syndication,rss,atom,markdown,multi-blog
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 3.2
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.2
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Django>=3.2
Provides-Extra: markdown
Requires-Dist: markdown>=3.5; extra == "markdown"
Provides-Extra: sanitize
Requires-Dist: bleach>=6.0; extra == "sanitize"
Provides-Extra: search
Provides-Extra: tags
Provides-Extra: social
Provides-Extra: images
Provides-Extra: all
Requires-Dist: markdown>=3.5; extra == "all"
Requires-Dist: bleach>=6.0; extra == "all"
Provides-Extra: testing
Requires-Dist: coverage; extra == "testing"
Requires-Dist: codecov; extra == "testing"
Requires-Dist: pytest; extra == "testing"
Requires-Dist: pytest-django; extra == "testing"
Requires-Dist: markdown>=3.5; extra == "testing"
Requires-Dist: bleach>=6.0; extra == "testing"
Provides-Extra: docs
Requires-Dist: mkdocs>=1.5; extra == "docs"
Requires-Dist: mkdocs-material>=9.0; extra == "docs"
Requires-Dist: pymdown-extensions>=10.0; extra == "docs"
Requires-Dist: mkdocs-git-revision-date-plugin>=2.0; extra == "docs"
Provides-Extra: linting
Requires-Dist: flake8; extra == "linting"
Requires-Dist: flake8-pyproject; extra == "linting"
Requires-Dist: flake8-bugbear; extra == "linting"
Requires-Dist: flake8-comprehensions; extra == "linting"
Requires-Dist: flake8-tidy-imports; extra == "linting"
Requires-Dist: flake8-typing-imports; extra == "linting"
Requires-Dist: pylint; extra == "linting"
Dynamic: license-file

# django-blog-core

[![CI/CD](https://github.com/DLRSP/django-blog-core/actions/workflows/ci.yaml/badge.svg)](https://github.com/DLRSP/django-blog-core/actions/workflows/ci.yaml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

Reusable **multi-blog** Django app: `Blog`, optional `Section`, and `Article`
with draft / scheduled / published / archived workflow, RSS + Atom feeds,
a Pinax-style **hookset**, secret preview URLs, and optional Markdown / HTML
sanitize extras.

> Status: **0.1.0** — publish-ready core. Extras `[search]`, `[tags]`,
> `[social]`, `[images]` are documented stubs.

## Install

```bash
pip install django-blog-core
pip install "django-blog-core[markdown]"   # Markdown bodies
pip install "django-blog-core[sanitize]"   # bleach HTML policy
pip install "django-blog-core[all]"        # markdown + sanitize
```

## Quickstart

```python
# settings.py
INSTALLED_APPS = [
    # ...
    "blog_core",
]

BLOG_CORE = {
    "PAGINATE_BY": 12,
    "SANITIZE_HTML": True,
    # "HOOKSET": "myapp.hooks.BlogHookSet",
}
# Or: BLOG_CORE_HOOKSET = "myapp.hooks.BlogHookSet"
```

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

urlpatterns = [
    path("blog/", include("blog_core.urls")),
]
```

```bash
python manage.py migrate
python manage.py publish_scheduled_articles   # cron / host scheduler
```

Create a `Blog` and `Article` in admin. Public list/detail live at
`/blog/<blog-slug>/` and `/blog/<blog-slug>/<article-slug>/`. Feeds:

- `/blog/<blog-slug>/feed/rss/`
- `/blog/<blog-slug>/feed/atom/`

Preview drafts with the secret URL from the admin “Preview” column.

### Public visibility

`Article.objects.published()` (alias `public()`) is the single source of truth:

`state == "published"` **and** `publish_at <= now`.

### Hookset

Subclass `blog_core.hookset.DefaultBlogHookSet` to customize absolute URLs,
who may view non-public articles, body rendering, and feed titles — without
forking the package. Hooksets must not widen the public queryset.

### HTML / Markdown safety

- Markdown → HTML via the `[markdown]` extra.
- HTML (and Markdown output) is sanitized with bleach when `[sanitize]` is
  installed and `BLOG_CORE["SANITIZE_HTML"]` is true (default).
- Without bleach, HTML is escaped rather than passed through.
- Set `ALLOW_RAW_HTML = True` only if you accept XSS risk. See `docs/sanitize.md`.

### Extras

| Extra | Purpose |
|-------|---------|
| `[markdown]` | Markdown body rendering |
| `[sanitize]` | bleach allow-list |
| `[search]` | stub — Haystack/`published()` index patterns |
| `[tags]` | stub — tagging integration |
| `[social]` | stub — share matrix |
| `[images]` | stub — media attachments |

## Development

```bash
pip install -e ".[testing]"
pytest
# or: tox -e py313-django52
```

## License

MIT — see [LICENSE](LICENSE).
