diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md new file mode 100644 index 00000000..1d5d21b9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/---bug-report.md @@ -0,0 +1,65 @@ +--- +name: "\U0001F41E Bug report" +about: Something isn't working as expected? Here is the right place to report. +title: "[BUG]" +labels: '' +assignees: '' + +--- + + + +## Description + + + +## Steps to reproduce + + + +## Expected behaviour + + + +## Actual behaviour + + + +## Screenshots + + + +## Additional information (CMS/Python/Django versions) + + + +## Do you want to help fix this issue? + + + +* [ ] Yes, I want to help fix this issue and I will join #workgroup-pr-review on [Slack](https://www.django-cms.org/slack) to confirm with the community that a PR is welcome. +* [ ] No, I only want to report the issue. diff --git a/.github/ISSUE_TEMPLATE/---documentation-report.md b/.github/ISSUE_TEMPLATE/---documentation-report.md new file mode 100644 index 00000000..2b599f7a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/---documentation-report.md @@ -0,0 +1,43 @@ +--- +name: "\U0001F4D8 Documentation report" +about: "Something isn't described correctly in the documentation or needs to be updated? + Here is the right place to report." +title: "[DOC]" +labels: 'component: documentation' +assignees: '' + +--- + + + +## Description + + + +## Screenshots + + + +## Additional information (CMS/Python/Django versions) + + + +## Do you want to help fix this documentation issue? + + + +* [ ] Yes, I want to help fix this issue and I will join #workgroup-documentation on [Slack](https://www.django-cms.org/slack) to confirm with the team that a PR is welcome. +* [ ] No, I only want to report the issue. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..4f76fcfa --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,28 @@ +## Description + + + +## Related resources + + + +* #... +* #... + +## Checklist + + + +* [ ] I have added or modified the tests when changing logic +* [ ] I have followed [the conventional commits guidelines](https://www.conventionalcommits.org/) to add meaningful information into the changelog +* [ ] I have read the [contribution guidelines ](https://github.com/django-cms/django-cms/blob/develop/CONTRIBUTING.rst) diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..14f01789 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..b005cb83 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,35 @@ +name: Docs + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + docs: + runs-on: ubuntu-latest + name: docs + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: 'pip' + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('docs/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - run: python -m pip install -r docs/requirements.txt + - run: python setup.py install + - run: codespell -w *.rst + - run: codespell -w --skip docs/spelling_wordlist docs + - name: Build docs + run: | + cd docs + sphinx-build -b dirhtml -n -d build/doctrees . build/dirhtml diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 4d88962e..cfdcaed4 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -10,9 +10,9 @@ jobs: node-version: [6.x] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6760a90a..b6da3f9d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,15 +8,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: '3.10' - name: Install flake8 run: pip install --upgrade flake8 - name: Run flake8 - uses: liskin/gh-problem-matcher-wrap@v1 + uses: liskin/gh-problem-matcher-wrap@v3 with: linters: flake8 run: flake8 @@ -25,14 +25,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: '3.10' - run: python -m pip install isort - name: isort - uses: liskin/gh-problem-matcher-wrap@v1 + uses: liskin/gh-problem-matcher-wrap@v3 with: linters: isort run: isort -c -rc -df ./ diff --git a/.github/workflows/publish-to-live-pypi.yml b/.github/workflows/publish-to-live-pypi.yml new file mode 100644 index 00000000..0776261d --- /dev/null +++ b/.github/workflows/publish-to-live-pypi.yml @@ -0,0 +1,41 @@ +name: Publish Python 🐍 distributions 📦 to pypi + +on: + release: + types: + - published + +jobs: + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to pypi + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/djangocms-moderation + permissions: + id-token: write + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + . + + - name: Publish distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/publish-to-test-pypi.yml b/.github/workflows/publish-to-test-pypi.yml new file mode 100644 index 00000000..ae0fd790 --- /dev/null +++ b/.github/workflows/publish-to-test-pypi.yml @@ -0,0 +1,43 @@ +name: Publish Python 🐍 distributions 📦 to TestPyPI + +on: + push: + branches: + - master + +jobs: + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to TestPyPI + runs-on: ubuntu-latest + environment: + name: test + url: https://test.pypi.org/p/djangocms-moderation + permissions: + id-token: write + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + . + + - name: Publish distribution 📦 to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + skip_existing: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 704336cc..c489abb2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,20 +10,26 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ 3.7, 3.8, 3.9 ] # latest release minus two + python-version: [ "3.10", "3.11", "3.12" ] # latest release minus two requirements-file: [ - dj22_cms40.txt, - dj32_cms40.txt, + dj50_cms41.txt, + dj42_cms41.txt, + dj42_cms40.txt, ] os: [ ubuntu-20.04, ] + exclude: + - requirements-file: "dj42_cms40.txt" + python-version: "3.12" #cms40 not support py3.12 yet + - requirements-file: "dj32_cms40.txt" + python-version: "3.12" steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -33,7 +39,7 @@ jobs: python setup.py install - name: Run coverage - run: coverage run setup.py test + run: coverage run ./tests/settings.py - name: Upload Coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v5 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..e99f5e66 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,53 @@ +ci: + autofix_commit_msg: | + ci: auto fixes from pre-commit hooks + + for more information, see https://pre-commit.ci + autofix_prs: false + autoupdate_commit_msg: 'ci: pre-commit autoupdate' + autoupdate_schedule: monthly + +repos: + - repo: https://github.com/asottile/pyupgrade + rev: v3.19.0 + hooks: + - id: pyupgrade + args: ["--py310-plus"] + + - repo: https://github.com/adamchainz/django-upgrade + rev: '1.22.1' + hooks: + - id: django-upgrade + args: [--target-version, "4.0"] + + - repo: https://github.com/PyCQA/flake8 + rev: 7.1.1 + hooks: + - id: flake8 + + - repo: https://github.com/asottile/yesqa + rev: v1.5.0 + hooks: + - id: yesqa + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-merge-conflict + - id: debug-statements + - id: mixed-line-ending + - id: trailing-whitespace +# upgrade the isort version to fix compatiable issue withe peotry: https://stackoverflow.com/questions/75269700/pre-commit-fails-to-install-isort-5-11-4-with-error-runtimeerror-the-poetry-co + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + exclude: > + (?x)^( + .*\.(js|po|json) + )$ diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..e1bd672f --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,20 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +sphinx: + configuration: docs/conf.py + fail_on_warning: false + +formats: + - epub + - pdf + +python: + install: + - requirements: docs/requirements.txt diff --git a/.tx/transifex.yaml b/.tx/transifex.yaml new file mode 100644 index 00000000..2370ac4c --- /dev/null +++ b/.tx/transifex.yaml @@ -0,0 +1,7 @@ +git: + filters: + - filter_type: file + file_format: PO + source_file: djangocms_moderation/locale/en/LC_MESSAGES/django.po + source_language: en + translation_files_expression: 'djangocms_moderation/locale//LC_MESSAGES/django.po' diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e7c2e8a8..1d7eabb0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,22 @@ Changelog Unreleased ========== +fix: Add data-popup attr to a tag in burger menu item +fix: Replace SortableAdminMixin by SortableAdminBase for WorkflowAdmin +fix: Restore "In Collection" button in the toolbar +fix: Update README.rst and add overview of settings + +2.2.1 (2024-07-02) +================== +* feat: Add multi select and add to moderation in admin for moderated_models + +2.2.0 (2024-05-16) +================== +* Python 3.8, 3.9 support removed +* Python 3.10, 3.11 and 3.12 support added +* Django 2.2 support removed +* Django 4.2 support added +* fix: Treebeard support improved by inheriting a treebeard template 2.1.6 (2022-09-07) ================== diff --git a/README.rst b/README.rst index 9e13b946..93572458 100644 --- a/README.rst +++ b/README.rst @@ -11,18 +11,20 @@ Requirements django CMS Moderation requires that you have a django CMS 4.0 (or higher) project already running and set up. +djangocms-versioning is also required along with django-fsm which should be installed with versioning. + To install ========== Run:: - pip install git+git://github.com/divio/djangocms-moderation@develop#egg=djangocms-moderation + pip install git+git://github.com/django-cms/djangocms-moderation@master#egg=djangocms-moderation Add the following to your project's ``INSTALLED_APPS``: - - ``'djangocms_moderation'`` - - ``'adminsortable2'`` +- ``'djangocms_moderation'`` +- ``'adminsortable2'`` Run:: @@ -30,6 +32,69 @@ Run:: to perform the application's database migrations. +Configuration +============= + +The following settings can be added to your project's settings file to configure django CMS Moderation's behavior: + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Setting + - Description + * - ``CMS_MODERATION_DEFAULT_COMPLIANCE_NUMBER_BACKEND`` + - Default backend class for generating compliance numbers. + Default is ``djangocms_moderation.backends.uuid4_backend``. + * - ``CMS_MODERATION_COMPLIANCE_NUMBER_BACKENDS`` + - List of available compliance number backend classes. + By default, three backends are configured: ``uuid4_backend``, + ``sequential_number_backend``, and + ``sequential_number_with_identifier_prefix_backend``. + * - ``CMS_MODERATION_ENABLE_WORKFLOW_OVERRIDE`` + - Enable/disable workflow override functionality. Defaults to ``False``. + * - ``CMS_MODERATION_DEFAULT_CONFIRMATION_PAGE_TEMPLATE`` + - Default template for confirmation pages. Defaults to + ``djangocms_moderation/moderation_confirmation.html`` + * - ``CMS_MODERATION_CONFIRMATION_PAGE_TEMPLATES`` + - List of available confirmation page templates. Only includes the + default template by default. + * - ``CMS_MODERATION_COLLECTION_COMMENTS_ENABLED`` + - Enable/disable comments on collections. Defaults to ``True``. + * - ``CMS_MODERATION_REQUEST_COMMENTS_ENABLED`` + - Enable/disable comments on requests. Defaults to ``True``. + * - ``CMS_MODERATION_COLLECTION_NAME_LENGTH_LIMIT`` + - Maximum length for collection names. Defaults to ``24``. + * - ``EMAIL_NOTIFICATIONS_FAIL_SILENTLY`` + - Control email notification error handling. Defaults to ``False``. + +Example Configuration +--------------------- + +Add these settings to your project's settings file: + +.. code-block:: python + + # Custom compliance number backend + CMS_MODERATION_DEFAULT_COMPLIANCE_NUMBER_BACKEND = 'myapp.backends.CustomComplianceNumberBackend' + + # Enable workflow override + CMS_MODERATION_ENABLE_WORKFLOW_OVERRIDE = True + + # Custom confirmation template + CMS_MODERATION_DEFAULT_CONFIRMATION_PAGE_TEMPLATE = 'custom_confirmation.html' + + # Enable comments + CMS_MODERATION_COLLECTION_COMMENTS_ENABLED = True + CMS_MODERATION_REQUEST_COMMENTS_ENABLED = True + + # Set collection name length limit + CMS_MODERATION_COLLECTION_NAME_LENGTH_LIMIT = 100 + + # Control email notification errors + EMAIL_NOTIFICATIONS_FAIL_SILENTLY = False + +============= Documentation ============= diff --git a/djangocms_moderation/__init__.py b/djangocms_moderation/__init__.py index 1e525f4c..b19ee4b7 100644 --- a/djangocms_moderation/__init__.py +++ b/djangocms_moderation/__init__.py @@ -1,3 +1 @@ -__version__ = "2.1.6" - -default_app_config = "djangocms_moderation.apps.ModerationConfig" +__version__ = "2.2.1" diff --git a/djangocms_moderation/admin.py b/djangocms_moderation/admin.py index 460bb8ee..f5c2b031 100644 --- a/djangocms_moderation/admin.py +++ b/djangocms_moderation/admin.py @@ -16,7 +16,7 @@ from cms.toolbar.utils import get_object_preview_url from cms.utils.helpers import is_editable_model -from adminsortable2.admin import SortableInlineAdminMixin +from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin from treebeard.admin import TreeAdmin from . import constants, signals @@ -73,12 +73,16 @@ def has_add_permission(self, request): def has_delete_permission(self, request, obj=None): return False + @admin.display( + description=_("Status") + ) def show_user(self, obj): _name = obj.get_by_user_name() return gettext("By {user}").format(user=_name) - show_user.short_description = _("Status") - + @admin.display( + description=_("Form Submission") + ) def form_submission(self, obj): instance = get_form_submission_for_step( obj.moderation_request, obj.step_approved @@ -89,15 +93,13 @@ def form_submission(self, obj): opts = ConfirmationFormSubmission._meta url = reverse( - "admin:{}_{}_change".format(opts.app_label, opts.model_name), + f"admin:{opts.app_label}_{opts.model_name}_change", args=[instance.pk], ) return format_html( '{}', url, obj.step_approved.role.name ) - form_submission.short_description = _("Form Submission") - def get_readonly_fields(self, request, obj=None): if obj.user_can_moderate(request.user) or obj.user_is_author(request.user): # Omit 'message' from readonly_fields when current user is a reviewer @@ -107,6 +109,7 @@ def get_readonly_fields(self, request, obj=None): return self.fields +@admin.register(ModerationRequestTreeNode) class ModerationRequestTreeAdmin(TreeAdmin): """ This admin is purely for the change list of Moderation Requests using the treebeard nodes to @@ -177,6 +180,9 @@ def get_list_display(self, request): ] return list_display + @admin.display( + description=_("actions") + ) def list_display_actions(self, obj): """Display links to state change endpoints """ @@ -184,8 +190,6 @@ def list_display_actions(self, obj): "", "{}", ((action(obj),) for action in self.get_list_display_actions()) ) - list_display_actions.short_description = _("actions") - def get_list_display_actions(self): actions = [] if conf.REQUEST_COMMENTS_ENABLED: @@ -210,6 +214,9 @@ def _get_configured_fields(self, request): return fields + @admin.display( + description=_('ID') + ) def get_id(self, obj): return format_html( '{id}', @@ -219,22 +226,30 @@ def get_id(self, obj): ), id=obj.moderation_request_id, ) - get_id.short_description = _('ID') + @admin.display( + description=_('Content type') + ) def get_content_type(self, obj): return ContentType.objects.get_for_model( obj.moderation_request.version.versionable.grouper_model ) - get_content_type.short_description = _('Content type') + @admin.display( + description=_('Title') + ) def get_title(self, obj): return obj.moderation_request.version.content - get_title.short_description = _('Title') + @admin.display( + description=_('Author') + ) def get_version_author(self, obj): return obj.moderation_request.version.created_by - get_version_author.short_description = _('Author') + @admin.display( + description=_("Preview") + ) def get_preview_link(self, obj): content = obj.moderation_request.version.content if is_editable_model(content.__class__): @@ -254,8 +269,9 @@ def get_preview_link(self, obj): object_preview_url, ) - get_preview_link.short_description = _("Preview") - + @admin.display( + description=_('Reviewer') + ) def get_reviewer(self, obj): last_action = obj.moderation_request.get_last_action() if not last_action: @@ -264,7 +280,6 @@ def get_reviewer(self, obj): next_step = obj.moderation_request.get_next_required() return next_step.role.name return last_action._get_user_name(last_action.by_user) - get_reviewer.short_description = _('Reviewer') def get_status(self, obj): # We can have moderation requests without any action (e.g. the @@ -466,6 +481,7 @@ def _traverse_moderation_nodes(node_item): return HttpResponseRedirect(redirect_url) +@admin.register(ModerationRequest) class ModerationRequestAdmin(admin.ModelAdmin): class Media: js = ('admin/js/jquery.init.js', 'djangocms_moderation/js/actions.js',) @@ -813,11 +829,13 @@ def changelist_view(self, request, extra_context=None): return tree_node_admin.changelist_view(request, extra_context) +@admin.register(Role) class RoleAdmin(admin.ModelAdmin): list_display = ["name", "user", "group", "confirmation_page"] fields = ["name", "user", "group", "confirmation_page"] +@admin.register(CollectionComment) class CollectionCommentAdmin(admin.ModelAdmin): list_display = ["date_created", "message", "author"] fields = ["collection", "message", "author"] @@ -900,6 +918,7 @@ def get_readonly_fields(self, request, obj=None): return self.list_display +@admin.register(RequestComment) class RequestCommentAdmin(admin.ModelAdmin): list_display = ["date_created", "message", "get_author"] fields = ["moderation_request", "message", "author"] @@ -907,11 +926,12 @@ class RequestCommentAdmin(admin.ModelAdmin): class Media: css = {"all": ("djangocms_moderation/css/comments_changelist.css",)} + @admin.display( + description=_("User") + ) def get_author(self, obj): return obj.author_name - get_author.short_description = _("User") - def get_changeform_initial_data(self, request): data = {"author": request.user} moderation_request_id = utils.extract_filter_param_from_changelist_url( @@ -990,7 +1010,8 @@ def get_extra(self, request, obj=None, **kwargs): return 1 -class WorkflowAdmin(admin.ModelAdmin): +@admin.register(Workflow) +class WorkflowAdmin(SortableAdminBase, admin.ModelAdmin): inlines = [WorkflowStepInline] list_display = ["name", "is_default"] fields = [ @@ -1002,6 +1023,7 @@ class WorkflowAdmin(admin.ModelAdmin): ] +@admin.register(ModerationCollection) class ModerationCollectionAdmin(admin.ModelAdmin): class Media: js = ("admin/js/jquery.init.js", "djangocms_moderation/js/actions.js",) @@ -1033,11 +1055,16 @@ def get_list_display(self, request): def job_id(self, obj): return obj.pk + @admin.display( + description=_('reviewers') + ) def commaseparated_reviewers(self, obj): reviewers = self.model.objects.reviewers(obj) return ", ".join(map(get_user_model().get_full_name, reviewers)) - commaseparated_reviewers.short_description = _('reviewers') + @admin.display( + description=_("actions") + ) def list_display_actions(self, obj): """Display links to state change endpoints """ @@ -1045,8 +1072,6 @@ def list_display_actions(self, obj): "", "{}", ((action(obj),) for action in self.get_list_display_actions()) ) - list_display_actions.short_description = _("actions") - def get_list_display_actions(self): actions = [self.get_edit_link, self.get_requests_link] if conf.COLLECTION_COMMENTS_ENABLED: @@ -1136,6 +1161,7 @@ def has_delete_permission(self, request, obj=None): return False +@admin.register(ConfirmationPage) class ConfirmationPageAdmin(PlaceholderAdminMixin, admin.ModelAdmin): view_on_site = True @@ -1153,6 +1179,7 @@ def _url(regex, fn, name, **kwargs): return url_patterns + super().get_urls() +@admin.register(ConfirmationFormSubmission) class ConfirmationFormSubmissionAdmin(admin.ModelAdmin): list_display = ["moderation_request", "for_step", "submitted_at"] fields = [ @@ -1178,16 +1205,21 @@ def change_view(self, request, object_id, form_url="", extra_context=None): request, object_id, form_url, extra_context=extra_context ) + @admin.display( + description=_("Request") + ) def moderation_request(self, obj): return obj.moderation_request_id - moderation_request.short_description = _("Request") - + @admin.display( + description=_("By User") + ) def show_user(self, obj): return obj.get_by_user_name() - show_user.short_description = _("By User") - + @admin.display( + description=_("Form Data") + ) def form_data(self, obj): data = obj.get_form_data() return format_html_join( @@ -1198,16 +1230,3 @@ def form_data(self, obj): for d in data ), ) - - form_data.short_description = _("Form Data") - - -admin.site.register(ModerationRequestTreeNode, ModerationRequestTreeAdmin) -admin.site.register(ModerationRequest, ModerationRequestAdmin) -admin.site.register(CollectionComment, CollectionCommentAdmin) -admin.site.register(RequestComment, RequestCommentAdmin) -admin.site.register(ModerationCollection, ModerationCollectionAdmin) -admin.site.register(Role, RoleAdmin) -admin.site.register(Workflow, WorkflowAdmin) -admin.site.register(ConfirmationPage, ConfirmationPageAdmin) -admin.site.register(ConfirmationFormSubmission, ConfirmationFormSubmissionAdmin) diff --git a/djangocms_moderation/admin_actions.py b/djangocms_moderation/admin_actions.py index 07fc3eea..926e1282 100644 --- a/djangocms_moderation/admin_actions.py +++ b/djangocms_moderation/admin_actions.py @@ -148,14 +148,14 @@ def add_items_to_collection(modeladmin, request, queryset): args=(), ), version_ids=",".join(version_ids), - return_to_url=request.META.get("HTTP_REFERER", ""), + return_to_url=request.headers.get("referer", ""), ) return HttpResponseRedirect(admin_url) else: modeladmin.message_user( request, _("No suitable items found to add to moderation collection") ) - return HttpResponseRedirect(request.META.get("HTTP_REFERER", "")) + return HttpResponseRedirect(request.headers.get("referer", "")) add_items_to_collection.short_description = _("Add to moderation collection") diff --git a/djangocms_moderation/apps.py b/djangocms_moderation/apps.py index cbeed482..b2b7dbf1 100644 --- a/djangocms_moderation/apps.py +++ b/djangocms_moderation/apps.py @@ -7,6 +7,6 @@ class ModerationConfig(AppConfig): verbose_name = _("django CMS Moderation") def ready(self): - import djangocms_moderation.handlers # noqa: F401 - import djangocms_moderation.monkeypatch # noqa: F401 + import djangocms_moderation.handlers + import djangocms_moderation.monkeypatch import djangocms_moderation.signals # noqa: F401 diff --git a/djangocms_moderation/backends.py b/djangocms_moderation/backends.py index 416606c0..565e9390 100644 --- a/djangocms_moderation/backends.py +++ b/djangocms_moderation/backends.py @@ -20,4 +20,4 @@ def sequential_number_with_identifier_prefix_backend(**kwargs): semi-sequential numbers, prefixed with `workflow.identifier` field, if set """ moderation_request = kwargs["moderation_request"] - return "{}{}".format(moderation_request.workflow.identifier, moderation_request.pk) + return f"{moderation_request.workflow.identifier}{moderation_request.pk}" diff --git a/djangocms_moderation/cms_config.py b/djangocms_moderation/cms_config.py index 6dfba873..4c71d13e 100644 --- a/djangocms_moderation/cms_config.py +++ b/djangocms_moderation/cms_config.py @@ -1,8 +1,11 @@ +from django.contrib import admin from django.core.exceptions import ImproperlyConfigured from cms.app_base import CMSAppConfig, CMSAppExtension from cms.models import PageContent +from .admin_actions import add_items_to_collection + class ModerationExtension(CMSAppExtension): def __init__(self): @@ -16,6 +19,16 @@ def handle_moderation_request_changelist_actions(self, moderation_request_change def handle_moderation_request_changelist_fields(self, moderation_request_changelist_fields): self.moderation_request_changelist_fields.extend(moderation_request_changelist_fields) + def handle_admin_actions(self, moderated_models): + """ + Add items to collection to admin actions in model admin + """ + for model in moderated_models: + if admin.site.is_registered(model): + admin_instance = admin.site._registry[model] + admin_instance.actions = admin_instance.actions or [] + admin_instance.actions.append(add_items_to_collection) + def configure_app(self, cms_config): versioning_enabled = getattr(cms_config, "djangocms_versioning_enabled", False) moderated_models = getattr(cms_config, "moderated_models", []) @@ -24,6 +37,8 @@ def configure_app(self, cms_config): raise ImproperlyConfigured("Versioning needs to be enabled for Moderation") self.moderated_models.extend(moderated_models) + if moderated_models: + self.handle_admin_actions(moderated_models) if hasattr(cms_config, "moderation_request_changelist_actions"): self.handle_moderation_request_changelist_actions(cms_config.moderation_request_changelist_actions) diff --git a/djangocms_moderation/cms_toolbars.py b/djangocms_moderation/cms_toolbars.py index 6181cfb7..a99a7b66 100644 --- a/djangocms_moderation/cms_toolbars.py +++ b/djangocms_moderation/cms_toolbars.py @@ -54,7 +54,7 @@ def _add_moderation_buttons(self): if not helpers.is_registered_for_moderation(self.toolbar.obj): return - if self._is_versioned() and self.toolbar.edit_mode_active: + if self._is_versioned() and (self.toolbar.edit_mode_active or self.toolbar.preview_mode_active): moderation_request = helpers.get_active_moderation_request(self.toolbar.obj) if moderation_request: title, url = helpers.get_moderation_button_title_and_url( @@ -68,7 +68,7 @@ def _add_moderation_buttons(self): opts = ModerationRequest._meta codename = get_permission_codename("add", opts) if not self.request.user.has_perm( - "{app_label}.{codename}".format(app_label=opts.app_label, codename=codename) + f"{opts.app_label}.{codename}" ): return version = Version.objects.get_for_content(self.toolbar.obj) @@ -91,7 +91,7 @@ def _add_moderation_menu(self): opts = ModerationCollection._meta codename = get_permission_codename("change", opts) if not self.request.user.has_perm( - "{app_label}.{codename}".format(app_label=opts.app_label, codename=codename) + f"{opts.app_label}.{codename}" ): return admin_menu = self.toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER) diff --git a/djangocms_moderation/compact.py b/djangocms_moderation/compact.py new file mode 100644 index 00000000..a010273a --- /dev/null +++ b/djangocms_moderation/compact.py @@ -0,0 +1,9 @@ +from django import get_version + +from packaging.version import Version + + +DJANGO_VERSION = get_version() + + +DJANGO_4_1 = Version(DJANGO_VERSION) < Version('4.2') diff --git a/djangocms_moderation/conf.py b/djangocms_moderation/conf.py index e7008be7..e051e5ef 100644 --- a/djangocms_moderation/conf.py +++ b/djangocms_moderation/conf.py @@ -9,7 +9,7 @@ ) CORE_COMPLIANCE_NUMBER_BACKENDS = ( - (UUID_BACKEND, _("Unique alpha-numeric string")), + (UUID_BACKEND, _("Unique alphanumeric string")), (SEQUENTIAL_NUMBER_BACKEND, _("Sequential number")), ( SEQUENTIAL_NUMBER_WITH_IDENTIFIER_PREFIX_BACKEND, diff --git a/djangocms_moderation/contrib/__init__.py b/djangocms_moderation/contrib/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/djangocms_moderation/contrib/moderation_forms/__init__.py b/djangocms_moderation/contrib/moderation_forms/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/djangocms_moderation/contrib/moderation_forms/cms_plugins.py b/djangocms_moderation/contrib/moderation_forms/cms_plugins.py deleted file mode 100644 index 61e23625..00000000 --- a/djangocms_moderation/contrib/moderation_forms/cms_plugins.py +++ /dev/null @@ -1,32 +0,0 @@ -from django.utils.translation import gettext_lazy as _ - -from cms.plugin_pool import plugin_pool - -from aldryn_forms.cms_plugins import FormPlugin - -from djangocms_moderation.helpers import get_page_or_404 -from djangocms_moderation.signals import confirmation_form_submission - -from .models import ModerationForm - - -class ModerationFormPlugin(FormPlugin): - name = _("Moderation Form") - model = ModerationForm - fieldsets = ((None, {"fields": ("name",)}),) - - def form_valid(self, instance, request, form): - fields = form.get_serialized_fields(is_confirmation=False) - fields_as_dicts = [field._asdict() for field in fields] - page = get_page_or_404(request.GET.get("page"), request.GET.get("language")) - - confirmation_form_submission.send( - sender=self.__class__, - page=page, - language=request.GET.get("language"), - user=request.user, - form_data=fields_as_dicts, - ) - - -plugin_pool.register_plugin(ModerationFormPlugin) diff --git a/djangocms_moderation/contrib/moderation_forms/migrations/0001_initial.py b/djangocms_moderation/contrib/moderation_forms/migrations/0001_initial.py deleted file mode 100644 index c2ab629c..00000000 --- a/djangocms_moderation/contrib/moderation_forms/migrations/0001_initial.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 1.11.12 on 2018-05-03 09:15 -from django.db import migrations - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [("aldryn_forms", "0011_auto_20180110_1300")] - - operations = [ - migrations.CreateModel( - name="ModerationForm", - fields=[], - options={ - "verbose_name": "Moderation Form", - "verbose_name_plural": "Moderation Forms", - "proxy": True, - "indexes": [], - }, - bases=("aldryn_forms.formplugin",), - ) - ] diff --git a/djangocms_moderation/contrib/moderation_forms/migrations/__init__.py b/djangocms_moderation/contrib/moderation_forms/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/djangocms_moderation/contrib/moderation_forms/models.py b/djangocms_moderation/contrib/moderation_forms/models.py deleted file mode 100644 index 72027ccd..00000000 --- a/djangocms_moderation/contrib/moderation_forms/models.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.utils.translation import gettext_lazy as _ - -from aldryn_forms.models import FormPlugin - - -class ModerationForm(FormPlugin): - class Meta: - proxy = True - verbose_name = _("Moderation Form") - verbose_name_plural = _("Moderation Forms") diff --git a/djangocms_moderation/emails.py b/djangocms_moderation/emails.py index 319a53e6..761c3b5b 100644 --- a/djangocms_moderation/emails.py +++ b/djangocms_moderation/emails.py @@ -35,7 +35,7 @@ def _send_email( "job_id": collection.job_id, "by_user": by_user, } - template = "djangocms_moderation/emails/moderation-request/{}".format(template) + template = f"djangocms_moderation/emails/moderation-request/{template}" # TODO What language should the email be sent in? e.g. `with force_language(lang):` subject = force_str(subject) @@ -61,7 +61,7 @@ def notify_collection_author(collection, moderation_requests, action, by_user): moderation_requests=moderation_requests, recipients=[collection.author.email], subject=email_subjects[action], - template="{}.txt".format(action), + template=f"{action}.txt", by_user=by_user, ) return status diff --git a/djangocms_moderation/handlers.py b/djangocms_moderation/handlers.py index 768ba335..50a9f9e4 100644 --- a/djangocms_moderation/handlers.py +++ b/djangocms_moderation/handlers.py @@ -11,7 +11,7 @@ def moderation_confirmation_form_submission( sender, page, language, user, form_data, **kwargs ): for field_data in form_data: - if not set(("label", "value")).issubset(field_data): + if not {"label", "value"}.issubset(field_data): raise ValueError("Each field dict should contain label and value keys.") # TODO Confirmation pages are not used/working in 1.0.x yet diff --git a/djangocms_moderation/helpers.py b/djangocms_moderation/helpers.py index 25249302..d266f7cd 100644 --- a/djangocms_moderation/helpers.py +++ b/djangocms_moderation/helpers.py @@ -21,9 +21,13 @@ User = get_user_model() try: - from djangocms_version_locking.helpers import content_is_unlocked_for_user + from djangocms_versioning.helpers import content_is_unlocked_for_user except ImportError: - content_is_unlocked_for_user = None + try: + # Before djangocms-versioning 2.0.0, version locking was in a separate package + from djangocms_version_locking.helpers import content_is_unlocked_for_user + except ImportError: + content_is_unlocked_for_user = None def get_page_or_404(obj_id, language): @@ -75,7 +79,7 @@ def get_active_moderation_request(content_object): If this returns None, it means there is no active_moderation request for this object, and it means it can be submitted for new moderation """ - from djangocms_moderation.models import ModerationRequest # noqa + from djangocms_moderation.models import ModerationRequest version = Version.objects.get_for_content(content_object) diff --git a/djangocms_moderation/locale/en/LC_MESSAGES/django.po b/djangocms_moderation/locale/en/LC_MESSAGES/django.po new file mode 100644 index 00000000..26ebb0bf --- /dev/null +++ b/djangocms_moderation/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,914 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-01-23 23:31+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: admin.py:67 models.py:650 +#: templates/djangocms_moderation/comment_list.html:10 +msgid "Action" +msgstr "" + +#: admin.py:68 models.py:651 +msgid "Actions" +msgstr "" + +#: admin.py:78 +#, python-brace-format +msgid "By {user}" +msgstr "" + +#: admin.py:80 +#: templates/admin/djangocms_moderation/moderationrequest/approve_confirmation.html:25 +#: templates/admin/djangocms_moderation/moderationrequest/delete_confirmation.html:26 +#: templates/admin/djangocms_moderation/moderationrequest/publish_confirmation.html:25 +#: templates/admin/djangocms_moderation/moderationrequest/resubmit_confirmation.html:25 +#: templates/admin/djangocms_moderation/moderationrequest/rework_confirmation.html:25 +#: templates/djangocms_moderation/items_to_collection.html:47 +#: templates/djangocms_moderation/moderation_request_change_list.html:23 +msgid "Status" +msgstr "" + +#: admin.py:99 +msgid "Form Submission" +msgstr "" + +#: admin.py:187 admin.py:1048 +msgid "actions" +msgstr "" + +#: admin.py:222 +msgid "ID" +msgstr "" + +#: admin.py:228 +msgid "Content type" +msgstr "" + +#: admin.py:232 +msgid "Title" +msgstr "" + +#: admin.py:236 +#: templates/admin/djangocms_moderation/moderationrequest/approve_confirmation.html:22 +#: templates/admin/djangocms_moderation/moderationrequest/delete_confirmation.html:23 +#: templates/admin/djangocms_moderation/moderationrequest/publish_confirmation.html:22 +#: templates/admin/djangocms_moderation/moderationrequest/resubmit_confirmation.html:22 +#: templates/admin/djangocms_moderation/moderationrequest/rework_confirmation.html:22 +#: templates/djangocms_moderation/comment_list.html:9 +#: templates/djangocms_moderation/items_to_collection.html:44 +msgid "Author" +msgstr "" + +#: admin.py:257 +msgid "Preview" +msgstr "" + +#: admin.py:267 +msgid "Reviewer" +msgstr "" + +#: admin.py:276 +msgid "Ready for publishing" +msgstr "" + +#: admin.py:278 +msgid "Pending author rework" +msgstr "" + +#: admin.py:282 +#, python-format +msgid "Pending %(role)s approval" +msgstr "" + +#: admin.py:291 +#, python-format +msgid "%(action)s by %(name)s" +msgstr "" + +#: admin.py:293 +msgid "Ready for submission" +msgstr "" + +#: admin.py:457 +#, python-format +msgid "%(count)d request successfully deleted" +msgid_plural "%(count)d requests successfully deleted" +msgstr[0] "" +msgstr[1] "" + +#: admin.py:599 +#, python-format +msgid "%(count)d request successfully resubmitted for review" +msgid_plural "%(count)d requests successfully resubmitted for review" +msgstr[0] "" +msgstr[1] "" + +#: admin.py:646 +#, python-format +msgid "%(count)d request successfully published" +msgid_plural "%(count)d requests successfully published" +msgstr[0] "" +msgstr[1] "" + +#: admin.py:705 +#, python-format +msgid "%(count)d request successfully submitted for rework" +msgid_plural "%(count)d requests successfully submitted for rework" +msgstr[0] "" +msgstr[1] "" + +#: admin.py:797 +#, python-format +msgid "%(count)d request successfully approved" +msgid_plural "%(count)d requests successfully approved" +msgstr[0] "" +msgstr[1] "" + +#: admin.py:863 +#: templates/admin/djangocms_moderation/collectioncomment/change_form.html:10 +msgid "Collection comments" +msgstr "" + +#: admin.py:913 +msgid "User" +msgstr "" + +#: admin.py:947 +msgid "Request comments" +msgstr "" + +#: admin.py:1039 +msgid "reviewers" +msgstr "" + +#: admin.py:1057 +msgid "Modify collection" +msgstr "" + +#: admin.py:1184 models.py:441 +msgid "Request" +msgstr "" + +#: admin.py:1189 +msgid "By User" +msgstr "" + +#: admin.py:1197 +msgid "Question" +msgstr "" + +#: admin.py:1197 +msgid "Answer" +msgstr "" + +#: admin.py:1202 +msgid "Form Data" +msgstr "" + +#: admin_actions.py:36 +msgid "Resubmit changes for review" +msgstr "" + +#: admin_actions.py:53 +msgid "Submit for rework" +msgstr "" + +#: admin_actions.py:66 +msgid "Approve" +msgstr "" + +#: admin_actions.py:82 +msgid "Remove selected" +msgstr "" + +#: admin_actions.py:99 +msgid "Publish selected requests" +msgstr "" + +#: admin_actions.py:156 +msgid "No suitable items found to add to moderation collection" +msgstr "" + +#: admin_actions.py:161 +msgid "Add to moderation collection" +msgstr "" + +#: admin_actions.py:165 +msgid "Add items to a collection to unpublish" +msgstr "" + +#: apps.py:7 +msgid "django CMS Moderation" +msgstr "" + +#: cms_toolbars.py:84 monkeypatch.py:56 +msgid "Submit for moderation" +msgstr "" + +#: cms_toolbars.py:109 +#: templates/djangocms_moderation/moderation_request_change_list.html:48 +msgid "Moderation collections" +msgstr "" + +#: conf.py:12 +msgid "Unique alpha-numeric string" +msgstr "" + +#: conf.py:13 +msgid "Sequential number" +msgstr "" + +#: conf.py:16 +msgid "Sequential number with identifier prefix" +msgstr "" + +#: conf.py:40 +msgid "Default" +msgstr "" + +#: constants.py:18 +msgid "Current page" +msgstr "" + +#: constants.py:19 +msgid "Page children (immediate)" +msgstr "" + +#: constants.py:20 +msgid "Page and children (immediate)" +msgstr "" + +#: constants.py:21 +msgid "Page descendants" +msgstr "" + +#: constants.py:22 +msgid "Page and descendants" +msgstr "" + +#: constants.py:33 +msgid "Resubmitted" +msgstr "" + +#: constants.py:34 +msgid "Started" +msgstr "" + +#: constants.py:35 +msgid "Rejected" +msgstr "" + +#: constants.py:36 +msgid "Approved" +msgstr "" + +#: constants.py:37 constants.py:54 +msgid "Cancelled" +msgstr "" + +#: constants.py:38 +msgid "Finished" +msgstr "" + +#: constants.py:51 +msgid "Collecting" +msgstr "" + +#: constants.py:52 +msgid "In Review" +msgstr "" + +#: constants.py:53 +msgid "Archived" +msgstr "" + +#: contrib/moderation_forms/cms_plugins.py:14 +#: contrib/moderation_forms/models.py:9 +msgid "Moderation Form" +msgstr "" + +#: contrib/moderation_forms/models.py:10 +msgid "Moderation Forms" +msgstr "" + +#: emails.py:16 +#: templates/djangocms_moderation/emails/moderation-request/approved.txt:8 +msgid "Approved moderation requests" +msgstr "" + +#: emails.py:17 +#: templates/djangocms_moderation/emails/moderation-request/rejected.txt:11 +msgid "Rejected moderation requests" +msgstr "" + +#: emails.py:18 +msgid "Request for moderation deleted" +msgstr "" + +#: emails.py:86 +msgid "Review requested" +msgstr "" + +#: filters.py:18 forms.py:64 models.py:213 +msgid "moderator" +msgstr "" + +#: filters.py:36 +msgid "reviewer" +msgstr "" + +#: filters.py:77 +msgid "All" +msgstr "" + +#: forms.py:67 +msgid "comment" +msgstr "" + +#: forms.py:100 forms.py:198 +#, python-brace-format +msgid "Any {role}" +msgstr "" + +#: forms.py:172 +msgid "" +"Your item is either locked, not enabled for moderation,or is part of another " +"active moderation request" +msgid_plural "" +"Your items are either locked, not enabled for moderation,or are part of " +"another active moderation request" +msgstr[0] "" +msgstr[1] "" + +#: forms.py:184 +msgid "Select review group" +msgstr "" + +#: forms.py:205 +msgid "This collection can't be submitted for a review" +msgstr "" + +#: forms.py:222 +msgid "This collection can't be cancelled" +msgstr "" + +#: forms.py:266 +msgid "You can only change your own comments" +msgstr "" + +#: helpers.py:112 +#, python-format +msgid "In collection \"%(collection_name)s (%(collection_id)s)\"" +msgstr "" + +#: helpers.py:117 +#, python-format +msgid "In moderation \"%(collection_name)s (%(collection_id)s)\"" +msgstr "" + +#: models.py:27 +msgid "Plain" +msgstr "" + +#: models.py:28 +msgid "Form" +msgstr "" + +#: models.py:31 models.py:72 models.py:115 +msgid "name" +msgstr "" + +#: models.py:34 +#: templates/admin/djangocms_moderation/moderationrequest/approve_confirmation.html:20 +#: templates/admin/djangocms_moderation/moderationrequest/delete_confirmation.html:21 +#: templates/admin/djangocms_moderation/moderationrequest/publish_confirmation.html:20 +#: templates/admin/djangocms_moderation/moderationrequest/resubmit_confirmation.html:20 +#: templates/admin/djangocms_moderation/moderationrequest/rework_confirmation.html:20 +#: templates/djangocms_moderation/items_to_collection.html:42 +msgid "Content Type" +msgstr "" + +#: models.py:40 +msgid "Template" +msgstr "" + +#: models.py:47 +msgid "Confirmation Page" +msgstr "" + +#: models.py:48 +msgid "Confirmation Pages" +msgstr "" + +#: models.py:77 +msgid "user" +msgstr "" + +#: models.py:80 +msgid "group" +msgstr "" + +#: models.py:84 models.py:740 +msgid "confirmation page" +msgstr "" + +#: models.py:92 +msgid "Role" +msgstr "" + +#: models.py:93 +msgid "Roles" +msgstr "" + +#: models.py:100 +msgid "Can't pick both user and group. Only one." +msgstr "" + +#: models.py:116 +msgid "is default" +msgstr "" + +#: models.py:118 +msgid "identifier" +msgstr "" + +#: models.py:123 +msgid "" +"Identifier is a 'free' field you could use for internal purposes. For " +"example, it could be used as a workflow specific prefix of a compliance " +"number" +msgstr "" + +#: models.py:129 +msgid "requires compliance number?" +msgstr "" + +#: models.py:132 +msgid "" +"Does the Compliance number need to be generated before the moderation " +"request is approved? Please select the compliance number backend below" +msgstr "" + +#: models.py:138 +msgid "compliance number backend" +msgstr "" + +#: models.py:145 +#: templates/djangocms_moderation/moderation_request_change_list.html:24 +msgid "Workflow" +msgstr "" + +#: models.py:146 +msgid "Workflows" +msgstr "" + +#: models.py:161 +msgid "Can't have two default workflows, only one is allowed." +msgstr "" + +#: models.py:171 +msgid "role" +msgstr "" + +#: models.py:173 +msgid "is mandatory" +msgstr "" + +#: models.py:176 models.py:217 +msgid "workflow" +msgstr "" + +#: models.py:185 +msgid "Step" +msgstr "" + +#: models.py:186 +msgid "Steps" +msgstr "" + +#: models.py:210 +msgid "collection name" +msgstr "" + +#: models.py:232 +msgid "collection" +msgstr "" + +#: models.py:234 +msgid "Can change collection author" +msgstr "" + +#: models.py:235 +msgid "Can cancel collection" +msgstr "" + +#: models.py:318 +msgid "Cancelled collection" +msgstr "" + +#: models.py:398 models.py:635 +msgid "moderation_request" +msgstr "" + +#: models.py:416 +msgid "version" +msgstr "" + +#: models.py:419 +msgid "language" +msgstr "" + +#: models.py:422 +msgid "is active" +msgstr "" + +#: models.py:424 +msgid "date sent" +msgstr "" + +#: models.py:426 +msgid "compliance number" +msgstr "" + +#: models.py:435 models.py:695 +msgid "author" +msgstr "" + +#: models.py:442 +#: templates/admin/djangocms_moderation/moderationrequest/change_form.html:10 +#: templates/admin/djangocms_moderation/requestcomment/change_form.html:10 +#: templates/admin/djangocms_moderation/requestcomment/change_list.html:10 +#: templates/djangocms_moderation/moderation_request_change_list.html:49 +msgid "Requests" +msgstr "" + +#: models.py:598 +msgid "status" +msgstr "" + +#: models.py:603 models.py:732 +msgid "by user" +msgstr "" + +#: models.py:609 +msgid "to user" +msgstr "" + +#: models.py:617 +msgid "to role" +msgstr "" + +#: models.py:627 +msgid "step approved" +msgstr "" + +#: models.py:632 models.py:693 +msgid "message" +msgstr "" + +#: models.py:640 +msgid "date taken" +msgstr "" + +#: models.py:720 +msgid "moderation request" +msgstr "" + +#: models.py:726 +msgid "for step" +msgstr "" + +#: models.py:749 +msgid "Confirmation Form Submission" +msgstr "" + +#: models.py:750 +msgid "Confirmation Form Submissions" +msgstr "" + +#: monkeypatch.py:134 monkeypatch.py:144 +msgid "Cannot archive a version in an active moderation collection" +msgstr "" + +#: monkeypatch.py:139 +msgid "Cannot revert when draft version is in an active moderation collection" +msgstr "" + +#: monkeypatch.py:148 +msgid "Version is in an active moderation collection" +msgstr "" + +#: monkeypatch.py:152 +msgid "Cannot edit a version in an active moderation collection" +msgstr "" + +#: templates/admin/djangocms_moderation/collectioncomment/change_form.html:7 +#: templates/admin/djangocms_moderation/collectioncomment/change_list.html:16 +#: templates/admin/djangocms_moderation/moderationrequest/change_form.html:7 +#: templates/admin/djangocms_moderation/requestcomment/change_form.html:7 +#: templates/admin/djangocms_moderation/requestcomment/change_list.html:7 +#: templates/djangocms_moderation/moderation_request_change_list.html:46 +msgid "Home" +msgstr "" + +#: templates/admin/djangocms_moderation/collectioncomment/change_form.html:9 +#: templates/admin/djangocms_moderation/collectioncomment/change_list.html:18 +#: templates/admin/djangocms_moderation/moderationrequest/change_form.html:9 +#: templates/admin/djangocms_moderation/requestcomment/change_form.html:9 +#: templates/admin/djangocms_moderation/requestcomment/change_list.html:9 +msgid "Collections" +msgstr "" + +#: templates/admin/djangocms_moderation/collectioncomment/change_form.html:11 +#: templates/admin/djangocms_moderation/moderationrequest/change_form.html:11 +#: templates/admin/djangocms_moderation/requestcomment/change_form.html:12 +#, python-format +msgid "Add %(name)s" +msgstr "" + +#: templates/admin/djangocms_moderation/collectioncomment/change_list.html:7 +#, python-format +msgid "" +"\n" +" %(collection_name)s comments\n" +" " +msgstr "" + +#: templates/admin/djangocms_moderation/collectioncomment/change_list.html:19 +#: templates/admin/djangocms_moderation/requestcomment/change_form.html:11 +#: templates/admin/djangocms_moderation/requestcomment/change_list.html:11 +msgid "Comments" +msgstr "" + +#: templates/admin/djangocms_moderation/moderationrequest/approve_confirmation.html:3 +#: templates/admin/djangocms_moderation/moderationrequest/delete_confirmation.html:3 +#: templates/admin/djangocms_moderation/moderationrequest/publish_confirmation.html:3 +#: templates/admin/djangocms_moderation/moderationrequest/resubmit_confirmation.html:3 +#: templates/admin/djangocms_moderation/moderationrequest/rework_confirmation.html:3 +msgid "Delete Confirmation" +msgstr "" + +#: templates/admin/djangocms_moderation/moderationrequest/approve_confirmation.html:14 +msgid "Are you sure you want to approve these items for publishing?" +msgstr "" + +#: templates/admin/djangocms_moderation/moderationrequest/approve_confirmation.html:19 +#: templates/admin/djangocms_moderation/moderationrequest/delete_confirmation.html:20 +#: templates/admin/djangocms_moderation/moderationrequest/publish_confirmation.html:19 +#: templates/admin/djangocms_moderation/moderationrequest/resubmit_confirmation.html:19 +#: templates/admin/djangocms_moderation/moderationrequest/rework_confirmation.html:19 +msgid "Id" +msgstr "" + +#: templates/admin/djangocms_moderation/moderationrequest/approve_confirmation.html:21 +#: templates/admin/djangocms_moderation/moderationrequest/delete_confirmation.html:22 +#: templates/admin/djangocms_moderation/moderationrequest/publish_confirmation.html:21 +#: templates/admin/djangocms_moderation/moderationrequest/resubmit_confirmation.html:21 +#: templates/admin/djangocms_moderation/moderationrequest/rework_confirmation.html:21 +#: templates/djangocms_moderation/items_to_collection.html:43 +msgid "Identifier" +msgstr "" + +#: templates/admin/djangocms_moderation/moderationrequest/approve_confirmation.html:23 +#: templates/admin/djangocms_moderation/moderationrequest/delete_confirmation.html:24 +#: templates/admin/djangocms_moderation/moderationrequest/publish_confirmation.html:23 +#: templates/admin/djangocms_moderation/moderationrequest/resubmit_confirmation.html:23 +#: templates/admin/djangocms_moderation/moderationrequest/rework_confirmation.html:23 +#: templates/djangocms_moderation/items_to_collection.html:45 +msgid "Edit Date" +msgstr "" + +#: templates/admin/djangocms_moderation/moderationrequest/approve_confirmation.html:24 +#: templates/admin/djangocms_moderation/moderationrequest/delete_confirmation.html:25 +#: templates/admin/djangocms_moderation/moderationrequest/publish_confirmation.html:24 +#: templates/admin/djangocms_moderation/moderationrequest/resubmit_confirmation.html:24 +#: templates/admin/djangocms_moderation/moderationrequest/rework_confirmation.html:24 +#: templates/djangocms_moderation/items_to_collection.html:46 +msgid "Version #" +msgstr "" + +#: templates/admin/djangocms_moderation/moderationrequest/approve_confirmation.html:47 +#: templates/admin/djangocms_moderation/moderationrequest/delete_confirmation.html:48 +#: templates/admin/djangocms_moderation/moderationrequest/publish_confirmation.html:47 +#: templates/admin/djangocms_moderation/moderationrequest/resubmit_confirmation.html:47 +#: templates/admin/djangocms_moderation/moderationrequest/rework_confirmation.html:47 +msgid "Yes, I\\" +msgstr "" + +#: templates/admin/djangocms_moderation/moderationrequest/approve_confirmation.html:51 +#: templates/admin/djangocms_moderation/moderationrequest/delete_confirmation.html:52 +#: templates/admin/djangocms_moderation/moderationrequest/publish_confirmation.html:51 +#: templates/admin/djangocms_moderation/moderationrequest/resubmit_confirmation.html:51 +#: templates/admin/djangocms_moderation/moderationrequest/rework_confirmation.html:51 +msgid "No, take me back" +msgstr "" + +#: templates/admin/djangocms_moderation/moderationrequest/delete_confirmation.html:15 +msgid "Are you sure you want to remove these items from this collection?" +msgstr "" + +#: templates/admin/djangocms_moderation/moderationrequest/publish_confirmation.html:14 +msgid "Are you sure you want to publish these items?" +msgstr "" + +#: templates/admin/djangocms_moderation/moderationrequest/resubmit_confirmation.html:14 +msgid " Are you sure you want to re-submit these items for review?" +msgstr "" + +#: templates/admin/djangocms_moderation/moderationrequest/rework_confirmation.html:14 +msgid "Are you sure you want to submit these items back for rework?" +msgstr "" + +#: templates/djangocms_moderation/cancel_collection.html:12 +msgid "Are you sure you'd like to cancel this collection?" +msgstr "" + +#: templates/djangocms_moderation/cancel_collection.html:16 +msgid "Cancel" +msgstr "" + +#: templates/djangocms_moderation/cancel_collection.html:18 +msgid "Confirm" +msgstr "" + +#: templates/djangocms_moderation/comment_icon.html:2 +msgid "View Comments" +msgstr "" + +#: templates/djangocms_moderation/comment_list.html:8 +#: templates/djangocms_moderation/emails/moderation-request/approved.txt:20 +#: templates/djangocms_moderation/emails/moderation-request/cancelled.txt:20 +#: templates/djangocms_moderation/emails/moderation-request/rejected.txt:23 +#: templates/djangocms_moderation/emails/moderation-request/request.txt:20 +msgid "Comment" +msgstr "" + +#: templates/djangocms_moderation/comment_list.html:11 +msgid "Date" +msgstr "" + +#: templates/djangocms_moderation/comment_list.html:21 +msgid "no comment provided" +msgstr "" + +#: templates/djangocms_moderation/confirmation_page.html:9 +#: templates/djangocms_moderation/select_workflow_form.html:27 +msgid "Next" +msgstr "" + +#: templates/djangocms_moderation/edit_icon.html:2 +msgid "Edit Collection Settings" +msgstr "" + +#: templates/djangocms_moderation/emails/moderation-request/approved.txt:2 +#, python-format +msgid "" +"\n" +"Hello %(collection_author)s, %(by_user)s has approved some moderation\n" +"requests in the collection %(collection_name)s.\n" +msgstr "" + +#: templates/djangocms_moderation/emails/moderation-request/approved.txt:25 +#: templates/djangocms_moderation/emails/moderation-request/cancelled.txt:25 +#: templates/djangocms_moderation/emails/moderation-request/rejected.txt:28 +#: templates/djangocms_moderation/emails/moderation-request/request.txt:25 +#: templates/djangocms_moderation/moderation_request_change_list.html:22 +msgid "Job ID" +msgstr "" + +#: templates/djangocms_moderation/emails/moderation-request/approved.txt:29 +#: templates/djangocms_moderation/emails/moderation-request/cancelled.txt:29 +#: templates/djangocms_moderation/emails/moderation-request/rejected.txt:32 +#: templates/djangocms_moderation/emails/moderation-request/request.txt:29 +msgid "Admin Url" +msgstr "" + +#: templates/djangocms_moderation/emails/moderation-request/cancelled.txt:2 +#, python-format +msgid "" +"\n" +"Hello %(collection_author)s, the following moderation requests in the " +"collection\n" +"%(collection_name)s have been cancelled\n" +msgstr "" + +#: templates/djangocms_moderation/emails/moderation-request/cancelled.txt:8 +msgid "Cancelled moderation requests" +msgstr "" + +#: templates/djangocms_moderation/emails/moderation-request/rejected.txt:2 +#, python-format +msgid "" +"\n" +"Hello %(collection_author)s, %(by_user)s has rejected some moderation\n" +"requests in the collection %(collection_name)s.\n" +msgstr "" + +#: templates/djangocms_moderation/emails/moderation-request/rejected.txt:7 +msgid "" +"You can resubmit the necessary changes for another review, or remove the " +"moderation requests from the collection." +msgstr "" + +#: templates/djangocms_moderation/emails/moderation-request/request.txt:2 +#, python-format +msgid "" +"\n" +"Hello, %(by_user)s has requested your approval for\n" +"changes in the collection %(collection_name)s.\n" +msgstr "" + +#: templates/djangocms_moderation/emails/moderation-request/request.txt:8 +msgid "Items included in this request" +msgstr "" + +#: templates/djangocms_moderation/items_to_collection.html:11 +msgid "Add items to collection" +msgstr "" + +#: templates/djangocms_moderation/items_to_collection.html:13 +#, python-format +msgid "" +"\n" +" Add '%(content_name)s' to collection\n" +" " +msgstr "" + +#: templates/djangocms_moderation/items_to_collection.html:18 +msgid "No items to add" +msgstr "" + +#: templates/djangocms_moderation/items_to_collection.html:36 +msgid "The selected collection currently contains the following items:" +msgstr "" + +#: templates/djangocms_moderation/items_to_collection.html:66 +msgid "This collection contains no items yet" +msgstr "" + +#: templates/djangocms_moderation/items_to_collection.html:70 +#: templates/djangocms_moderation/request_form.html:37 +msgid "Submit" +msgstr "" + +#: templates/djangocms_moderation/moderation_request_change_list.html:25 +msgid "Owner" +msgstr "" + +#: templates/djangocms_moderation/moderation_request_change_list.html:34 +msgid "Cancel this collection" +msgstr "" + +#: templates/djangocms_moderation/moderation_request_change_list.html:39 +#: views.py:221 +msgid "Submit collection for review" +msgstr "" + +#: templates/djangocms_moderation/request_form.html:7 +#: templates/djangocms_moderation/select_workflow_form.html:7 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "" +msgstr[1] "" + +#: templates/djangocms_moderation/request_form.html:26 +msgid "Completed Forms" +msgstr "" + +#: templates/djangocms_moderation/request_icon.html:2 +msgid "View Requests" +msgstr "" + +#: views.py:74 +#, python-format +msgid "%(count)d item successfully added to moderation collection" +msgid_plural "%(count)d items successfully added to moderation collection" +msgstr[0] "" +msgstr[1] "" + +#: views.py:206 +msgid "Your collection has been submitted for review" +msgstr "" + +#: views.py:250 +msgid "Your collection has been cancelled" +msgstr "" + +#: views.py:259 +msgid "Cancel collection" +msgstr "" diff --git a/djangocms_moderation/migrations/0001_initial.py b/djangocms_moderation/migrations/0001_initial.py index b1464ba4..c714385e 100644 --- a/djangocms_moderation/migrations/0001_initial.py +++ b/djangocms_moderation/migrations/0001_initial.py @@ -311,7 +311,7 @@ class Migration(migrations.Migration): models.CharField( blank=True, default="", - help_text="Identifier is a 'free' field you could use for internal purposes. For example, it could be used as a workflow specific prefix of a compliance number", + help_text="Identifier is a 'free' field you could use for internal purposes. For example, it could be used as a workflow specific prefix of a compliance number", # noqa: E501 max_length=128, verbose_name="identifier", ), @@ -320,7 +320,7 @@ class Migration(migrations.Migration): "requires_compliance_number", models.BooleanField( default=False, - help_text="Does the Compliance number need to be generated before the moderation request is approved? Please select the compliance number backend below", + help_text="Does the Compliance number need to be generated before the moderation request is approved? Please select the compliance number backend below", # noqa: E501 verbose_name="requires compliance number?", ), ), @@ -454,14 +454,14 @@ class Migration(migrations.Migration): ), ), migrations.AlterUniqueTogether( - name="workflowstep", unique_together=set([("role", "workflow")]) + name="workflowstep", unique_together={("role", "workflow")} ), migrations.AlterUniqueTogether( name="moderationrequest", - unique_together=set([("collection", "object_id", "content_type")]), + unique_together={("collection", "object_id", "content_type")}, ), migrations.AlterUniqueTogether( name="confirmationformsubmission", - unique_together=set([("request", "for_step")]), + unique_together={("request", "for_step")}, ), ] diff --git a/djangocms_moderation/migrations/0001_squashed_0017_auto_20220831_0727.py b/djangocms_moderation/migrations/0001_squashed_0017_auto_20220831_0727.py new file mode 100644 index 00000000..df8856a5 --- /dev/null +++ b/djangocms_moderation/migrations/0001_squashed_0017_auto_20220831_0727.py @@ -0,0 +1,663 @@ +# Generated by Django 3.2.25 on 2024-03-11 10:45 + +import cms.models.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + replaces = [ + ("djangocms_moderation", "0001_initial"), + ("djangocms_moderation", "0002_auto_20180905_1152"), + ("djangocms_moderation", "0003_auto_20180903_1206"), + ("djangocms_moderation", "0004_auto_20180907_1206"), + ("djangocms_moderation", "0005_auto_20180919_1348"), + ("djangocms_moderation", "0006_auto_20181001_1840"), + ("djangocms_moderation", "0007_auto_20181002_1725"), + ("djangocms_moderation", "0008_auto_20181002_1833"), + ("djangocms_moderation", "0009_auto_20181005_1534"), + ("djangocms_moderation", "0010_auto_20181008_1317"), + ("djangocms_moderation", "0011_auto_20181008_1328"), + ("djangocms_moderation", "0012_auto_20181016_1319"), + ("djangocms_moderation", "0013_auto_20181122_1110"), + ("djangocms_moderation", "0014_auto_20190313_1638"), + ("djangocms_moderation", "0014_auto_20190315_1723"), + ("djangocms_moderation", "0016_moderationrequesttreenode"), + ("djangocms_moderation", "0017_auto_20220831_0727"), + ] + + initial = True + + dependencies = [ + ("cms", "0028_remove_page_placeholders"), + ("djangocms_versioning", "0010_version_proxies"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("auth", "0008_alter_user_username_max_length"), + ("contenttypes", "0002_remove_content_type_name"), + ("cms", "0020_old_tree_cleanup"), + ] + + operations = [ + migrations.CreateModel( + name="ConfirmationPage", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=50, verbose_name="name")), + ( + "content_type", + models.CharField( + choices=[("plain", "Plain"), ("form", "Form")], + default="form", + max_length=50, + verbose_name="Content Type", + ), + ), + ( + "template", + models.CharField( + choices=[ + ( + "djangocms_moderation/moderation_confirmation.html", + "Default", + ) + ], + default="djangocms_moderation/moderation_confirmation.html", + max_length=100, + verbose_name="Template", + ), + ), + ( + "content", + cms.models.fields.PlaceholderField( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + slotname="confirmation_content", + to="cms.placeholder", + ), + ), + ], + options={ + "verbose_name": "Confirmation Page", + "verbose_name_plural": "Confirmation Pages", + }, + ), + migrations.CreateModel( + name="ModerationCollection", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField(max_length=128, verbose_name="collection name"), + ), + ( + "status", + models.CharField( + choices=[ + ("COLLECTING", "Collecting"), + ("IN_REVIEW", "In Review"), + ("ARCHIVED", "Archived"), + ("CANCELLED", "Cancelled"), + ], + db_index=True, + default="COLLECTING", + max_length=10, + ), + ), + ("date_created", models.DateTimeField(auto_now_add=True)), + ("date_modified", models.DateTimeField(auto_now=True)), + ( + "author", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to=settings.AUTH_USER_MODEL, + verbose_name="author", + ), + ), + ], + ), + migrations.CreateModel( + name="ModerationRequest", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "language", + models.CharField( + choices=[("en", "English"), ("de", "German")], + max_length=20, + verbose_name="language", + ), + ), + ( + "is_active", + models.BooleanField( + db_index=True, default=True, verbose_name="is active" + ), + ), + ( + "date_sent", + models.DateTimeField(auto_now_add=True, verbose_name="date sent"), + ), + ( + "compliance_number", + models.CharField( + blank=True, + editable=False, + max_length=32, + null=True, + unique=True, + verbose_name="compliance number", + ), + ), + ( + "collection", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="moderation_requests", + to="djangocms_moderation.moderationcollection", + ), + ), + ( + "version", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="djangocms_versioning.version", + verbose_name="version", + ), + ), + ( + "author", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to=settings.AUTH_USER_MODEL, + verbose_name="author", + ), + ), + ], + options={ + "verbose_name": "Request", + "verbose_name_plural": "Requests", + "ordering": ["id"], + "unique_together": {("collection", "version")}, + }, + ), + migrations.CreateModel( + name="Role", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField(max_length=120, unique=True, verbose_name="name"), + ), + ( + "confirmation_page", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="djangocms_moderation.confirmationpage", + verbose_name="confirmation page", + ), + ), + ( + "group", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="auth.group", + verbose_name="group", + ), + ), + ( + "user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="user", + ), + ), + ], + options={ + "verbose_name": "Role", + "verbose_name_plural": "Roles", + }, + ), + migrations.CreateModel( + name="Workflow", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField(max_length=120, unique=True, verbose_name="name"), + ), + ( + "is_default", + models.BooleanField(default=False, verbose_name="is default"), + ), + ( + "identifier", + models.CharField( + blank=True, + default="", + help_text="Identifier is a 'free' field you could use for internal purposes. For example, " + "it could be used as a workflow specific prefix of a compliance number", + max_length=128, + verbose_name="identifier", + ), + ), + ( + "requires_compliance_number", + models.BooleanField( + default=False, + help_text="Does the Compliance number need to be generated before the moderation request is " + "approved? Please select the compliance number backend below", + verbose_name="requires compliance number?", + ), + ), + ( + "compliance_number_backend", + models.CharField( + choices=[ + ( + "djangocms_moderation.backends.uuid4_backend", + "Unique alpha-numeric string", + + ), + ( + "djangocms_moderation.backends.sequential_number_backend", + "Sequential number", + ), + ( + "djangocms_moderation.backends.sequential_number_with_identifier_prefix_backend", + "Sequential number with identifier prefix", + ), + ], + default="djangocms_moderation.backends.uuid4_backend", + max_length=255, + verbose_name="compliance number backend", + ), + ), + ], + options={ + "verbose_name": "Workflow", + "verbose_name_plural": "Workflows", + }, + ), + migrations.CreateModel( + name="WorkflowStep", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "is_required", + models.BooleanField(default=True, verbose_name="is mandatory"), + ), + ("order", models.PositiveIntegerField()), + ( + "role", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="djangocms_moderation.role", + verbose_name="role", + ), + ), + ( + "workflow", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="steps", + to="djangocms_moderation.workflow", + verbose_name="workflow", + ), + ), + ], + options={ + "verbose_name": "Step", + "verbose_name_plural": "Steps", + "ordering": ("order",), + "unique_together": {("role", "workflow")}, + }, + ), + migrations.AddField( + model_name="moderationcollection", + name="workflow", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="moderation_collections", + to="djangocms_moderation.workflow", + verbose_name="workflow", + ), + ), + migrations.CreateModel( + name="RequestComment", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("message", models.TextField(blank=True, verbose_name="message")), + ("date_created", models.DateTimeField(auto_now_add=True)), + ( + "author", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="author", + ), + ), + ( + "moderation_request", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="djangocms_moderation.moderationrequest", + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.AlterField( + model_name="moderationcollection", + name="author", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + migrations.AlterModelOptions( + name="moderationcollection", + options={ + "permissions": (("can_change_author", "Can change collection author"),), + "verbose_name": "collection", + }, + ), + migrations.CreateModel( + name="CollectionComment", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("message", models.TextField(blank=True, verbose_name="message")), + ("date_created", models.DateTimeField(auto_now_add=True)), + ( + "author", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="author", + ), + ), + ( + "collection", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="djangocms_moderation.moderationcollection", + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="ConfirmationFormSubmission", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("data", models.TextField(blank=True, editable=False)), + ("submitted_at", models.DateTimeField(auto_now_add=True)), + ( + "by_user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to=settings.AUTH_USER_MODEL, + verbose_name="by user", + ), + ), + ( + "confirmation_page", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="djangocms_moderation.confirmationpage", + verbose_name="confirmation page", + ), + ), + ( + "for_step", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="djangocms_moderation.workflowstep", + verbose_name="for step", + ), + ), + ( + "moderation_request", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="form_submissions", + to="djangocms_moderation.moderationrequest", + verbose_name="moderation request", + ), + ), + ], + options={ + "verbose_name": "Confirmation Form Submission", + "verbose_name_plural": "Confirmation Form Submissions", + "unique_together": {("moderation_request", "for_step")}, + }, + ), + migrations.CreateModel( + name="ModerationRequestAction", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "action", + models.CharField( + choices=[ + ("resubmitted", "Resubmitted"), + ("start", "Started"), + ("rejected", "Rejected"), + ("approved", "Approved"), + ("cancelled", "Cancelled"), + ("finished", "Finished"), + ], + max_length=30, + verbose_name="status", + ), + ), + ("message", models.TextField(blank=True, verbose_name="message")), + ( + "date_taken", + models.DateTimeField(auto_now_add=True, verbose_name="date taken"), + ), + ("is_archived", models.BooleanField(default=False)), + ( + "by_user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to=settings.AUTH_USER_MODEL, + verbose_name="by user", + ), + ), + ( + "step_approved", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="djangocms_moderation.workflowstep", + verbose_name="step approved", + ), + ), + ( + "to_role", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="djangocms_moderation.role", + verbose_name="to role", + ), + ), + ( + "to_user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="to user", + ), + ), + ( + "moderation_request", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="actions", + to="djangocms_moderation.moderationrequest", + verbose_name="moderation_request", + ), + ), + ], + options={ + "verbose_name": "Action", + "verbose_name_plural": "Actions", + "ordering": ("date_taken",), + }, + ), + migrations.AlterField( + model_name="moderationcollection", + name="author", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="moderator", + ), + ), + migrations.AlterModelOptions( + name="moderationcollection", + options={ + "permissions": ( + ("can_change_author", "Can change collection author"), + ("cancel_moderationcollection", "Can cancel collection"), + ), + "verbose_name": "collection", + }, + ), + migrations.CreateModel( + name="ModerationRequestTreeNode", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("path", models.CharField(max_length=255, unique=True)), + ("depth", models.PositiveIntegerField()), + ("numchild", models.PositiveIntegerField(default=0)), + ( + "moderation_request", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="djangocms_moderation.moderationrequest", + verbose_name="moderation_request", + ), + ), + ], + options={ + "ordering": ("id",), + }, + ), + ] diff --git a/djangocms_moderation/migrations/0003_auto_20180903_1206.py b/djangocms_moderation/migrations/0003_auto_20180903_1206.py index 8bbc691c..46e11fa2 100644 --- a/djangocms_moderation/migrations/0003_auto_20180903_1206.py +++ b/djangocms_moderation/migrations/0003_auto_20180903_1206.py @@ -22,7 +22,7 @@ class Migration(migrations.Migration): preserve_default=False, ), migrations.AlterUniqueTogether( - name="moderationrequest", unique_together=set([("collection", "version")]) + name="moderationrequest", unique_together={("collection", "version")} ), migrations.RemoveField(model_name="moderationrequest", name="content_type"), migrations.RemoveField(model_name="moderationrequest", name="object_id"), diff --git a/djangocms_moderation/migrations/0007_auto_20181002_1725.py b/djangocms_moderation/migrations/0007_auto_20181002_1725.py index 651be569..ba92c49c 100644 --- a/djangocms_moderation/migrations/0007_auto_20181002_1725.py +++ b/djangocms_moderation/migrations/0007_auto_20181002_1725.py @@ -13,4 +13,4 @@ class Migration(migrations.Migration): dependencies = [("djangocms_moderation", "0006_auto_20181001_1840")] - operations = [migrations.RunPython(moderationrequest_author)] + operations = [migrations.RunPython(moderationrequest_author, elidable=True)] diff --git a/djangocms_moderation/migrations/0010_auto_20181008_1317.py b/djangocms_moderation/migrations/0010_auto_20181008_1317.py index a6f69b54..87168449 100644 --- a/djangocms_moderation/migrations/0010_auto_20181008_1317.py +++ b/djangocms_moderation/migrations/0010_auto_20181008_1317.py @@ -33,6 +33,6 @@ class Migration(migrations.Migration): operations = [ migrations.RunPython( - forward_copy_moderation_requests, reverse_copy_moderation_requests + forward_copy_moderation_requests, reverse_copy_moderation_requests, elidable=True ) ] diff --git a/djangocms_moderation/migrations/0011_auto_20181008_1328.py b/djangocms_moderation/migrations/0011_auto_20181008_1328.py index 23829d74..cca7ac2b 100644 --- a/djangocms_moderation/migrations/0011_auto_20181008_1328.py +++ b/djangocms_moderation/migrations/0011_auto_20181008_1328.py @@ -1,6 +1,4 @@ # Generated by Django 1.11.13 on 2018-10-08 12:28 -from django.conf import settings -from django.db import migrations from django.db import migrations, models import django.db.models.deletion @@ -13,7 +11,7 @@ class Migration(migrations.Migration): # AlterUniqueTogether needs to happen before removing field migrations.AlterUniqueTogether( name="confirmationformsubmission", - unique_together=set([("moderation_request", "for_step")]), + unique_together={("moderation_request", "for_step")}, ), migrations.RemoveField(model_name="confirmationformsubmission", name="request"), migrations.RemoveField(model_name="moderationrequestaction", name="request"), diff --git a/djangocms_moderation/migrations/0014_auto_20190315_1723.py b/djangocms_moderation/migrations/0014_auto_20190315_1723.py index d420dc9c..6cc014da 100644 --- a/djangocms_moderation/migrations/0014_auto_20190315_1723.py +++ b/djangocms_moderation/migrations/0014_auto_20190315_1723.py @@ -11,6 +11,11 @@ class Migration(migrations.Migration): operations = [ migrations.AlterModelOptions( name='moderationcollection', - options={'permissions': (('can_change_author', 'Can change collection author'), ('cancel_moderationcollection', 'Can cancel collection')), 'verbose_name': 'collection'}, + options={'permissions': ( + ('can_change_author', 'Can change collection author'), + ('cancel_moderationcollection', 'Can cancel collection') + ), + 'verbose_name': 'collection' + }, ), ] diff --git a/djangocms_moderation/migrations/0016_moderationrequesttreenode.py b/djangocms_moderation/migrations/0016_moderationrequesttreenode.py index 592c7f89..6f37e42c 100644 --- a/djangocms_moderation/migrations/0016_moderationrequesttreenode.py +++ b/djangocms_moderation/migrations/0016_moderationrequesttreenode.py @@ -17,7 +17,11 @@ class Migration(migrations.Migration): ('path', models.CharField(max_length=255, unique=True)), ('depth', models.PositiveIntegerField()), ('numchild', models.PositiveIntegerField(default=0)), - ('moderation_request', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='djangocms_moderation.ModerationRequest', verbose_name='moderation_request')), + ('moderation_request', models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to='djangocms_moderation.ModerationRequest', + verbose_name='moderation_request') + ), # noqa: E124 ], options={ 'ordering': ('id',), diff --git a/djangocms_moderation/models.py b/djangocms_moderation/models.py index 1e92dccc..c143770f 100644 --- a/djangocms_moderation/models.py +++ b/djangocms_moderation/models.py @@ -22,6 +22,16 @@ from . import conf, constants, signals # isort:skip +try: + from djangocms_versioning.helpers import version_is_locked + + def version_is_unlocked_for_moderation(version, user): + return version_is_locked(version) is None +except ImportError: + def version_is_unlocked_for_moderation(version, user): + return version.created_by == user + + class ConfirmationPage(models.Model): CONTENT_TYPES = ( (constants.CONTENT_TYPE_PLAIN, _("Plain")), @@ -144,6 +154,7 @@ class Workflow(models.Model): class Meta: verbose_name = _("Workflow") verbose_name_plural = _("Workflows") + ordering = ("name",) def __str__(self): return self.name @@ -240,7 +251,7 @@ def __str__(self): @property def job_id(self): - return "{}".format(self.pk) + return f"{self.pk}" @property def author_name(self): @@ -381,8 +392,8 @@ def _add_nested_children(self, version, parent_node): for child_version in get_moderated_children_from_placeholder( placeholder, version.versionable.grouping_values(parent) ): - # Don't add the version if it's already part of the collection or another users item - if version.created_by == child_version.created_by: + # Don't add the version if it's already part of the collection or locked by another user + if version_is_unlocked_for_moderation(child_version, version.created_by): moderation_request, _added_items = self.add_version( child_version, parent=parent_node, include_children=True ) @@ -444,7 +455,7 @@ class Meta: ordering = ["id"] def __str__(self): - return "{} {}".format(self.pk, self.version_id) + return f"{self.pk} {self.version_id}" @cached_property def workflow(self): @@ -651,7 +662,7 @@ class Meta: verbose_name_plural = _("Actions") def __str__(self): - return "{} - {}".format(self.moderation_request_id, self.get_action_display()) + return f"{self.moderation_request_id} - {self.get_action_display()}" def get_by_user_name(self): if not self.to_user: @@ -686,7 +697,7 @@ def save(self, **kwargs): if next_step: self.to_role_id = next_step.role_id - super(ModerationRequestAction, self).save(**kwargs) + super().save(**kwargs) class AbstractComment(models.Model): @@ -743,7 +754,7 @@ class ConfirmationFormSubmission(models.Model): ) def __str__(self): - return "{} - {}".format(self.request_id, self.for_step) + return f"{self.request_id} - {self.for_step}" class Meta: verbose_name = _("Confirmation Form Submission") diff --git a/djangocms_moderation/monkeypatch.py b/djangocms_moderation/monkeypatch.py index a439850e..a1ad0f06 100644 --- a/djangocms_moderation/monkeypatch.py +++ b/djangocms_moderation/monkeypatch.py @@ -120,6 +120,17 @@ def inner(self, obj, request): return inner +def _check_registered_for_moderation(message): + """ + Fail check if object is registered for moderation + """ + def inner(version, user): + if is_registered_for_moderation(version.content): + raise ConditionFailed(message) + + return inner + + admin.VersionAdmin._get_publish_link = _get_publish_link( admin.VersionAdmin._get_publish_link ) @@ -152,5 +163,8 @@ def inner(self, obj, request): _("Cannot edit a version in an active moderation collection") ) ] +models.Version.check_publish += [ + _check_registered_for_moderation(_("Content cannot be published directly. Use the moderation process.")) +] fields.PlaceholderRelationField.default_checks += [_is_placeholder_review_unlocked] diff --git a/djangocms_moderation/static/djangocms_moderation/css/actions.css b/djangocms_moderation/static/djangocms_moderation/css/actions.css index e86c02d9..d1c2c989 100644 --- a/djangocms_moderation/static/djangocms_moderation/css/actions.css +++ b/djangocms_moderation/static/djangocms_moderation/css/actions.css @@ -33,9 +33,9 @@ a.btn.cms-moderation-action-btn span { display: inline-block; } span.svg-juxtaposed-font { - text-rendering: auto; - display: inline-block; - line-height: 1rem; - vertical-align: 20%; - margin-top: -2px !important; + text-rendering: auto; + display: inline-block; + line-height: 1rem; + vertical-align: 20%; + margin-top: -2px !important; } \ No newline at end of file diff --git a/djangocms_moderation/static/djangocms_moderation/js/burger.js b/djangocms_moderation/static/djangocms_moderation/js/burger.js index 85f6fa8d..93efd262 100644 --- a/djangocms_moderation/static/djangocms_moderation/js/burger.js +++ b/djangocms_moderation/static/djangocms_moderation/js/burger.js @@ -12,7 +12,7 @@ .on('click', function (e) { e.preventDefault(); - // action currently being targetted + // action currently being targeted let action = $(e.currentTarget); // get the form method being used? let formMethod = action.attr('class').indexOf('cms-form-get-method') === 1 ? 'POST' : 'GET'; @@ -147,6 +147,7 @@ let li_anchor = document.createElement('a'); const itemId = $(item).attr('id'); const itemTarget = $(item).attr('target'); + const itemDataPopup = $(item).attr('data-popup'); li_anchor.setAttribute('class', 'cms-actions-dropdown-menu-item-anchor'); li_anchor.setAttribute('href', $(item).attr('href')); @@ -158,6 +159,10 @@ if (itemTarget !== undefined) { li_anchor.setAttribute('target', itemTarget); } + // Copy the data-popup attribute if it is set + if (itemDataPopup !== undefined) { + li_anchor.setAttribute('data-popup', itemDataPopup); + } if ($(item).hasClass('cms-form-get-method')) { // Ensure the fake-form selector is propagated to the new anchor diff --git a/djangocms_moderation/static/djangocms_moderation/js/libs/diffview.js b/djangocms_moderation/static/djangocms_moderation/js/libs/diffview.js index 65e2e411..999f8f6f 100644 --- a/djangocms_moderation/static/djangocms_moderation/js/libs/diffview.js +++ b/djangocms_moderation/static/djangocms_moderation/js/libs/diffview.js @@ -177,7 +177,7 @@ var diffview = { var botrows = []; for (var i = 0; i < rowcnt; i++) { - // jump ahead if we've alredy provided leading context or if this is the first range + // jump ahead if we've already provided leading context or if this is the first range if ( contextSize && opcodes.length > 1 && diff --git a/djangocms_moderation/templates/admin/djangocms_moderation/treebeard/tree_change_list.html b/djangocms_moderation/templates/admin/djangocms_moderation/treebeard/tree_change_list.html index 401ee10b..d78b39bf 100644 --- a/djangocms_moderation/templates/admin/djangocms_moderation/treebeard/tree_change_list.html +++ b/djangocms_moderation/templates/admin/djangocms_moderation/treebeard/tree_change_list.html @@ -1,15 +1,6 @@ {# Used for MP and NS trees #} -{% extends "admin/change_list.html" %} -{% load admin_list admin_tree i18n %} - -{% block extrastyle %} - {{ block.super }} - {% treebeard_css %} -{% endblock %} - -{% block extrahead %} - {{ block.super }} -{% endblock %} +{% extends "admin/tree_change_list.html" %} +{% load admin_list admin_tree %} {% block result_list %} {% if action_form and actions_on_top and cl.full_result_count %} diff --git a/djangocms_moderation/templates/djangocms_moderation/base_confirmation_build.html b/djangocms_moderation/templates/djangocms_moderation/base_confirmation_build.html index 89b3eab2..cc55a8dd 100644 --- a/djangocms_moderation/templates/djangocms_moderation/base_confirmation_build.html +++ b/djangocms_moderation/templates/djangocms_moderation/base_confirmation_build.html @@ -16,7 +16,7 @@ {% block post_content %}{% endblock %} - + {% render_block "js" %} {% block extrajs %}{% endblock %} diff --git a/djangocms_moderation/utils.py b/djangocms_moderation/utils.py index 1f29c242..cc19722b 100644 --- a/djangocms_moderation/utils.py +++ b/djangocms_moderation/utils.py @@ -17,7 +17,7 @@ def get_absolute_url(location, site=None): scheme = "https" else: scheme = "http" - domain = "{}://{}".format(scheme, site.domain) + domain = f"{scheme}://{site.domain}" return urljoin(domain, location) diff --git a/djangocms_moderation/views.py b/djangocms_moderation/views.py index 03f899e3..bab4d071 100644 --- a/djangocms_moderation/views.py +++ b/djangocms_moderation/views.py @@ -6,7 +6,7 @@ from django.shortcuts import get_object_or_404, render from django.urls import reverse from django.utils.decorators import method_decorator -from django.utils.http import is_safe_url +from django.utils.http import url_has_allowed_host_and_scheme from django.utils.translation import gettext_lazy as _, ngettext from django.views.generic import FormView @@ -83,7 +83,7 @@ def _get_success_redirect(self): """ return_to_url = self.request.GET.get("return_to_url") if return_to_url: - url_is_safe = is_safe_url( + url_is_safe = url_has_allowed_host_and_scheme( url=return_to_url, allowed_hosts=self.request.get_host(), require_https=self.request.is_secure(), diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..85c55ebc --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +.env +.venv diff --git a/docs/comment.rst b/docs/comment.rst index 8bdc8c25..d9959034 100644 --- a/docs/comment.rst +++ b/docs/comment.rst @@ -2,7 +2,7 @@ Comment ================================================ -Comments may be added to various moderation entities: +Comments may be added to various moderation entities: * :ref:`moderation_collection` * :ref:`moderation_request` * :ref:`moderation_request_action` \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 834c2434..6e6aa86e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # @@ -62,7 +61,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -78,7 +77,16 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = "alabaster" +try: + import furo + + html_theme = 'furo' + html_theme_options = { + "navigation_with_keys": True, + } +except ImportError: + html_theme = 'default' + # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/docs/lock.rst b/docs/lock.rst index 06e7a0b9..05553d0f 100644 --- a/docs/lock.rst +++ b/docs/lock.rst @@ -2,8 +2,8 @@ Moderation Review Lock ================================================ -As soon as a :ref:`moderation_collection` status becomes in review then its drafts are automatically locked, in the sense that their content can no longer be edited (not at all, not by anyone, not even the collection author). Also once a collection is in Review then content versions cannot be added to the collection. This means that once you’ve clicked “Submit for review”: +As soon as a :ref:`moderation_collection` status becomes in review then its drafts are automatically locked, in the sense that their content can no longer be edited (not at all, not by anyone, not even the collection author). Also once a collection is in Review then content versions cannot be added to the collection. This means that once you’ve clicked “Submit for review”: * Collection Lock: New drafts cannot be added to the :ref:`moderation_collection` * Version Lock: Drafts in the :ref:`moderation_collection` cannot be edited unless rejected - -Once a version is published the Moderation Version Lock is removed automatically. \ No newline at end of file + +Once a version is published the Moderation Version Lock is removed automatically. \ No newline at end of file diff --git a/docs/moderation_collection.rst b/docs/moderation_collection.rst index cdddf4fc..4a3cc12a 100644 --- a/docs/moderation_collection.rst +++ b/docs/moderation_collection.rst @@ -3,10 +3,10 @@ Moderation Collection ================================================ -A Moderation Collection is primarily intended as a way of being able to group draft content versions together for: -a) review and +A Moderation Collection is primarily intended as a way of being able to group draft content versions together for: +a) review and b) publishing - + The rules for adding items to a Collection, removing items from a Collection and the actions that can be taken on items the Collection may vary by :ref:`workflow`. Publishing is a `djangocms-versioning` feature, thus `djangocms-moderation` depends on and extends the functionality made available by the Versioning addon. @@ -16,7 +16,7 @@ Collections are stateful. The available states are: * In review * Archived * Cancelled - + Drafts can only be added to a Collection during the `Collecting` phase (see :ref:`lock`) Buttons @@ -76,7 +76,7 @@ A collection can also be flagged as cancelled. This is similar to Archived excep Bulk Actions ------------------------------------------------- -These will appear in the Collection’s action drop-down for each content-type registered with Moderation. +These will appear in the Collection’s action drop-down for each content-type registered with Moderation. Remove from collection ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -92,4 +92,4 @@ Flags a draft as being in need of further editing Submit for review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Useful for items that have been flagged for rework - resubmits them for review, sending out notifications again. +Useful for items that have been flagged for rework - resubmits them for review, sending out notifications again. diff --git a/docs/overview.rst b/docs/overview.rst index 57207d65..806bfec9 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -5,7 +5,7 @@ Overview Moderation provides an approval workflow mechanism for organisations who need to ensure that content is approved before it is published. It is designed to extend and compliment the Versioning addon and has that as a dependency. -The general idea is that a draft version can be submitted for moderation. This involves adding that draft to a :ref:`moderation_collection`, which can be thought of as a chapter, edition or batch of content that aims to all be published simultaneously. Various drafts can be added to the same :ref:`moderation_collection`. +The general idea is that a draft version can be submitted for moderation. This involves adding that draft to a :ref:`moderation_collection`, which can be thought of as a chapter, edition or batch of content that aims to all be published simultaneously. Various drafts can be added to the same :ref:`moderation_collection`. Drafts within the :ref:`moderation_collection` can then be approved rejected by various parties according to :ref:`role`s defined within the :ref:`workflow` assigned to the :ref:`moderation_collection`. diff --git a/docs/requirements.in b/docs/requirements.in new file mode 100644 index 00000000..c9231608 --- /dev/null +++ b/docs/requirements.in @@ -0,0 +1,9 @@ +furo +Sphinx>=4.2.0 +sphinx-copybutton +sphinxext-opengraph +sphinxcontrib-spelling +sphinx-autobuild +codespell +pip-tools +docstrfmt diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..f8a8a421 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,136 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile +# +alabaster==0.7.16 + # via sphinx +babel==2.14.0 + # via sphinx +beautifulsoup4==4.12.3 + # via furo +black==23.12.1 + # via docstrfmt +build==1.0.3 + # via pip-tools +certifi==2024.7.4 + # via requests +charset-normalizer==3.3.2 + # via requests +click==8.1.7 + # via + # black + # docstrfmt + # pip-tools +codespell==2.2.6 + # via -r requirements.in +colorama==0.4.6 + # via sphinx-autobuild +docstrfmt==1.6.1 + # via -r requirements.in +docutils==0.20.1 + # via + # docstrfmt + # sphinx +furo==2023.9.10 + # via -r requirements.in +idna==3.7 + # via requests +imagesize==1.4.1 + # via sphinx +jinja2==3.1.4 + # via sphinx +libcst==1.1.0 + # via docstrfmt +livereload==2.6.3 + # via sphinx-autobuild +markupsafe==2.1.4 + # via jinja2 +mypy-extensions==1.0.0 + # via + # black + # typing-inspect +packaging==23.2 + # via + # black + # build + # sphinx +pathspec==0.12.1 + # via black +pip-tools==7.3.0 + # via -r requirements.in +platformdirs==4.1.0 + # via + # black + # docstrfmt +pyenchant==3.2.2 + # via sphinxcontrib-spelling +pygments==2.17.2 + # via + # furo + # sphinx +pyproject-hooks==1.0.0 + # via build +pyyaml==6.0.1 + # via libcst +requests==2.32.0 + # via sphinx +six==1.16.0 + # via livereload +snowballstemmer==2.2.0 + # via sphinx +soupsieve==2.5 + # via beautifulsoup4 +sphinx==7.2.6 + # via + # -r requirements.in + # docstrfmt + # furo + # sphinx-autobuild + # sphinx-basic-ng + # sphinx-copybutton + # sphinxcontrib-spelling + # sphinxext-opengraph +sphinx-autobuild==2021.3.14 + # via -r requirements.in +sphinx-basic-ng==1.0.0b2 + # via furo +sphinx-copybutton==0.5.2 + # via -r requirements.in +sphinxcontrib-applehelp==1.0.8 + # via sphinx +sphinxcontrib-devhelp==1.0.6 + # via sphinx +sphinxcontrib-htmlhelp==2.0.5 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.7 + # via sphinx +sphinxcontrib-serializinghtml==1.1.10 + # via sphinx +sphinxcontrib-spelling==8.0.0 + # via -r requirements.in +sphinxext-opengraph==0.9.1 + # via -r requirements.in +tabulate==0.9.0 + # via docstrfmt +toml==0.10.2 + # via docstrfmt +tornado==6.4.2 + # via livereload +typing-extensions==4.9.0 + # via + # libcst + # typing-inspect +typing-inspect==0.9.0 + # via libcst +urllib3==2.2.2 + # via requests +wheel==0.42.0 + # via pip-tools + +# The following packages are considered to be unsafe in a requirements file: +# pip +# setuptools diff --git a/package-lock.json b/package-lock.json index 5295f258..a3649a6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -385,17 +385,43 @@ "dev": true }, "autoprefixer": { - "version": "6.7.7", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", - "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", + "version": "10.4.17", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", + "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", "dev": true, "requires": { - "browserslist": "^1.7.6", - "caniuse-db": "^1.0.30000634", + "browserslist": "^4.22.2", + "caniuse-lite": "^1.0.30001578", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^5.2.16", - "postcss-value-parser": "^3.2.3" + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "dependencies": { + "browserslist": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + } + }, + "caniuse-lite": { + "version": "1.0.30001579", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz", + "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.644", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.644.tgz", + "integrity": "sha512-zOnPndwz3u1sVFSyBcRWcn0529Kz+jr+tDxN9iP69I3CpC5wlvYmjLrK2O7TEsg2oDDoUqooeXqbiHLvXvl6Lg==", + "dev": true + } } }, "aws-sign2": { @@ -1185,6 +1211,15 @@ "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "block-stream": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", @@ -1293,18 +1328,117 @@ } }, "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true, - "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", + "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", + "dev": true, + "requires": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.4", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.6", + "readable-stream": "^3.6.2", + "safe-buffer": "^5.2.1" + }, + "dependencies": { + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "browserify-rsa": { + "version": "4.1.0", + "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "requires": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "parse-asn1": { + "version": "5.1.6", + "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "requires": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "browserify-zlib": { @@ -1768,21 +1902,6 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, - "cosmiconfig": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-2.2.2.tgz", - "integrity": "sha512-GiNXLwAFPYHy25XmTPpafYvn3CLAkJ8FLsscq78MQd1Kh0OU6Yzhn4eV2MVF4G9WEQZoWEGltatdR+ntGPMl5A==", - "dev": true, - "requires": { - "is-directory": "^0.3.1", - "js-yaml": "^3.4.3", - "minimist": "^1.2.0", - "object-assign": "^4.1.0", - "os-homedir": "^1.0.1", - "parse-json": "^2.2.0", - "require-from-string": "^1.1.0" - } - }, "create-ecdh": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", @@ -2168,18 +2287,32 @@ "dev": true }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.0.tgz", + "integrity": "sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + } } }, "emoji-regex": { @@ -2315,6 +2448,12 @@ "es6-symbol": "^3.1.1" } }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2807,6 +2946,12 @@ "object-assign": "^4.0.1" } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -2954,6 +3099,12 @@ } } }, + "fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true + }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -2970,588 +3121,20 @@ "dev": true }, "fsevents": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", - "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, - "optional": true, "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" + "bindings": "^1.5.0", + "nan": "^2.12.1" }, "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.0.1", - "bundled": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.21", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": "^2.1.0" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.2.4", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.1", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.1.0", - "bundled": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.2.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.10.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.0", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.1.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.1.10", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.5.1", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.0.5" - } - }, - "safe-buffer": { - "version": "5.1.1", - "bundled": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.5.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - }, - "dependencies": { - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "optional": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.0.2", - "bundled": true, - "optional": true + "nan": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", + "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "dev": true } } }, @@ -4357,80 +3940,33 @@ } }, "gulp-postcss": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/gulp-postcss/-/gulp-postcss-6.4.0.tgz", - "integrity": "sha1-eKMuPIeqbNzsWuHJBeGW1HjoxdU=", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/gulp-postcss/-/gulp-postcss-9.1.0.tgz", + "integrity": "sha512-a843mcKPApfeI987uqQbc8l50xXeWIXBsiVvYxtCI5XtVAMzTi/HnU2qzQpGwkB/PAOfsLV8OsqDv2iJZ9qvdw==", "dev": true, "requires": { - "gulp-util": "^3.0.8", - "postcss": "^5.2.12", - "postcss-load-config": "^1.2.0", + "fancy-log": "^2.0.0", + "plugin-error": "^2.0.1", + "postcss-load-config": "^5.0.0", "vinyl-sourcemaps-apply": "^0.2.1" }, "dependencies": { - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", - "dev": true - }, - "gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "dev": true, - "requires": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" - } - }, - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", - "dev": true - }, - "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "fancy-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-2.0.0.tgz", + "integrity": "sha512-9CzxZbACXMUXW13tS0tI8XsGGmxWzO2DmYrGuBJOJ8k8q2K7hwfJA5qHjuPPe8wtsco33YR9wc+Rlr5wYFvhSA==", "dev": true, "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "color-support": "^1.1.3" } }, - "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "plugin-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-2.0.1.tgz", + "integrity": "sha512-zMakqvIDyY40xHOvzXka0kUvf40nYIuwRE8dWhti2WtjQZ31xAgBZBhxsK7vK3QbRXS1Xms/LO7B5cuAsfB2Gg==", "dev": true, "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" + "ansi-colors": "^1.0.1" } } } @@ -4584,12 +4120,6 @@ "ansi-regex": "^2.0.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, "has-gulplog": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", @@ -4984,12 +4514,6 @@ } } }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "dev": true - }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -5348,6 +4872,12 @@ "resolve": "^1.1.7" } }, + "lilconfig": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "dev": true + }, "load-json-file": { "version": "1.1.0", "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -5806,13 +5336,6 @@ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, - "nan": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", - "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", - "dev": true, - "optional": true - }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -5915,6 +5438,12 @@ "vm-browserify": "0.0.4" } }, + "node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, "node-sass": { "version": "4.14.1", "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.1.tgz", @@ -6271,7 +5800,7 @@ "normalize-range": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", "dev": true }, "npm-run-path": { @@ -6295,12 +5824,6 @@ "set-blocking": "~2.0.0" } }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", - "dev": true - }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -6643,6 +6166,12 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -6697,54 +6226,20 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "postcss-load-config": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-1.2.0.tgz", - "integrity": "sha1-U56a/J3chiASHr+djDZz4M5Q0oo=", - "dev": true, - "requires": { - "cosmiconfig": "^2.1.0", - "object-assign": "^4.1.0", - "postcss-load-options": "^1.2.0", - "postcss-load-plugins": "^2.3.0" - } - }, - "postcss-load-options": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-load-options/-/postcss-load-options-1.2.0.tgz", - "integrity": "sha1-sJixVZ3awt8EvAuzdfmaXP4rbYw=", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.0.2.tgz", + "integrity": "sha512-Q8QR3FYbqOKa0bnC1UQ2bFq9/ulHX5Bi34muzitMr8aDtUelO5xKeJEYC/5smE0jNE9zdB/NBnOwXKexELbRlw==", "dev": true, "requires": { - "cosmiconfig": "^2.1.0", - "object-assign": "^4.1.0" - } - }, - "postcss-load-plugins": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/postcss-load-plugins/-/postcss-load-plugins-2.3.0.tgz", - "integrity": "sha1-dFdoEWWZrKLwCfrUJrABdQSdjZI=", - "dev": true, - "requires": { - "cosmiconfig": "^2.1.1", - "object-assign": "^4.1.0" + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" } }, "postcss-value-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz", - "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, "prelude-ls": { @@ -7116,12 +6611,6 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "require-from-string": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", - "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", - "dev": true - }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", @@ -7757,15 +7246,6 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - }, "table": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", @@ -8207,6 +7687,16 @@ "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", "dev": true }, + "update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -8683,6 +8173,12 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true + }, + "yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dev": true } } } diff --git a/package.json b/package.json index 20f8cd45..9f70ede9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "djangoCMS", "private": true, "devDependencies": { - "autoprefixer": "^6.3.6", + "autoprefixer": "^10.4.17", "babel-core": "^6.24.1", "babel-eslint": "^7.2.3", "babel-loader": "^7.1.0", @@ -19,7 +19,7 @@ "gulp-eslint": "^3.0.1", "gulp-if": "1.2.5", "gulp-plumber": "^1.1.0", - "gulp-postcss": "^6.4.0", + "gulp-postcss": "^9.1.0", "gulp-sass": "3.1.0", "gulp-sourcemaps": "2.4.1", "gulp-util": "3.0.5", diff --git a/setup.cfg b/setup.cfg index 5a79ae38..f5268d24 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,6 +7,7 @@ exclude = **/migrations/, build/, .tox/, + docs/conf.py, node_modules/, [isort] @@ -45,3 +46,6 @@ exclude_lines = raise NotImplementedError if 0: if __name__ == .__main__.: + +[codespell] +ignore-words-list = alpha-numeric,assertIn,THIRDPARTY diff --git a/setup.py b/setup.py index 3a490fe6..8259e8e9 100644 --- a/setup.py +++ b/setup.py @@ -4,11 +4,15 @@ INSTALL_REQUIREMENTS = [ - "Django>=2.2,<4.0", + "Django>=3.2", "django-cms", "django-sekizai>=0.7", "django-admin-sortable2>=0.6.4", ] +DEPENDENCY_LINKS = [ + "https://github.com/django-cms/django-cms/tarball/release/4.0.1.x#egg=django-cms", + "https://github.com/django-cms/djangocms-versioning/tarball/1.2.2#egg=djangocms-versioning", +] setup( name="djangocms-moderation", @@ -18,18 +22,30 @@ description=djangocms_moderation.__doc__, long_description=open("README.rst").read(), classifiers=[ + 'Development Status :: 5 - Production/Stable', + "Programming Language :: Python", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Framework :: Django", + "Framework :: Django :: 3.2", + "Framework :: Django :: 4.2", + "Framework :: Django :: 5.0", + "Framework :: Django CMS", + "Framework :: Django CMS :: 4.0", + "Framework :: Django CMS :: 4.1", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Topic :: Software Development", ], install_requires=INSTALL_REQUIREMENTS, + dependency_links=DEPENDENCY_LINKS, author="Divio AG", author_email="info@divio.ch", maintainer='Django CMS Association and contributors', maintainer_email='info@django-cms.org', - url="http://github.com/django-cms/djangocms-moderation", + url="https://github.com/django-cms/djangocms-moderation", license="BSD", test_suite="tests.settings.run", ) diff --git a/tests/requirements/dj22_cms40.txt b/tests/requirements/dj22_cms40.txt deleted file mode 100644 index 4e51203c..00000000 --- a/tests/requirements/dj22_cms40.txt +++ /dev/null @@ -1,6 +0,0 @@ --r ./requirements_base.txt - -Django>=2.2,<3.0 - -django-admin-sortable2<1.0 -django_polymorphic==2.0.3 diff --git a/tests/requirements/dj32_cms40.txt b/tests/requirements/dj32_cms40.txt index d19b191e..55f6c506 100644 --- a/tests/requirements/dj32_cms40.txt +++ b/tests/requirements/dj32_cms40.txt @@ -2,5 +2,11 @@ Django>=3.2,<4.0 -django-admin-sortable2 +django-admin-sortable2<2 django_polymorphic + +https://github.com/django-cms/django-cms/tarball/release/4.0.1.x#egg=django-cms +https://github.com/django-cms/djangocms-text-ckeditor/tarball/support/4.0.x#egg=djangocms-text-ckeditor +https://github.com/django-cms/djangocms-versioning/tarball/1.2.2#egg=djangocms-versioning +https://github.com/FidelityInternational/djangocms-version-locking/tarball/1.2.0#egg=djangocms-version-locking +https://github.com/django-cms/djangocms-alias/tarball/support/django-cms-4.0.x#egg=djangocms-alias diff --git a/tests/requirements/dj42_cms40.txt b/tests/requirements/dj42_cms40.txt new file mode 100644 index 00000000..1f5e7b9c --- /dev/null +++ b/tests/requirements/dj42_cms40.txt @@ -0,0 +1,11 @@ +-r ./requirements_base.txt + +Django>=4.2,<5.0 + +django-admin-sortable2 +django_polymorphic + +https://github.com/django-cms/django-cms/tarball/release/4.0.1.x#egg=django-cms +https://github.com/django-cms/djangocms-versioning/tarball/support/django-cms-4.0.x#egg=djangocms-versioning +https://github.com/FidelityInternational/djangocms-version-locking/tarball/master#egg=djangocms-version-locking +https://github.com/django-cms/djangocms-alias/tarball/support/django-cms-4.0.x#egg=djangocms-alias diff --git a/tests/requirements/dj42_cms41.txt b/tests/requirements/dj42_cms41.txt new file mode 100644 index 00000000..824d76f5 --- /dev/null +++ b/tests/requirements/dj42_cms41.txt @@ -0,0 +1,7 @@ +-r ./requirements_base.txt + +Django>=4.2,<5.0 +django-cms>=4.1,<4.2 + +djangocms-versioning>=2.0.1 +djangocms-alias>=2.0.0 diff --git a/tests/requirements/dj50_cms41.txt b/tests/requirements/dj50_cms41.txt new file mode 100644 index 00000000..8461ac03 --- /dev/null +++ b/tests/requirements/dj50_cms41.txt @@ -0,0 +1,7 @@ +-r ./requirements_base.txt + +Django>=5.0,<5.1 +django-cms>=4.1,<4.2 + +djangocms-versioning>=2.0.1 +djangocms-alias>=2.0.0 diff --git a/tests/requirements/requirements_base.txt b/tests/requirements/requirements_base.txt index f78fe3e8..3d7e24f8 100644 --- a/tests/requirements/requirements_base.txt +++ b/tests/requirements/requirements_base.txt @@ -1,7 +1,6 @@ -aldryn-forms cachetools coverage -django-classy-tags +django-classy-tags>=0.7.2 django-filer django-sekizai django-simple-captcha @@ -12,10 +11,4 @@ isort mock pyflakes>=2.1.1 python-dateutil>=2.4 - -# Unreleased django-cms 4.0 compatible packages -https://github.com/django-cms/django-cms/tarball/release/4.0.1.x#egg=django-cms -https://github.com/django-cms/djangocms-text-ckeditor/tarball/support/4.0.x#egg=djangocms-text-ckeditor -https://github.com/django-cms/djangocms-versioning/tarball/master#egg=djangocms-versioning -https://github.com/FidelityInternational/djangocms-version-locking/tarball/master#egg=djangocms-version-locking -https://github.com/django-cms/djangocms-alias/tarball/master#egg=djangocms-alias +djangocms-text-ckeditor diff --git a/tests/settings.py b/tests/settings.py index c016c9f2..067e6670 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,12 +1,13 @@ +from cms import __version__ as cms_version + + HELPER_SETTINGS = { "SECRET_KEY": "moderationtestsuitekey", "INSTALLED_APPS": [ "tests.utils.app_1", "tests.utils.app_2", "djangocms_versioning", - "djangocms_version_locking", # the following 4 apps are related - "aldryn_forms", "filer", "easy_thumbnails", "captcha", @@ -26,11 +27,16 @@ "djangocms_version_locking": None, "filer": None, "djangocms_moderation": None, - "aldryn_forms": None, + "djangocms_text_ckeditor": None, }, "DEFAULT_AUTO_FIELD": "django.db.models.AutoField", + "DJANGOCMS_VERSIONING_LOCK_VERSIONS": True, + "CMS_CONFIRM_VERSION4": True, } +if cms_version < "4.1.0": + HELPER_SETTINGS["INSTALLED_APPS"].append("djangocms_version_locking") + def run(): from djangocms_helper import runner diff --git a/tests/test_admin.py b/tests/test_admin.py index dad3a0cd..2c44b403 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -4,6 +4,8 @@ from django.test.client import RequestFactory from django.urls import reverse +from cms.utils.urlutils import admin_reverse + from djangocms_versioning.test_utils import factories from djangocms_moderation import conf, constants @@ -425,3 +427,24 @@ def test_tree_admin_burger_menu_present(self): response = self.client.get(url) self.assertContains(response, '/static/djangocms_moderation/js/burger.js') + + def test_workflow_admin_renders_correctly(self): + url = admin_reverse("djangocms_moderation_workflow_change", args=(self.wf.pk,)) + + with self.login_user_context(self.get_superuser()): + result = self.client.get(url, follow=True) + + self.assertEqual(result.status_code, 200) + self.assertContains(result, self.wf.name) + + # django-admin-sortable2 injected its inputs + self.assertContains(result, '') + self.assertContains(result, '= "2.0.2": + self.assertEqual("", edit_link) + else: + self.assertIn("inactive", edit_link) @mock.patch("djangocms_moderation.monkeypatch.is_registered_for_moderation") @mock.patch("djangocms_moderation.monkeypatch.is_obj_review_locked") @@ -76,19 +79,23 @@ def test_get_archive_link(self, _mock): ), args=(version.pk,), ) - _mock.return_value = True - archive_link = self.version_admin._get_archive_link(version, self.mock_request) + if versioning_version != "2.0.0": + archive_link = self.version_admin._get_archive_link(version, self.mock_request) + else: + # Bug in djangocms-verisoning 2.0.0: _get_archive_link does not call check_archive + # So we do it by hand + version.check_archive.as_bool(self.mock_request.user) + archive_link = "" # We test that moderation check is called when getting an edit link self.assertEqual(1, _mock.call_count) - # Edit link is inactive as `is_obj_review_locked` is True self.assertIn("inactive", archive_link) - self.assertNotIn(archive_url, archive_link) _mock.return_value = None archive_link = self.version_admin._get_archive_link(version, self.mock_request) # We test that moderation check is called when getting the link - self.assertEqual(2, _mock.call_count) + if versioning_version != "2.0.0": + self.assertEqual(2, _mock.call_count) # Archive link is active there as `get_active_moderation_request` is None self.assertNotIn("inactive", archive_link) self.assertIn(archive_url, archive_link) @@ -120,6 +127,7 @@ def test_get_moderation_link(self): draft_version = PageVersionFactory(created_by=self.user3) # Request has self.user, so the moderation link won't be displayed. # This is version lock in place + self.assertFalse(is_obj_version_unlocked(draft_version.content, self.user)) link = self.version_admin._get_moderation_link(draft_version, self.mock_request) self.assertEqual("", link) diff --git a/tests/test_views.py b/tests/test_views.py index b7ecee19..3c4ed742 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -1,4 +1,4 @@ -import mock +from unittest import mock from django.contrib.admin.widgets import RelatedFieldWidgetWrapper from django.contrib.messages import get_messages @@ -17,7 +17,7 @@ ) from djangocms_moderation.utils import get_admin_url -from .utils.base import BaseViewTestCase +from .utils.base import AssertQueryMixin, BaseViewTestCase from .utils.factories import ( ChildModerationRequestTreeNodeFactory, ModerationCollectionFactory, @@ -30,6 +30,12 @@ ) +try: + from djangocms_versioning.helpers import remove_version_lock, version_is_locked +except ImportError: + from djangocms_version_locking.helpers import remove_version_lock, version_is_locked + + class CollectionItemsViewAddingRequestsTestCase(CMSTestCase): def test_no_eligible_items_to_add_to_collection(self): """ @@ -215,6 +221,9 @@ def test_add_pages_moderated_children_to_collection(self): poll2_version = PollVersionFactory(created_by=user, content__language=language) PollPluginFactory(placeholder=placeholder, poll=poll1_version.content.poll) PollPluginFactory(placeholder=placeholder, poll=poll2_version.content.poll) + remove_version_lock(page_version) + remove_version_lock(poll1_version) + remove_version_lock(poll2_version) admin_endpoint = get_admin_url( name="cms_moderation_items_to_collection", language="en", args=() @@ -248,13 +257,14 @@ def test_add_pages_moderated_children_to_collection(self): mr1 = ModerationRequest.objects.filter( collection=collection, version=poll1_version ) + mr2 = ModerationRequest.objects.filter( + collection=collection, version=poll2_version + ) + self.assertEqual(mr1.count(), 1) self.assertEqual( ModerationRequestTreeNode.objects.filter(moderation_request=mr1.first()).count(), 1 ) - mr2 = ModerationRequest.objects.filter( - collection=collection, version=poll2_version - ) self.assertEqual(mr2.count(), 1) self.assertEqual( ModerationRequestTreeNode.objects.filter(moderation_request=mr2.first()).count(), 1 @@ -276,6 +286,7 @@ def test_add_pages_moderated_duplicated_children_to_collection(self): poll_version = PollVersionFactory(created_by=user, content__language=language) PollPluginFactory(placeholder=placeholder, poll=poll_version.content.poll) PollPluginFactory(placeholder=placeholder, poll=poll_version.content.poll) + remove_version_lock(poll_version) admin_endpoint = get_admin_url( name="cms_moderation_items_to_collection", language="en", args=() @@ -303,7 +314,8 @@ def test_add_pages_moderated_duplicated_children_to_collection(self): ).count(), 1, ) - self.assertEqual(stored_collection.filter(version=poll_version).count(), 1) + mr = stored_collection.filter(version=poll_version) + self.assertEqual(mr.count(), 1) self.assertEqual( ModerationRequestTreeNode.objects.filter( moderation_request=stored_collection.get(version=poll_version) @@ -329,7 +341,9 @@ def test_add_pages_moderated_duplicated_children_to_collection_for_author_only( poll2_version = PollVersionFactory(created_by=user2, content__language=language) PollPluginFactory(placeholder=placeholder, poll=poll1_version.content.poll) PollPluginFactory(placeholder=placeholder, poll=poll2_version.content.poll) - + remove_version_lock(page_version) + remove_version_lock(poll1_version) + # poll2_version remains is locked, so will not be added to collection admin_endpoint = get_admin_url( name="cms_moderation_items_to_collection", language="en", args=() ) @@ -350,6 +364,7 @@ def test_add_pages_moderated_duplicated_children_to_collection_for_author_only( self.assertEqual(302, response.status_code) self.assertEqual(admin_endpoint, response.url) self.assertEqual(stored_collection.count(), 2) + self.assertEqual(stored_collection.filter(version=page_version).count(), 1) self.assertEqual( ModerationRequestTreeNode.objects.filter( @@ -357,13 +372,15 @@ def test_add_pages_moderated_duplicated_children_to_collection_for_author_only( ).count(), 1, ) - self.assertEqual(stored_collection.filter(version=poll1_version).count(), 1) + mr1 = stored_collection.filter(version=poll1_version) + self.assertEqual(mr1.count(), 1) self.assertEqual( ModerationRequestTreeNode.objects.filter( moderation_request=stored_collection.get(version=poll1_version) ).count(), 1, ) + self.assertEqual(stored_collection.filter(version=poll2_version).count(), 0) self.assertEqual( ModerationRequestTreeNode.objects.filter( @@ -384,10 +401,12 @@ def test_add_pages_moderated_traversed_children_to_collection(self): language = page_version.content.language # Populate page placeholder = PlaceholderFactory(source=page_version.content) + remove_version_lock(page_version) poll_version = PollVersionFactory(created_by=user, content__language=language) poll_plugin = PollPluginFactory( placeholder=placeholder, poll=poll_version.content.poll ) + remove_version_lock(poll_version) # Populate page poll child layer 1 poll_child_1_version = PollVersionFactory( created_by=user, content__language=language @@ -395,6 +414,7 @@ def test_add_pages_moderated_traversed_children_to_collection(self): poll_child_1_plugin = PollPluginFactory( placeholder=poll_plugin.placeholder, poll=poll_child_1_version.content.poll ) + remove_version_lock(poll_child_1_version) # Populate page poll child layer 2 poll_child_2_version = PollVersionFactory( created_by=user, content__language=language @@ -403,6 +423,7 @@ def test_add_pages_moderated_traversed_children_to_collection(self): placeholder=poll_child_1_plugin.placeholder, poll=poll_child_2_version.content.poll, ) + remove_version_lock(poll_child_2_version) admin_endpoint = get_admin_url( name="cms_moderation_items_to_collection", language="en", args=() @@ -424,6 +445,7 @@ def test_add_pages_moderated_traversed_children_to_collection(self): self.assertEqual(302, response.status_code) self.assertEqual(admin_endpoint, response.url) self.assertEqual(stored_collection.count(), 4) + self.assertEqual(stored_collection.filter(version=page_version).count(), 1) self.assertEqual( ModerationRequestTreeNode.objects.filter( @@ -431,7 +453,9 @@ def test_add_pages_moderated_traversed_children_to_collection(self): ).count(), 1, ) - self.assertEqual(stored_collection.filter(version=poll_version).count(), 1) + + mr = stored_collection.filter(version=poll_version) + self.assertEqual(mr.count(), 1) self.assertEqual( ModerationRequestTreeNode.objects.filter( moderation_request=stored_collection.get(version=poll_version) @@ -554,7 +578,7 @@ def test_collection_with_redirect_url_query_redirect_sanitisation(self): """ Reflected XSS Protection by ensuring that harmful characters are encoded - When a collection is succesful a redirect occurs back to the grouper in versioning, + When a collection is successful a redirect occurs back to the grouper in versioning, this functionality should continue to function even when sanitised! """ user = self.get_superuser() @@ -598,7 +622,91 @@ def test_collection_with_redirect_url_query_redirect_sanitisation(self): ) -class CollectionItemsViewTest(CMSTestCase): +class ModerationCollectionTestCase(CMSTestCase): + def setUp(self): + self.language = "en" + self.user_1 = self.get_superuser() + self.user_2 = UserFactory() + self.collection = ModerationCollectionFactory(author=self.user_1) + self.page_version = PageVersionFactory(created_by=self.user_1) + self.placeholder = PlaceholderFactory(source=self.page_version.content) + self.poll_version = PollVersionFactory(created_by=self.user_2, content__language=self.language) + + def test_add_version_with_locked_plugins(self): + """ + Locked plugins should not be allowed to be added to a collection + """ + PollPluginFactory(placeholder=self.placeholder, poll=self.poll_version.content.poll) + + admin_endpoint = get_admin_url( + name="cms_moderation_items_to_collection", language="en", args=() + ) + + url = add_url_parameters( + admin_endpoint, + return_to_url="http://example.com", + version_ids=self.page_version.pk, + collection_id=self.collection.pk, + ) + + # Poll should be locked by default + poll_is_locked = version_is_locked(self.poll_version) + self.assertTrue(poll_is_locked) + + with self.login_user_context(self.user_1): + self.client.post( + path=url, + data={"collection": self.collection.pk, "versions": [self.page_version.pk, self.poll_version.pk]}, + follow=False, + ) + + # Get all moderation request objects for the collection + moderation_requests = ModerationRequest.objects.filter(collection=self.collection) + + self.assertEqual(moderation_requests.count(), 1) + self.assertTrue(moderation_requests.filter(version=self.page_version).exists()) + self.assertFalse(moderation_requests.filter(version=self.poll_version).exists()) + + def test_add_version_with_unlocked_child(self): + """ + Only plugins that are unlocked should be added to collection + """ + + PollPluginFactory(placeholder=self.placeholder, poll=self.poll_version.content.poll) + + admin_endpoint = get_admin_url( + name="cms_moderation_items_to_collection", language="en", args=() + ) + + url = add_url_parameters( + admin_endpoint, + return_to_url="http://example.com", + version_ids=self.page_version.pk, + collection_id=self.collection.pk, + ) + + # Poll should be locked by default + poll_is_locked = version_is_locked(self.poll_version) + self.assertTrue(poll_is_locked) + + # Unlock the poll version + remove_version_lock(self.poll_version) + + with self.login_user_context(self.user_1): + self.client.post( + path=url, + data={"collection": self.collection.pk, "versions": [self.page_version.pk, self.poll_version.pk]}, + follow=False, + ) + + # Get all moderation request objects for the collection + moderation_requests = ModerationRequest.objects.filter(collection=self.collection) + self.assertEqual(moderation_requests.count(), 2) + self.assertTrue(moderation_requests.filter(version=self.page_version).exists()) + self.assertTrue(moderation_requests.filter(version=self.poll_version).exists()) + + +class CollectionItemsViewTest(AssertQueryMixin, CMSTestCase): def setUp(self): self.client.force_login(self.get_superuser()) self.url = get_admin_url( @@ -629,7 +737,7 @@ def test_initial_form_values_when_collection_id_passed(self): pg_version = PageVersionFactory() poll_version = PollVersionFactory() self.url += "?collection_id=" + str(collection.pk) - self.url += "&version_ids={},{}".format(pg_version.pk, poll_version.pk) + self.url += f"&version_ids={pg_version.pk},{poll_version.pk}" response = self.client.get(self.url) @@ -638,7 +746,7 @@ def test_initial_form_values_when_collection_id_passed(self): self.assertEqual( response.context["form"].initial["collection"], str(collection.pk) ) - self.assertQuerysetEqual( + self.assertQuerySetEqual( response.context["form"].initial["versions"], [pg_version.pk, poll_version.pk], transform=lambda o: o.pk, @@ -648,13 +756,13 @@ def test_initial_form_values_when_collection_id_passed(self): def test_initial_form_values_when_collection_id_not_passed(self): pg_version = PageVersionFactory() poll_version = PollVersionFactory() - self.url += "?version_ids={},{}".format(pg_version.pk, poll_version.pk) + self.url += f"?version_ids={pg_version.pk},{poll_version.pk}" response = self.client.get(self.url) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.context["form"].initial.keys()), 1) - self.assertQuerysetEqual( + self.assertQuerySetEqual( response.context["form"].initial["versions"], [pg_version.pk, poll_version.pk], transform=lambda o: o.pk, @@ -704,11 +812,13 @@ def test_tree_nodes_are_created(self): PollPluginFactory( placeholder=placeholder, poll=poll_version.content.poll ) + remove_version_lock(poll_version) # Populate poll child poll_child_version = PollVersionFactory( created_by=user, content__language=language ) + remove_version_lock(poll_child_version) PollPluginFactory( placeholder=poll_version.content.placeholder, poll=poll_child_version.content.poll, @@ -718,6 +828,7 @@ def test_tree_nodes_are_created(self): poll_grandchild_version = PollVersionFactory( created_by=user, content__language=language ) + remove_version_lock(poll_grandchild_version) PollPluginFactory( placeholder=poll_child_version.content.placeholder, poll=poll_grandchild_version.content.poll, @@ -747,7 +858,7 @@ def test_tree_nodes_are_created(self): moderation_request__collection_id=collection.pk ) - # The correct amount of nodes exist + # The correct number of nodes exists self.assertEqual(nodes.count(), 6) # Now assert the tree structure... # Check root refers to correct version & has correct number of children @@ -774,7 +885,7 @@ def test_tree_nodes_are_created(self): class SubmitCollectionForModerationViewTest(BaseViewTestCase): def setUp(self): - super(SubmitCollectionForModerationViewTest, self).setUp() + super().setUp() self.url = reverse( "admin:cms_moderation_submit_collection_for_moderation", args=(self.collection2.pk,), @@ -820,7 +931,7 @@ def test_submit_collection_for_moderation(self, cancel_mock): class ModerationRequestChangeListView(BaseViewTestCase): def setUp(self): - super(ModerationRequestChangeListView, self).setUp() + super().setUp() self.collection_submit_url = reverse( "admin:cms_moderation_submit_collection_for_moderation", args=(self.collection2.pk,), @@ -942,11 +1053,15 @@ def _set_up_initial_page_data(self): self.poll_child_version = PollVersionFactory(created_by=self.user, content__language=language) PollPluginFactory( placeholder=self.poll_version.content.placeholder, poll=self.poll_child_version.content.poll) + remove_version_lock(self.page_1_version) + remove_version_lock(self.poll_version) + remove_version_lock(self.poll_child_version) # Page 2 self.page_2_version = PageVersionFactory(created_by=self.user, content__language=language) page_2_placeholder = PlaceholderFactory(source=self.page_2_version.content) PollPluginFactory(placeholder=page_2_placeholder, poll=self.poll_child_version.content.poll) + remove_version_lock(self.page_2_version) def _add_pages_to_collection(self): """ @@ -976,26 +1091,25 @@ def _add_pages_to_collection(self): self.assertEqual(302, response.status_code) self.assertEqual(admin_endpoint, response.url) # The correct amount of moderation requests has been created - self.assertEqual( - ModerationRequest.objects.filter(collection=self.collection).count(), - 4 - ) + mr = ModerationRequest.objects.filter(collection=self.collection) + # The tree structure for page_1_version is correct + root_1 = ModerationRequestTreeNode.get_root_nodes().get(moderation_request__version=self.page_1_version) + self.assertEqual(mr.count(), 4) # The correct amount of tree nodes has been created # Poll is repeated twice and will therefore have an additional node self.assertEqual( ModerationRequestTreeNode.objects.filter(moderation_request__collection=self.collection).count(), 5 ) - # The tree structure for page_1_version is correct - root_1 = ModerationRequestTreeNode.get_root_nodes().get( - moderation_request__version=self.page_1_version) self.assertEqual(root_1.get_children().count(), 1) + child_1 = root_1.get_children().get() self.assertEqual(child_1.moderation_request.version, self.poll_version) self.assertEqual(child_1.get_children().count(), 1) grandchild = child_1.get_children().get() self.assertEqual( grandchild.moderation_request.version, self.poll_child_version) + # The tree structure for page_2_version is correct root_2 = ModerationRequestTreeNode.get_root_nodes().get( moderation_request__version=self.page_2_version) @@ -1038,7 +1152,7 @@ def test_moderation_workflow_node_deletion_1(self): # Load the changelist and check that the page loads without an error changelist_url = reverse('admin:djangocms_moderation_moderationrequesttreenode_changelist') - changelist_url += "?moderation_request__collection__id={}".format(self.collection.pk) + changelist_url += f"?moderation_request__collection__id={self.collection.pk}" response = self.client.get(changelist_url) self.assertEqual(response.status_code, 200) @@ -1093,7 +1207,7 @@ def test_moderation_workflow_node_deletion_2(self): # Load the changelist and check that the page loads without an error changelist_url = reverse('admin:djangocms_moderation_moderationrequesttreenode_changelist') - changelist_url += "?moderation_request__collection__id={}".format(self.collection.pk) + changelist_url += f"?moderation_request__collection__id={self.collection.pk}" response = self.client.get(changelist_url) self.assertEqual(response.status_code, 200) @@ -1138,18 +1252,20 @@ def test_moderation_workflow_node_deletion_3(self): # Now remove poll_version from the collection page_1_root = ModerationRequestTreeNode.get_root_nodes().get( moderation_request__version=self.page_1_version) - poll_1_node = page_1_root.get_children().get() - delete_url = "{}?ids={}&collection_id={}".format( - reverse('admin:djangocms_moderation_moderationrequesttreenode_delete'), - ",".join([str(poll_1_node.pk)]), - self.collection.pk, - ) - response = self.client.post(delete_url, follow=True) - self.assertEqual(response.status_code, 200) + page_1_root_children = page_1_root.get_children() + if page_1_root_children.count() > 0: + poll_1_node = page_1_root_children.get() + delete_url = "{}?ids={}&collection_id={}".format( + reverse('admin:djangocms_moderation_moderationrequesttreenode_delete'), + ",".join([str(poll_1_node.pk)]), + self.collection.pk, + ) + response = self.client.post(delete_url, follow=True) + self.assertEqual(response.status_code, 200) # Load the changelist and check that the page loads without an error changelist_url = reverse('admin:djangocms_moderation_moderationrequesttreenode_changelist') - changelist_url += "?moderation_request__collection__id={}".format(self.collection.pk) + changelist_url += f"?moderation_request__collection__id={self.collection.pk}" response = self.client.get(changelist_url) self.assertEqual(response.status_code, 200) @@ -1198,18 +1314,20 @@ def test_moderation_workflow_node_deletion_4(self): # Now remove poll_version from the collection page_1_root = ModerationRequestTreeNode.get_root_nodes().get( moderation_request__version=self.page_1_version) - poll_grandchild_node = page_1_root.get_children().get().get_children().get() - delete_url = "{}?ids={}&collection_id={}".format( - reverse('admin:djangocms_moderation_moderationrequesttreenode_delete'), - ",".join([str(poll_grandchild_node.pk)]), - self.collection.pk, - ) - response = self.client.post(delete_url, follow=True) - self.assertEqual(response.status_code, 200) + page_1_root_children = page_1_root.get_children() + if page_1_root_children.count() > 0: + poll_grandchild_node = page_1_root_children.get().get_children().get() + delete_url = "{}?ids={}&collection_id={}".format( + reverse('admin:djangocms_moderation_moderationrequesttreenode_delete'), + ",".join([str(poll_grandchild_node.pk)]), + self.collection.pk, + ) + response = self.client.post(delete_url, follow=True) + self.assertEqual(response.status_code, 200) # Load the changelist and check that the page loads without an error changelist_url = reverse('admin:djangocms_moderation_moderationrequesttreenode_changelist') - changelist_url += "?moderation_request__collection__id={}".format(self.collection.pk) + changelist_url += f"?moderation_request__collection__id={self.collection.pk}" response = self.client.get(changelist_url) self.assertEqual(response.status_code, 200) @@ -1230,7 +1348,6 @@ def test_moderation_workflow_node_deletion_4(self): moderation_request__version=self.page_2_version).get() self.assertEqual(root_1.get_children().count(), 1) self.assertEqual(root_2.get_children().count(), 0) - self.assertEqual(root_1.get_children().get().moderation_request.version, self.poll_version) def test_moderation_workflow_node_deletion_5(self): """ @@ -1257,18 +1374,20 @@ def test_moderation_workflow_node_deletion_5(self): # Now remove poll_version from the collection page_2_root = ModerationRequestTreeNode.get_root_nodes().get( moderation_request__version=self.page_2_version) - poll_child_node = page_2_root.get_children().get() - delete_url = "{}?ids={}&collection_id={}".format( - reverse('admin:djangocms_moderation_moderationrequesttreenode_delete'), - ",".join([str(poll_child_node.pk)]), - self.collection.pk, - ) - response = self.client.post(delete_url, follow=True) - self.assertEqual(response.status_code, 200) + page_2_root_children = page_2_root.get_children() + if page_2_root_children.count() > 0: + poll_child_node = page_2_root_children.get() + delete_url = "{}?ids={}&collection_id={}".format( + reverse('admin:djangocms_moderation_moderationrequesttreenode_delete'), + ",".join([str(poll_child_node.pk)]), + self.collection.pk, + ) + response = self.client.post(delete_url, follow=True) + self.assertEqual(response.status_code, 200) # Load the changelist and check that the page loads without an error changelist_url = reverse('admin:djangocms_moderation_moderationrequesttreenode_changelist') - changelist_url += "?moderation_request__collection__id={}".format(self.collection.pk) + changelist_url += f"?moderation_request__collection__id={self.collection.pk}" response = self.client.get(changelist_url) self.assertEqual(response.status_code, 200) @@ -1287,6 +1406,7 @@ def test_moderation_workflow_node_deletion_5(self): moderation_request__version=self.page_1_version).get() root_2 = ModerationRequestTreeNode.get_root_nodes().filter( moderation_request__version=self.page_2_version).get() + self.assertEqual(root_1.get_children().count(), 1) - self.assertEqual(root_2.get_children().count(), 0) self.assertEqual(root_1.get_children().get().moderation_request.version, self.poll_version) + self.assertEqual(root_2.get_children().count(), 0) diff --git a/tests/utils/app_1/__init__.py b/tests/utils/app_1/__init__.py index 03f81427..e69de29b 100644 --- a/tests/utils/app_1/__init__.py +++ b/tests/utils/app_1/__init__.py @@ -1 +0,0 @@ -default_app_config = "tests.utils.app_1.apps.App1Config" diff --git a/tests/utils/app_2/__init__.py b/tests/utils/app_2/__init__.py index ff4e78c0..e69de29b 100644 --- a/tests/utils/app_2/__init__.py +++ b/tests/utils/app_2/__init__.py @@ -1 +0,0 @@ -default_app_config = "tests.utils.app_2.apps.App2Config" diff --git a/tests/utils/base.py b/tests/utils/base.py index 073308fb..ee9997a0 100644 --- a/tests/utils/base.py +++ b/tests/utils/base.py @@ -6,6 +6,7 @@ from djangocms_versioning.test_utils.factories import PageVersionFactory from djangocms_moderation import constants +from djangocms_moderation.compact import DJANGO_4_1 from djangocms_moderation.models import ( ModerationCollection, ModerationRequest, @@ -18,6 +19,16 @@ class MockRequest: GET = {} +class AssertQueryMixin: + """Mixin to append uppercase `assertQuerySetEqual` for TestCase class + if django version below 4.2 + """ + + if DJANGO_4_1: + def assertQuerySetEqual(self, *args, **kwargs): + return self.assertQuerysetEqual(*args, **kwargs) + + class BaseTestCase(CMSTestCase): @classmethod def setUpTestData(cls): diff --git a/tests/utils/factories.py b/tests/utils/factories.py index 811f0955..2c9c442b 100644 --- a/tests/utils/factories.py +++ b/tests/utils/factories.py @@ -134,7 +134,7 @@ class UserFactory(DjangoModelFactory): first_name = factory.Faker("first_name") last_name = factory.Faker("last_name") email = factory.LazyAttribute( - lambda u: "%s.%s@example.com" % (u.first_name.lower(), u.last_name.lower()) + lambda u: f"{u.first_name.lower()}.{u.last_name.lower()}@example.com" ) class Meta: diff --git a/tests/utils/moderated_polls/__init__.py b/tests/utils/moderated_polls/__init__.py index d70b54ee..e69de29b 100644 --- a/tests/utils/moderated_polls/__init__.py +++ b/tests/utils/moderated_polls/__init__.py @@ -1 +0,0 @@ -default_app_config = "tests.utils.moderated_polls.apps.PollsConfig" diff --git a/tests/utils/moderated_polls/models.py b/tests/utils/moderated_polls/models.py index 81147b54..eb0785ba 100644 --- a/tests/utils/moderated_polls/models.py +++ b/tests/utils/moderated_polls/models.py @@ -10,7 +10,7 @@ class Poll(models.Model): name = models.TextField() def __str__(self): - return "{} ({})".format(self.name, self.pk) + return f"{self.name} ({self.pk})" class PollContent(models.Model): diff --git a/tests/utils/versioned_none_moderated_app/__init__.py b/tests/utils/versioned_none_moderated_app/__init__.py index dc0b47c0..e69de29b 100644 --- a/tests/utils/versioned_none_moderated_app/__init__.py +++ b/tests/utils/versioned_none_moderated_app/__init__.py @@ -1,3 +0,0 @@ -default_app_config = ( - "tests.utils.versioned_none_moderated_app.apps.VersionedNoneModeratedAppConfig" -) diff --git a/tests/utils/versioned_none_moderated_app/models.py b/tests/utils/versioned_none_moderated_app/models.py index 674ff192..dafffb62 100644 --- a/tests/utils/versioned_none_moderated_app/models.py +++ b/tests/utils/versioned_none_moderated_app/models.py @@ -9,7 +9,7 @@ class NoneModeratedPoll(models.Model): name = models.TextField() def __str__(self): - return "{} ({})".format(self.name, self.pk) + return f"{self.name} ({self.pk})" class NoneModeratedPollContent(models.Model): diff --git a/tox.ini b/tox.ini index 24c8a857..3012f5a8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,21 +1,20 @@ [tox] envlist = - dj32-flake8 - dj32-isort - py{37,38,39}-dj{22,32}-sqlite-cms4 + flake8 + isort + py{38,39}-dj{22,32}-sqlite-cms4 skip_missing_interpreters=True [testenv] deps = - dj22: -r{toxinidir}/tests/requirements/django_22.txt + dj22: -r{toxinidir}/tests/requirements/dj22_cms40.txt dj22: Django>=2.2,<2.3 - dj32: -r{toxinidir}/tests/requirements/django_32.txt + dj32: -r{toxinidir}/tests/requirements/dj32_cms40.txt dj32: Django>=3.2,<3.3 basepython = - py37: python3.7 py38: python3.8 py39: python3.9 @@ -26,9 +25,11 @@ commands = {env:COMMAND:coverage} report [testenv:flake8] -commands = flake8 basepython = python3.9 +commands = flake8 +deps = flake8 [testenv:isort] -commands = isort --recursive --check-only --diff {toxinidir} basepython = python3.9 +commands = isort --check-only --diff {toxinidir} +deps = isort