entirius-django-contentdb 5.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- entirius_django_contentdb-5.0.0/.gitattributes +2 -0
- entirius_django_contentdb-5.0.0/.github/workflows/ci.yml +49 -0
- entirius_django_contentdb-5.0.0/.github/workflows/publish.yml +18 -0
- entirius_django_contentdb-5.0.0/.gitignore +33 -0
- entirius_django_contentdb-5.0.0/.license-header.txt +3 -0
- entirius_django_contentdb-5.0.0/.pre-commit-config.yaml +39 -0
- entirius_django_contentdb-5.0.0/AGENTS.md +295 -0
- entirius_django_contentdb-5.0.0/CLAUDE.md +1 -0
- entirius_django_contentdb-5.0.0/LICENSE +373 -0
- entirius_django_contentdb-5.0.0/Makefile +16 -0
- entirius_django_contentdb-5.0.0/PKG-INFO +74 -0
- entirius_django_contentdb-5.0.0/README.md +38 -0
- entirius_django_contentdb-5.0.0/docs/api-reference.md +1101 -0
- entirius_django_contentdb-5.0.0/docs/erd-config.yaml +47 -0
- entirius_django_contentdb-5.0.0/docs/models-reference.md +766 -0
- entirius_django_contentdb-5.0.0/pyproject.toml +78 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/__init__.py +0 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/admin.py +324 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/api/__init__.py +0 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/api/v2/__init__.py +0 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/api/v2/pagination.py +11 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/api/v2/urls.py +18 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/api/v2/views/__init__.py +0 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/api/v2/views/author_views.py +128 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/api/v2/views/navigation_views.py +41 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/apps.py +15 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/enums.py +16 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/filters/__init__.py +7 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/filters/attribute.py +14 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/filters/content.py +118 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/filters/content_set.py +13 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/filters/draft.py +71 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/filters/image.py +21 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/filters/published.py +72 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/filters/route.py +13 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/image_manager.py +90 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/management/__init__.py +0 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/management/commands/__init__.py +0 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/management/commands/images-optimize.py +31 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/management/commands/propagate_content_channels.py +47 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/management/commands/sync_contentdb_channels.py +16 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/management/commands/sync_contentdb_languages.py +20 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/migrations/0001_squashed_0007_draft_is_system.py +637 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/migrations/__init__.py +0 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/__init__.py +26 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/access_rights.py +13 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/activity_log.py +23 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/attribute.py +79 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/attribute_set.py +25 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/attribute_to_set.py +11 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/attribute_value.py +55 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/author.py +49 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/category.py +39 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/content.py +47 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/content_attribute.py +11 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/content_channel.py +23 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/content_set.py +18 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/content_type.py +22 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/content_type_permission.py +35 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/deleted.py +11 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/draft.py +27 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/draft_author.py +19 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/draft_co_author.py +19 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/draft_to_content_set.py +14 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/image.py +33 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/image_tag.py +17 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/image_to_tag.py +17 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/language.py +22 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/published.py +18 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/route.py +43 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/models/thumbnail.py +37 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/permissions.py +39 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/schemas/__init__.py +0 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/schemas/navigation.py +69 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/schemas/requests/__init__.py +0 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/schemas/requests/author.py +101 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/schemas/responses/__init__.py +0 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/schemas/responses/author.py +48 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/schemas/responses/navigation.py +15 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/__init__.py +15 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/attribute.py +45 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/attribute_set.py +15 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/attribute_value.py +45 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/author.py +45 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/category.py +63 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/content.py +110 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/content_channel.py +18 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/content_set.py +49 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/content_type.py +33 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/draft.py +192 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/fields/__init__.py +8 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/fields/author_brief.py +45 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/fields/category.py +27 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/fields/content_set.py +14 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/fields/createable_slug_related.py +18 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/fields/draft_content_set_members.py +28 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/fields/extended_base64_image.py +16 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/fields/language.py +16 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/fields/published_content_set_members.py +46 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/image.py +63 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/image_tag.py +16 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/language.py +13 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/published.py +72 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/serializers/route.py +15 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/services/__init__.py +0 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/services/author_service.py +217 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/services/navigation_service.py +118 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/services/sync_service.py +95 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/settings.py +12 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/signals.py +16 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/tasks.py +62 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/tests.py +135 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/urls.py +68 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/utils.py +384 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/__init__.py +0 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/__init__.py +15 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/attribute.py +29 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/attribute_set.py +25 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/attribute_value.py +46 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/categories.py +37 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/content_channel.py +21 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/content_set.py +35 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/content_type.py +29 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/draft.py +222 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/image.py +29 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/image_tag.py +21 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/language.py +27 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/published.py +94 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/route.py +23 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/view_content_permissions.py +41 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/admin/view_layout_permissions.py +41 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/public/__init__.py +14 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/public/attribute.py +27 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/public/attribute_set.py +23 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/public/attribute_value.py +44 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/public/author.py +41 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/public/categories.py +54 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/public/content_channel.py +19 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/public/content_set.py +33 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/public/content_type.py +27 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/public/draft.py +72 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/public/image.py +27 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/public/image_tag.py +19 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/public/language.py +25 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/public/published.py +121 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/views/public/route.py +21 -0
- entirius_django_contentdb-5.0.0/src/django_contentdb/viewsets.py +185 -0
- entirius_django_contentdb-5.0.0/tests/__init__.py +0 -0
- entirius_django_contentdb-5.0.0/tests/conftest.py +30 -0
- entirius_django_contentdb-5.0.0/tests/settings.py +71 -0
- entirius_django_contentdb-5.0.0/tests/test_author_api.py +494 -0
- entirius_django_contentdb-5.0.0/tests/test_author_model.py +228 -0
- entirius_django_contentdb-5.0.0/tests/test_author_service.py +371 -0
- entirius_django_contentdb-5.0.0/tests/test_channel_filters.py +83 -0
- entirius_django_contentdb-5.0.0/tests/test_content_channel.py +69 -0
- entirius_django_contentdb-5.0.0/tests/test_draft_channels.py +50 -0
- entirius_django_contentdb-5.0.0/tests/test_navigation_api.py +528 -0
- entirius_django_contentdb-5.0.0/tests/test_published_siblings.py +437 -0
- entirius_django_contentdb-5.0.0/tests/test_sync_service.py +68 -0
- entirius_django_contentdb-5.0.0/uv.lock +2045 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [develop]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master, develop]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v4
|
|
15
|
+
- run: uv sync --frozen --all-extras
|
|
16
|
+
- run: uv run ruff check .
|
|
17
|
+
- run: uv run ruff format --check .
|
|
18
|
+
- run: uv run pre-commit run insert-license --all-files
|
|
19
|
+
|
|
20
|
+
test:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
needs: lint
|
|
23
|
+
services:
|
|
24
|
+
postgres:
|
|
25
|
+
image: postgres:16
|
|
26
|
+
env:
|
|
27
|
+
POSTGRES_PASSWORD: postgres
|
|
28
|
+
ports: ['5432:5432']
|
|
29
|
+
options: >-
|
|
30
|
+
--health-cmd pg_isready --health-interval 5s --health-timeout 5s --health-retries 5
|
|
31
|
+
env:
|
|
32
|
+
DATABASE_URL: postgresql://postgres:postgres@localhost/test
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
- uses: astral-sh/setup-uv@v4
|
|
36
|
+
- run: uv sync --frozen --all-extras
|
|
37
|
+
- run: uv run pytest -x -q
|
|
38
|
+
|
|
39
|
+
secret-scan:
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v4
|
|
43
|
+
with:
|
|
44
|
+
fetch-depth: 0
|
|
45
|
+
- name: gitleaks
|
|
46
|
+
run: |
|
|
47
|
+
V=v8.30.0 # keep equal to the gitleaks rev in .pre-commit-config.yaml
|
|
48
|
+
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/${V}/gitleaks_${V#v}_linux_x64.tar.gz" | sudo tar -xz -C /usr/local/bin gitleaks
|
|
49
|
+
gitleaks git --redact -v
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi # GH environment bound to the PyPI trusted publisher
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write # OIDC - short-lived PyPI token, no stored secrets
|
|
13
|
+
contents: read
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v4
|
|
17
|
+
- run: uv build
|
|
18
|
+
- run: uv publish --trusted-publishing always
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
|
|
8
|
+
# Virtualenv / uv
|
|
9
|
+
.venv/
|
|
10
|
+
.python-version
|
|
11
|
+
|
|
12
|
+
# Django (test project artifacts)
|
|
13
|
+
db.sqlite3
|
|
14
|
+
db.sqlite3-journal
|
|
15
|
+
*.log
|
|
16
|
+
|
|
17
|
+
# Test / coverage
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
.coverage
|
|
20
|
+
htmlcov/
|
|
21
|
+
coverage.xml
|
|
22
|
+
|
|
23
|
+
# Tooling caches
|
|
24
|
+
.ruff_cache/
|
|
25
|
+
.mypy_cache/
|
|
26
|
+
|
|
27
|
+
# IDE / OS
|
|
28
|
+
.idea/
|
|
29
|
+
.vscode/
|
|
30
|
+
.DS_Store
|
|
31
|
+
|
|
32
|
+
# Secret scanning (forbidden-names blocklist - local symlink, never committed)
|
|
33
|
+
.gitleaks.toml
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
|
3
|
+
rev: v1.5.6
|
|
4
|
+
hooks:
|
|
5
|
+
- id: insert-license
|
|
6
|
+
files: \.py$
|
|
7
|
+
exclude: (^|/)__init__\.py$
|
|
8
|
+
args:
|
|
9
|
+
- --license-filepath
|
|
10
|
+
- .license-header.txt
|
|
11
|
+
- --comment-style
|
|
12
|
+
- "#"
|
|
13
|
+
|
|
14
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
15
|
+
rev: v0.15.20
|
|
16
|
+
hooks:
|
|
17
|
+
- id: ruff-check
|
|
18
|
+
args: [--fix]
|
|
19
|
+
- id: ruff-format
|
|
20
|
+
|
|
21
|
+
- repo: local
|
|
22
|
+
hooks:
|
|
23
|
+
- id: require-canonical-gitleaks-config
|
|
24
|
+
name: require canonical .gitleaks.toml (forbidden-names)
|
|
25
|
+
entry: bash -c 'grep -q "forbidden-names" .gitleaks.toml 2>/dev/null || { echo "Missing or non-canonical .gitleaks.toml - symlink the config per the internal secret-scanning standard"; exit 1; }'
|
|
26
|
+
language: system
|
|
27
|
+
always_run: true
|
|
28
|
+
pass_filenames: false
|
|
29
|
+
- id: gitleaks-config-untracked
|
|
30
|
+
name: .gitleaks.toml must stay gitignored
|
|
31
|
+
entry: bash -c '! git ls-files --error-unmatch .gitleaks.toml 2>/dev/null || { echo ".gitleaks.toml is tracked - add it to .gitignore"; exit 1; }'
|
|
32
|
+
language: system
|
|
33
|
+
always_run: true
|
|
34
|
+
pass_filenames: false
|
|
35
|
+
|
|
36
|
+
- repo: https://github.com/gitleaks/gitleaks
|
|
37
|
+
rev: v8.30.0
|
|
38
|
+
hooks:
|
|
39
|
+
- id: gitleaks
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
## Quick Reference
|
|
4
|
+
|
|
5
|
+
Content management module for the Volkanos ecommerce platform. JSON-based page content with draft/publish workflow, routing, access control, media management, and blog authoring. v1 API for content CRUD, v2 API for author management (Pydantic + drf-spectacular).
|
|
6
|
+
|
|
7
|
+
**Tech:** Python >=3.10, Django >=4.0, DRF, django-filter, Pillow, Pydantic, drf-spectacular, djangorestframework-simplejwt
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
| Command | Meaning |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `make install` | sync dependencies (uv, incl. extras) |
|
|
14
|
+
| `make check` | lint + format-check (ruff) |
|
|
15
|
+
| `make fix` | auto-fix lint + format |
|
|
16
|
+
| `make test` | test suite (pytest + pytest-django) |
|
|
17
|
+
|
|
18
|
+
## Conventions
|
|
19
|
+
|
|
20
|
+
- English only: code, docs, commits, branches, PRs.
|
|
21
|
+
- MPL-2.0: every non-trivial source file carries the license header (pre-commit inserts it).
|
|
22
|
+
- Toolchain: uv + ruff + hatchling + pytest; all config in `pyproject.toml`; `uv.lock` committed.
|
|
23
|
+
- Git flow: `master` (production) + `develop` (integration); changes land via PR; semver tag on `master`.
|
|
24
|
+
- Never rename the package / Django app_label / DB table prefix `django_contentdb` — it is a schema contract.
|
|
25
|
+
- Migrations are part of the public contract — never edit an already released migration.
|
|
26
|
+
- Default: do not commit — git is the user's call.
|
|
27
|
+
|
|
28
|
+
## Architecture
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
src/django_contentdb/
|
|
32
|
+
├── models/ # 26 ORM models (one file per entity)
|
|
33
|
+
│ ├── content.py # Content (UUID pk, JSONField body)
|
|
34
|
+
│ ├── content_type.py # ContentType (slug↑, is_layout_extender, supports_authors)
|
|
35
|
+
│ ├── content_channel.py # ContentChannel (idx↑, name, default_language FK, is_default)
|
|
36
|
+
│ ├── draft.py # Draft (FK→ContentType, O2O→Content, FK→Language, M2M→ContentChannel, M2M→Author)
|
|
37
|
+
│ ├── published.py # Published (FK→Draft, O2O→Content, immutable snapshot)
|
|
38
|
+
│ ├── route.py # Route (url↑, placement, M2M→Draft)
|
|
39
|
+
│ ├── author.py # Author (UUID, name, slug↑, role/bio/tag _t9n, photo FK→Image)
|
|
40
|
+
│ ├── draft_author.py # DraftAuthor (through: Draft↔Author, position)
|
|
41
|
+
│ ├── draft_co_author.py # DraftCoAuthor (through: Draft↔Author, position)
|
|
42
|
+
│ ├── attribute.py # Attribute (slug↑, 6 type flags, filter/search flags)
|
|
43
|
+
│ ├── attribute_set.py # AttributeSet (slug↑, M2M→Attribute)
|
|
44
|
+
│ ├── attribute_value.py # AttributeValue (typed value fields)
|
|
45
|
+
│ ├── attribute_to_set.py # AttributeToSet (M2M through)
|
|
46
|
+
│ ├── content_attribute.py # ContentAttribute (M2M through: Content↔AttributeValue)
|
|
47
|
+
│ ├── image.py # Image (UUID, HashedImageField, SHA256 dedup)
|
|
48
|
+
│ ├── thumbnail.py # Thumbnail (method, source→Image, unique per dims)
|
|
49
|
+
│ ├── image_tag.py # ImageTag (slug↑)
|
|
50
|
+
│ ├── image_to_tag.py # ImageToTag (M2M through)
|
|
51
|
+
│ ├── access_rights.py # AccessRights (access_level = PK, pk=1 = public) [DEPRECATED]
|
|
52
|
+
│ ├── language.py # Language (iso2↑, iso3↑, name_en, name_pl)
|
|
53
|
+
│ ├── category.py # Category (UUID, auto url_key from name)
|
|
54
|
+
│ ├── content_type_permission.py # ContentTypePermission (action×content_type unique)
|
|
55
|
+
│ ├── content_set.py # ContentSet (UUID, M2M→Draft)
|
|
56
|
+
│ ├── draft_to_content_set.py # DraftToContentSet (M2M through)
|
|
57
|
+
│ ├── activity_log.py # ActivityLog (GenericFK audit trail)
|
|
58
|
+
│ └── deleted.py # Deleted (O2O→Content, soft-delete audit)
|
|
59
|
+
│
|
|
60
|
+
├── services/ # Business logic
|
|
61
|
+
│ ├── sync_service.py # sync_channels_from_pim(), sync_languages_from_pim()
|
|
62
|
+
│ └── author_service.py # CRUD + reassignment + set_draft_authors
|
|
63
|
+
│
|
|
64
|
+
├── api/ # v2 API (Pydantic + drf-spectacular)
|
|
65
|
+
│ └── v2/
|
|
66
|
+
│ ├── views/author_views.py # AuthorViewSet (JWT + IsAdminUser)
|
|
67
|
+
│ ├── urls.py # /api/contentdb/v2/admin/authors/
|
|
68
|
+
│ └── pagination.py # AdminPageNumberPagination (20/page, max 100)
|
|
69
|
+
│
|
|
70
|
+
├── schemas/ # Pydantic schemas (v2 API)
|
|
71
|
+
│ ├── requests/author.py # CreateAuthorRequest, UpdateAuthorRequest, DeleteAuthorRequest
|
|
72
|
+
│ └── responses/author.py # AuthorResponse, AuthorBriefResponse, AuthorListResponse
|
|
73
|
+
│
|
|
74
|
+
├── views/ # DRF ViewSets (v1 admin + public mirrors)
|
|
75
|
+
│ ├── admin/ # 17 ViewSets + 2 function views (CRUD + publish)
|
|
76
|
+
│ └── public/ # 16 ViewSets (read-only)
|
|
77
|
+
│
|
|
78
|
+
├── serializers/ # DRF serializers (v1 API)
|
|
79
|
+
│ ├── content.py # ContentSerializer, DraftContentSerializer
|
|
80
|
+
│ ├── content_channel.py # ContentChannelSerializer
|
|
81
|
+
│ ├── published.py # PublishedContentSerializer
|
|
82
|
+
│ ├── attribute.py, attribute_value.py, attribute_set.py
|
|
83
|
+
│ ├── content_type.py, route.py, language.py, category.py
|
|
84
|
+
│ ├── image.py, image_tag.py, content_set.py
|
|
85
|
+
│ └── fields/ # Custom fields (AuthorBrief, CreateableSlugRelated, etc.)
|
|
86
|
+
│
|
|
87
|
+
├── filters/ # django-filter classes
|
|
88
|
+
│ ├── published.py # access_rights, channel, language, routes, author
|
|
89
|
+
│ ├── draft.py # access_rights, channel, language, routes, author
|
|
90
|
+
│ ├── content_set.py, image.py
|
|
91
|
+
│ └── ordering.py # sort by created_at
|
|
92
|
+
│
|
|
93
|
+
├── viewsets.py # Base classes: ContentDBModelViewSet, ROContentDBModelViewSet
|
|
94
|
+
├── permissions.py # ContentTypePermission (action-based per content_type)
|
|
95
|
+
├── enums.py # Action: CREATE, UPDATE, DELETE, PUBLISH, VIEW
|
|
96
|
+
├── settings.py # ADMIN_BASE_URL, PUBLIC_BASE_URL, THUMBNAIL_QUALITY, IMAGE_MAX_WIDTH
|
|
97
|
+
├── utils.py # StandardPagination, HashedImageField, DjangoAuth, exception_handler
|
|
98
|
+
├── image_manager.py # PIL utilities: resize, crop, transparency removal
|
|
99
|
+
├── management/commands/
|
|
100
|
+
│ ├── sync_contentdb_channels.py # Sync ContentChannels from PIM
|
|
101
|
+
│ ├── sync_contentdb_languages.py # Sync Languages from django_regional
|
|
102
|
+
│ └── propagate_content_channels.py # Assign channels to drafts with empty M2M
|
|
103
|
+
├── admin.py # Django admin (20 registered models, Author + DraftAuthor/DraftCoAuthor inlines)
|
|
104
|
+
├── urls.py # Admin + public routers (19 routes each)
|
|
105
|
+
└── tasks.py # Celery: image optimization
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Layer rule: v1 views use DRF serializers directly (no service layer for CRUD). v2 Author API uses Pydantic schemas + service layer. `viewsets.py` base classes auto-log to ActivityLog. Sync logic lives in `services/sync_service.py`, author CRUD in `services/author_service.py`.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Content Pipeline
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
Content (UUID pk, JSON blob: {tiles, sections, tiles_order, sections_order})
|
|
116
|
+
→ Draft (links ContentType + Content + Language + ContentChannel M2M)
|
|
117
|
+
→ Published (immutable snapshot of Draft + Content)
|
|
118
|
+
→ Route (URL path, M2M→Draft, placement: top|bottom)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
- **ContentType** defines the template: `static-page`, `header`, `blog-post`, `product-rich-content`, `category-rich-content`
|
|
122
|
+
- **ContentType.is_layout_extender** = true → header type (separate API routes)
|
|
123
|
+
- **ContentType.supports_authors** = true → enables author picker in CMS (only `blog-post` by default)
|
|
124
|
+
- **Author** — author profiles with name, slug, role/bio/tag translations, photo, contacts, social links
|
|
125
|
+
- **DraftAuthor / DraftCoAuthor** — through-tables linking Draft ↔ Author with position ordering
|
|
126
|
+
- **ContentChannel** — local mirror of PIM Channel (idx↑, name, default_language FK, is_default). Syncs from PIM via admin action or management command. Empty `Draft.channels` M2M = content is public (all channels).
|
|
127
|
+
- **AccessRights** — DEPRECATED. Replaced by ContentChannel. Kept for backward compatibility during transition.
|
|
128
|
+
- Content/Route `delete()` prevents deletion of `home` and `header` records
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## API
|
|
133
|
+
|
|
134
|
+
**Response wrapper** (all endpoints): `{"meta": {"status": "OK|CREATED|UPDATED|DELETED|ERROR"}, "data": ...}`
|
|
135
|
+
|
|
136
|
+
**Pagination:** `StandardPagination` — page_size=6, max=100, query param: `limit`
|
|
137
|
+
|
|
138
|
+
### Admin API — `/{ADMIN_BASE_URL}/contentdb/{version}/`
|
|
139
|
+
|
|
140
|
+
Default: `/api-admin/contentdb/v1/`. Auth: DjangoAuth + IsAdminUser | ContentTypePermission.
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
contentdb/v1/
|
|
144
|
+
├── attributes/ CRUD
|
|
145
|
+
│ └── {attribute_slug}/values/ CRUD (nested)
|
|
146
|
+
├── attribute-sets/ CRUD
|
|
147
|
+
├── content-types/ CRUD
|
|
148
|
+
├── routes/ CRUD
|
|
149
|
+
├── content-sets/ CRUD
|
|
150
|
+
├── content/{content_type}/ CRUD drafts
|
|
151
|
+
│ └── {uid}/published/ POST (publish), DELETE (unpublish)
|
|
152
|
+
├── published/{content_type}/ RO
|
|
153
|
+
├── layout-extender-types/ CRUD (is_layout_extender=True only)
|
|
154
|
+
├── layout-extender-sets/ CRUD
|
|
155
|
+
├── layout-extender/{content_type}/ CRUD layout drafts
|
|
156
|
+
├── layout-extender-published/... RO
|
|
157
|
+
├── images/ CRUD
|
|
158
|
+
├── image-tags/ CRUD
|
|
159
|
+
├── languages/ CRUD
|
|
160
|
+
├── channels/ RO (ContentChannel list/retrieve)
|
|
161
|
+
├── category/ CRUD
|
|
162
|
+
├── content-permissions/ FBV (manage per-type permissions)
|
|
163
|
+
└── layout-extender-permissions/ FBV
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Public API — `/{PUBLIC_BASE_URL}/contentdb/{version}/`
|
|
167
|
+
|
|
168
|
+
Default: `/api/contentdb/v1/`. Auth: AllowAny.
|
|
169
|
+
|
|
170
|
+
Same routes as admin but **read-only** (list + retrieve only). Key query:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
GET /api/contentdb/v1/published/{content_type_slug}/?routes=home&language=EN&channel=default
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Filters: `routes` (url), `language` (iso2), `channel` (ContentChannel idx), `author` (slug or UUID), `access_rights` (deprecated). Sort: `created_at` (maps to draft's created_at). Channel filter: returns content assigned to that channel + public content (empty channels M2M). Author filter: searches both authors and co-authors on drafts.
|
|
177
|
+
|
|
178
|
+
Full endpoint details: `docs/api-reference.md`
|
|
179
|
+
|
|
180
|
+
### v2 Admin API — /api/contentdb/v2/admin/
|
|
181
|
+
|
|
182
|
+
| Method | Endpoint | Purpose |
|
|
183
|
+
|--------|----------|---------|
|
|
184
|
+
| GET | /authors/ | List authors (search, is_active, channel post count) |
|
|
185
|
+
| POST | /authors/ | Create author (auto-slug) |
|
|
186
|
+
| GET | /authors/{uid}/ | Author detail |
|
|
187
|
+
| PATCH | /authors/{uid}/ | Update author |
|
|
188
|
+
| DELETE | /authors/{uid}/ | Delete with reassignment |
|
|
189
|
+
|
|
190
|
+
Auth: JWT + IsAdminUser. Pagination: 20/page, max 100. Pydantic schemas for request/response validation.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Data Model — Authoring
|
|
195
|
+
|
|
196
|
+
| Entity | Key Fields |
|
|
197
|
+
|--------|------------|
|
|
198
|
+
| Author | uid (UUID), slug↑, name, role_t9n, description_t9n, tag_t9n, photo→Image, contacts, social_profiles, is_active |
|
|
199
|
+
| DraftAuthor | draft→Draft, author→Author, position (unique together: draft+author) |
|
|
200
|
+
| DraftCoAuthor | draft→Draft, author→Author, position (unique together: draft+author) |
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Image Handling
|
|
205
|
+
|
|
206
|
+
- **HashedImageField** — SHA256 content-addressed storage: `aa/bb/cccccccc...{ext}`
|
|
207
|
+
- **Auto-resize** on upload: max width = `CONTENTDB_IMAGE_MAX_WIDTH` (default 2560)
|
|
208
|
+
- **Thumbnail** model: unique per (method, source, width, height)
|
|
209
|
+
- **Deduplication**: same file content → same hash → same path (no duplicates)
|
|
210
|
+
- Uses `image_transformations` library with PIL fallback
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Key Enums
|
|
215
|
+
|
|
216
|
+
| Enum | Values |
|
|
217
|
+
|------|--------|
|
|
218
|
+
| **Action** | CREATE, UPDATE, DELETE, PUBLISH, VIEW |
|
|
219
|
+
| **Thumbnail.method** | "optimize" |
|
|
220
|
+
| **Route.placement** | "top", "bottom" |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Settings
|
|
225
|
+
|
|
226
|
+
| Setting | Default | Description |
|
|
227
|
+
|---------|---------|-------------|
|
|
228
|
+
| `API_ADMIN_BASE_URL` | `/api-admin/` | Admin API prefix |
|
|
229
|
+
| `API_PUBLIC_BASE_URL` | `/api/` | Public API prefix |
|
|
230
|
+
| `CONTENTDB_THUMBNAIL_QUALITY` | 60 | JPEG thumbnail quality |
|
|
231
|
+
| `CONTENTDB_IMAGE_MAX_WIDTH` | 2560 | Max upload width (0 = no resize) |
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Testing
|
|
236
|
+
|
|
237
|
+
Factory classes available:
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
tests/__init__.py: AttributeSetFactory, ContentTypeFactory, ContentFactory, DraftFactory, PublishedFactory, ContentChannelFactory
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
make test # pytest (requires Postgres — set DATABASE_URL or use the CI default)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Management Commands
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
# Sync commands (run inside Django service context)
|
|
253
|
+
python manage.py sync_contentdb_channels # Sync from PIM
|
|
254
|
+
python manage.py sync_contentdb_languages # Sync from django_regional
|
|
255
|
+
python manage.py propagate_content_channels --channel=default # Assign channel to public content
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Gotchas
|
|
261
|
+
|
|
262
|
+
- Content/Route `delete()` protects `home` and `header` from full removal — raises `ValidationError` (HTTP 400 via `standard_exception_handler`). Duplicates (multiple drafts sharing the same `home` route across channels) are deletable; only the last instance is blocked. `bypass=True` skips validation. System content (`Draft.is_system=True`) is always blocked regardless of routes.
|
|
263
|
+
- `AccessRights` is DEPRECATED — replaced by `ContentChannel`. Both `access_rights` and `channel` query params work during transition
|
|
264
|
+
- `ContentChannel` syncs from PIM via `services/sync_service.py`. When PIM not installed, sync returns 0 gracefully
|
|
265
|
+
- Empty `Draft.channels` M2M = public content (visible to all channel filters)
|
|
266
|
+
- Published is immutable — creating a new Published from a Draft creates a new snapshot, doesn't update existing
|
|
267
|
+
- Admin API prefix is `/api-admin/` (not `/api/v2/`) — this is v1 architecture, pre-Pydantic
|
|
268
|
+
- DRF serializers (not Pydantic) — no drf-spectacular, no OpenAPI auto-generation
|
|
269
|
+
- `HashedImageField` uses SHA256 of content for path — same image uploaded twice = same file on disk
|
|
270
|
+
- `ContentTypePermission` checks `content_type` slug from URL kwargs — requires content_type in URL path
|
|
271
|
+
- `standard_exception_handler` wraps ALL errors in `{"meta": {...}, "data": ...}` format
|
|
272
|
+
- Layout extender routes are separate from content routes — `is_layout_extender=True` ContentTypes use `layout-extender/*` paths
|
|
273
|
+
- Author cannot appear in both `authors` and `co_authors` M2M on the same Draft — validated in service layer and API
|
|
274
|
+
- v2 Author endpoints use JWT + IsAdminUser (not DjangoAuth like v1)
|
|
275
|
+
- ContentType.supports_authors controls which types show author picker in CMS — only blog-post by default
|
|
276
|
+
- Published author data is a live reference through draft — changing authors on draft changes what published shows without re-publishing
|
|
277
|
+
- `?author=` filter on v1 endpoints searches both authors and co-authors by slug or UUID
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Module Boundaries
|
|
282
|
+
|
|
283
|
+
**Does:** Store/serve JSON page content, draft/publish lifecycle, image uploads + thumbnails, attribute metadata, access control, activity audit logging, blog author management
|
|
284
|
+
|
|
285
|
+
**Does NOT:** Render content (storefront's job), define page layouts (CMS Blueprint's job). Optional sync from PIM/django_regional (graceful fallback when not installed)
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Reference Docs
|
|
290
|
+
|
|
291
|
+
| File | Content |
|
|
292
|
+
|------|---------|
|
|
293
|
+
| `docs/api-reference.md` | Full API surface: all endpoints, serializer fields, filters, permissions |
|
|
294
|
+
| `docs/models-reference.md` | Complete model inventory: fields, relationships, constraints, enums |
|
|
295
|
+
| `docs/erd-config.yaml` | ERD diagram config for `make erd` |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|