diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e54e2822..f85da70a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,7 +11,11 @@ jobs: steps: - uses: actions/checkout@v5 - - run: python -Im pip install --user ruff + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - name: Install ruff + run: uv tool install ruff - name: Run ruff - run: ruff check --output-format=github djangocms_versioning tests + run: uvx ruff check --output-format=github djangocms_versioning tests diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a6d0907b..553b315f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,40 +12,53 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ 3.9, "3.10", "3.11", "3.12" ] + python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ] requirements-file: [ dj42_cms41.txt, dj50_cms41.txt, dj51_cms41.txt, dj52_cms41.txt, dj52_cms50.txt, + dj60_cms50.txt, ] exclude: + - requirements-file: dj52_cms41.txt + python-version: "3.10" + - requirements-file: dj52_cms50.txt + python-version: "3.10" + - requirements-file: dj60_cms50.txt + python-version: "3.10" + - requirements-file: dj60_cms50.txt + python-version: "3.11" + - requirements-file: dj42_cms41.txt + python-version: "3.14" - requirements-file: dj50_cms41.txt - python-version: 3.9 + python-version: "3.14" - requirements-file: dj51_cms41.txt - python-version: 3.9 + python-version: "3.14" - requirements-file: dj52_cms41.txt - python-version: 3.9 - - requirements-file: dj52_cms41.txt - python-version: 3.10 - - requirements-file: dj52_cms50.txt - python-version: 3.9 + python-version: "3.14" - requirements-file: dj52_cms50.txt - python-version: 3.10 + python-version: "3.14" steps: - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + - name: Install uv + uses: astral-sh/setup-uv@v5 - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install -r tests/requirements/${{ matrix.requirements-file }} - pip install -e . + sudo apt install gettext gcc -y + sudo apt install gettext gcc -y + sudo apt install gettext gcc -y + python -m pip install --upgrade pip uv + python -m pip install --upgrade pip uv + python -m pip install --upgrade pip uv + uv pip install --system -r tests/requirements/${{ matrix.requirements-file }} + uv pip install --system -e . - name: Run coverage run: coverage run ./test_settings.py @@ -58,12 +71,36 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ "3.11", "3.12", "3.13" ] + python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ] requirements-file: [ dj42_cms41.txt, + dj50_cms41.txt, + dj51_cms41.txt, dj52_cms41.txt, dj52_cms50.txt, - ] + dj60_cms50.txt, + ] + exclude: + - requirements-file: dj52_cms41.txt + python-version: "3.10" + - requirements-file: dj52_cms50.txt + python-version: "3.10" + - requirements-file: dj60_cms50.txt + python-version: "3.10" + - requirements-file: dj60_cms50.txt + python-version: "3.11" + - requirements-file: dj42_cms41.txt + python-version: "3.14" + - requirements-file: dj50_cms41.txt + python-version: "3.14" + - requirements-file: dj51_cms41.txt + python-version: "3.14" + - requirements-file: dj52_cms41.txt + python-version: "3.14" + - requirements-file: dj52_cms50.txt + python-version: "3.14" + - requirements-file: dj60_cms50.txt + python-version: "3.14" services: postgres: @@ -80,15 +117,15 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + - name: Install uv + uses: astral-sh/setup-uv@v5 - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install -r tests/requirements/${{ matrix.requirements-file }} - python setup.py install + uv pip install --system -r tests/requirements/${{ matrix.requirements-file }} + uv pip install --system . - name: Run coverage run: coverage run ./test_settings.py @@ -103,17 +140,36 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ "3.11", "3.12", "3.13" ] + python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ] requirements-file: [ dj42_cms41.txt, + dj50_cms41.txt, + dj51_cms41.txt, dj52_cms41.txt, dj52_cms50.txt, - ] + dj60_cms50.txt, + ] exclude: + - requirements-file: dj52_cms41.txt + python-version: "3.10" + - requirements-file: dj52_cms50.txt + python-version: "3.10" + - requirements-file: dj60_cms50.txt + python-version: "3.10" + - requirements-file: dj60_cms50.txt + python-version: "3.11" + - requirements-file: dj42_cms41.txt + python-version: "3.14" - requirements-file: dj50_cms41.txt - python-version: 3.9 + python-version: "3.14" - requirements-file: dj51_cms41.txt - python-version: 3.9 + python-version: "3.14" + - requirements-file: dj52_cms41.txt + python-version: "3.14" + - requirements-file: dj52_cms50.txt + python-version: "3.14" + - requirements-file: dj60_cms50.txt + python-version: "3.14" services: mysql: @@ -128,15 +184,15 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + - name: Install uv + uses: astral-sh/setup-uv@v5 - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install -r tests/requirements/${{ matrix.requirements-file }} - python setup.py install + uv pip install --system -r tests/requirements/${{ matrix.requirements-file }} + uv pip install --system . - name: Run coverage run: coverage run ./test_settings.py @@ -163,17 +219,17 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + - name: Install uv + uses: astral-sh/setup-uv@v5 - name: Install dependencies run: | - python -m pip install --upgrade pip - python -m pip install -r tests/requirements/${{ matrix.requirements-file }} - python -m pip uninstall -y django-cms - python -m pip install ${{ matrix.cms-version }} - python setup.py install + uv pip install --system -r tests/requirements/${{ matrix.requirements-file }} + uv pip uninstall --system django-cms + uv pip install --system ${{ matrix.cms-version }} + uv pip install --system . - name: Run coverage run: coverage run ./test_settings.py @@ -186,29 +242,29 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ "3.12" ] + python-version: [ "3.13" ] cms-version: [ 'https://github.com/django-cms/django-cms/archive/main.tar.gz' ] django-version: [ 'https://github.com/django/django/archive/main.tar.gz' ] - requirements-file: ['dj52_cms50.txt'] + requirements-file: ['dj60_cms50.txt'] steps: - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + - name: Install uv + uses: astral-sh/setup-uv@v5 - name: Install dependencies run: | - python -m pip install --upgrade pip - python -m pip install -r tests/requirements/${{ matrix.requirements-file }} - python -m pip uninstall -y Django django-cms - python -m pip install ${{ matrix.cms-version }} ${{ matrix.django-version }} - python setup.py install + uv pip install --system -r tests/requirements/${{ matrix.requirements-file }} + uv pip uninstall --system Django django-cms + uv pip install --system ${{ matrix.cms-version }} ${{ matrix.django-version }} + uv pip install --system . - name: Run coverage run: coverage run ./test_settings.py diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..4a14d7b1 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,185 @@ +# AGENTS.md + +This file contains information for AI assistants working with the djangocms-versioning codebase. + + +Use ast-grep and gh cli tools freely. + +## Project Overview + +django CMS Versioning is a Django package that provides versioning capabilities for django CMS 4.0+. It's a hybrid Python/JavaScript project with a Django backend and frontend assets built with webpack and gulp. + +## Frequently Used Commands + +### Testing +```bash +# Install test dependencies (using uv for faster installation) +uv pip install -r tests/requirements.txt + +# Run all tests +python setup.py test + +# Run tests with coverage +coverage erase +coverage run setup.py test +coverage report + +# Run tests with tox (multiple Python/Django versions) +tox + +# Run specific tox environment +tox -e py311-dj42-sqlite +``` + +### Linting and Code Quality +```bash +# Run ruff linter (this is the primary linter) +ruff djangocms_versioning +ruff tests + +# Run ruff with auto-fix +ruff --fix djangocms_versioning tests + +# Run pre-commit hooks (automatically runs on commit) +pre-commit run --all-files + +# Run ESLint for JavaScript +npm run lint # or gulp lint +``` + +### Frontend Development +```bash +# Install JavaScript dependencies +npm install + +# Build frontend assets +npm run build # or use gulp/webpack directly +``` + +### Database Migrations +```bash +# Run migrations +python manage.py migrate djangocms_versioning + +# Create versions for existing database (only for migration from non-versioned setup) +python manage.py create_versions --userid + +# Compile translations +python manage.py compilemessages +``` + +### Documentation +```bash +# Generate HTML documentation +cd docs/ +make html +# Output will be in docs/_build/html/ +``` + +### Translations +```bash +# Update translations from Transifex (requires transifex CLI) +tx pull + +# Compile message files +python manage.py compilemessages +``` + +## Code Style and Conventions + +### Python +- **Linter**: ruff (configured in pyproject.toml) +- **Package manager**: uv (for faster dependency installation in CI and local development) +- **Line length**: 120 characters +- **Import style**: Use `isort` via ruff (combine-as-imports = true) +- **Code quality**: Follows ruff rules including pycodestyle, pyflakes, flake8-bugbear, and pyupgrade +- **Type hints**: Prefer modern Python type annotations (PEP 604 union syntax: `X | Y` instead of `Union[X, Y]`) +- **Django version**: Support Django 4.2, 5.0, 5.1, 5.2, 6.0 +- **Python version**: Python 3.10+ + +### JavaScript +- **Linter**: ESLint (configured in .eslintrc.js) +- **Bundler**: webpack 3.x +- **Task runner**: gulp +- **Transpiler**: Babel with env preset +- **Dependencies**: jQuery, lodash (debounce, memoize), nprogress + +### General +- Use pre-commit hooks for automatic code quality checks +- Follow existing patterns in the codebase +- Migrations go in `djangocms_versioning/migrations/` +- Templates go in `djangocms_versioning/templates/` +- Static files go in `djangocms_versioning/static/` + +## Project Structure + +``` +djangocms-versioning/ +├── djangocms_versioning/ # Main package +│ ├── admin.py # Django admin configuration +│ ├── models.py # Database models +│ ├── forms.py # Django forms +│ ├── managers.py # Custom model managers +│ ├── signals.py # Django signals +│ ├── handlers.py # Signal handlers +│ ├── versionables.py # Versionable configuration +│ ├── cms_config.py # CMS app configuration +│ ├── cms_toolbars.py # Toolbar customization +│ ├── management/ # Management commands +│ ├── templates/ # Django templates +│ ├── static/ # CSS, JS, images +│ ├── locale/ # Translations +│ ├── test_utils/ # Test utilities and example app (polls) +│ └── migrations/ # Database migrations +├── tests/ # Test suite +│ └── requirements/ # Test dependency specifications +├── docs/ # Documentation (Sphinx) +├── pyproject.toml # Python package configuration +├── setup.py # Legacy setup file +├── test_settings.py # Django settings for tests +├── tox.ini # Tox configuration +├── package.json # JavaScript dependencies +├── webpack.config.js # Webpack configuration +├── gulpfile.js # Gulp tasks +└── .pre-commit-config.yaml # Pre-commit hooks +``` + +## Key Dependencies + +### Python +- Django >= 4.2 +- django-cms >= 4.1.1 +- django-fsm < 3 +- django-fsm-2==4.1.0 +- packaging + +### JavaScript +- jquery ^3.3.1 +- webpack ^3.0.0 +- babel ^6.x +- gulp 3.9.1 + +## Testing Strategy + +- Tests are in the `tests/` directory +- Use Django's test framework +- Test against multiple Python versions (3.10, 3.11, 3.12, 3.13, 3.14) +- Test against multiple Django versions (4.2, 5.0, 5.1, 5.2, 6.0) +- Example implementation in `djangocms_versioning/test_utils/polls/` +- Coverage configured to omit migrations, test utils, and tests themselves + +## Integration Notes + +- Versioning integration docs: `docs/versioning_integration.rst` +- Example implementation: `djangocms_versioning/test_utils/polls/cms_config.py` +- Requires django CMS 4.0 or higher +- Uses django-fsm for state machine functionality +- Frontend integrates with django CMS toolbar system + +## Common Patterns + +- **Versionable models**: See `test_utils/polls/models.py` and `test_utils/polls/cms_config.py` +- **Admin customization**: Versioning-specific admin in `admin.py` +- **State management**: Uses django-fsm for version states +- **Signals**: Version lifecycle signals in `signals.py` +- **Templates**: Override in `templates/djangocms_versioning/` diff --git a/README.rst b/README.rst index ff1a30ee..4c369a0d 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,8 @@ Requirements django CMS Versioning requires that you have a django CMS 4.0 (or higher) project already running and set up. +Note: This package uses `django-fsm-2 `_ (actively maintained by Django Commons) instead of the original `django-fsm `_ for better support and maintenance. + To install ========== diff --git a/djangocms_versioning/conditions.py b/djangocms_versioning/conditions.py index 8820955b..0809099c 100644 --- a/djangocms_versioning/conditions.py +++ b/djangocms_versioning/conditions.py @@ -1,4 +1,3 @@ -import typing from django.conf import settings @@ -11,7 +10,7 @@ class Conditions(list): def __add__(self, other: list) -> "Conditions": return Conditions(super().__add__(other)) - def __get__(self, instance: object, cls) -> typing.Union["Conditions", "BoundConditions"]: + def __get__(self, instance: object, cls) -> "Conditions | BoundConditions": if instance: return BoundConditions(self, instance) return self diff --git a/djangocms_versioning/helpers.py b/djangocms_versioning/helpers.py index d6d59d60..bad4e108 100644 --- a/djangocms_versioning/helpers.py +++ b/djangocms_versioning/helpers.py @@ -72,9 +72,11 @@ def _replace_admin_for_model(modeladmin: type[admin.ModelAdmin], mixin: type, ad admin_site.register(modeladmin.model, new_admin_class) -def replace_admin_for_models(pairs: tuple[type[models.Model], type], admin_site: admin.AdminSite | None = None): +def replace_admin_for_models( + pairs: Iterable[tuple[type[models.Model], type]], admin_site: admin.AdminSite | None = None +): """ - :param models: List of (model class, admin mixin class) tuples + :param pairs: Iterable of (model class, admin mixin class) tuples :param admin_site: AdminSite instance """ if admin_site is None: diff --git a/djangocms_versioning/indicators.py b/djangocms_versioning/indicators.py index c39ca3df..6bbf7e75 100644 --- a/djangocms_versioning/indicators.py +++ b/djangocms_versioning/indicators.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from cms.utils.urlutils import admin_reverse from django.db import models from django.utils.http import urlencode diff --git a/pyproject.toml b/pyproject.toml index 251df154..25b07935 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" name = "djangocms-versioning" description = "Versioning for django CMS" # Dies muss manuell aktualisiert werden, da pyproject.toml keine dynamische Beschreibung unterstützt readme = "README.rst" -requires-python = ">=3.6" +requires-python = ">=3.10" license = {text = "BSD License"} authors = [ {name = "Divio AG", email = "info@divio.ch"}, @@ -20,16 +20,22 @@ classifiers = [ "Framework :: Django :: 5.0", "Framework :: Django :: 5.1", "Framework :: Django :: 5.2", + "Framework :: Django :: 6.0", "Framework :: Django CMS :: 4.1", "Framework :: Django CMS :: 5.0", "Intended Audience :: Developers", "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Software Development", ] dependencies = [ - "Django>=4.2", "django-cms>=4.1.1", - "django-fsm<3", + "django-fsm-2", "packaging", ] diff --git a/tests/requirements/dj42_cms41.txt b/tests/requirements/dj42_cms41.txt index bf600a57..2d81202b 100644 --- a/tests/requirements/dj42_cms41.txt +++ b/tests/requirements/dj42_cms41.txt @@ -4,5 +4,5 @@ django-cms>=4.1,<4.2 Django>=4.2,<5 django-classy-tags -django-fsm>=2.6,<3 +django-fsm-2==4.1.0 django-sekizai diff --git a/tests/requirements/dj50_cms41.txt b/tests/requirements/dj50_cms41.txt index 4326bfd7..ce7126b7 100644 --- a/tests/requirements/dj50_cms41.txt +++ b/tests/requirements/dj50_cms41.txt @@ -4,5 +4,5 @@ django-cms>=4.1,<4.2 Django>=5.0,<5.1 django-classy-tags -django-fsm>=2.6,<3 +django-fsm-2==4.1.0 django-sekizai diff --git a/tests/requirements/dj51_cms41.txt b/tests/requirements/dj51_cms41.txt index 14b5770e..29973ddf 100644 --- a/tests/requirements/dj51_cms41.txt +++ b/tests/requirements/dj51_cms41.txt @@ -4,5 +4,5 @@ django-cms>=4.1,<4.2 Django>=5.1,<5.2 django-classy-tags -django-fsm>=2.6,<3 +django-fsm-2==4.1.0 django-sekizai diff --git a/tests/requirements/dj52_cms41.txt b/tests/requirements/dj52_cms41.txt index 21b539dc..c0d84d3a 100644 --- a/tests/requirements/dj52_cms41.txt +++ b/tests/requirements/dj52_cms41.txt @@ -4,5 +4,5 @@ django-cms>=4.1,<4.2 Django>=5.2,<6.0 django-classy-tags -django-fsm>=2.6,<3 +django-fsm-2==4.1.0 django-sekizai diff --git a/tests/requirements/dj52_cms50.txt b/tests/requirements/dj52_cms50.txt index c5f5251b..40fe958f 100644 --- a/tests/requirements/dj52_cms50.txt +++ b/tests/requirements/dj52_cms50.txt @@ -4,5 +4,5 @@ django-cms>=5.0,<5.1 Django>=5.2,<6.0 django-classy-tags -django-fsm>=2.6,<3 +django-fsm-2==4.1.0 django-sekizai diff --git a/tests/requirements/dj60_cms50.txt b/tests/requirements/dj60_cms50.txt new file mode 100644 index 00000000..b23867c0 --- /dev/null +++ b/tests/requirements/dj60_cms50.txt @@ -0,0 +1,8 @@ +-r requirements_base.txt + +django-cms>=5.0,<5.1 + +Django>=6.0a1,<6.1 +django-classy-tags +django-fsm-2==4.1.0 +django-sekizai diff --git a/tests/requirements/requirements_base.txt b/tests/requirements/requirements_base.txt index 1d3793f0..fbd0213d 100644 --- a/tests/requirements/requirements_base.txt +++ b/tests/requirements/requirements_base.txt @@ -10,7 +10,7 @@ mock pillow pyflakes>=2.1.1 python-dateutil -mysqlclient==2.0.3 +mysqlclient>=2.2.1 psycopg2 setuptools diff --git a/tox.ini b/tox.ini index 52c40c51..649deab0 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,9 @@ [tox] envlist = ruff - py{39.310,311}-dj{32,40,41,42}-sqlite - py{311,312}-djmain-cms-develop4-sqlite + py{310,311,312,313,314}-dj{42,50,51,52}-sqlite + py{312,313,314}-dj60-sqlite + py{312,313}-djmain-cms-develop4-sqlite skip_missing_interpreters=True @@ -10,17 +11,20 @@ skip_missing_interpreters=True deps = -r{toxinidir}/tests/requirements/requirements_base.txt - dj32: -r{toxinidir}/tests/requirements/dj32_cms41.txt - dj40: -r{toxinidir}/tests/requirements/dj40_cms41.txt - dj41: -r{toxinidir}/tests/requirements/dj41_cms41.txt dj42: -r{toxinidir}/tests/requirements/dj42_cms41.txt + dj50: -r{toxinidir}/tests/requirements/dj50_cms41.txt + dj51: -r{toxinidir}/tests/requirements/dj51_cms41.txt + dj52: -r{toxinidir}/tests/requirements/dj52_cms50.txt + dj60: -r{toxinidir}/tests/requirements/dj60_cms50.txt djmain: https://github.com/django/django/archive/main.tar.gz develop4: https://github.com/django-cms/django-cms/archive/develop-4.tar.gz basepython = - py39: python3.9 py310: python3.10 py311: python3.11 + py312: python3.12 + py313: python3.13 + py314: python3.14 commands = {envpython} --version