diff --git a/.circleci/config.yml b/.circleci/config.yml index c940e212e0..68474a2213 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,20 +33,6 @@ jobs: paths: - .venv - integreat_cms.egg-info - black: - docker: - - image: cimg/python:3.11.7 - resource_class: small - steps: - - checkout - - attach_workspace: - at: . - - run: - name: Enable virtual environment - command: echo "source .venv/bin/activate" >> $BASH_ENV - - run: - name: Check black code style - command: black --check . djlint: docker: - image: cimg/python:3.11.7 @@ -61,17 +47,6 @@ jobs: - run: name: Check formatting of Django templates command: djlint --check --lint integreat_cms - pylint: - docker: - - image: cimg/python:3.11.7 - resource_class: small - steps: - - checkout - - attach_workspace: - at: . - - run: - name: Run pylint - command: ./tools/pylint.sh ruff: docker: - image: cimg/python:3.11.7 @@ -87,7 +62,7 @@ jobs: name: Run ruff command: | ruff check - # ruff format + ruff format --check mypy: docker: - image: cimg/python:3.11.7 @@ -668,15 +643,9 @@ workflows: - /.*-publish-dev-package/ requires: - build-dev-package - - black: - requires: - - pip-install - djlint: requires: - pip-install - - pylint: - requires: - - pip-install - ruff: requires: - pip-install diff --git a/.circleci/scripts/create_release.py b/.circleci/scripts/create_release.py index 4cc6baa63b..3d906cd46f 100755 --- a/.circleci/scripts/create_release.py +++ b/.circleci/scripts/create_release.py @@ -17,21 +17,33 @@ def main() -> None: # Parse command line arguments parser = argparse.ArgumentParser(description="Create GitHub release") parser.add_argument( - "-p", "--prerelease", action="store_true", help="mark the release as prerelease" + "-p", + "--prerelease", + action="store_true", + help="mark the release as prerelease", ) parser.add_argument("token", metavar="TOKEN", help="GitHub API token") parser.add_argument("tag", metavar="TAG", help="tag for the release") parser.add_argument( - "prev_tag", metavar="PREV_TAG", help="the previous tag of the release" + "prev_tag", + metavar="PREV_TAG", + help="the previous tag of the release", ) parser.add_argument( - "changelog", metavar="CHANGELOG", help="changelog of the release" + "changelog", + metavar="CHANGELOG", + help="changelog of the release", ) parser.add_argument( - "contributors", metavar="CONTRIBUTORS", help="contributors of the release" + "contributors", + metavar="CONTRIBUTORS", + help="contributors of the release", ) parser.add_argument( - "assets", metavar="ASSET", help="uploadable asset file", nargs="*" + "assets", + metavar="ASSET", + help="uploadable asset file", + nargs="*", ) args = parser.parse_args() diff --git a/.circleci/scripts/get_access_token.py b/.circleci/scripts/get_access_token.py index e7ae97b550..3d46fbf6f5 100755 --- a/.circleci/scripts/get_access_token.py +++ b/.circleci/scripts/get_access_token.py @@ -24,7 +24,7 @@ def main() -> None: deliverino_private_key = os.environ["DELIVERINO_PRIVATE_KEY"] except KeyError as e: raise RuntimeError( - "Please make sure this step has access to the 'deliverino' CircleCI context." + "Please make sure this step has access to the 'deliverino' CircleCI context.", ) from e # Generate payload for the JWT @@ -39,7 +39,9 @@ def main() -> None: # Sign payload and encode JWT encoded_jwt = jwt.encode( - payload, b64decode(deliverino_private_key), algorithm="RS256" + payload, + b64decode(deliverino_private_key), + algorithm="RS256", ) # Request access token diff --git a/.circleci/scripts/get_contributors.py b/.circleci/scripts/get_contributors.py index c6c0d40ba2..cfc6d07899 100755 --- a/.circleci/scripts/get_contributors.py +++ b/.circleci/scripts/get_contributors.py @@ -26,13 +26,17 @@ def parse_args() -> argparse.Namespace: # Parse command line arguments parser = argparse.ArgumentParser( - description="Get contributors between two given git references" + description="Get contributors between two given git references", ) parser.add_argument("token", metavar="TOKEN", help="GitHub API token") parser.add_argument("base", metavar="BASE", help="the base branch/tag/commit") parser.add_argument("head", metavar="HEAD", help="the head branch/tag/commit") parser.add_argument( - "-v", "--verbose", action="count", default=0, help="increase logging verbosity" + "-v", + "--verbose", + action="count", + default=0, + help="increase logging verbosity", ) args: argparse.Namespace = parser.parse_args() diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8311272939..69c015c38d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,8 +21,7 @@ repos: - id: ruff name: ruff (custom script) entry: ./tools/ruff.sh - args: ["--as-precommit"] - types_or: ["python", "pyi"] + args: ["--as-precommit", "."] language: script pass_filenames: false - repo: https://github.com/shellcheck-py/shellcheck-py @@ -30,15 +29,6 @@ repos: hooks: - id: shellcheck args: [--external-sources] - - repo: local - hooks: - - id: black - name: black (custom script) - entry: ./tools/black.sh - args: ["--as-precommit"] - types_or: ["python", "pyi"] - language: script - pass_filenames: true - repo: local hooks: - id: djlint-django @@ -92,12 +82,3 @@ repos: types_or: ["python", "pyi"] language: script pass_filenames: true - - repo: local - hooks: - - id: pylint - name: pylint (custom script) - entry: ./tools/pylint.sh - args: ["--as-precommit"] - types_or: ["python", "pyi"] - language: script - pass_filenames: true diff --git a/.prettierignore b/.prettierignore index 8df1aec0cf..96c98d5cba 100644 --- a/.prettierignore +++ b/.prettierignore @@ -8,3 +8,4 @@ build/** webpack-stats.json .mypy_cache/** +.ruff_cache/** diff --git a/README.md b/README.md index c2fa18d189..adbae4e354 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ [![CircleCI](https://circleci.com/gh/digitalfabrik/integreat-cms.svg?style=shield)](https://circleci.com/gh/digitalfabrik/integreat-cms) -[![Pylint](https://img.shields.io/badge/pylint-10.00-brightgreen)](https://www.pylint.org/) +[![Ruff](https://img.shields.io/badge/ruff-0.9.2-brightgreen)](https://docs.astral.sh/ruff/) ![Coverage](https://img.shields.io/codeclimate/coverage/digitalfabrik/integreat-cms) [![PyPi](https://img.shields.io/pypi/v/integreat-cms.svg)](https://pypi.org/project/integreat-cms/) [![Release Notes](https://img.shields.io/badge/%F0%9F%93%9C-release%20notes-blue)](https://digitalfabrik.github.io/integreat-cms/release-notes.html) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) # Integreat Django CMS @@ -93,8 +92,9 @@ and then start your code editor (`code .`, `nvim`,...) from within that same she On MacOS, installing `libmagic` separately through `brew install libmagic` might be required. ### CMS configuration file -After setup you will be able to run the cms with most of it's functionality. In order to use a couple of features like translations or the HIX value, you need to set some settings which can be defined in a configuration file. It should be located at `/etc/integreat-cms.ini`. If you want to place the file at a different location, pass the absolute path via the environment variable `INTEGREAT_CMS_CONFIG`. An example file is located at `example-configs` in the project folder. -All these settings can also be configured via environment variables with the prefix `INTEGREAT_CMS_`, e.g. `INTEGREAT_CMS_SECRET_KEY`. +After setup you will be able to run the cms with most of its functionality. In order to use a couple of features like translations or the HIX value, you need to set some settings which can be defined as individual environment variables or in a configuration file. It should be located at `/etc/integreat-cms.ini`. If you want to place the file at a different location, pass the absolute path via the environment variable `INTEGREAT_CMS_CONFIG`. An example file is located at `example-configs` in the project folder. +In general, all environment variables should be the name of the setting in the system but with the prefix `INTEGREAT_CMS_`, e.g. `INTEGREAT_CMS_SECRET_KEY`. +Note that, contrary to how other programs work, in integreat cms the settings from the configuration file overwrite the values loaded from environment variables, not the other way around. Feel free to submit a pull request fixing this. #### Using a CMS configuration file with devcontainer diff --git a/docs/src/code-style.rst b/docs/src/code-style.rst index 5e18298235..ba5ec79da2 100644 --- a/docs/src/code-style.rst +++ b/docs/src/code-style.rst @@ -2,20 +2,19 @@ Code Style Guidelines ********************* +.. _ruff: -.. _black-code-style: - -Black ------ +Ruff +------ -We use the `black `_ coding style, a flavour of `PEP-8 `_ for Python. +We use `ruff `__ to format and lint our Python files. -We use a `pre-commit-hook `_ to apply this style before committing, so you don't have to bother about formatting. +We use a `pre-commit-hook `_ to apply these rules before committing, so you don't have to bother about doing so manually. Just code how you feel comfortable and let the tool do the work for you (see :ref:`pre-commit-hooks`). -If you want to apply the formatting without committing, use our developer tool :github-source:`tools/black.sh`:: +If you want to lint your code and apply the formatting without committing, use our developer tool :github-source:`tools/ruff.sh`:: - ./tools/black.sh + ./tools/ruff.sh .. _djlint: @@ -48,36 +47,6 @@ If you want to apply the formatting without committing, use our developer tool : ./tools/prettier.sh -.. _pylint: - -Linting -------- - -In addition to black, we use pylint to check the code for semantic correctness. -Run pylint with our developer tool :github-source:`tools/pylint.sh`:: - - ./tools/pylint.sh - -When you think a warning is a false positive, add a comment (see :doc:`pylint:user_guide/messages/message_control`):: - - def some_function( - something: SomeModel, # pylint: disable=unused-argument - *args, - **kwargs) -> None: - # pylint: disable=too-many-branches - -.. Note:: - - Please use the string identifiers (``unused-argument``) instead of the alphanumeric code (``W0613``) when disabling warnings. - -.. Hint:: - - If you want to run both tools at once, use our developer tool :github-source:`tools/code_style.sh`:: - - ./tools/code_style.sh - -.. include:: _docstrings.rst - .. _mypy: @@ -104,6 +73,14 @@ If you want to apply the formatting without committing, use our developer tool : If a third party library is incorrectly typed or missing type hints, and this results in errors you cannot fix, then you can add ``# type: ignore[]`` (where the ```` is the identifier of the error you're experiencing, e.g. ``attr-defined``) in the end of the line where the error is thrown. +.. Hint:: + + If you want to run all tools at once, use our developer tool :github-source:`tools/code_style.sh`:: + + ./tools/code_style.sh + +.. include:: _docstrings.rst + .. _shellcheck: diff --git a/docs/src/conf.py b/docs/src/conf.py index c42755e35d..fda4e9d830 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -15,13 +15,15 @@ import os import sys from datetime import date -from typing import Final +from typing import Final, TYPE_CHECKING -from django import VERSION as django_version_tuple -from sphinx.application import Sphinx +from django import VERSION as DJANGO_VERSION_TUPLE from integreat_cms.core import settings +if TYPE_CHECKING: + from sphinx.application import Sphinx + # Append project source directory to path environment variable sys.path.append(os.path.abspath("../..")) # Append sphinx source directory to path environment variable to allow documentation for this file @@ -29,13 +31,12 @@ #: The path to the django settings module (see :doc:`sphinxcontrib-django:readme`) django_settings: Final[str] = "integreat_cms.core.sphinx_settings" #: The "major.minor" version of Django -django_version: Final[str] = f"{django_version_tuple[0]}.{django_version_tuple[1]}" +django_version: Final[str] = f"{DJANGO_VERSION_TUPLE[0]}.{DJANGO_VERSION_TUPLE[1]}" # -- Project information ----------------------------------------------------- #: The project name project: Final[str] = "integreat-cms" -# pylint: disable=redefined-builtin #: The copyright notice copyright: Final[str] = f"{date.today().year} {settings.COMPANY}" #: The project author @@ -77,7 +78,6 @@ "dateutil": ("https://dateutil.readthedocs.io/en/stable/", None), "geopy": ("https://geopy.readthedocs.io/en/stable/", None), "lxml": ("https://lxml.de/apidoc/", None), - "pylint": ("https://pylint.readthedocs.io/en/latest/", None), "python": ( f"https://docs.python.org/{sys.version_info.major}.{sys.version_info.minor}/", None, @@ -139,9 +139,7 @@ "django-source": (f"{django_github_url}/%s", "%s"), } #: A string of reStructuredText that will be included at the end of every source file that is read. Used for substitutions. -rst_epilog: Final[ - str -] = f""" +rst_epilog: Final[str] = f""" .. |github-username| replace:: {github_username} .. |github-repository| replace:: {github_repository} .. |github-pages-url| replace:: {github_pages_url} diff --git a/docs/src/continuous-integration.rst b/docs/src/continuous-integration.rst index e2e084dd6f..bc11563364 100644 --- a/docs/src/continuous-integration.rst +++ b/docs/src/continuous-integration.rst @@ -44,16 +44,6 @@ This job executes ``npm ci`` and makes use of the `CircleCI Dependency Cache `_) -* ``black``: A formatter which applies automatic code formatting to Python files (see :ref:`black-code-style`) +* ``ruff``: A formatter and linter which applies automatic code formatting to Python files (see :ref:`ruff`) * ``prettier``: A formatter which applies automatic code formatting to CSS/JS files (see :ref:`prettier-code-style`) * ``djlint``: A formatter which applies automatic code formatting to Django HTML templates (see :ref:`djlint`) * ``translations`` A script which checks whether the translation file is up-to-date (see: :doc:`internationalization` and :ref:`translations`) diff --git a/docs/src/tools.rst b/docs/src/tools.rst index f8e3e358a5..e4afe603fc 100644 --- a/docs/src/tools.rst +++ b/docs/src/tools.rst @@ -63,9 +63,9 @@ Delete all database content with :github-source:`tools/prune_database.sh`:: ./tools/prune_database.sh -| Update testdata file based on the *current* database with :github-source:`tools/update_test_data.sh` +| Update testdata file based on the *current* database with :github-source:`tools/update_test_data.sh` -**Disclaimer**: make sure to first run ``prune_database.sh`` and make *only* the desired changes before running this tool:: +**Disclaimer**: make sure to first run ``prune_database.sh`` and make *only* the desired changes before running this tool:: ./tools/update_test_data.sh @@ -129,22 +129,14 @@ with :github-source:`tools/integreat-cms-cli`:: Code Quality ============ -Automatically run python linting with :github-source:`tools/ruff.sh`:: +Automatically run Python linting and formatting with :github-source:`tools/ruff.sh`:: ./tools/ruff.sh -Automatically apply our python style with :github-source:`tools/black.sh`:: - - ./tools/black.sh - Automatically apply our HTML formatting with :github-source:`tools/djlint.sh`:: ./tools/djlint.sh -Check the code for semantic correctness with :github-source:`tools/pylint.sh`:: - - ./tools/pylint.sh - Automatically run static file linting with :github-source:`tools/eslint.sh`:: ./tools/eslint.sh diff --git a/flake.lock b/flake.lock index 03a25c96fe..c2164f273a 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1712163089, - "narHash": "sha256-Um+8kTIrC19vD4/lUCN9/cU9kcOsD1O1m+axJqQPyMM=", + "lastModified": 1736883708, + "narHash": "sha256-uQ+NQ0/xYU0N1CnXsa2zghgNaOPxWpMJXSUJJ9W7140=", "owner": "nixos", "repo": "nixpkgs", - "rev": "fd281bd6b7d3e32ddfa399853946f782553163b5", + "rev": "eb62e6aa39ea67e0b8018ba8ea077efe65807dc8", "type": "github" }, "original": { @@ -42,11 +42,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index cc191a1dd1..7644c84f6a 100644 --- a/flake.nix +++ b/flake.nix @@ -66,7 +66,7 @@ python311Packages.platformdirs nodePackages.npm - nodejs_21 + nodejs_22 gettext netcat-gnu diff --git a/integreat_cms/README.md b/integreat_cms/README.md index f977c32bb3..00a4a06f96 100644 --- a/integreat_cms/README.md +++ b/integreat_cms/README.md @@ -1,7 +1,6 @@ [![CircleCI](https://circleci.com/gh/digitalfabrik/integreat-cms.svg?style=shield)](https://circleci.com/gh/digitalfabrik/integreat-cms) -[![Pylint](https://img.shields.io/badge/pylint-10.00-brightgreen)](https://www.pylint.org/) +[![Ruff](https://img.shields.io/badge/ruff-0.9.2-brightgreen)](https://docs.astral.sh/ruff/) ![Coverage](https://img.shields.io/codeclimate/coverage/digitalfabrik/integreat-cms) -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) # Integreat Django CMS diff --git a/integreat_cms/api/decorators.py b/integreat_cms/api/decorators.py index 8fdcb56696..ea175f23c6 100644 --- a/integreat_cms/api/decorators.py +++ b/integreat_cms/api/decorators.py @@ -40,7 +40,9 @@ def feedback_handler(func: Callable) -> Callable: @csrf_exempt @wraps(func) def handle_feedback( - request: HttpRequest, region_slug: str, language_slug: str + request: HttpRequest, + region_slug: str, + language_slug: str, ) -> JsonResponse: """ Parse feedback API request parameters @@ -57,11 +59,13 @@ def handle_feedback( language = Language.objects.get(slug=language_slug) except Region.DoesNotExist: return JsonResponse( - {"error": f'No region found with slug "{region_slug}"'}, status=404 + {"error": f'No region found with slug "{region_slug}"'}, + status=404, ) except Language.DoesNotExist: return JsonResponse( - {"error": f'No language found with slug "{language_slug}"'}, status=404 + {"error": f'No language found with slug "{language_slug}"'}, + status=404, ) data = ( json.loads(request.body.decode()) @@ -76,7 +80,8 @@ def handle_feedback( return JsonResponse({"error": "Invalid rating."}, status=400) if not comment and not rating: return JsonResponse( - {"error": "Either comment or rating is required."}, status=400 + {"error": "Either comment or rating is required."}, + status=400, ) rating_normalized: bool | None = feedback_ratings.NOT_STATED if rating == "up": @@ -100,7 +105,9 @@ def json_response(function: Callable) -> Callable: @wraps(function) def wrap( - request: dict[str, str] | HttpRequest, *args: Any, **kwargs: Any + request: dict[str, str] | HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect | JsonResponse: r""" The inner function for this decorator. @@ -141,16 +148,17 @@ def matomo_request(host: str, data: dict) -> None: """ data_str = parse.urlencode(data) url = f"{host}/matomo.php?{data_str}" - req = request.Request(url) + if not url.startswith(("http:", "https:")): + raise ValueError("URL must start with 'http:' or 'https:'") + req = request.Request(url) # noqa: S310 try: - with request.urlopen(req): + with request.urlopen(req): # noqa: S310 pass - except error.HTTPError as e: - logger.error( - "Matomo Tracking API request to %r failed with: %s", + except error.HTTPError: + logger.exception( + "Matomo Tracking API request to %r failed", # Mask auth token in log re.sub(r"&token_auth=[^&]+", "&token_auth=********", url), - e, ) @wraps(func) @@ -176,7 +184,7 @@ def wrap(request: HttpRequest, *args: Any, **kwargs: Any) -> JsonResponse: "url": request.build_absolute_uri(), "urlref": settings.BASE_URL, "ua": request.META.get("HTTP_USER_AGENT", "unknown user agent"), - "cip": f"{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(0, 255)}", + "cip": f"{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(0, 255)}", # noqa: S311 } t = threading.Thread(target=matomo_request, args=(settings.MATOMO_URL, data)) diff --git a/integreat_cms/api/middleware/json_debug_toolbar_middleware.py b/integreat_cms/api/middleware/json_debug_toolbar_middleware.py index dcc36bc40a..865d2efda8 100644 --- a/integreat_cms/api/middleware/json_debug_toolbar_middleware.py +++ b/integreat_cms/api/middleware/json_debug_toolbar_middleware.py @@ -18,7 +18,6 @@ class JsonDebugToolbarMiddleware: - # pylint: disable=too-few-public-methods """ The Django Debug Toolbar usually only works for views that return HTML. This middleware wraps any JSON response in HTML if the request @@ -44,7 +43,7 @@ def __call__(self, request: HttpRequest) -> Any: if "debug" in request.GET and response["Content-Type"] == "application/json": content = json.dumps(json.loads(response.content), sort_keys=True, indent=2) response = HttpResponse( - f"
{content}
" + f"
{content}
", ) return response diff --git a/integreat_cms/api/urls.py b/integreat_cms/api/urls.py index edf4a8cf65..17d668ff25 100644 --- a/integreat_cms/api/urls.py +++ b/integreat_cms/api/urls.py @@ -76,7 +76,9 @@ path("offers/", offers, name="offers"), path("extras/", offers, name="offers"), path( - "pushpage/", push_page_translation_content, name="push_page_translation_content" + "pushpage/", + push_page_translation_content, + name="push_page_translation_content", ), re_path( r"^feedback/?$", @@ -95,7 +97,9 @@ re_path(r"^page/?$", page_feedback.page_feedback, name="page_feedback"), re_path(r"^poi/?$", poi_feedback.poi_feedback, name="poi_feedback"), re_path( - r"^event/?$", event_feedback.event_feedback, name="event_feedback" + r"^event/?$", + event_feedback.event_feedback, + name="event_feedback", ), re_path( r"^events/?$", @@ -124,12 +128,16 @@ name="offer_list_feedback", ), re_path( - r"^offer/?$", offer_feedback.offer_feedback, name="offer_feedback" + r"^offer/?$", + offer_feedback.offer_feedback, + name="offer_feedback", ), re_path( - r"^extra/?$", offer_feedback.offer_feedback, name="offer_feedback" + r"^extra/?$", + offer_feedback.offer_feedback, + name="offer_feedback", ), - ] + ], ), ), path("chat//", user_chat.chat, name="chat"), @@ -158,7 +166,7 @@ root_social_media_headers, name=f"social_root_reserved_default_language_{reserved}", ), - ] + ], ), ) for reserved in settings.RESERVED_REGION_SLUGS @@ -211,7 +219,7 @@ page_social_media_headers, name="social_region_content", ), - ] + ], ), ), ] @@ -235,7 +243,7 @@ name="is_chat_enabled_for_user", ), path("/", include(content_api_urlpatterns)), - ] + ], ), ), path( @@ -243,7 +251,9 @@ include( [ path( - "de/wp-json/extensions/v3/languages/", languages, name="languages" + "de/wp-json/extensions/v3/languages/", + languages, + name="languages", ), path( "/wp-json/extensions/v3/", @@ -254,7 +264,7 @@ pdf_export, name="pdf_export", ), - ] + ], ), ), ] diff --git a/integreat_cms/api/v3/chat/user_chat.py b/integreat_cms/api/v3/chat/user_chat.py index 748bca632f..7f80fe1d9b 100644 --- a/integreat_cms/api/v3/chat/user_chat.py +++ b/integreat_cms/api/v3/chat/user_chat.py @@ -11,7 +11,6 @@ from django.http import HttpResponse, JsonResponse from django.shortcuts import get_object_or_404 -from django.utils.translation import gettext_lazy as _ from django.views.decorators.csrf import csrf_exempt from ....cms.models import ABTester, AttachmentMap, Language, Region, UserChat @@ -53,7 +52,7 @@ def get_attachment( if ( not ( attachment_map := AttachmentMap.objects.filter( - random_hash=attachment_id + random_hash=attachment_id, ).first() ) or (not user_chat) @@ -139,7 +138,7 @@ def send_message( status=500, ) return response_or_error( - client.send_message(user_chat.zammad_id, request.POST.get("message")) # type: ignore[union-attr] + client.send_message(user_chat.zammad_id, request.POST.get("message")), # type: ignore[union-attr] ) @@ -147,14 +146,13 @@ def send_message( @json_response def is_chat_enabled_for_user( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, device_id: str, ) -> JsonResponse: """ Function to check if the chat feature is enabled for the given region and the given user. :param request: Django request - :param region_slug: slug of a region :param device_id: ID of the user attempting to use the chat :return: JSON object according to APIv3 chat endpoint definition """ @@ -164,10 +162,12 @@ def is_chat_enabled_for_user( is_enabled = bool( request.region.zammad_url and request.region.zammad_access_token - and random.random() < (0.01 * request.region.chat_beta_tester_percentage) + and random.random() < (0.01 * request.region.chat_beta_tester_percentage), # noqa: S311 ) ABTester.objects.create( - device_id=device_id, region=request.region, is_tester=is_enabled + device_id=device_id, + region=request.region, + is_tester=is_enabled, ) return JsonResponse({"is_chat_enabled": is_enabled}, status=200) @@ -176,7 +176,7 @@ def is_chat_enabled_for_user( @json_response def chat( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, device_id: str, attachment_id: str = "", @@ -186,7 +186,6 @@ def chat( or to create one if no chat exists or the user requested a new one. :param request: Django request - :param region_slug: slug of a region :param language_slug: language slug :param device_id: ID of the user requesting the messages :param attachment_id: ID of the requested attachment (optional) @@ -208,7 +207,8 @@ def chat( # since new chat creation also depends on passing this check if user_chat.ratelimit_exceeded(): logger.warning( - "Client with device ID %s has exceeded their ratelimit.", device_id + "Client with device ID %s has exceeded their ratelimit.", + device_id, ) return JsonResponse({"error": "You're doing that too often."}, status=429) user_chat.record_hit() @@ -227,7 +227,8 @@ def zammad_webhook(request: HttpRequest) -> JsonResponse: Receive webhooks from Zammad to update the latest article translation """ region = get_object_or_404( - Region, zammad_webhook_token=request.GET.get("token", None) + Region, + zammad_webhook_token=request.GET.get("token", None), ) if not region.integreat_chat_enabled: return JsonResponse({"status": "Integreat Chat disabled"}) @@ -240,7 +241,7 @@ def zammad_webhook(request: HttpRequest) -> JsonResponse: { "region": region.slug, "results": "skipped internal message", - } + }, ) if ( webhook_message["article"]["created_by"]["login"] @@ -248,17 +249,17 @@ def zammad_webhook(request: HttpRequest) -> JsonResponse: ): actions.append("question translation queued") process_user_message.apply_async( - args=[message_text, region.slug, webhook_message["ticket"]["id"]] + args=[message_text, region.slug, webhook_message["ticket"]["id"]], ) else: actions.append("answer translation queued") process_answer.apply_async( - args=[message_text, region.slug, webhook_message["ticket"]["id"]] + args=[message_text, region.slug, webhook_message["ticket"]["id"]], ) return JsonResponse( { "original_message": message_text, "region": region.slug, "actions": actions, - } + }, ) diff --git a/integreat_cms/api/v3/chat/utils/chat_bot.py b/integreat_cms/api/v3/chat/utils/chat_bot.py index edc8dde7f6..52c78e802b 100644 --- a/integreat_cms/api/v3/chat/utils/chat_bot.py +++ b/integreat_cms/api/v3/chat/utils/chat_bot.py @@ -16,7 +16,10 @@ async def automatic_answer( - message: str, region_slug: str, language_slug: str, session: aiohttp.ClientSession + message: str, + region_slug: str, + language_slug: str, + session: aiohttp.ClientSession, ) -> dict: """ Get automatic answer to question asynchronously @@ -26,7 +29,9 @@ async def automatic_answer( ) body = {"message": message, "language": language_slug, "region": region_slug} async with session.post( - url, json=body, timeout=settings.INTEGREAT_CHAT_BACK_END_TIMEOUT + url, + json=body, + timeout=settings.INTEGREAT_CHAT_BACK_END_TIMEOUT, ) as response: return await response.json() @@ -47,7 +52,9 @@ async def automatic_translation( "target_language": target_language_slug, } async with session.post( - url, json=body, timeout=settings.INTEGREAT_CHAT_BACK_END_TIMEOUT + url, + json=body, + timeout=settings.INTEGREAT_CHAT_BACK_END_TIMEOUT, ) as response: return await response.json() @@ -69,7 +76,10 @@ async def async_process_user_message( session, ) answer_task = automatic_answer( - message_text, region_slug, zammad_chat_language_slug, session + message_text, + region_slug, + zammad_chat_language_slug, + session, ) translation, answer = await asyncio.gather(translation_task, answer_task) return translation, answer @@ -77,7 +87,9 @@ async def async_process_user_message( @shared_task def process_user_message( - message_text: str, region_slug: str, zammad_ticket_id: int + message_text: str, + region_slug: str, + zammad_ticket_id: int, ) -> None: """ Call the async processing of the message from an Integreat App user @@ -91,7 +103,7 @@ def process_user_message( region_slug, region.default_language.slug, message_text, - ) + ), ) if translation: client.send_message( @@ -110,14 +122,19 @@ def process_user_message( async def async_process_answer( - message_text: str, source_language: str, target_language: str + message_text: str, + source_language: str, + target_language: str, ) -> dict: """ Process automatic or counselor answers """ async with aiohttp.ClientSession() as session: translation_task = automatic_translation( - message_text, source_language, target_language, session + message_text, + source_language, + target_language, + session, ) return await translation_task @@ -132,8 +149,10 @@ def process_answer(message_text: str, region_slug: str, zammad_ticket_id: int) - client = ZammadChatAPI(region) translation = asyncio.run( async_process_answer( - message_text, region.default_language.slug, zammad_chat.language.slug - ) + message_text, + region.default_language.slug, + zammad_chat.language.slug, + ), ) if translation: client.send_message( diff --git a/integreat_cms/api/v3/chat/utils/zammad_api.py b/integreat_cms/api/v3/chat/utils/zammad_api.py index 7bbf3987a9..f812f3c9d7 100644 --- a/integreat_cms/api/v3/chat/utils/zammad_api.py +++ b/integreat_cms/api/v3/chat/utils/zammad_api.py @@ -21,25 +21,7 @@ AUTO_ANSWER_STRING = "automatically generated message" -def _raise_or_return_json( - self: Any, response: HttpResponse # pylint: disable=unused-argument -) -> dict: - """ - Raise HTTPError before converting response to json - - :param response: Request response object - """ - response.raise_for_status() - - try: - json_value = response.json() - except ValueError: - return response.content - return json_value - - class ZammadChatAPI: - # pylint: disable=too-many-instance-attributes """ Class providing an API for Zammad used in the context of user chats. @@ -49,18 +31,19 @@ class ZammadChatAPI: def __init__(self, region: Region): self.client = ZammadAPI( - url=f"{region.zammad_url}/api/v1/", http_token=region.zammad_access_token + url=f"{region.zammad_url}/api/v1/", + http_token=region.zammad_access_token, ) # Patch the relevant methods to allow us to capture error response codes self.client.ticket.__class__.__base__._raise_or_return_json = ( - _raise_or_return_json + self._raise_or_return_json ) self.client.ticket_article.__class__.__base__._raise_or_return_json = ( - _raise_or_return_json + self._raise_or_return_json ) self.client.user.__class__.__base__._raise_or_return_json = ( - _raise_or_return_json + self._raise_or_return_json ) try: @@ -77,6 +60,23 @@ def __init__(self, region: Region): self.ticket_group = settings.USER_CHAT_TICKET_GROUP self.responsible_handlers = region.zammad_chat_handlers + @staticmethod + def _raise_or_return_json( + response: HttpResponse, + ) -> dict: + """ + Raise HTTPError before converting response to json + + :param response: Request response object + """ + response.raise_for_status() + + try: + json_value = response.json() + except ValueError: + return response.content + return json_value + @staticmethod def _attempt_call(call: Callable, *args: Any, **kwargs: Any) -> Any: try: @@ -111,7 +111,6 @@ def _parse_response(self, response: dict | list[dict]) -> dict | list[dict]: return {key: response[key] for key in keys_to_keep if key in response} def create_ticket(self, device_id: str, language_slug: str) -> dict: - # pylint: disable=method-hidden """ Create a new ticket (i.e. initialize a new chat conversation) @@ -124,12 +123,14 @@ def create_ticket(self, device_id: str, language_slug: str) -> dict: "customer": self.client_identity, } return self._parse_response( # type: ignore[return-value] - self._attempt_call(self.client.ticket.create, params=params) + self._attempt_call(self.client.ticket.create, params=params), ) @staticmethod def _transform_attachment( - chat: UserChat, article_id: int, attachment: dict + chat: UserChat, + article_id: int, + attachment: dict, ) -> dict: return { "filename": attachment.get("filename", ""), @@ -143,7 +144,6 @@ def _transform_attachment( } def get_messages(self, chat: UserChat) -> dict[str, dict | list[dict] | str]: - # pylint: disable=method-hidden """ Get all non-internal messages for a given ticket @@ -154,7 +154,7 @@ def get_messages(self, chat: UserChat) -> dict[str, dict | list[dict] | str]: return self._parse_response(raw_response) # type: ignore[return-value] response = self._parse_response( - [article for article in raw_response if not article.get("internal")] + [article for article in raw_response if not article.get("internal")], ) for message in response: if "attachments" in message: @@ -175,7 +175,6 @@ def send_message( internal: bool = False, automatic_message: bool = False, ) -> dict: - # pylint: disable=method-hidden """ Post a new message to the given ticket @@ -199,11 +198,10 @@ def send_message( "sender": "Customer" if not automatic_message else "Agent", } return self._parse_response( # type: ignore[return-value] - self._attempt_call(self.client.ticket_article.create, params=params) + self._attempt_call(self.client.ticket_article.create, params=params), ) def get_attachment(self, attachment_map: AttachmentMap) -> bytes | dict: - # pylint: disable=method-hidden """ Get the (binary) attachment file from Zammad. diff --git a/integreat_cms/api/v3/events.py b/integreat_cms/api/v3/events.py index 1d94624967..2aa65125ab 100644 --- a/integreat_cms/api/v3/events.py +++ b/integreat_cms/api/v3/events.py @@ -16,8 +16,9 @@ from .locations import transform_poi if TYPE_CHECKING: + from collections.abc import Iterator from datetime import date - from typing import Any, Iterator + from typing import Any from django.http import HttpRequest @@ -41,7 +42,9 @@ def transform_event(event: Event, custom_date: date | None = None) -> dict[str, tzinfo=end_local.tzinfo, ) start_local = datetime.combine( - custom_date, start_local.time(), tzinfo=start_local.tzinfo + custom_date, + start_local.time(), + tzinfo=start_local.tzinfo, ) return { @@ -108,7 +111,8 @@ def transform_event_translation( def transform_available_languages( - event_translation: EventTranslation, recurrence_date: date + event_translation: EventTranslation, + recurrence_date: date, ) -> dict[str, dict[str, str | None]]: """ Function to create a JSON object of all available translations of an event translation. @@ -163,28 +167,29 @@ def transform_event_recurrences( # Calculate all recurrences of this event for recurrence_date in event.recurrence_rule.iter_after(start_date): if recurrence_date - max(start_date, today) > timedelta( - days=settings.API_EVENTS_MAX_TIME_SPAN_DAYS + days=settings.API_EVENTS_MAX_TIME_SPAN_DAYS, ): break if recurrence_date < today: continue yield transform_event_translation( - event_translation, poi_translation, recurrence_date + event_translation, + poi_translation, + recurrence_date, ) @json_response def events( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, ) -> JsonResponse: """ List all events of the region and transform result into JSON :param request: The current request - :param region_slug: The slug of the requested region :param language_slug: The slug of the requested language :return: JSON object according to APIv3 events endpoint definition """ @@ -208,15 +213,18 @@ def events( result.extend( iter( transform_event_recurrences( - event_translation, poi_translation, now - ) - ) + event_translation, + poi_translation, + now, + ), + ), ) else: result.append( - transform_event_translation(event_translation, poi_translation) + transform_event_translation(event_translation, poi_translation), ) return JsonResponse( - result, safe=False + result, + safe=False, ) # Turn off Safe-Mode to allow serializing arrays diff --git a/integreat_cms/api/v3/feedback/event_feedback.py b/integreat_cms/api/v3/feedback/event_feedback.py index 260d6d22e9..4cde4a6900 100644 --- a/integreat_cms/api/v3/feedback/event_feedback.py +++ b/integreat_cms/api/v3/feedback/event_feedback.py @@ -38,7 +38,12 @@ def event_feedback( :return: decorated function that saves feedback in database """ return event_feedback_internal( - data, region, language, comment, rating, is_technical + data, + region, + language, + comment, + rating, + is_technical, ) @@ -92,7 +97,7 @@ def event_feedback_internal( raise Http404("No matching event found for slug.") event_translation = event.get_translation(language.slug) or event.get_translation( - region.default_language.slug + region.default_language.slug, ) EventFeedback.objects.create( diff --git a/integreat_cms/api/v3/feedback/event_list_feedback.py b/integreat_cms/api/v3/feedback/event_list_feedback.py index 9cc2bb1e59..d249bd4458 100644 --- a/integreat_cms/api/v3/feedback/event_list_feedback.py +++ b/integreat_cms/api/v3/feedback/event_list_feedback.py @@ -18,7 +18,7 @@ @feedback_handler @json_response def event_list_feedback( - data: dict, # pylint: disable=unused-argument + _data: dict, region: Region, language: Language, comment: str, @@ -28,7 +28,6 @@ def event_list_feedback( """ Store feedback on events list in database - :param data: HTTP request body data :param region: The region of this sitemap's urls :param language: The language of this sitemap's urls :param comment: The comment sent as feedback diff --git a/integreat_cms/api/v3/feedback/imprint_page_feedback.py b/integreat_cms/api/v3/feedback/imprint_page_feedback.py index 6dabdc681e..2aae1a0d3c 100644 --- a/integreat_cms/api/v3/feedback/imprint_page_feedback.py +++ b/integreat_cms/api/v3/feedback/imprint_page_feedback.py @@ -40,12 +40,17 @@ def imprint_page_feedback( :return: decorated function that saves feedback in database """ return imprint_page_feedback_internal( - data, region, language, comment, rating, is_technical + data, + region, + language, + comment, + rating, + is_technical, ) def imprint_page_feedback_internal( - data: dict, # pylint: disable=unused-argument + _data: dict, region: Region, language: Language, comment: str, @@ -55,7 +60,6 @@ def imprint_page_feedback_internal( """ Store feedback about imprint in database - :param data: HTTP request body data :param region: The region of this sitemap's urls :param language: The language of this sitemap's urls :param comment: The comment sent as feedback @@ -81,5 +85,5 @@ def imprint_page_feedback_internal( language, ) raise Http404( - "The imprint does not exist in this region for the selected language." + "The imprint does not exist in this region for the selected language.", ) diff --git a/integreat_cms/api/v3/feedback/legacy_feedback_endpoint.py b/integreat_cms/api/v3/feedback/legacy_feedback_endpoint.py index f4e5ce090b..0a73a1103b 100644 --- a/integreat_cms/api/v3/feedback/legacy_feedback_endpoint.py +++ b/integreat_cms/api/v3/feedback/legacy_feedback_endpoint.py @@ -45,11 +45,21 @@ def legacy_feedback_endpoint( link_components = list(filter(None, link.split("/"))) if link_components[-1] == settings.IMPRINT_SLUG: return imprint_page_feedback_internal( - data, region, language, comment, rating, is_technical + data, + region, + language, + comment, + rating, + is_technical, ) data["slug"] = link_components[-1] if link_components[-2] == "events": return event_feedback_internal( - data, region, language, comment, rating, is_technical + data, + region, + language, + comment, + rating, + is_technical, ) return page_feedback_internal(data, region, language, comment, rating, is_technical) diff --git a/integreat_cms/api/v3/feedback/map_feedback.py b/integreat_cms/api/v3/feedback/map_feedback.py index 7e0859d76c..1523ab699a 100644 --- a/integreat_cms/api/v3/feedback/map_feedback.py +++ b/integreat_cms/api/v3/feedback/map_feedback.py @@ -18,7 +18,7 @@ @feedback_handler @json_response def map_feedback( - data: dict, # pylint: disable=unused-argument + _data: dict, region: Region, language: Language, comment: str, @@ -28,7 +28,6 @@ def map_feedback( """ Store feedback on map in database - :param data: HTTP request body data :param region: The region of this sitemap's urls :param language: The language of this sitemap's urls :param comment: The comment sent as feedback diff --git a/integreat_cms/api/v3/feedback/offer_list_feedback.py b/integreat_cms/api/v3/feedback/offer_list_feedback.py index 1ba80bdfb0..b2c8b2f23d 100644 --- a/integreat_cms/api/v3/feedback/offer_list_feedback.py +++ b/integreat_cms/api/v3/feedback/offer_list_feedback.py @@ -18,7 +18,7 @@ @feedback_handler @json_response def offer_list_feedback( - data: dict, # pylint: disable=unused-argument + _data: dict, region: Region, language: Language, comment: str, @@ -28,7 +28,6 @@ def offer_list_feedback( """ Store feedback about offers list in database - :param data: HTTP request body data :param region: The region of this sitemap's urls :param language: The language of this sitemap's urls :param comment: The comment sent as feedback diff --git a/integreat_cms/api/v3/feedback/poi_feedback.py b/integreat_cms/api/v3/feedback/poi_feedback.py index fb661145cd..6de65f0090 100644 --- a/integreat_cms/api/v3/feedback/poi_feedback.py +++ b/integreat_cms/api/v3/feedback/poi_feedback.py @@ -65,7 +65,7 @@ def poi_feedback( raise Http404("No matching location found for slug.") poi_translation = poi.get_translation(language.slug) or poi.get_translation( - region.default_language.slug + region.default_language.slug, ) POIFeedback.objects.create( diff --git a/integreat_cms/api/v3/feedback/region_feedback.py b/integreat_cms/api/v3/feedback/region_feedback.py index fff27eeafe..8ad58cb1b2 100644 --- a/integreat_cms/api/v3/feedback/region_feedback.py +++ b/integreat_cms/api/v3/feedback/region_feedback.py @@ -18,7 +18,7 @@ @feedback_handler @json_response def region_feedback( - data: dict, # pylint: disable=unused-argument + _data: dict, region: Region, language: Language, comment: str, @@ -28,7 +28,6 @@ def region_feedback( """ Store feedback about region / main pages in database - :param data: HTTP request body data :param region: The region of this sitemap's urls :param language: The language of this sitemap's urls :param comment: The comment sent as feedback diff --git a/integreat_cms/api/v3/imprint.py b/integreat_cms/api/v3/imprint.py index 9e1b92e2b4..7e67d08bd0 100644 --- a/integreat_cms/api/v3/imprint.py +++ b/integreat_cms/api/v3/imprint.py @@ -51,26 +51,27 @@ def transform_imprint(imprint_translation: ImprintPageTranslation) -> dict[str, @json_response def imprint( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument language_slug: str, + **kwargs: Any, ) -> JsonResponse: - """ + r""" Get imprint for language and return JSON object to client. If no imprint translation is available in the selected language, try to return the translation in the region default language. :param request: Django request - :param region_slug: slug of a region :param language_slug: language slug + :param \**kwargs: additional keyword args :return: JSON object according to APIv3 imprint endpoint definition """ region = request.region # Throw a 404 error when the language does not exist or is disabled region.get_language_or_404(language_slug, only_active=True) # Check if an imprint is available for that region - if region.imprint: - if imprint_translation := region.imprint.get_public_translation(language_slug): - return JsonResponse(transform_imprint(imprint_translation)) + if region.imprint and ( + imprint_translation := region.imprint.get_public_translation(language_slug) + ): + return JsonResponse(transform_imprint(imprint_translation)) # If imprint does not exist, return an empty response. Turn off Safe-Mode to allow serializing arrays logger.warning("The imprint for region %r does not exist", region) return JsonResponse([], safe=False) diff --git a/integreat_cms/api/v3/languages.py b/integreat_cms/api/v3/languages.py index 856751a76f..6d9733ddd8 100644 --- a/integreat_cms/api/v3/languages.py +++ b/integreat_cms/api/v3/languages.py @@ -37,18 +37,19 @@ def transform_language(language: Language) -> dict[str, Any]: @json_response def languages( - request: HttpRequest, region_slug: str # pylint: disable=unused-argument + request: HttpRequest, + region_slug: str, ) -> JsonResponse: """ Function to add all languages related to a region to a JSON. :param request: Django request - :param region_slug: slug of a region :return: JSON object according to APIv3 languages endpoint definition """ if request.region.status == region_status.ARCHIVED: raise Http404("This region is archived.") return JsonResponse( - list(map(transform_language, request.region.visible_languages)), safe=False + list(map(transform_language, request.region.visible_languages)), + safe=False, ) # Turn off Safe-Mode to allow serializing arrays diff --git a/integreat_cms/api/v3/location_categories.py b/integreat_cms/api/v3/location_categories.py index d4a0cfa12c..26394d055c 100644 --- a/integreat_cms/api/v3/location_categories.py +++ b/integreat_cms/api/v3/location_categories.py @@ -20,7 +20,8 @@ def transform_location_category( - location_category: POICategory, language_slug: str + location_category: POICategory, + language_slug: str, ) -> dict[str, Any] | None: """ Function to create a JSON from a single location category object. @@ -52,14 +53,13 @@ def transform_location_category( @json_response def location_categories( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, ) -> JsonResponse: """ Function to return all POI categories as JSON. :param request: The current request - :param region_slug: The slug of the requested region :param language_slug: The slug of the requested language :return: JSON object of all POI categories """ @@ -71,5 +71,6 @@ def location_categories( for location_category in POICategory.objects.all() ] return JsonResponse( - result, safe=False + result, + safe=False, ) # Turn off Safe-Mode to allow serializing arrays diff --git a/integreat_cms/api/v3/locations.py b/integreat_cms/api/v3/locations.py index 3c9231d470..3ce11ca53c 100644 --- a/integreat_cms/api/v3/locations.py +++ b/integreat_cms/api/v3/locations.py @@ -91,7 +91,8 @@ def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any] "email": poi.email or None, "phone_number": poi.phone_number or None, "category": transform_location_category( - poi.category, poi_translation.language.slug + poi.category, + poi_translation.language.slug, ), "temporarily_closed": poi.temporarily_closed, # Only return opening hours if not temporarily closed and they differ from the default value @@ -117,14 +118,13 @@ def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any] @json_response def locations( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument language_slug: str, + **kwargs: Any, ) -> JsonResponse: """ List all POIs of the region and transform result into JSON :param request: The current request - :param region_slug: The slug of the requested region :param language_slug: The slug of the requested language :return: JSON object according to APIv3 locations endpoint definition """ @@ -146,7 +146,7 @@ def locations( Prefetch( "category__translations", queryset=POICategoryTranslation.objects.select_related("language"), - ) + ), ) ) @@ -162,5 +162,6 @@ def locations( result.append(transform_poi_translation(translation)) return JsonResponse( - result, safe=False + result, + safe=False, ) # Turn off Safe-Mode to allow serializing arrays diff --git a/integreat_cms/api/v3/offers.py b/integreat_cms/api/v3/offers.py index 3c6959b8c5..e9651fe117 100644 --- a/integreat_cms/api/v3/offers.py +++ b/integreat_cms/api/v3/offers.py @@ -79,19 +79,18 @@ def transform_offer(offer: OfferTemplate, region: Region) -> dict[str, Any]: @json_response def offers( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument - language_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str, + language_slug: str | None = None, ) -> JsonResponse: """ Function to iterate through all offers related to a region and adds them to a JSON. :param request: Django request - :param region_slug: slug of a region - :param language_slug: language slug :return: JSON object according to APIv3 offers endpoint definition """ region = request.region result = [transform_offer(offer, region) for offer in region.offers.all()] return JsonResponse( - result, safe=False + result, + safe=False, ) # Turn off Safe-Mode to allow serializing arrays diff --git a/integreat_cms/api/v3/pages.py b/integreat_cms/api/v3/pages.py index d6d8232500..b2eb2a0fa3 100644 --- a/integreat_cms/api/v3/pages.py +++ b/integreat_cms/api/v3/pages.py @@ -51,7 +51,7 @@ def transform_page(page_translation: PageTranslation) -> dict[str, Any]: parent_page = page_translation.page.cached_parent if parent_page and not parent_page.explicitly_archived: if parent_public_translation := parent_page.get_public_translation( - page_translation.language.slug + page_translation.language.slug, ): parent_absolute_url = parent_public_translation.get_absolute_url() parent = { @@ -114,14 +114,13 @@ def transform_page(page_translation: PageTranslation) -> dict[str, Any]: @json_response def pages( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, ) -> JsonResponse: """ Function to iterate through all non-archived pages of a region and return them as JSON. :param request: Django request - :param region_slug: slug of a region :param language_slug: language slug :return: JSON object according to APIv3 pages endpoint definition """ @@ -142,7 +141,8 @@ def pages( if page_translation := page.get_public_translation(language_slug): result.append(transform_page(page_translation)) return JsonResponse( - result, safe=False + result, + safe=False, ) # Turn off Safe-Mode to allow serializing arrays @@ -189,7 +189,7 @@ def get_single_page(request: HttpRequest, language_slug: str) -> Page: filtered_pages, ) raise MultipleObjectsReturned( - "This page translation slug is not unique, please contact your server administrator." + "This page translation slug is not unique, please contact your server administrator.", ) if not filtered_pages: raise Http404("No matching page translation found for url.") @@ -210,7 +210,7 @@ def get_single_page(request: HttpRequest, language_slug: str) -> Page: @json_response def single_page( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, ) -> JsonResponse: """ @@ -218,7 +218,6 @@ def single_page( requested page does not exist. :param request: The request that has been sent to the Django server - :param region_slug: Slug defining the region :param language_slug: Code to identify the desired language :raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists. @@ -237,7 +236,9 @@ def single_page( @matomo_tracking @json_response def children( - request: HttpRequest, region_slug: str, language_slug: str + request: HttpRequest, + region_slug: str, + language_slug: str, ) -> JsonResponse: """ Retrieves all children for a single page @@ -266,7 +267,8 @@ def children( request.region.pages.select_related("organization__icon") .prefetch_related("embedded_offers") .filter( - explicitly_archived=False, tree_id__in=[page.tree_id for page in root_pages] + explicitly_archived=False, + tree_id__in=[page.tree_id for page in root_pages], ) .cache_tree(archived=False, language_slug=language_slug) ) @@ -275,7 +277,7 @@ def children( for descendant in public_region_pages: if descendant in descendants: result.append( - transform_page(descendant.get_public_translation(language_slug)) + transform_page(descendant.get_public_translation(language_slug)), ) return JsonResponse(result, safe=False) @@ -283,7 +285,7 @@ def children( @json_response def parents( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, ) -> JsonResponse: """ @@ -291,7 +293,6 @@ def parents( If any ancestor is archived, an 404 is raised. :param request: The request that has been sent to the Django server - :param region_slug: Slug defining the region :param language_slug: Code to identify the desired language :raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists. @@ -308,7 +309,8 @@ def parents( def get_public_ancestor_translations( - current_page: Page, language_slug: str + current_page: Page, + language_slug: str, ) -> list[dict[str, Any]]: """ Retrieves all ancestors (parent and all nodes up to the root node) of a page. @@ -335,14 +337,13 @@ def get_public_ancestor_translations( @json_response def push_page_translation_content( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, ) -> JsonResponse: """ Retrieves all ancestors (parent and all nodes up to the root node) of a page :param request: The request that has been sent to the Django server - :param region_slug: Slug defining the region :param language_slug: Code to identify the desired language :raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists. @@ -350,8 +351,8 @@ def push_page_translation_content( """ try: data = json.loads(request.body) - except json.decoder.JSONDecodeError as e: - logger.error("Push Content: failed to parse JSON: %s", e) + except json.decoder.JSONDecodeError: + logger.exception("Push Content: failed to parse JSON") return JsonResponse({"status": "error"}, status=405) if not all(key in data for key in ["content", "token"]): diff --git a/integreat_cms/api/v3/pdf_export.py b/integreat_cms/api/v3/pdf_export.py index 0d8556def8..7eb25a8026 100644 --- a/integreat_cms/api/v3/pdf_export.py +++ b/integreat_cms/api/v3/pdf_export.py @@ -26,7 +26,7 @@ @never_cache def pdf_export( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, ) -> HttpResponseRedirect: """ @@ -35,7 +35,6 @@ def pdf_export( by forwarding the request to :func:`~integreat_cms.cms.utils.pdf_utils.generate_pdf` :param request: request that was sent to the server - :param region_slug: Slug defining the region :param language_slug: current language slug :raises ~django.http.Http404: HTTP status 404 if the requested page translation cannot be found. diff --git a/integreat_cms/api/v3/push_notifications.py b/integreat_cms/api/v3/push_notifications.py index ab85845a2f..f4e5f2b24c 100644 --- a/integreat_cms/api/v3/push_notifications.py +++ b/integreat_cms/api/v3/push_notifications.py @@ -21,7 +21,9 @@ @json_response def sent_push_notifications( - request: HttpRequest, region_slug: str, language_slug: str + request: HttpRequest, + region_slug: str, + language_slug: str, ) -> JsonResponse: """ Function to iterate through all sent push notifications related to a region and adds them to a JSON. @@ -34,11 +36,11 @@ def sent_push_notifications( channel = request.GET.get("channel", "all") query_result = ( PushNotificationTranslation.objects.filter( - push_notification__regions__slug=region_slug + push_notification__regions__slug=region_slug, ) .filter( push_notification__sent_date__gte=timezone.now() - - timezone.timedelta(days=settings.FCM_HISTORY_DAYS) + - timezone.timedelta(days=settings.FCM_HISTORY_DAYS), ) .filter(language__slug=language_slug) .filter(push_notification__draft=False) diff --git a/integreat_cms/api/v3/regions.py b/integreat_cms/api/v3/regions.py index 31316b4387..31bb1ae73c 100644 --- a/integreat_cms/api/v3/regions.py +++ b/integreat_cms/api/v3/regions.py @@ -48,7 +48,7 @@ def transform_region(region: Region) -> dict[str, Any]: "is_chat_enabled": bool( region.integreat_chat_enabled and region.zammad_url - and region.zammad_access_token + and region.zammad_access_token, ), } @@ -61,16 +61,18 @@ def regions(_: HttpRequest) -> JsonResponse: :return: JSON object according to APIv3 regions endpoint definition """ result = list( - map(transform_region, Region.objects.exclude(status=region_status.ARCHIVED)) + map(transform_region, Region.objects.exclude(status=region_status.ARCHIVED)), ) return JsonResponse( - result, safe=False + result, + safe=False, ) # Turn off Safe-Mode to allow serializing arrays @json_response def region_by_slug( - request: HttpRequest, region_slug: str # pylint: disable=unused-argument + request: HttpRequest, + region_slug: str, ) -> JsonResponse: """ Retrieve a single region and transform result into JSON @@ -81,5 +83,6 @@ def region_by_slug( raise Http404("This region is archived.") return JsonResponse( - transform_region(request.region), safe=False + transform_region(request.region), + safe=False, ) # Turn off Safe-Mode to allow serializing arrays diff --git a/integreat_cms/api/v3/social_media_headers.py b/integreat_cms/api/v3/social_media_headers.py index 9448c11562..a3a2772bfe 100644 --- a/integreat_cms/api/v3/social_media_headers.py +++ b/integreat_cms/api/v3/social_media_headers.py @@ -19,7 +19,6 @@ from ...cms.models.push_notifications.push_notification_translation import ( PushNotificationTranslation, ) -from ...cms.models.regions.region import Region from ...cms.utils.internal_link_utils import ( get_public_translation_for_webapp_link_parts, ) @@ -35,6 +34,8 @@ JsonResponse, ) + from ...cms.models.regions.region import Region + logger = logging.getLogger(__name__) @@ -86,7 +87,9 @@ def partial_html_response(function: Callable) -> Callable: @wraps(function) def wrap( - request: dict[str, str] | HttpRequest, *args: Any, **kwargs: Any + request: dict[str, str] | HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect | JsonResponse: r""" The inner function for this decorator. @@ -111,7 +114,11 @@ def wrap( def render_social_media_headers( - request: HttpRequest, title: str, language_code: str, excerpt: str | None, url: str + request: HttpRequest, + title: str, + language_code: str, + excerpt: str | None, + url: str, ) -> HttpResponse: """ Renders the social media headers with the specified arguments @@ -159,7 +166,8 @@ def render_error_headers(request: HttpRequest, error: str) -> HttpResponse: @partial_html_response def root_social_media_headers( - request: HttpRequest, language_slug: str = settings.LANGUAGE_CODE + request: HttpRequest, + language_slug: str = settings.LANGUAGE_CODE, ) -> HttpResponse: """ Renders the social media headers for a root page @@ -185,7 +193,7 @@ def root_social_media_headers( @partial_html_response def region_social_media_headers( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str | None = None, ) -> HttpResponse: """ @@ -193,7 +201,6 @@ def region_social_media_headers( This is also used as a fallback for any routes in a region, where no content can be found. :param request: The current request - :param region_slug: The region the request refers to :param language_slug: The current language :return: HTML social meta headers required by social media platforms @@ -214,7 +221,7 @@ def region_social_media_headers( @partial_html_response def page_social_media_headers( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, path: str, ) -> HttpResponse: @@ -222,7 +229,6 @@ def page_social_media_headers( Tries rendering the social media headers for a page in a specified region and language. :param request: The current request - :param region_slug: The region slug for the region, which the page belongs to :param language_slug: The language slug of the language, which the page belongs to :param path: The page path (url_infix + slug) @@ -234,13 +240,15 @@ def page_social_media_headers( path_parts = unquote(path).strip("/").split("/") if not ( page_translation := get_public_translation_for_webapp_link_parts( - region.slug, language_slug, path_parts + region.slug, + language_slug, + path_parts, ) ): raise Http404("Page not found in this region with this language.") - # pylint: disable=fixme - # TODO: add breadcrumb json-ld if content_translation exists + # TODO(sarahsporck): add breadcrumb json-ld if content_translation exists + # https://github.com/digitalfabrik/integreat-cms/issues/3287 return render_social_media_headers( request=request, title=get_region_title(region, page_translation.title), @@ -253,7 +261,7 @@ def page_social_media_headers( @partial_html_response def event_social_media_headers( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, slug: str, ) -> HttpResponse: @@ -261,7 +269,6 @@ def event_social_media_headers( Tries rendering the social_media headers for an event page in a specified region and language. :param request: The current request - :param region_slug: The region slug for the region, which the event belongs to :param language_slug: The language slug of the language, which the event belongs to :param slug: The event slug @@ -272,13 +279,15 @@ def event_social_media_headers( if not ( event_translation := get_public_translation_for_webapp_link_parts( - region.slug, language_slug, ["events", slug] + region.slug, + language_slug, + ["events", slug], ) ): raise Http404("Event not found in this region with this language.") - # pylint: disable=fixme - # TODO: add event json-ld + # TODO(sarahsporck): add event json-ld + # https://github.com/digitalfabrik/integreat-cms/issues/3287 return render_social_media_headers( request=request, title=get_region_title(region, event_translation.title), @@ -291,7 +300,7 @@ def event_social_media_headers( @partial_html_response def news_social_media_headers( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, slug: str, ) -> HttpResponse: @@ -299,7 +308,6 @@ def news_social_media_headers( Tries rendering the social media headers for a news page in a specified region and language. :param request: The current request - :param region_slug: The region slug for the region, which the push notification belongs to :param language_slug: The language slug of the language, which the push notification belongs to :param slug: The news specific slug of the news route e.g. /local/ @@ -329,7 +337,7 @@ def news_social_media_headers( @partial_html_response def location_social_media_headers( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, slug: str, ) -> HttpResponse: @@ -337,7 +345,6 @@ def location_social_media_headers( Tries rendering the social media headers for a location page in a specified region and language. :param request: The current request - :param region_slug: The region slug for the region, which the location belongs to :param language_slug: The language slug of the language, which the location belongs to :param slug: The location slug @@ -348,7 +355,9 @@ def location_social_media_headers( if not ( location_translation := get_public_translation_for_webapp_link_parts( - region.slug, language_slug, ["locations", slug] + region.slug, + language_slug, + ["locations", slug], ) ): raise Http404("POI not found in this region with this language.") diff --git a/integreat_cms/cms/apps.py b/integreat_cms/cms/apps.py index 497e34a4a0..e3902013fb 100644 --- a/integreat_cms/cms/apps.py +++ b/integreat_cms/cms/apps.py @@ -29,7 +29,6 @@ class CmsConfig(AppConfig): verbose_name: Final[Promise] = _("CMS") def ready(self) -> None: - # pylint: disable=import-outside-toplevel """ Monkeypatch the checking of internal URLs """ diff --git a/integreat_cms/cms/auth.py b/integreat_cms/cms/auth.py index 4a5bdbd630..f5734ca1c6 100644 --- a/integreat_cms/cms/auth.py +++ b/integreat_cms/cms/auth.py @@ -22,9 +22,9 @@ def verify(self, password: str, encoded: str) -> bool: """ bcrypt = self._load_library() algorithm, data = encoded.split("$", 1) - assert algorithm == self.algorithm + if algorithm != self.algorithm: + raise ValueError data_parts = data.split("$", 2) if data_parts[1] != "2y": return super().verify(password, encoded) - check = bcrypt.checkpw(bytes(password, "utf-8"), bytes(data, "utf-8")) - return check + return bcrypt.checkpw(bytes(password, "utf-8"), bytes(data, "utf-8")) diff --git a/integreat_cms/cms/constants/calendar.py b/integreat_cms/cms/constants/calendar_filters.py similarity index 100% rename from integreat_cms/cms/constants/calendar.py rename to integreat_cms/cms/constants/calendar_filters.py diff --git a/integreat_cms/cms/constants/language_color.py b/integreat_cms/cms/constants/language_color.py index 8013358ea1..c219b18ae5 100644 --- a/integreat_cms/cms/constants/language_color.py +++ b/integreat_cms/cms/constants/language_color.py @@ -8,8 +8,6 @@ from django.utils.translation import gettext_lazy as _ -from ..utils.translation_utils import gettext_many_lazy as __ - if TYPE_CHECKING: from typing import Final diff --git a/integreat_cms/cms/constants/linkcheck.py b/integreat_cms/cms/constants/linkcheck.py index b9cb918392..749b124435 100644 --- a/integreat_cms/cms/constants/linkcheck.py +++ b/integreat_cms/cms/constants/linkcheck.py @@ -11,13 +11,13 @@ LINKCHECK_STATUS_TRANSLATIONS: Final = [ _( "New Connection Error: Failed to establish a new connection: " - "[Errno -2] Name or service not known" + "[Errno -2] Name or service not known", ), _("SSL Error: wrong version number"), _("The read operation timed out"), _( "Connection Error: ('Connection aborted.', " - "ConnectionResetError(104, 'Connection reset by peer'))" + "ConnectionResetError(104, 'Connection reset by peer'))", ), ] diff --git a/integreat_cms/cms/constants/machine_translation_providers.py b/integreat_cms/cms/constants/machine_translation_providers.py index 3556ede1c9..44aa270179 100644 --- a/integreat_cms/cms/constants/machine_translation_providers.py +++ b/integreat_cms/cms/constants/machine_translation_providers.py @@ -11,12 +11,12 @@ from ...summ_ai_api.summ_ai_provider import SummAiProvider if TYPE_CHECKING: - from typing import Final, Type + from typing import Final from ...core.utils.machine_translation_provider import MachineTranslationProvider -CHOICES: Final[list[Type[MachineTranslationProvider]]] = [ +CHOICES: Final[list[type[MachineTranslationProvider]]] = [ DeepLProvider, GoogleTranslateProvider, SummAiProvider, diff --git a/integreat_cms/cms/constants/roles.py b/integreat_cms/cms/constants/roles.py index ad175ed7af..b508ea260c 100644 --- a/integreat_cms/cms/constants/roles.py +++ b/integreat_cms/cms/constants/roles.py @@ -84,21 +84,24 @@ ] #: The permissions of the author role -AUTHOR_PERMISSIONS: Final[list[str]] = EVENT_MANAGER_PERMISSIONS + [ +AUTHOR_PERMISSIONS: Final[list[str]] = [ + *EVENT_MANAGER_PERMISSIONS, "change_page", "view_page", ] #: The permissions of the editor role -EDITOR_PERMISSIONS: Final[list[str]] = AUTHOR_PERMISSIONS + [ +EDITOR_PERMISSIONS: Final[list[str]] = [ + *AUTHOR_PERMISSIONS, "publish_page", "view_translation_report", "view_broken_links", ] #: The permissions of the management role -MANAGEMENT_PERMISSIONS: Final[list[str]] = EDITOR_PERMISSIONS + [ +MANAGEMENT_PERMISSIONS: Final[list[str]] = [ + *EDITOR_PERMISSIONS, "change_feedback", "change_imprintpage", "change_organization", @@ -144,7 +147,8 @@ ] #: The permissions of the app team -APP_TEAM_PERMISSIONS: Final[list[str]] = MARKETING_TEAM_PERMISSIONS + [ +APP_TEAM_PERMISSIONS: Final[list[str]] = [ + *MARKETING_TEAM_PERMISSIONS, "add_directory", "change_directory", "change_event", @@ -164,7 +168,8 @@ ] #: The permissions of the service team -SERVICE_TEAM_PERMISSIONS: Final[list[str]] = APP_TEAM_PERMISSIONS + [ +SERVICE_TEAM_PERMISSIONS: Final[list[str]] = [ + *APP_TEAM_PERMISSIONS, "change_externalcalendar", "change_language", "change_languagetreenode", diff --git a/integreat_cms/cms/constants/translation_status.py b/integreat_cms/cms/constants/translation_status.py index 17c07ba6cf..25a1c3fc6b 100644 --- a/integreat_cms/cms/constants/translation_status.py +++ b/integreat_cms/cms/constants/translation_status.py @@ -33,7 +33,7 @@ (IN_TRANSLATION, _("Currently in translation")), (OUTDATED, _("Translation outdated")), # Do not show fallback translations in translation coverage - # (FALLBACK, _("Default language is duplicated")), + # (FALLBACK, _("Default language is duplicated")), # noqa: ERA001 (MISSING, _("Translation missing")), (MACHINE_TRANSLATED, _("Machine translated")), ] diff --git a/integreat_cms/cms/decorators.py b/integreat_cms/cms/decorators.py index d75c6e6f61..df8743e497 100644 --- a/integreat_cms/cms/decorators.py +++ b/integreat_cms/cms/decorators.py @@ -75,7 +75,7 @@ def wrap(request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: if request.region in user.regions.all(): return function(request, *args, **kwargs) raise PermissionDenied( - f"{user!r} does not have the permission to access {request.region!r}" + f"{user!r} does not have the permission to access {request.region!r}", ) return wrap diff --git a/integreat_cms/cms/forms/contacts/contact_form.py b/integreat_cms/cms/forms/contacts/contact_form.py index da1a14edb1..1c891a0383 100644 --- a/integreat_cms/cms/forms/contacts/contact_form.py +++ b/integreat_cms/cms/forms/contacts/contact_form.py @@ -1,16 +1,12 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING from django.utils.translation import gettext_lazy as _ from ...models import Contact from ..custom_model_form import CustomModelForm -if TYPE_CHECKING: - from typing import Any - logger = logging.getLogger(__name__) @@ -38,5 +34,5 @@ class Meta: ] error_messages = { - "location": {"invalid_choice": _("Location cannot be empty.")} + "location": {"invalid_choice": _("Location cannot be empty.")}, } diff --git a/integreat_cms/cms/forms/custom_content_model_form.py b/integreat_cms/cms/forms/custom_content_model_form.py index 99f5389491..0900ff5e10 100644 --- a/integreat_cms/cms/forms/custom_content_model_form.py +++ b/integreat_cms/cms/forms/custom_content_model_form.py @@ -1,5 +1,6 @@ from __future__ import annotations +from contextlib import suppress from typing import TYPE_CHECKING from django import forms @@ -47,11 +48,8 @@ def __init__(self, **kwargs: Any) -> None: self.fields["slug"].required = False if not self.locked_by_user: - try: + with suppress(ObjectDoesNotExist): self.locked_by_user = self.instance.foreign_object.get_locking_user() - except ObjectDoesNotExist: - # If the foreign object does not exist yet, there ist also no lock so nothing must be done - pass def clean(self) -> dict[str, Any]: """ @@ -73,7 +71,7 @@ def clean(self) -> dict[str, Any]: None, forms.ValidationError( _( - "Could not update because this content because it is already being edited by another user" + "Could not update because this content because it is already being edited by another user", ), code="invalid", ), @@ -84,7 +82,7 @@ def clean(self) -> dict[str, Any]: None, forms.ValidationError( _( - 'The options "automatic translation" and "minor edit" are mutually exclusive.' + 'The options "automatic translation" and "minor edit" are mutually exclusive.', ), code="invalid", ), @@ -111,7 +109,8 @@ def clean_slug(self) -> str: :return: A unique slug based on the input value """ unique_slug = generate_unique_slug_helper( - self, self._meta.model.foreign_field() + self, + self._meta.model.foreign_field(), ) self.data = self.data.copy() self.data["slug"] = unique_slug diff --git a/integreat_cms/cms/forms/custom_model_form.py b/integreat_cms/cms/forms/custom_model_form.py index f341165ea1..86c2f0a734 100644 --- a/integreat_cms/cms/forms/custom_model_form.py +++ b/integreat_cms/cms/forms/custom_model_form.py @@ -34,7 +34,8 @@ def __init__(self, **kwargs: Any) -> None: # pop kwarg to make sure the super class does not get this params disabled = kwargs.pop("disabled", False) additional_instance_attributes = kwargs.pop( - "additional_instance_attributes", {} + "additional_instance_attributes", + {}, ) # Instantiate ModelForm @@ -76,27 +77,24 @@ def __init__(self, **kwargs: Any) -> None: # Set placeholder for every text input fields if isinstance( field.widget, - ( - forms.TextInput, - forms.Textarea, - forms.EmailInput, - forms.URLInput, - forms.PasswordInput, - forms.NumberInput, - ), + forms.TextInput + | forms.Textarea + | forms.EmailInput + | forms.URLInput + | forms.PasswordInput + | forms.NumberInput, ): try: # Use verbose_name of model field instead of field label because label is capitalized - # pylint: disable=no-member model_field = self._meta.model._meta.get_field( - field_name + field_name, ).verbose_name except FieldDoesNotExist: # In case field is not a model field, just use the label and make it lowercase model_field = lowfirst(field.label) field.widget.attrs.update( - {"placeholder": capfirst(_("Enter {} here").format(model_field))} + {"placeholder": capfirst(_("Enter {} here").format(model_field))}, ) def clean(self) -> dict[str, Any]: @@ -151,7 +149,9 @@ def add_error_messages(self, request: HttpRequest) -> None: # Add debug logging in english with translation.override("en"): self.logger.debug( - "%r submitted with errors: %r", type(self).__name__, self.errors + "%r submitted with errors: %r", + type(self).__name__, + self.errors, ) def get_error_messages(self) -> list[dict[str, str]]: @@ -165,7 +165,7 @@ def get_error_messages(self) -> list[dict[str, str]]: for field in self: for error in field.errors: error_messages.append( - {"type": "error", "text": field.label + ": " + error} + {"type": "error", "text": field.label + ": " + error}, ) # Add non-field errors for error in self.non_field_errors(): diff --git a/integreat_cms/cms/forms/custom_tree_node_form.py b/integreat_cms/cms/forms/custom_tree_node_form.py index af735ef03e..2b94dfd529 100644 --- a/integreat_cms/cms/forms/custom_tree_node_form.py +++ b/integreat_cms/cms/forms/custom_tree_node_form.py @@ -47,7 +47,8 @@ def _clean_cleaned_data(self) -> tuple[str, int]: return super()._clean_cleaned_data() def _get_position_ref_node( - self, instance: LanguageTreeNode | Page + self, + instance: LanguageTreeNode | Page, ) -> dict[str, str]: """ Get the initial values for the referenced node and the position @@ -67,7 +68,9 @@ def _get_position_ref_node( # If the next sibling exists and is of this region, reference this instance to the left of the next sibling if next_sibling and next_sibling.region == instance.region: logger.debug( - "Node %r is now referenced left to node %r", instance, next_sibling + "Node %r is now referenced left to node %r", + instance, + next_sibling, ) return {"_ref_node_id": str(next_sibling.id), "_position": "left"} # If the page is the only root page of this region, do not reference other nodes @@ -82,7 +85,9 @@ def _get_position_ref_node( @classmethod def mk_dropdown_tree( - cls, model: ModelBase, for_node: None | (LanguageTreeNode | Page) = None + cls, + model: ModelBase, # noqa: ARG003 + for_node: None | (LanguageTreeNode | Page) = None, # noqa: ARG003 ) -> list: """ Creates a tree-like list of choices. Overwrites the parent method because the field is hidden anyway and diff --git a/integreat_cms/cms/forms/events/event_filter_form.py b/integreat_cms/cms/forms/events/event_filter_form.py index a0a6e6f7ea..cb88a78cc1 100644 --- a/integreat_cms/cms/forms/events/event_filter_form.py +++ b/integreat_cms/cms/forms/events/event_filter_form.py @@ -10,13 +10,12 @@ from typing import TYPE_CHECKING from django import forms -from django.utils.translation import gettext_lazy as _ if TYPE_CHECKING: from ...models import Region from ...models.events.event import EventQuerySet -from ...constants import all_day, calendar, events_time_range, recurrence +from ...constants import all_day, calendar_filters, events_time_range, recurrence from ...models import EventTranslation from ..custom_filter_form import CustomFilterForm @@ -64,7 +63,7 @@ class EventFilterForm(CustomFilterForm): attrs={ "data-default-checked-value": events_time_range.UPCOMING, "data-custom-time-range-value": events_time_range.CUSTOM, - } + }, ), choices=events_time_range.CHOICES, initial=[events_time_range.UPCOMING], @@ -73,9 +72,9 @@ class EventFilterForm(CustomFilterForm): imported_event = forms.TypedMultipleChoiceField( widget=forms.CheckboxSelectMultiple(attrs={"class": "default-checked"}), - choices=calendar.CHOICES, - initial=[key for (key, val) in calendar.CHOICES], - coerce=calendar.DATATYPE, + choices=calendar_filters.CHOICES, + initial=[key for (key, val) in calendar_filters.CHOICES], + coerce=calendar_filters.DATATYPE, required=False, ) @@ -84,9 +83,11 @@ class EventFilterForm(CustomFilterForm): query = forms.CharField(required=False) def apply( - self, events: EventQuerySet, region: Region, language_slug: str + self, + events: EventQuerySet, + region: Region, + language_slug: str, ) -> tuple[EventQuerySet, None, None]: - # pylint: disable=too-many-branches """ Filter the events according to the given filter data @@ -102,7 +103,7 @@ def apply( # Filter events by time range cleaned_time_range = self.cleaned_data["events_time_range"] if not cleaned_time_range or set(cleaned_time_range) == set( - events_time_range.ALL_EVENTS + events_time_range.ALL_EVENTS, ): # Either post & upcoming or no checkboxes are checked => skip filtering # We do this to check if either all options or none were selected from this group @@ -115,7 +116,9 @@ def apply( else: from_local = datetime.min.replace(tzinfo=zoneinfo.ZoneInfo(key="UTC")) to_local = datetime.combine( - self.cleaned_data["date_to"] or date.max, time.max, tzinfo=tzinfo + self.cleaned_data["date_to"] or date.max, + time.max, + tzinfo=tzinfo, ) events = events.filter_upcoming(from_local).filter(end__lte=to_local) elif events_time_range.UPCOMING in cleaned_time_range: @@ -161,24 +164,25 @@ def apply( events = events.filter(recurrence_rule__isnull=True) if ( - len(self.cleaned_data["imported_event"]) == len(calendar.CHOICES) + len(self.cleaned_data["imported_event"]) == len(calendar_filters.CHOICES) or not self.cleaned_data["imported_event"] ): pass elif ( - calendar.EVENT_NOT_FROM_EXTERNAL_CALENDAR + calendar_filters.EVENT_NOT_FROM_EXTERNAL_CALENDAR in self.cleaned_data["imported_event"] ): events = events.filter(external_calendar__isnull=True) elif ( - calendar.EVENT_FROM_EXTERNAL_CALENDAR in self.cleaned_data["imported_event"] + calendar_filters.EVENT_FROM_EXTERNAL_CALENDAR + in self.cleaned_data["imported_event"] ): events = events.filter(external_calendar__isnull=False) # Filter events by the search query if query := self.cleaned_data["query"]: event_ids = EventTranslation.search(region, language_slug, query).values( - "event__pk" + "event__pk", ) events = events.filter(pk__in=event_ids) diff --git a/integreat_cms/cms/forms/events/event_form.py b/integreat_cms/cms/forms/events/event_form.py index 3d79fcd43b..3e753986f1 100644 --- a/integreat_cms/cms/forms/events/event_form.py +++ b/integreat_cms/cms/forms/events/event_form.py @@ -86,7 +86,7 @@ class Meta: error_messages = { "location": { "invalid_choice": _( - "Either disable the event location or provide a valid location" + "Either disable the event location or provide a valid location", ), }, } @@ -169,7 +169,7 @@ def clean(self) -> dict[str, Any]: "end_date", forms.ValidationError( _( - "The end of the event can't be before the start of the event" + "The end of the event can't be before the start of the event", ), code="invalid", ), @@ -185,7 +185,7 @@ def clean(self) -> dict[str, Any]: "end_time", forms.ValidationError( _( - "The end of the event can't be before the start of the event" + "The end of the event can't be before the start of the event", ), code="invalid", ), @@ -195,7 +195,7 @@ def clean(self) -> dict[str, Any]: "end_date", forms.ValidationError( _( - "The maximum duration for events is {} days. Consider using recurring events if the event is not continuous." + "The maximum duration for events is {} days. Consider using recurring events if the event is not continuous.", ).format(settings.MAX_EVENT_DURATION), code="invalid", ), diff --git a/integreat_cms/cms/forms/events/event_translation_form.py b/integreat_cms/cms/forms/events/event_translation_form.py index 65a043c2d6..f77c267a22 100644 --- a/integreat_cms/cms/forms/events/event_translation_form.py +++ b/integreat_cms/cms/forms/events/event_translation_form.py @@ -9,7 +9,6 @@ class EventTranslationForm(MachineTranslationForm): - # pylint: disable=too-many-ancestors """ Form for creating and modifying event translation objects """ @@ -23,4 +22,4 @@ class Meta: #: The model of this :class:`django.forms.ModelForm` model = EventTranslation #: The fields of the model which should be handled by this form - fields = MachineTranslationForm.Meta.fields + ["slug"] + fields = [*MachineTranslationForm.Meta.fields, "slug"] diff --git a/integreat_cms/cms/forms/events/recurrence_rule_form.py b/integreat_cms/cms/forms/events/recurrence_rule_form.py index f31fc79ae8..8de1234f36 100644 --- a/integreat_cms/cms/forms/events/recurrence_rule_form.py +++ b/integreat_cms/cms/forms/events/recurrence_rule_form.py @@ -22,7 +22,8 @@ class RecurrenceRuleForm(CustomModelForm): """ has_recurrence_end_date = forms.BooleanField( - required=False, label=_("Recurrence ends") + required=False, + label=_("Recurrence ends"), ) class Meta: @@ -46,7 +47,8 @@ class Meta: widgets = { "weekdays_for_weekly": forms.SelectMultiple(choices=weekdays.CHOICES), "recurrence_end_date": forms.DateInput( - format="%Y-%m-%d", attrs={"type": "date"} + format="%Y-%m-%d", + attrs={"type": "date"}, ), } @@ -66,7 +68,7 @@ def __init__(self, **kwargs: Any) -> None: if self.instance.id: # Initialize BooleanField based on RecurrenceRule properties self.fields["has_recurrence_end_date"].initial = bool( - self.instance.recurrence_end_date + self.instance.recurrence_end_date, ) def clean(self) -> dict[str, Any]: @@ -81,16 +83,18 @@ def clean(self) -> dict[str, Any]: self.add_error( "frequency", forms.ValidationError( - _("No recurrence frequency selected"), code="required" + _("No recurrence frequency selected"), + code="required", ), ) elif cleaned_data.get("frequency") == frequency.WEEKLY and not cleaned_data.get( - "weekdays_for_weekly" + "weekdays_for_weekly", ): self.add_error( "weekdays_for_weekly", forms.ValidationError( - _("No weekdays for weekly recurrence selected"), code="required" + _("No weekdays for weekly recurrence selected"), + code="required", ), ) elif cleaned_data.get("frequency") == frequency.MONTHLY: @@ -98,14 +102,16 @@ def clean(self) -> dict[str, Any]: self.add_error( "weekday_for_monthly", forms.ValidationError( - _("No weekday for monthly recurrence selected"), code="required" + _("No weekday for monthly recurrence selected"), + code="required", ), ) if not cleaned_data.get("week_for_monthly"): self.add_error( "week_for_monthly", forms.ValidationError( - _("No week for monthly recurrence selected"), code="required" + _("No week for monthly recurrence selected"), + code="required", ), ) @@ -115,7 +121,7 @@ def clean(self) -> dict[str, Any]: "recurrence_end_date", forms.ValidationError( _( - "If the recurrence ends, the recurrence end date is required" + "If the recurrence ends, the recurrence end date is required", ), code="required", ), @@ -128,7 +134,7 @@ def clean(self) -> dict[str, Any]: "recurrence_end_date", forms.ValidationError( _( - "The recurrence end date has to be after the event's start date" + "The recurrence end date has to be after the event's start date", ), code="invalid", ), @@ -137,7 +143,8 @@ def clean(self) -> dict[str, Any]: cleaned_data["recurrence_end_date"] = None logger.debug( - "RecurrenceRuleForm validated [2] with cleaned data %r", cleaned_data + "RecurrenceRuleForm validated [2] with cleaned data %r", + cleaned_data, ) return cleaned_data @@ -155,7 +162,9 @@ def has_changed(self) -> bool: except ValueError: return super().has_changed() value = self.fields["weekdays_for_weekly"].widget.value_from_datadict( - self.data, self.files, self.add_prefix("weekdays_for_weekly") + self.data, + self.files, + self.add_prefix("weekdays_for_weekly"), ) initial = self["weekdays_for_weekly"].initial if value: diff --git a/integreat_cms/cms/forms/feedback/region_feedback_filter_form.py b/integreat_cms/cms/forms/feedback/region_feedback_filter_form.py index d1589a9bd2..91b5407a99 100644 --- a/integreat_cms/cms/forms/feedback/region_feedback_filter_form.py +++ b/integreat_cms/cms/forms/feedback/region_feedback_filter_form.py @@ -8,7 +8,6 @@ from django.utils.translation import gettext_lazy as _ if TYPE_CHECKING: - from ..models import Region from ..models.feedback.feedback import CascadeDeletePolymorphicQuerySet from ...constants import feedback_ratings, feedback_read_status @@ -69,7 +68,8 @@ class RegionFeedbackFilterForm(CustomFilterForm): query = forms.CharField(required=False) def apply( - self, feedback: CascadeDeletePolymorphicQuerySet + self, + feedback: CascadeDeletePolymorphicQuerySet, ) -> tuple[CascadeDeletePolymorphicQuerySet, None]: """ Filter the feedback list according to the given filter data diff --git a/integreat_cms/cms/forms/icon_widget.py b/integreat_cms/cms/forms/icon_widget.py index 3f9208fb37..2e57725660 100644 --- a/integreat_cms/cms/forms/icon_widget.py +++ b/integreat_cms/cms/forms/icon_widget.py @@ -19,7 +19,10 @@ class IconWidget(forms.HiddenInput): template_name = "icon_widget.html" def get_context( - self, name: str, value: Any | None, attrs: dict[str, Any] + self, + name: str, + value: Any | None, + attrs: dict[str, Any], ) -> dict[str, dict[str, Any]]: """ This function gets the context of icon fields diff --git a/integreat_cms/cms/forms/language_tree/language_tree_node_form.py b/integreat_cms/cms/forms/language_tree/language_tree_node_form.py index 058ea0d04d..79662ef6ea 100644 --- a/integreat_cms/cms/forms/language_tree/language_tree_node_form.py +++ b/integreat_cms/cms/forms/language_tree/language_tree_node_form.py @@ -19,7 +19,6 @@ class LanguageTreeNodeForm(CustomModelForm, CustomTreeNodeForm): - # pylint: disable=too-many-ancestors """ Form for creating and modifying language tree node objects """ @@ -64,7 +63,7 @@ def __init__(self, **kwargs: Any) -> None: descendant_ids = [ descendant.id for descendant in self.instance.get_cached_descendants( - include_self=True + include_self=True, ) ] parent_queryset = parent_queryset.exclude(id__in=descendant_ids) @@ -75,7 +74,7 @@ def __init__(self, **kwargs: Any) -> None: # Make sure it's not possible to create multiple nodes for the same language # by limiting possible languages to those which are not yet included in the tree self.fields["language"].queryset = Language.objects.exclude( - id__in=[language.id for language in self.instance.region.languages] + id__in=[language.id for language in self.instance.region.languages], ) # limit possible parents to nodes of current region @@ -114,13 +113,14 @@ def clean(self) -> dict[str, Any]: forms.ValidationError( _( "This region has already a default language." - "Please specify a source language for this language." + "Please specify a source language for this language.", ), code="invalid", ), ) logger.debug( - "LanguageTreeNodeForm validated [2] with cleaned data %r", cleaned_data + "LanguageTreeNodeForm validated [2] with cleaned data %r", + cleaned_data, ) return cleaned_data diff --git a/integreat_cms/cms/forms/languages/language_form.py b/integreat_cms/cms/forms/languages/language_form.py index 56c8737dde..ebf486da5d 100644 --- a/integreat_cms/cms/forms/languages/language_form.py +++ b/integreat_cms/cms/forms/languages/language_form.py @@ -53,7 +53,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # Sort countries by translated name sorted_language_choices = sorted( - self.fields["primary_country_code"].choices, key=lambda x: x[1] + self.fields["primary_country_code"].choices, + key=lambda x: x[1], ) self.fields["primary_country_code"].choices = sorted_language_choices self.fields["secondary_country_code"].choices = sorted_language_choices @@ -80,12 +81,12 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # Make left border rounded if no flag is selected yet if not self.instance.primary_country_code: - self.fields["primary_country_code"].widget.attrs[ - "class" - ] = "rounded-l border-l" + self.fields["primary_country_code"].widget.attrs["class"] = ( + "rounded-l border-l" + ) if not self.instance.secondary_country_code: - self.fields["secondary_country_code"].widget.attrs[ - "class" - ] = "rounded-l border-l" + self.fields["secondary_country_code"].widget.attrs["class"] = ( + "rounded-l border-l" + ) if not self.instance.language_color: self.fields["language_color"].widget.attrs["class"] = "rounded-l border-l" diff --git a/integreat_cms/cms/forms/linkcheck/edit_url_form.py b/integreat_cms/cms/forms/linkcheck/edit_url_form.py index 606f72f9e0..1d92154385 100644 --- a/integreat_cms/cms/forms/linkcheck/edit_url_form.py +++ b/integreat_cms/cms/forms/linkcheck/edit_url_form.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING from django import forms from django.core.validators import EmailValidator, URLValidator diff --git a/integreat_cms/cms/forms/machine_translation_form.py b/integreat_cms/cms/forms/machine_translation_form.py index 0070ddfa65..6bb5375378 100644 --- a/integreat_cms/cms/forms/machine_translation_form.py +++ b/integreat_cms/cms/forms/machine_translation_form.py @@ -30,7 +30,10 @@ class MachineTranslationForm(CustomContentModelForm): mt_translations_to_create = forms.ModelMultipleChoiceField( widget=forms.CheckboxSelectMultiple( - attrs={"class": "bulk-select-language", "name": "selected_language_slugs[]"} + attrs={ + "class": "bulk-select-language", + "name": "selected_language_slugs[]", + }, ), queryset=LanguageTreeNode.objects.none(), required=False, @@ -65,7 +68,9 @@ def __init__(self, **kwargs: Any) -> None: return if not MachineTranslationProvider.is_permitted( - self.request.region, self.request.user, self._meta.model + self.request.region, + self.request.user, + self._meta.model, ): return @@ -87,12 +92,12 @@ def __init__(self, **kwargs: Any) -> None: ) target_type.append(target.id) - self.fields["mt_translations_to_create"].queryset = ( - self.request.region.language_tree_nodes.filter(id__in=to_create) - ) - self.fields["mt_translations_to_update"].queryset = ( - self.request.region.language_tree_nodes.filter(id__in=to_update) - ) + self.fields[ + "mt_translations_to_create" + ].queryset = self.request.region.language_tree_nodes.filter(id__in=to_create) + self.fields[ + "mt_translations_to_update" + ].queryset = self.request.region.language_tree_nodes.filter(id__in=to_update) self.initial["mt_translations_to_update"] = to_update localized_fields = ["title"] @@ -129,7 +134,9 @@ def clean(self) -> dict[str, Any]: return cleaned_data def save( - self, commit: bool = True, foreign_form_changed: bool = False + self, + commit: bool = True, + foreign_form_changed: bool = False, ) -> EventTranslation | (PageTranslation | POITranslation): """ Create machine translations and save them to the database @@ -141,7 +148,7 @@ def save( self.instance = super().save(commit, foreign_form_changed) language_nodes = self.cleaned_data["mt_translations_to_create"].union( - self.cleaned_data["mt_translations_to_update"] + self.cleaned_data["mt_translations_to_update"], ) if commit and language_nodes and check_hix_score(self.request, self.instance): for language_node in language_nodes: @@ -152,12 +159,14 @@ def save( self.instance, ) api_client = language_node.mt_provider.api_client( - self.request, type(self) + self.request, + type(self), ) # Invalidate cached property to take new version into account self.instance.foreign_object.invalidate_cached_translations() api_client.translate_object( - self.instance.foreign_object, language_node.slug + self.instance.foreign_object, + language_node.slug, ) return self.instance diff --git a/integreat_cms/cms/forms/media/create_directory_form.py b/integreat_cms/cms/forms/media/create_directory_form.py index 76b638236c..1fcb750d9f 100644 --- a/integreat_cms/cms/forms/media/create_directory_form.py +++ b/integreat_cms/cms/forms/media/create_directory_form.py @@ -48,7 +48,7 @@ def clean(self) -> dict: "parent", forms.ValidationError( _( - "The directory cannot be created in a directory of another region." + "The directory cannot be created in a directory of another region.", ), code="invalid", ), @@ -58,7 +58,7 @@ def clean(self) -> dict: if self.instance.region: # If directory is created for a region, only limit choices to the global library and the regional one queryset = queryset.filter( - Q(region=self.instance.region) | Q(region__isnull=True) + Q(region=self.instance.region) | Q(region__isnull=True), ) if queryset.filter( parent=cleaned_data.get("parent"), @@ -68,13 +68,14 @@ def clean(self) -> dict: "name", forms.ValidationError( _('A directory with the name "{}" already exists.').format( - cleaned_data.get("name") + cleaned_data.get("name"), ), code="invalid", ), ) logger.debug( - "CreateDirectoryForm validated [2] with cleaned data %r", cleaned_data + "CreateDirectoryForm validated [2] with cleaned data %r", + cleaned_data, ) return cleaned_data diff --git a/integreat_cms/cms/forms/media/replace_media_file_form.py b/integreat_cms/cms/forms/media/replace_media_file_form.py index f3eca4c187..06d18b5a05 100644 --- a/integreat_cms/cms/forms/media/replace_media_file_form.py +++ b/integreat_cms/cms/forms/media/replace_media_file_form.py @@ -102,8 +102,8 @@ def clean(self) -> dict: "file", forms.ValidationError( _( - "The file type of the new file ({}) does not match the original file's type ({})." - ).format(new_type_display, self.instance.get_type_display()) + "The file type of the new file ({}) does not match the original file's type ({}).", + ).format(new_type_display, self.instance.get_type_display()), ), ) @@ -113,7 +113,9 @@ def clean(self) -> dict: # If everything looks good until now, generate a thumbnail and an optimized image if not self.errors and self.instance.type.startswith("image"): if optimized_image := generate_thumbnail( - file, settings.MEDIA_OPTIMIZED_SIZE, False + file, + settings.MEDIA_OPTIMIZED_SIZE, + False, ): cleaned_data["file"] = optimized_image cleaned_data["thumbnail"] = generate_thumbnail(file) @@ -121,7 +123,8 @@ def clean(self) -> dict: self.add_error( "file", forms.ValidationError( - _("This image file is corrupt."), code="invalid" + _("This image file is corrupt."), + code="invalid", ), ) # Add the calculated file_size to the form data @@ -130,7 +133,8 @@ def clean(self) -> dict: cleaned_data["last_modified"] = timezone.now() logger.debug( - "ReplaceMediaFileForm validated [2] with cleaned data %r", cleaned_data + "ReplaceMediaFileForm validated [2] with cleaned data %r", + cleaned_data, ) return cleaned_data @@ -142,7 +146,8 @@ def save(self, commit: bool = True) -> MediaFile: logger.debug("Removed old file %r", self.original_file_path) except FileNotFoundError: logger.debug( - "The file %r could not be removed", self.original_file_path + "The file %r could not be removed", + self.original_file_path, ) # Remove old thumbnail if self.original_thumbnail_path: @@ -153,7 +158,8 @@ def save(self, commit: bool = True) -> MediaFile: # Update the file url in content new_url = self.instance.url - assert self.original_file_url + if not self.original_file_url: + raise ValueError replace_links( self.original_file_url, new_url, diff --git a/integreat_cms/cms/forms/media/upload_media_file_form.py b/integreat_cms/cms/forms/media/upload_media_file_form.py index 5a15b3be44..fab3519e8a 100644 --- a/integreat_cms/cms/forms/media/upload_media_file_form.py +++ b/integreat_cms/cms/forms/media/upload_media_file_form.py @@ -91,13 +91,13 @@ def clean(self) -> dict: "type", forms.ValidationError( _("The file type {} is not allowed.").format( - cleaned_data.get("type") + cleaned_data.get("type"), ) + " " + _("Allowed file types") + ": " + ", ".join( - map(str, dict(allowed_media.UPLOAD_CHOICES).values()) + map(str, dict(allowed_media.UPLOAD_CHOICES).values()), ), code="invalid", ), @@ -106,7 +106,7 @@ def clean(self) -> dict: name, extension = splitext(file.name) # Replace file extension if it doesn't match it's mime type valid_extensions = mimetypes.guess_all_extensions( - cleaned_data.get("type", "") + cleaned_data.get("type", ""), ) if extension not in valid_extensions and valid_extensions: file.name = name + valid_extensions[0] @@ -131,7 +131,9 @@ def clean(self) -> dict: and img_type.startswith("image") ): if optimized_image := generate_thumbnail( - file, settings.MEDIA_OPTIMIZED_SIZE, False + file, + settings.MEDIA_OPTIMIZED_SIZE, + False, ): cleaned_data["file"] = optimized_image cleaned_data["thumbnail"] = generate_thumbnail(file) @@ -140,7 +142,8 @@ def clean(self) -> dict: self.add_error( "file", forms.ValidationError( - _("This image file is corrupt."), code="invalid" + _("This image file is corrupt."), + code="invalid", ), ) # Add the calculated file_size and the modification date to the form data @@ -149,6 +152,7 @@ def clean(self) -> dict: cleaned_data["last_modified"] = timezone.now() logger.debug( - "UploadMediaFileForm validated [2] with cleaned data %r", cleaned_data + "UploadMediaFileForm validated [2] with cleaned data %r", + cleaned_data, ) return cleaned_data diff --git a/integreat_cms/cms/forms/organizations/organization_form.py b/integreat_cms/cms/forms/organizations/organization_form.py index 34b7990b69..343ba2829a 100644 --- a/integreat_cms/cms/forms/organizations/organization_form.py +++ b/integreat_cms/cms/forms/organizations/organization_form.py @@ -75,7 +75,7 @@ def clean_name(self) -> str: "name", forms.ValidationError( _( - "An organization with the same name already exists in this region. Please choose another name." + "An organization with the same name already exists in this region. Please choose another name.", ), code="invalid", ), @@ -83,6 +83,4 @@ def clean_name(self) -> str: return cleaned_name def save(self, commit: bool = True) -> Any: - result = super().save(commit) - - return result + return super().save(commit) diff --git a/integreat_cms/cms/forms/pages/mirrored_page_field_widget.py b/integreat_cms/cms/forms/pages/mirrored_page_field_widget.py index d85a014a73..c55afc6668 100644 --- a/integreat_cms/cms/forms/pages/mirrored_page_field_widget.py +++ b/integreat_cms/cms/forms/pages/mirrored_page_field_widget.py @@ -29,7 +29,6 @@ def create_option( subindex: int | None = None, attrs: dict | None = None, ) -> dict: - # pylint: disable=too-many-arguments, too-many-positional-arguments """ This function creates an option which can be selected in the parent field @@ -49,7 +48,13 @@ def create_option( # Create dictionary of options option_dict = super().create_option( - name, value, label, selected, index, subindex=subindex, attrs=attrs + name, + value, + label, + selected, + index, + subindex=subindex, + attrs=attrs, ) preview_url = reverse( "get_page_content_ajax", diff --git a/integreat_cms/cms/forms/pages/page_filter_form.py b/integreat_cms/cms/forms/pages/page_filter_form.py index cd0f34db0a..5fe63a76b7 100644 --- a/integreat_cms/cms/forms/pages/page_filter_form.py +++ b/integreat_cms/cms/forms/pages/page_filter_form.py @@ -24,7 +24,7 @@ class PageFilterForm(CustomFilterForm): status = forms.ChoiceField( label=_("Publication status"), - choices=[("", _("All"))] + status.CHOICES, + choices=[("", _("All")), *status.CHOICES], required=False, ) @@ -115,7 +115,9 @@ def filter_by_query(self, pages: list[Page], language_slug: str) -> list[Page]: return filtered_pages def filter_by_translation_status( - self, pages: list[Page], language_slug: str + self, + pages: list[Page], + language_slug: str, ) -> list[Page]: """ Filter the pages list by a given translation status @@ -134,7 +136,9 @@ def filter_by_translation_status( return filtered_pages def filter_by_publication_status( - self, pages: list[Page], language_slug: str + self, + pages: list[Page], + language_slug: str, ) -> list[Page]: """ Filter the pages list by publication status @@ -185,7 +189,9 @@ def filter_by_end_date(self, pages: list[Page], language_slug: str) -> list[Page return filtered_pages def filter_by_pages_with_content( - self, pages: list[Page], language_slug: str + self, + pages: list[Page], + language_slug: str, ) -> list[Page]: """ Filter only by pages that have content (including empty pages with live content) diff --git a/integreat_cms/cms/forms/pages/page_form.py b/integreat_cms/cms/forms/pages/page_form.py index 66809268da..09d57f2f0f 100644 --- a/integreat_cms/cms/forms/pages/page_form.py +++ b/integreat_cms/cms/forms/pages/page_form.py @@ -27,7 +27,6 @@ class PageForm(CustomModelForm, CustomTreeNodeForm): - # pylint: disable=too-many-ancestors """ Form for creating and modifying page objects """ @@ -37,7 +36,7 @@ class PageForm(CustomModelForm, CustomTreeNodeForm): required=False, label=_("Authors"), help_text=_( - "These users can edit this page, but are not allowed to publish it." + "These users can edit this page, but are not allowed to publish it.", ), ) editors = forms.ModelChoiceField( @@ -107,16 +106,16 @@ def __init__(self, **kwargs: Any) -> None: # Set the initial value for the mirrored page region if self.instance.mirrored_page: - self.fields["mirrored_page_region"].initial = ( - self.instance.mirrored_page.region_id - ) + self.fields[ + "mirrored_page_region" + ].initial = self.instance.mirrored_page.region_id # Let mirrored page queryset be empty per default and only fill it if a region is selected mirrored_page_queryset = Page.objects.none() # Filter the offer providers available for embedding self.fields["embedded_offers"].queryset = self.instance.region.offers.filter( - supported_by_app_in_content=True + supported_by_app_in_content=True, ) # Filter Zammad forms out if the region has no Zammad-URL set @@ -131,7 +130,7 @@ def __init__(self, **kwargs: Any) -> None: # If no region was selected, allow no mirrored page if mirrored_page_region := self.data["mirrored_page_region"]: mirrored_page_queryset = Region.objects.get( - id=mirrored_page_region + id=mirrored_page_region, ).pages.all() # Dirty hack to remove fields when submitted by POST (since they are handles by AJAX) del self.fields["authors"] @@ -171,9 +170,9 @@ def __init__(self, **kwargs: Any) -> None: for page in mirrored_page_queryset.cache_tree(archived=False) ] - self.fields["organization"].queryset = ( - self.instance.region.organizations.filter(archived=False) - ) + self.fields[ + "organization" + ].queryset = self.instance.region.organizations.filter(archived=False) # Set choices of parent and _ref_node_id fields manually to make use of cache_tree() logger.debug("Set choices for parent field:") @@ -182,11 +181,11 @@ def __init__(self, **kwargs: Any) -> None: [ (page.id, str(page)) for page in parent_queryset.cache_tree(archived=False) - ] + ], ) ref_node_choices = [("", "---------")] ref_node_choices.extend( - [(page.id, str(page)) for page in parent_queryset.cache_tree()] + [(page.id, str(page)) for page in parent_queryset.cache_tree()], ) self.fields["parent"].choices = cached_parent_choices self.fields["_ref_node_id"].choices = ref_node_choices @@ -221,12 +220,12 @@ def get_author_queryset(self) -> QuerySet: Q(groups__permissions__codename="change_page") | Q(user_permissions__codename="change_page") | Q(editable_pages=self.instance) - | Q(publishable_pages=self.instance) + | Q(publishable_pages=self.instance), ) ) if self.instance.id: users_without_permissions = users_without_permissions.difference( - self.instance.authors.all() + self.instance.authors.all(), ) return users_without_permissions @@ -249,12 +248,12 @@ def get_editor_queryset(self) -> QuerySet: .exclude( Q(groups__permissions__codename="publish_page") | Q(user_permissions__codename="publish_page") - | Q(publishable_pages=self.instance) + | Q(publishable_pages=self.instance), ) ) if self.instance.id: users_without_permissions = users_without_permissions.difference( - self.instance.editors.all() + self.instance.editors.all(), ) return users_without_permissions diff --git a/integreat_cms/cms/forms/pages/page_translation_form.py b/integreat_cms/cms/forms/pages/page_translation_form.py index c7e9621baf..b438509a8a 100644 --- a/integreat_cms/cms/forms/pages/page_translation_form.py +++ b/integreat_cms/cms/forms/pages/page_translation_form.py @@ -9,7 +9,6 @@ class PageTranslationForm(MachineTranslationForm): - # pylint: disable=too-many-ancestors """ Form for creating and modifying page translation objects """ @@ -23,7 +22,8 @@ class Meta: #: The model of this :class:`django.forms.ModelForm` model = PageTranslation #: The fields of the model which should be handled by this form - fields = MachineTranslationForm.Meta.fields + [ + fields = [ + *MachineTranslationForm.Meta.fields, "slug", "hix_score", "hix_feedback", diff --git a/integreat_cms/cms/forms/pages/parent_field_widget.py b/integreat_cms/cms/forms/pages/parent_field_widget.py index b97095e8f8..d5f19014c2 100644 --- a/integreat_cms/cms/forms/pages/parent_field_widget.py +++ b/integreat_cms/cms/forms/pages/parent_field_widget.py @@ -29,7 +29,6 @@ def create_option( subindex: Any | None = None, attrs: dict[str, Any] | None = None, ) -> dict[str, Any]: - # pylint: disable=too-many-arguments,too-many-positional-arguments """ This function creates an option which can be selected in the parent field @@ -47,7 +46,13 @@ def create_option( assert self.form.instance # Create dictionary of options option_dict = super().create_option( - name, value, label, selected, index, subindex=subindex, attrs=attrs + name, + value, + label, + selected, + index, + subindex=subindex, + attrs=attrs, ) kwargs = {"region_slug": self.form.instance.region.slug} if value: diff --git a/integreat_cms/cms/forms/poi_categories/poi_category_translation_form.py b/integreat_cms/cms/forms/poi_categories/poi_category_translation_form.py index e792c275fe..a598f91d29 100644 --- a/integreat_cms/cms/forms/poi_categories/poi_category_translation_form.py +++ b/integreat_cms/cms/forms/poi_categories/poi_category_translation_form.py @@ -40,7 +40,7 @@ def __init__(self, **kwargs: Any) -> None: # Set custom language labels language_name = self.instance.language.translated_name self.fields["name"].widget.attrs.update( - {"placeholder": _("Enter name in {} here").format(language_name)} + {"placeholder": _("Enter name in {} here").format(language_name)}, ) self.fields["name"].label = _("Translation in {}").format(language_name) @@ -93,12 +93,13 @@ def get_form_kwargs(self, index: int) -> dict[str, dict[str, Language]]: if self.instance.id: languages = Language.objects.exclude( id__in=self.instance.translations.values_list( - "language__id", flat=True - ) + "language__id", + flat=True, + ), ) # Assign the language to the form with this index kwargs["additional_instance_attributes"] = { - "language": languages[rel_index] + "language": languages[rel_index], } return kwargs diff --git a/integreat_cms/cms/forms/pois/poi_form.py b/integreat_cms/cms/forms/pois/poi_form.py index b3645cc466..542c0fbafe 100644 --- a/integreat_cms/cms/forms/pois/poi_form.py +++ b/integreat_cms/cms/forms/pois/poi_form.py @@ -74,12 +74,11 @@ def __init__(self, **kwargs: Any) -> None: """ super().__init__(**kwargs) - self.fields["organization"].queryset = ( - self.instance.region.organizations.filter(archived=False) - ) + self.fields[ + "organization" + ].queryset = self.instance.region.organizations.filter(archived=False) def clean_opening_hours(self) -> list[dict[str, Any]]: - # pylint: disable=too-many-return-statements """ Validate the opening hours field (see :ref:`overriding-modelform-clean-method`). @@ -111,7 +110,9 @@ def clean_opening_hours(self) -> list[dict[str, Any]]: ) except ValidationError as e: logger.warning( - "Opening hours of %r: JSON does not match schema: %r", self.instance, e + "Opening hours of %r: JSON does not match schema: %r", + self.instance, + e, ) self.add_error("opening_hours", generic_error) return cleaned_opening_hours @@ -194,7 +195,7 @@ def clean(self) -> dict[str, Any]: distance( (cleaned_data["latitude"], cleaned_data["longitude"]), (latitude, longitude), - ).km + ).km, ) if cleaned_data.get("location_on_map"): @@ -205,7 +206,7 @@ def clean(self) -> dict[str, Any]: forms.ValidationError( _( "Could not derive the coordinates from the address, please fill " - "the field manually if the location is to be displayed on the map." + "the field manually if the location is to be displayed on the map.", ), code="required", ), @@ -216,7 +217,7 @@ def clean(self) -> dict[str, Any]: forms.ValidationError( _( "Could not derive the coordinates from the address, please fill " - "the field manually if the location is to be displayed on the map." + "the field manually if the location is to be displayed on the map.", ), code="required", ), diff --git a/integreat_cms/cms/forms/pois/poi_translation_form.py b/integreat_cms/cms/forms/pois/poi_translation_form.py index 81366de174..ec5cf45f2e 100644 --- a/integreat_cms/cms/forms/pois/poi_translation_form.py +++ b/integreat_cms/cms/forms/pois/poi_translation_form.py @@ -13,7 +13,6 @@ class POITranslationForm(MachineTranslationForm): - # pylint: disable=too-many-ancestors """ Form for creating and modifying POI translation objects """ @@ -27,7 +26,7 @@ class Meta: #: The model of this :class:`django.forms.ModelForm` model = POITranslation #: The fields of the model which should be handled by this form - fields = MachineTranslationForm.Meta.fields + ["meta_description", "slug"] + fields = [*MachineTranslationForm.Meta.fields, "meta_description", "slug"] def __init__(self, **kwargs: Any) -> None: r""" diff --git a/integreat_cms/cms/forms/push_notifications/push_notification_form.py b/integreat_cms/cms/forms/push_notifications/push_notification_form.py index d52916f023..8c35fcc287 100644 --- a/integreat_cms/cms/forms/push_notifications/push_notification_form.py +++ b/integreat_cms/cms/forms/push_notifications/push_notification_form.py @@ -76,7 +76,7 @@ def __init__( self.fields["scheduled_send_date_time"].disabled = True self.fields["scheduled_send_date_day"].widget.attrs["min"] = str( - timezone.now().date() + timezone.now().date(), ) # Set the day and time fields @@ -108,7 +108,7 @@ def clean(self) -> dict[str, Any]: cleaned_data = super().clean() if cleaned_data.get("schedule_send") and not cleaned_data.get( - "scheduled_send_date_day" + "scheduled_send_date_day", ): self.add_error( "scheduled_send_date_day", @@ -122,7 +122,8 @@ def clean(self) -> dict[str, Any]: "schedule_send", forms.ValidationError( _('News "{}" was already sent on {}').format( - self.instance, localize(localtime(self.instance.sent_date)) + self.instance, + localize(localtime(self.instance.sent_date)), ), ), ) @@ -140,7 +141,8 @@ def clean(self) -> dict[str, Any]: time = cleaned_data["scheduled_send_date_time"] or datetime.time() cleaned_data["scheduled_send_date"] = datetime.datetime.combine( - cleaned_data["scheduled_send_date_day"], time + cleaned_data["scheduled_send_date_day"], + time, ).replace(tzinfo=tzinfo) if cleaned_data["scheduled_send_date"] < timezone.now(): diff --git a/integreat_cms/cms/forms/push_notifications/push_notification_translation_form.py b/integreat_cms/cms/forms/push_notifications/push_notification_translation_form.py index 7db175ef53..c1896a939b 100644 --- a/integreat_cms/cms/forms/push_notifications/push_notification_translation_form.py +++ b/integreat_cms/cms/forms/push_notifications/push_notification_translation_form.py @@ -51,12 +51,14 @@ def add_error_messages(self, request: HttpRequest) -> None: # Add non-field errors for error in self.non_field_errors(): messages.error( - request, f"{self.instance.language.translated_name}: {_(error)}" + request, + f"{self.instance.language.translated_name}: {_(error)}", ) # Add debug logging in english with override("en"): logger.debug( - "PushNotificationTranslationForm submitted with errors: %r", self.errors + "PushNotificationTranslationForm submitted with errors: %r", + self.errors, ) def has_changed(self) -> bool: diff --git a/integreat_cms/cms/forms/regions/region_form.py b/integreat_cms/cms/forms/regions/region_form.py index fcf57dc60f..cb88a80eb3 100644 --- a/integreat_cms/cms/forms/regions/region_form.py +++ b/integreat_cms/cms/forms/regions/region_form.py @@ -84,7 +84,7 @@ def get_timezone_area_choices() -> list[tuple[str, str]]: tz.split("/")[0] for tz in available_timezones() if "/" in tz and "Etc" not in tz and "SystemV" not in tz - } + }, ) timezone_regions.sort() return ( @@ -110,7 +110,7 @@ class RegionForm(CustomModelForm): required=False, label=_("Keep publication status of pages"), help_text=_( - "Enable it to keep the initial publication status of the pages and don't overwrite them to draft." + "Enable it to keep the initial publication status of the pages and don't overwrite them to draft.", ), ) @@ -118,7 +118,7 @@ class RegionForm(CustomModelForm): required=False, label=_("Copy languages and content translations"), help_text=_( - "Disable to skip copying of the language tree and all content translations." + "Disable to skip copying of the language tree and all content translations.", ), initial=True, ) @@ -130,7 +130,7 @@ class RegionForm(CustomModelForm): required=False, label=_("Page based offers cloning behavior"), help_text=_( - "Decide whether offers which have not been activated but are required for embedded content should be auto-activated, or their embeddings be ignored during cloning." + "Decide whether offers which have not been activated but are required for embedded content should be auto-activated, or their embeddings be ignored during cloning.", ), ) @@ -146,7 +146,7 @@ class RegionForm(CustomModelForm): help_text=__( _("Enable to set an add-on starting date differing from the renewal date."), format_mt_help_text( - _("Budget will be set as a monthly fraction of {} credits") + _("Budget will be set as a monthly fraction of {} credits"), ), ), ) @@ -156,7 +156,7 @@ class RegionForm(CustomModelForm): required=False, label=_("Zammad forms"), help_text=_( - "Zammad forms are a type of offer which can only be used if a Zammad-URL is provided for the region." + "Zammad forms are a type of offer which can only be used if a Zammad-URL is provided for the region.", ), widget=CheckboxSelectMultipleWithDisabled(), ) @@ -250,13 +250,13 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: else OfferTemplate.objects.none() ) self.fields["offers"].queryset = OfferTemplate.objects.filter( - is_zammad_form=False + is_zammad_form=False, ) self.fields["offers"].widget.disabled_options = self.disabled_offer_options - self.fields["zammad_offers"].widget.disabled_options = ( - self.disabled_offer_options - ) + self.fields[ + "zammad_offers" + ].widget.disabled_options = self.disabled_offer_options self.fields["zammad_offers"].initial = ( self.instance.offers.filter(is_zammad_form=True) if self.instance.id else [] ) @@ -298,13 +298,15 @@ def save(self, commit: bool = True) -> Region: ) else: offers_to_discard = required_offers.exclude( - id__in=region.offers.values_list("id", flat=True) + id__in=region.offers.values_list("id", flat=True), ) # Duplicate language tree logger.info("Duplicating language tree of %r to %r", source_region, region) duplicate_language_tree( - source_region, region, only_root=not keep_translations + source_region, + region, + only_root=not keep_translations, ) # Disable linkcheck listeners to prevent links to be created for outdated versions with disable_listeners(): @@ -320,7 +322,9 @@ def save(self, commit: bool = True) -> Region: # Duplicate Imprint if source_region.imprint: logger.info( - "Duplicating imprint of %r to %r", source_region, region + "Duplicating imprint of %r to %r", + source_region, + region, ) duplicate_imprint(source_region, region) # Duplicate media content @@ -332,7 +336,6 @@ def save(self, commit: bool = True) -> Region: return region def clean(self) -> dict[str, Any]: - # pylint: disable=too-many-branches """ Validate form fields which depend on each other, see :meth:`django.forms.Form.clean` @@ -345,19 +348,20 @@ def clean(self) -> dict[str, Any]: self.add_error( "statistics_enabled", _( - "Statistics can only be enabled when a valid access token is supplied." + "Statistics can only be enabled when a valid access token is supplied.", ), ) # Automatically set the Matomo ID if cleaned_data["matomo_token"]: try: cleaned_data["matomo_id"] = self.instance.statistics.get_matomo_id( - token_auth=cleaned_data["matomo_token"] + token_auth=cleaned_data["matomo_token"], ) - except MatomoException as e: - logger.exception(e) + except MatomoException: + logger.exception("") self.add_error( - "matomo_token", _("The provided access token is invalid.") + "matomo_token", + _("The provided access token is invalid."), ) else: cleaned_data["matomo_id"] = None @@ -370,7 +374,7 @@ def clean(self) -> dict[str, Any]: self.add_error( "mt_midyear_start_month", _( - "Please provide a valid budget year start date for foreign language translation." + "Please provide a valid budget year start date for foreign language translation.", ), ) elif ( @@ -384,7 +388,7 @@ def clean(self) -> dict[str, Any]: if not cleaned_data["zammad_url"]: cleaned_data["zammad_offers"] = [] cleaned_data["offers"] = list(cleaned_data["offers"]) + list( - cleaned_data["zammad_offers"] + cleaned_data["zammad_offers"], ) if self.disabled_offer_options and not all( offer in cleaned_data["offers"] for offer in self.disabled_offer_options @@ -392,7 +396,7 @@ def clean(self) -> dict[str, Any]: self.add_error( "offers", _( - "Some offers could not be disabled, since they are currently embedded in at least one page." + "Some offers could not be disabled, since they are currently embedded in at least one page.", ), ) @@ -405,7 +409,7 @@ def clean(self) -> dict[str, Any]: self.add_error( "integreat_chat_enabled", _( - "A Zammad URL, Zammad Webhook Token and Access Token are required in order to enable the Integreat Chat." + "A Zammad URL, Zammad Webhook Token and Access Token are required in order to enable the Integreat Chat.", ), ) @@ -432,7 +436,7 @@ def clean(self) -> dict[str, Any]: "latitude", forms.ValidationError( _( - "Could not retrieve the coordinates automatically, please fill the field manually." + "Could not retrieve the coordinates automatically, please fill the field manually.", ), code="required", ), @@ -442,7 +446,7 @@ def clean(self) -> dict[str, Any]: "longitude", forms.ValidationError( _( - "Could not retrieve the coordinates automatically, please fill the field manually." + "Could not retrieve the coordinates automatically, please fill the field manually.", ), code="required", ), @@ -450,7 +454,7 @@ def clean(self) -> dict[str, Any]: # If a region is being cloned but no PBO cloning behavior has been selected, throw an error if cleaned_data.get("duplicated_region") and not cleaned_data.get( - "duplication_pbo_behavior" + "duplication_pbo_behavior", ): self.add_error( "duplication_pbo_behavior", @@ -493,7 +497,7 @@ def clean_custom_prefix(self) -> str: with override(language_slug): # Force evaluation of lazy-translated text translated_administrative_divisions = list( - map(str, administrative_divisions) + map(str, administrative_divisions), ) # Check if custom prefix could also be set via the administrative division if ( @@ -503,17 +507,17 @@ def clean_custom_prefix(self) -> str: error_messages = [] # Get currently selected administrative division selected_administrative_division = dict( - self.fields["administrative_division"].choices + self.fields["administrative_division"].choices, )[cleaned_data.get("administrative_division")] # Check if administrative division needs to be changed to translated version if cleaned_data.get("custom_prefix") in administrative_divisions: desired_administrative_division = cleaned_data.get( - "custom_prefix" + "custom_prefix", ) else: # Get index of translated administrative division index = translated_administrative_divisions.index( - cleaned_data.get("custom_prefix") + cleaned_data.get("custom_prefix"), ) # Get original label which needs to be selected in list desired_administrative_division = administrative_divisions[ @@ -525,14 +529,14 @@ def clean_custom_prefix(self) -> str: ): error_messages.append( _( - "'{}' is already selected as administrative division." - ).format(selected_administrative_division) + "'{}' is already selected as administrative division.", + ).format(selected_administrative_division), ) else: error_messages.append( _("Please select '{}' as administrative division.").format( - desired_administrative_division - ) + desired_administrative_division, + ), ) # Check if default language needs to be changed in order to use this administrative division if ( @@ -541,15 +545,15 @@ def clean_custom_prefix(self) -> str: ): error_messages.append( _( - "Please set {} as default language for this region." - ).format(_(language_name)) + "Please set {} as default language for this region.", + ).format(_(language_name)), ) # Check if administrative division is included in name yet if not cleaned_data.get("administrative_division_included"): error_messages.append( _("Please enable '{}'.").format( - self.fields["administrative_division_included"].label - ) + self.fields["administrative_division_included"].label, + ), ) self.add_error( "custom_prefix", @@ -557,12 +561,12 @@ def clean_custom_prefix(self) -> str: ) # Check if administrative division is also included in the name and allow only one of both prefix options if cleaned_data.get("custom_prefix") and cleaned_data.get( - "administrative_division_included" + "administrative_division_included", ): self.add_error( "custom_prefix", _( - "You cannot include the administrative division into the name and use a custom prefix at the same time." + "You cannot include the administrative division into the name and use a custom prefix at the same time.", ), ) return cleaned_data.get("custom_prefix") @@ -591,7 +595,8 @@ def clean_summ_ai_enabled(self) -> bool: """ if self.cleaned_data.get("summ_ai_enabled") and not settings.SUMM_AI_ENABLED: self.add_error( - "summ_ai_enabled", _("Currently SUMM.AI is globally deactivated") + "summ_ai_enabled", + _("Currently SUMM.AI is globally deactivated"), ) return False return self.cleaned_data.get("summ_ai_enabled") @@ -606,7 +611,8 @@ def clean_hix_enabled(self) -> bool: # Check whether someone tries to activate hix when no API key is set if cleaned_hix_enabled and not settings.TEXTLAB_API_ENABLED: self.add_error( - "hix_enabled", _("No Textlab API key is set on this system.") + "hix_enabled", + _("No Textlab API key is set on this system."), ) return cleaned_hix_enabled @@ -620,8 +626,7 @@ def clean_zammad_url(self) -> str: return "" # Remove superfluous path parts cleaned_zammad_url = cleaned_zammad_url.split("/api/v1")[0] - cleaned_zammad_url = cleaned_zammad_url.rstrip("/") - return cleaned_zammad_url + return cleaned_zammad_url.rstrip("/") def clean_zammad_access_token(self) -> str: """ @@ -762,7 +767,6 @@ def duplicate_pages( offers_to_discard: QuerySet[OfferTemplate] | None = None, only_root: bool = False, ) -> None: - # pylint: disable=too-many-locals, too-many-positional-arguments, too-many-arguments """ Function to duplicate all non-archived pages from one region to another @@ -790,7 +794,8 @@ def duplicate_pages( # At first, get all pages from the source region with a specific parent page, except archived ones # As the parent will be None for the initial call, this returns all pages from the root level source_pages = source_region.pages.filter( - parent=source_parent, explicitly_archived=False + parent=source_parent, + explicitly_archived=False, ) num_source_pages = len(source_pages) for i, source_page in enumerate(source_pages): @@ -835,7 +840,7 @@ def duplicate_pages( source_page.embedded_offers.all() if not offers_to_discard else source_page.embedded_offers.exclude( - id__in=offers_to_discard.values_list("id", flat=True) + id__in=offers_to_discard.values_list("id", flat=True), ) ) target_page.embedded_offers.add(*embedded_offers) @@ -845,7 +850,10 @@ def duplicate_pages( target_page, ) duplicate_page_translations( - source_page, target_page, row_logging_prefix, keep_status + source_page, + target_page, + row_logging_prefix, + keep_status, ) if not source_page.is_leaf(): # Recursively call this function with the current pages as new parents @@ -862,7 +870,10 @@ def duplicate_pages( def duplicate_page_translations( - source_page: Page, target_page: Page, logging_prefix: str, keep_status: bool + source_page: Page, + target_page: Page, + logging_prefix: str, + keep_status: bool, ) -> None: """ Duplicate all translations of a given source page to a given target page @@ -880,7 +891,7 @@ def duplicate_page_translations( source_page_translations = source_page.translations.filter( language__in=[ node.language for node in target_page.region.language_tree_nodes.all() - ] + ], ) num_translations = len(source_page_translations) translation_row_logging_prefix = logging_prefix + ( @@ -908,7 +919,9 @@ def duplicate_page_translations( def duplicate_imprint( - source_region: Region, target_region: Region, only_root: bool = False + source_region: Region, + target_region: Region, + only_root: bool = False, ) -> None: """ Function to duplicate the imprint from one region to another. @@ -947,16 +960,14 @@ def duplicate_imprint( def duplicate_media( - source_region: Region, target_region: Region # pylint: disable=unused-argument + _source_region: Region, + _target_region: Region, ) -> None: """ Function to duplicate all media of one region to another. - - :param source_region: the source region from which the pages should be duplicated - :param target_region: the target region """ - # pylint: disable=fixme - # TODO: implement duplication of all media files + # TODO(timobrembeck): implement duplication of all media files + # https://github.com/digitalfabrik/integreat-cms/issues/1414 def create_and_replace_links_async(source_region: Region, region: Region) -> None: @@ -991,7 +1002,8 @@ def find_links(region: Region) -> None: logger.info("Scanning for broken links in region %r", region) # Get the latest page translations of the region translations = PageTranslation.objects.filter(page__region=region).distinct( - "page_id", "language_id" + "page_id", + "language_id", ) # Trigger post-save signal to create link objects for translation in translations: @@ -1001,7 +1013,8 @@ def find_links(region: Region) -> None: tasks_queue.join() # Check whether finding links succeeded logger.debug( - "Found links: %r", Link.objects.filter(Q(page_translation__page__region=region)) + "Found links: %r", + Link.objects.filter(Q(page_translation__page__region=region)), ) diff --git a/integreat_cms/cms/forms/statistics/statistics_filter_form.py b/integreat_cms/cms/forms/statistics/statistics_filter_form.py index 13e8d7240b..8aecc18259 100644 --- a/integreat_cms/cms/forms/statistics/statistics_filter_form.py +++ b/integreat_cms/cms/forms/statistics/statistics_filter_form.py @@ -73,6 +73,7 @@ def clean(self) -> dict: ) logger.debug( - "StatisticsFilterForm validated [2] with cleaned data %r", cleaned_data + "StatisticsFilterForm validated [2] with cleaned data %r", + cleaned_data, ) return cleaned_data diff --git a/integreat_cms/cms/forms/translations/translations_management_form.py b/integreat_cms/cms/forms/translations/translations_management_form.py index 81450cdf51..f821b5ee7c 100644 --- a/integreat_cms/cms/forms/translations/translations_management_form.py +++ b/integreat_cms/cms/forms/translations/translations_management_form.py @@ -5,13 +5,13 @@ from typing import TYPE_CHECKING from django import forms -from django.utils.translation import gettext_lazy as _ from ...models import LanguageTreeNode, Region from ..custom_model_form import CustomModelForm if TYPE_CHECKING: - from typing import Any, Iterator + from collections.abc import Iterator + from typing import Any from ....core.utils.machine_translation_provider import ( MachineTranslationProviderType, @@ -100,13 +100,14 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self.languages_dict = kwargs.pop("languages_dict", {}) languages = self.instance.language_tree_nodes.filter( - active=True, parent__isnull=False + active=True, + parent__isnull=False, ).select_related("language") self.unavailable_languages = [] for language in languages: if language.mt_provider: self.languages_dict[language.slug] = TranslationLanguageOptions( - language_tree_node=language + language_tree_node=language, ) else: self.unavailable_languages.append(language.translated_name) @@ -143,7 +144,10 @@ def save(self, commit: bool = True) -> Region: ) language_options.language_tree_node.save() logger.info( - "%s: Set provider %s for %s", region.slug, provider_name, lang + "%s: Set provider %s for %s", + region.slug, + provider_name, + lang, ) else: logger.info( @@ -155,7 +159,9 @@ def save(self, commit: bool = True) -> Region: ) else: logger.info( - "%s: Provider for language %s was not sent", region.slug, lang + "%s: Provider for language %s was not sent", + region.slug, + lang, ) return region diff --git a/integreat_cms/cms/forms/users/organization_field.py b/integreat_cms/cms/forms/users/organization_field.py index 9d449f6512..23420f961e 100644 --- a/integreat_cms/cms/forms/users/organization_field.py +++ b/integreat_cms/cms/forms/users/organization_field.py @@ -28,7 +28,6 @@ def create_option( subindex: Any | None = None, attrs: dict[str, str] | None = None, ) -> dict[str, Any]: - # pylint: disable=too-many-arguments, too-many-positional-arguments """ This function creates an option which can be selected in the organization field @@ -50,7 +49,13 @@ def create_option( region_id = None # Create dictionary of options option_dict = super().create_option( - name, value, label, selected, index, subindex=subindex, attrs=attrs + name, + value, + label, + selected, + index, + subindex=subindex, + attrs=attrs, ) if region_id: # Add organization's region id as data attribute diff --git a/integreat_cms/cms/forms/users/password_reset_form.py b/integreat_cms/cms/forms/users/password_reset_form.py index 23555fd086..336c905a52 100644 --- a/integreat_cms/cms/forms/users/password_reset_form.py +++ b/integreat_cms/cms/forms/users/password_reset_form.py @@ -29,21 +29,20 @@ class CustomPasswordResetForm(PasswordResetForm): def send_mail( self, - subject_template_name: str, + _subject_template_name: str, email_template_name: str, context: dict[str, Any], - from_email: Any | None, + _from_email: Any | None, to_email: str, html_email_template_name: str, ) -> None: - # pylint: disable=signature-differs,too-many-positional-arguments """ Send a django.core.mail.EmailMultiAlternatives to `to_email`. - :param subject_template_name: The template to be used to render the subject of the email + :param _subject_template_name: The template to be used to render the subject of the email :param email_template_name: The template to be used to render the text email :param context: The template context variables - :param from_email: The email address of the sender + :param _from_email: The email address of the sender :param to_email: The email address of the recipient :param html_email_template_name: The template to be used to render the HTML email """ diff --git a/integreat_cms/cms/forms/users/passwordless_authentication_form.py b/integreat_cms/cms/forms/users/passwordless_authentication_form.py index c7019528cf..f76b0cfc90 100644 --- a/integreat_cms/cms/forms/users/passwordless_authentication_form.py +++ b/integreat_cms/cms/forms/users/passwordless_authentication_form.py @@ -38,7 +38,7 @@ class PasswordlessAuthenticationForm(forms.Form): _("Please use the default login."), ), "not_available": _( - "In order to use passwordless authentication, you have to configure at least one 2-factor authentication method." + "In order to use passwordless authentication, you have to configure at least one 2-factor authentication method.", ), } @@ -60,7 +60,7 @@ def __init__( # Set the max length and label for the "username" field. self.username_field = get_user_model()._meta.get_field( - get_user_model().USERNAME_FIELD + get_user_model().USERNAME_FIELD, ) username_max_length = self.username_field.max_length or 254 self.fields["username"].max_length = username_max_length diff --git a/integreat_cms/cms/forms/users/region_user_form.py b/integreat_cms/cms/forms/users/region_user_form.py index 059d495837..44a2ef4d48 100644 --- a/integreat_cms/cms/forms/users/region_user_form.py +++ b/integreat_cms/cms/forms/users/region_user_form.py @@ -35,12 +35,13 @@ def __init__(self, region: Region, **kwargs: Any) -> None: if self.instance.id: # If the user exists, limit organization choices to the user's regions self.fields["organization"].queryset = Organization.objects.filter( - region__in=self.instance.regions.all(), archived=False + region__in=self.instance.regions.all(), + archived=False, ) else: # If the user does not yet exist, only allow the current region self.fields["organization"].queryset = region.organizations.filter( - archived=False + archived=False, ) class Meta: diff --git a/integreat_cms/cms/forms/users/user_filter_form.py b/integreat_cms/cms/forms/users/user_filter_form.py index c8a233f0ac..8206796ad9 100644 --- a/integreat_cms/cms/forms/users/user_filter_form.py +++ b/integreat_cms/cms/forms/users/user_filter_form.py @@ -33,8 +33,8 @@ class UserFilterForm(CustomFilterForm): ) permissions = forms.ChoiceField( label=_("Permissions"), - choices=BLANK_CHOICE_DASH - + [ + choices=[ + *BLANK_CHOICE_DASH, ("is_superuser", _("Administrator")), ("is_staff", _("Integreat team member")), ], @@ -75,5 +75,4 @@ def filter_by_query(self, users: QuerySet[User]) -> QuerySet[User]: """ query = self.cleaned_data["query"].lower() user_keys = search_users(region=None, query=query).values("pk") - users = users.filter(pk__in=user_keys) - return users + return users.filter(pk__in=user_keys) diff --git a/integreat_cms/cms/forms/users/user_form.py b/integreat_cms/cms/forms/users/user_form.py index c0e5eb7907..81a078bec0 100644 --- a/integreat_cms/cms/forms/users/user_form.py +++ b/integreat_cms/cms/forms/users/user_form.py @@ -53,10 +53,10 @@ class UserForm(CustomModelForm): label=_("Send activation link"), help_text=__( _( - "Select this option to create an inactive user account and send an activation link per email to the user." + "Select this option to create an inactive user account and send an activation link per email to the user.", ), _( - "This link allows the user to choose a password and activates the account after confirmation." + "This link allows the user to choose a password and activates the account after confirmation.", ), ), ) @@ -108,7 +108,7 @@ def __init__(self, **kwargs: Any) -> None: self.fields["password"].required = False # adapt placeholder of password input field self.fields["password"].widget.attrs.update( - {"placeholder": _("Leave empty to keep unchanged")} + {"placeholder": _("Leave empty to keep unchanged")}, ) else: self.fields["is_active"].initial = False @@ -195,7 +195,8 @@ def clean(self) -> dict[str, Any]: if cleaned_data.get("is_staff"): if cleaned_data.get("role"): logger.warning( - "Staff member %r can only have staff roles", self.instance + "Staff member %r can only have staff roles", + self.instance, ) self.add_error( "staff_role", @@ -216,7 +217,8 @@ def clean(self) -> dict[str, Any]: else: if cleaned_data.get("staff_role"): logger.warning( - "Non-staff member %r cannot have staff roles", self.instance + "Non-staff member %r cannot have staff roles", + self.instance, ) self.add_error( "role", @@ -244,7 +246,7 @@ def clean(self) -> dict[str, Any]: "send_activation_link", forms.ValidationError( _( - "Please choose either to send an activation link or set a password." + "Please choose either to send an activation link or set a password.", ), code="required", ), @@ -255,7 +257,8 @@ def clean(self) -> dict[str, Any]: if "regions" in self.fields and cleaned_data.get("organization"): if cleaned_data.get("is_superuser") or cleaned_data.get("is_staff"): logger.warning( - "Staff member %r cannot be member of an organization", self.instance + "Staff member %r cannot be member of an organization", + self.instance, ) self.add_error( "organization", @@ -265,7 +268,8 @@ def clean(self) -> dict[str, Any]: ), ) elif cleaned_data["organization"].region not in cleaned_data.get( - "regions", [] + "regions", + [], ): logger.warning( "User %r cannot be member of organization %r of other region", @@ -276,7 +280,7 @@ def clean(self) -> dict[str, Any]: "organization", forms.ValidationError( _( - "Users can only be members of organizations in regions they have access to" + "Users can only be members of organizations in regions they have access to", ), code="invalid", ), diff --git a/integreat_cms/cms/forms/users/user_password_form.py b/integreat_cms/cms/forms/users/user_password_form.py index 61d89d0bb0..36f526fff7 100644 --- a/integreat_cms/cms/forms/users/user_password_form.py +++ b/integreat_cms/cms/forms/users/user_password_form.py @@ -93,6 +93,7 @@ def clean(self) -> dict[str, Any]: self.add_error("new_password_confirm", _("The new passwords do not match.")) logger.debug( - "UserPasswordForm validated [2] with cleaned data %r", cleaned_data + "UserPasswordForm validated [2] with cleaned data %r", + cleaned_data, ) return cleaned_data diff --git a/integreat_cms/cms/linklists.py b/integreat_cms/cms/linklists.py index 06bc17bf6f..beca8fcc81 100644 --- a/integreat_cms/cms/linklists.py +++ b/integreat_cms/cms/linklists.py @@ -46,8 +46,8 @@ def filter_callable(cls, objects: QuerySet) -> QuerySet: region=OuterRef(f"{objects.model.foreign_field()}__region"), language=OuterRef("language"), active=True, - ) - ) + ), + ), ).filter(active=True) return objects @@ -103,12 +103,10 @@ def filter_callable(cls, objects: QuerySet) -> QuerySet: # Apply filter of parent class objects = super().filter_callable(objects) # Exclude archived events/locations - objects = objects.filter( - **{f"{objects.model.foreign_field()}__archived": False} + return objects.filter( + **{f"{objects.model.foreign_field()}__archived": False}, ).distinct(f"{objects.model.foreign_field()}__pk", "language__pk") - return objects - class EventTranslationLinklist(NonArchivedLinkList): """ @@ -129,9 +127,7 @@ def filter_callable(cls, objects: QuerySet) -> QuerySet: objects = super().filter_callable(objects) # Exclude past events upcoming_events = Event.objects.filter_upcoming() - objects = objects.filter(event__in=upcoming_events) - - return objects + return objects.filter(event__in=upcoming_events) class POITranslationLinklist(NonArchivedLinkList): diff --git a/integreat_cms/cms/migrations/0001_initial.py b/integreat_cms/cms/migrations/0001_initial.py index 4c36a2d412..83bb924bf0 100644 --- a/integreat_cms/cms/migrations/0001_initial.py +++ b/integreat_cms/cms/migrations/0001_initial.py @@ -44,7 +44,9 @@ class Migration(migrations.Migration): ( "last_login", models.DateTimeField( - blank=True, null=True, verbose_name="last login" + blank=True, + null=True, + verbose_name="last login", ), ), ( @@ -59,13 +61,13 @@ class Migration(migrations.Migration): "username", models.CharField( error_messages={ - "unique": "A user with that username already exists." + "unique": "A user with that username already exists.", }, help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", max_length=150, unique=True, validators=[ - django.contrib.auth.validators.UnicodeUsernameValidator() + django.contrib.auth.validators.UnicodeUsernameValidator(), ], verbose_name="username", ), @@ -73,19 +75,25 @@ class Migration(migrations.Migration): ( "first_name", models.CharField( - blank=True, max_length=150, verbose_name="first name" + blank=True, + max_length=150, + verbose_name="first name", ), ), ( "last_name", models.CharField( - blank=True, max_length=150, verbose_name="last name" + blank=True, + max_length=150, + verbose_name="last name", ), ), ( "email", models.EmailField( - blank=True, max_length=254, verbose_name="email address" + blank=True, + max_length=254, + verbose_name="email address", ), ), ( @@ -107,15 +115,14 @@ class Migration(migrations.Migration): ( "date_joined", models.DateTimeField( - default=django.utils.timezone.now, verbose_name="date joined" + default=django.utils.timezone.now, + verbose_name="date joined", ), ), ( "chat_last_visited", models.DateTimeField( - default=datetime.datetime( - 1, 1, 1, 0, 0, tzinfo=datetime.timezone.utc - ), + default=datetime.datetime(1, 1, 1, 0, 0, tzinfo=datetime.UTC), help_text="The date and time when the user did read the chat the last time", verbose_name="last chat visit date", ), @@ -211,7 +218,8 @@ class Migration(migrations.Migration): ( "created_date", models.DateTimeField( - default=django.utils.timezone.now, verbose_name="creation date" + default=django.utils.timezone.now, + verbose_name="creation date", ), ), ("start_date", models.DateField(verbose_name="start date")), @@ -270,7 +278,8 @@ class Migration(migrations.Migration): ( "created_date", models.DateTimeField( - auto_now_add=True, verbose_name="creation date" + auto_now_add=True, + verbose_name="creation date", ), ), ], @@ -296,7 +305,8 @@ class Migration(migrations.Migration): ( "created_date", models.DateTimeField( - default=django.utils.timezone.now, verbose_name="creation date" + default=django.utils.timezone.now, + verbose_name="creation date", ), ), ( @@ -749,13 +759,15 @@ class Migration(migrations.Migration): ( "created_date", models.DateTimeField( - default=django.utils.timezone.now, verbose_name="creation date" + default=django.utils.timezone.now, + verbose_name="creation date", ), ), ( "last_updated", models.DateTimeField( - auto_now=True, verbose_name="modification date" + auto_now=True, + verbose_name="modification date", ), ), ( @@ -835,7 +847,9 @@ class Migration(migrations.Migration): ( "alt_text", models.CharField( - blank=True, max_length=512, verbose_name="description" + blank=True, + max_length=512, + verbose_name="description", ), ), ( @@ -930,13 +944,15 @@ class Migration(migrations.Migration): ( "created_date", models.DateTimeField( - default=django.utils.timezone.now, verbose_name="creation date" + default=django.utils.timezone.now, + verbose_name="creation date", ), ), ( "last_updated", models.DateTimeField( - auto_now=True, verbose_name="modification date" + auto_now=True, + verbose_name="modification date", ), ), ], @@ -972,13 +988,15 @@ class Migration(migrations.Migration): ( "created_date", models.DateTimeField( - default=django.utils.timezone.now, verbose_name="creation date" + default=django.utils.timezone.now, + verbose_name="creation date", ), ), ( "last_updated", models.DateTimeField( - auto_now=True, verbose_name="modification date" + auto_now=True, + verbose_name="modification date", ), ), ( @@ -1014,7 +1032,8 @@ class Migration(migrations.Migration): ( "created_date", models.DateTimeField( - default=django.utils.timezone.now, verbose_name="creation date" + default=django.utils.timezone.now, + verbose_name="creation date", ), ), ( @@ -1132,13 +1151,15 @@ class Migration(migrations.Migration): ( "created_date", models.DateTimeField( - default=django.utils.timezone.now, verbose_name="creation date" + default=django.utils.timezone.now, + verbose_name="creation date", ), ), ( "address", models.CharField( - max_length=250, verbose_name="street and house number" + max_length=250, + verbose_name="street and house number", ), ), ( @@ -1150,13 +1171,15 @@ class Migration(migrations.Migration): ( "latitude", models.FloatField( - help_text="The latitude coordinate", verbose_name="latitude" + help_text="The latitude coordinate", + verbose_name="latitude", ), ), ( "longitude", models.FloatField( - help_text="The longitude coordinate", verbose_name="longitude" + help_text="The longitude coordinate", + verbose_name="longitude", ), ), ( @@ -1242,7 +1265,7 @@ class Migration(migrations.Migration): (4, "Friday"), (5, "Saturday"), (6, "Sunday"), - ] + ], ), blank=True, help_text="If the frequency is weekly, this field determines on which days the event takes place", @@ -1610,13 +1633,15 @@ class Migration(migrations.Migration): ( "created_date", models.DateTimeField( - default=django.utils.timezone.now, verbose_name="creation date" + default=django.utils.timezone.now, + verbose_name="creation date", ), ), ( "last_updated", models.DateTimeField( - auto_now=True, verbose_name="modification date" + auto_now=True, + verbose_name="modification date", ), ), ( @@ -1747,7 +1772,8 @@ class Migration(migrations.Migration): ( "created_date", models.DateTimeField( - auto_now_add=True, verbose_name="creation date" + auto_now_add=True, + verbose_name="creation date", ), ), ( @@ -1844,7 +1870,8 @@ class Migration(migrations.Migration): ( "last_updated", models.DateTimeField( - auto_now=True, verbose_name="modification date" + auto_now=True, + verbose_name="modification date", ), ), ( @@ -1959,7 +1986,8 @@ class Migration(migrations.Migration): ( "last_updated", models.DateTimeField( - auto_now=True, verbose_name="modification date" + auto_now=True, + verbose_name="modification date", ), ), ( @@ -2073,7 +2101,8 @@ class Migration(migrations.Migration): ( "last_updated", models.DateTimeField( - auto_now=True, verbose_name="modification date" + auto_now=True, + verbose_name="modification date", ), ), ( @@ -2228,7 +2257,8 @@ class Migration(migrations.Migration): ( "last_updated", models.DateTimeField( - auto_now=True, verbose_name="modification date" + auto_now=True, + verbose_name="modification date", ), ), ( @@ -2434,7 +2464,8 @@ class Migration(migrations.Migration): ( "created_at", models.DateTimeField( - auto_now_add=True, verbose_name="creation date" + auto_now_add=True, + verbose_name="creation date", ), ), ( @@ -2473,13 +2504,16 @@ class Migration(migrations.Migration): ( "text", models.TextField( - blank=True, max_length=250, verbose_name="content" + blank=True, + max_length=250, + verbose_name="content", ), ), ( "last_updated", models.DateTimeField( - auto_now=True, verbose_name="modification date" + auto_now=True, + verbose_name="modification date", ), ), ( @@ -2637,13 +2671,15 @@ class Migration(migrations.Migration): ( "created_date", models.DateTimeField( - default=django.utils.timezone.now, verbose_name="creation date" + default=django.utils.timezone.now, + verbose_name="creation date", ), ), ( "last_updated", models.DateTimeField( - auto_now=True, verbose_name="modification date" + auto_now=True, + verbose_name="modification date", ), ), ( diff --git a/integreat_cms/cms/migrations/0002_roles.py b/integreat_cms/cms/migrations/0002_roles.py index 92ebf15349..9733b898f8 100644 --- a/integreat_cms/cms/migrations/0002_roles.py +++ b/integreat_cms/cms/migrations/0002_roles.py @@ -237,13 +237,12 @@ def add_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Add the default roles for users :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ # Emit post-migrate signal to make sure the Permission objects are created before they can be assigned emit_post_migrate_signal(2, False, "default") @@ -262,20 +261,19 @@ def add_roles( staff_role=role_conf.get("staff_role"), ) permissions = Permission.objects.filter( - codename__in=role_conf.get("permissions") + codename__in=role_conf.get("permissions"), ) group.permissions.add(*permissions) def remove_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Remove the default roles for users :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ # We can't import the Person model directly as it may be a newer # version than this migration expects. We use the historical version. diff --git a/integreat_cms/cms/migrations/0005_grant_imprint_deletion_permission.py b/integreat_cms/cms/migrations/0005_grant_imprint_deletion_permission.py index 9b199ae45e..b7b358b324 100644 --- a/integreat_cms/cms/migrations/0005_grant_imprint_deletion_permission.py +++ b/integreat_cms/cms/migrations/0005_grant_imprint_deletion_permission.py @@ -12,13 +12,12 @@ def add_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Add the default roles for users :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ # We can't import the Person model directly as it may be a newer # version than this migration expects. We use the historical version. @@ -32,13 +31,12 @@ def add_roles( def remove_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Remove the default roles for users :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ # We can't import the Person model directly as it may be a newer # version than this migration expects. We use the historical version. diff --git a/integreat_cms/cms/migrations/0007_change_role_permissions.py b/integreat_cms/cms/migrations/0007_change_role_permissions.py index 752b53a0ff..39abe82208 100644 --- a/integreat_cms/cms/migrations/0007_change_role_permissions.py +++ b/integreat_cms/cms/migrations/0007_change_role_permissions.py @@ -36,13 +36,12 @@ def update_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Update the permissions of roles :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ # We can't import the Person model directly as it may be a newer # version than this migration expects. We use the historical version. @@ -52,24 +51,23 @@ def update_roles( for role_conf in ROLES: group = Group.objects.get(name=role_conf.get("name")) add_permissions = Permission.objects.filter( - codename__in=role_conf.get("add_permissions") + codename__in=role_conf.get("add_permissions"), ) group.permissions.add(*add_permissions) remove_permissions = Permission.objects.filter( - codename__in=role_conf.get("remove_permissions") + codename__in=role_conf.get("remove_permissions"), ) group.permissions.remove(*remove_permissions) def revert_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Revert the permission changes of this migration :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ # We can't import the Person model directly as it may be a newer # version than this migration expects. We use the historical version. @@ -80,12 +78,12 @@ def revert_roles( group = Group.objects.get(name=role_conf.get("name")) # The permissions that were added with this migration need to be removed add_permissions = Permission.objects.filter( - codename__in=role_conf.get("add_permissions") + codename__in=role_conf.get("add_permissions"), ) group.permissions.remove(*add_permissions) # The migrations that were removed with this migration need to be added again remove_permissions = Permission.objects.filter( - codename__in=role_conf.get("remove_permissions") + codename__in=role_conf.get("remove_permissions"), ) group.permissions.add(*remove_permissions) diff --git a/integreat_cms/cms/migrations/0008_alter_pushnotification_channel.py b/integreat_cms/cms/migrations/0008_alter_pushnotification_channel.py index 4cff3c51d3..890cbdd3c5 100644 --- a/integreat_cms/cms/migrations/0008_alter_pushnotification_channel.py +++ b/integreat_cms/cms/migrations/0008_alter_pushnotification_channel.py @@ -19,7 +19,9 @@ class Migration(migrations.Migration): model_name="pushnotification", name="channel", field=models.CharField( - choices=[("news", "News")], max_length=60, verbose_name="channel" + choices=[("news", "News")], + max_length=60, + verbose_name="channel", ), ), ] diff --git a/integreat_cms/cms/migrations/0012_mediafile_file_size.py b/integreat_cms/cms/migrations/0012_mediafile_file_size.py index 0a12f93a64..593f8c6b81 100644 --- a/integreat_cms/cms/migrations/0012_mediafile_file_size.py +++ b/integreat_cms/cms/migrations/0012_mediafile_file_size.py @@ -15,19 +15,18 @@ def calculate_file_fields( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Calculates the file size for already existing MediaFiles on the system. :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ MediaFiles = apps.get_model("cms", "MediaFile") for media in MediaFiles.objects.all(): media.file_size = media.file.size media.last_modified = make_aware( - datetime.fromtimestamp(getmtime(media.file.path)) + datetime.fromtimestamp(getmtime(media.file.path)), ) media.save() diff --git a/integreat_cms/cms/migrations/0015_user_unique_email_field.py b/integreat_cms/cms/migrations/0015_user_unique_email_field.py index 4efd7d2dc0..914b128304 100644 --- a/integreat_cms/cms/migrations/0015_user_unique_email_field.py +++ b/integreat_cms/cms/migrations/0015_user_unique_email_field.py @@ -15,13 +15,12 @@ def make_user_email_field_unique( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Make sure that the email field of users is unique :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ # "Import" historic version of user model User = apps.get_model("cms.User") diff --git a/integreat_cms/cms/migrations/0017_region_timezone.py b/integreat_cms/cms/migrations/0017_region_timezone.py index 64a6003e17..c3ed2c7933 100644 --- a/integreat_cms/cms/migrations/0017_region_timezone.py +++ b/integreat_cms/cms/migrations/0017_region_timezone.py @@ -19,7 +19,9 @@ class Migration(migrations.Migration): model_name="region", name="timezone", field=models.CharField( - default="Europe/Berlin", max_length=150, verbose_name="timezone" + default="Europe/Berlin", + max_length=150, + verbose_name="timezone", ), ), ] diff --git a/integreat_cms/cms/migrations/0019_add_location_fields.py b/integreat_cms/cms/migrations/0019_add_location_fields.py index cd20a46b86..d0b021b754 100644 --- a/integreat_cms/cms/migrations/0019_add_location_fields.py +++ b/integreat_cms/cms/migrations/0019_add_location_fields.py @@ -19,14 +19,18 @@ class Migration(migrations.Migration): model_name="poi", name="email", field=models.EmailField( - blank=True, max_length=254, verbose_name="email address" + blank=True, + max_length=254, + verbose_name="email address", ), ), migrations.AddField( model_name="poi", name="phone_number", field=models.CharField( - blank=True, max_length=250, verbose_name="phone number" + blank=True, + max_length=250, + verbose_name="phone number", ), ), migrations.AddField( diff --git a/integreat_cms/cms/migrations/0020_alter_last_updated_field.py b/integreat_cms/cms/migrations/0020_alter_last_updated_field.py index d39e83f060..70d079f724 100644 --- a/integreat_cms/cms/migrations/0020_alter_last_updated_field.py +++ b/integreat_cms/cms/migrations/0020_alter_last_updated_field.py @@ -20,28 +20,32 @@ class Migration(migrations.Migration): model_name="eventtranslation", name="last_updated", field=models.DateTimeField( - default=django.utils.timezone.now, verbose_name="modification date" + default=django.utils.timezone.now, + verbose_name="modification date", ), ), migrations.AlterField( model_name="imprintpagetranslation", name="last_updated", field=models.DateTimeField( - default=django.utils.timezone.now, verbose_name="modification date" + default=django.utils.timezone.now, + verbose_name="modification date", ), ), migrations.AlterField( model_name="pagetranslation", name="last_updated", field=models.DateTimeField( - default=django.utils.timezone.now, verbose_name="modification date" + default=django.utils.timezone.now, + verbose_name="modification date", ), ), migrations.AlterField( model_name="poitranslation", name="last_updated", field=models.DateTimeField( - default=django.utils.timezone.now, verbose_name="modification date" + default=django.utils.timezone.now, + verbose_name="modification date", ), ), ] diff --git a/integreat_cms/cms/migrations/0022_rename_media_directories.py b/integreat_cms/cms/migrations/0022_rename_media_directories.py index d26d187916..6888c35442 100644 --- a/integreat_cms/cms/migrations/0022_rename_media_directories.py +++ b/integreat_cms/cms/migrations/0022_rename_media_directories.py @@ -55,7 +55,7 @@ def replace_media_prefix(media: MediaFile, before: str, after: str) -> None: def rename_media_directories( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Renames the location of the physical files of the already existing MediaFiles on the system. @@ -64,7 +64,6 @@ def rename_media_directories( The region media library is now located at /media/regions/... :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ # Rename all directories on the hard drive media_directory = Path(settings.MEDIA_ROOT) @@ -113,7 +112,8 @@ def rename_media_directories( ): # Replace region directory translation.content = translation.content.replace( - f"{settings.BASE_URL}/media/sites/", f"{settings.BASE_URL}/media/regions/" + f"{settings.BASE_URL}/media/sites/", + f"{settings.BASE_URL}/media/regions/", ) # Replace global directory translation.content = re.sub( @@ -130,7 +130,7 @@ def rename_media_directories( def reverse_rename_media_directories( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Reverse the renaming of the media directories @@ -139,7 +139,6 @@ def reverse_rename_media_directories( The region media library is reversed to /media/sites/... :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ # Rename all directories on the hard drive media_directory = Path(settings.MEDIA_ROOT) @@ -183,11 +182,13 @@ def reverse_rename_media_directories( ): # Replace region directory translation.content = translation.content.replace( - f"{settings.BASE_URL}/media/regions/", f"{settings.BASE_URL}/media/sites/" + f"{settings.BASE_URL}/media/regions/", + f"{settings.BASE_URL}/media/sites/", ) # Replace global directory translation.content = translation.content.replace( - f"{settings.BASE_URL}/media/global/", f"{settings.BASE_URL}/media/" + f"{settings.BASE_URL}/media/global/", + f"{settings.BASE_URL}/media/", ) translation.save(update_fields=["content"]) print( @@ -207,6 +208,7 @@ class Migration(migrations.Migration): operations = [ migrations.RunPython( - rename_media_directories, reverse_rename_media_directories + rename_media_directories, + reverse_rename_media_directories, ), ] diff --git a/integreat_cms/cms/migrations/0025_update_roles.py b/integreat_cms/cms/migrations/0025_update_roles.py index 8065fd143f..9fb4e8a491 100644 --- a/integreat_cms/cms/migrations/0025_update_roles.py +++ b/integreat_cms/cms/migrations/0025_update_roles.py @@ -14,13 +14,12 @@ def update_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Update the role definitions :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ # We can't import the Person model directly as it may be a newer # version than this migration expects. We use the historical version. @@ -46,7 +45,7 @@ def update_roles( group.permissions.clear() # Set permissions group.permissions.add( - *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]) + *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]), ) diff --git a/integreat_cms/cms/migrations/0034_organization_region.py b/integreat_cms/cms/migrations/0034_organization_region.py index afdb47119b..9f2ff626c9 100644 --- a/integreat_cms/cms/migrations/0034_organization_region.py +++ b/integreat_cms/cms/migrations/0034_organization_region.py @@ -18,13 +18,12 @@ def delete_organizations( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Delete all existing organizations because they have to be region-specific now :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ Organization = apps.get_model("cms", "Organization") if organizations := Organization.objects.all(): diff --git a/integreat_cms/cms/migrations/0035_alter_chatmessage_options.py b/integreat_cms/cms/migrations/0035_alter_chatmessage_options.py index 378fc29436..a2829b8127 100644 --- a/integreat_cms/cms/migrations/0035_alter_chatmessage_options.py +++ b/integreat_cms/cms/migrations/0035_alter_chatmessage_options.py @@ -16,13 +16,12 @@ def update_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Update permissions for service and management group :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ Group = apps.get_model("auth", "Group") Permission = apps.get_model("auth", "Permission") @@ -37,7 +36,7 @@ def update_roles( group.permissions.clear() # Set permissions group.permissions.add( - *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]) + *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]), ) diff --git a/integreat_cms/cms/migrations/0036_add_non_political_flags.py b/integreat_cms/cms/migrations/0036_add_non_political_flags.py index 9f279686b6..783c7bc820 100644 --- a/integreat_cms/cms/migrations/0036_add_non_political_flags.py +++ b/integreat_cms/cms/migrations/0036_add_non_political_flags.py @@ -13,13 +13,12 @@ def update_flags( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Update flags of Arabic and Farsi to be non-political :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ Language = apps.get_model("cms", "Language") @@ -38,13 +37,12 @@ def update_flags( def reverse_flags( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Update flags of Arabic and Farsi to be political :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ Language = apps.get_model("cms", "Language") diff --git a/integreat_cms/cms/migrations/0037_event_datetime.py b/integreat_cms/cms/migrations/0037_event_datetime.py index 8f93e9bb0f..dfb8507dc4 100644 --- a/integreat_cms/cms/migrations/0037_event_datetime.py +++ b/integreat_cms/cms/migrations/0037_event_datetime.py @@ -14,14 +14,13 @@ def start_and_end_init( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Initialize the new event datetime fields 'start' and 'end' from the respective existing fields *_date and *_time. :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ Event = apps.get_model("cms", "Event") for event in Event.objects.all(): @@ -35,13 +34,12 @@ def start_and_end_init( def start_and_end_reverse( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Initialize the old event date and time fields from the respective new fields start and end. :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ Event = apps.get_model("cms", "Event") for event in Event.objects.all(): diff --git a/integreat_cms/cms/migrations/0039_poi_category.py b/integreat_cms/cms/migrations/0039_poi_category.py index f7c51ff151..62cb90c025 100644 --- a/integreat_cms/cms/migrations/0039_poi_category.py +++ b/integreat_cms/cms/migrations/0039_poi_category.py @@ -17,13 +17,12 @@ def update_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Update permissions for service and management group :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ Group = apps.get_model("auth", "Group") Permission = apps.get_model("auth", "Permission") @@ -38,7 +37,7 @@ def update_roles( group.permissions.clear() # Set permissions group.permissions.add( - *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]) + *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]), ) diff --git a/integreat_cms/cms/migrations/0043_update_organization_permissions.py b/integreat_cms/cms/migrations/0043_update_organization_permissions.py index 682cff9563..6a02b370fb 100644 --- a/integreat_cms/cms/migrations/0043_update_organization_permissions.py +++ b/integreat_cms/cms/migrations/0043_update_organization_permissions.py @@ -14,13 +14,12 @@ def update_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Update the role definitions :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ # We can't import the Person model directly as it may be a newer # version than this migration expects. We use the historical version. @@ -34,7 +33,7 @@ def update_roles( group.permissions.clear() # Set permissions group.permissions.add( - *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]) + *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]), ) diff --git a/integreat_cms/cms/migrations/0044_alter_user_options.py b/integreat_cms/cms/migrations/0044_alter_user_options.py index 2fa26b7845..6bed77ce26 100644 --- a/integreat_cms/cms/migrations/0044_alter_user_options.py +++ b/integreat_cms/cms/migrations/0044_alter_user_options.py @@ -16,13 +16,12 @@ def update_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Update analytics permissions :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ Group = apps.get_model("auth", "Group") Permission = apps.get_model("auth", "Permission") @@ -37,7 +36,7 @@ def update_roles( group.permissions.clear() # Set permissions group.permissions.add( - *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]) + *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]), ) diff --git a/integreat_cms/cms/migrations/0050_increase_max_url_length.py b/integreat_cms/cms/migrations/0050_increase_max_url_length.py index ba6aff107d..2421c579af 100644 --- a/integreat_cms/cms/migrations/0050_increase_max_url_length.py +++ b/integreat_cms/cms/migrations/0050_increase_max_url_length.py @@ -30,7 +30,9 @@ class Migration(migrations.Migration): ] def mutate_state( - self, project_state: ProjectState, preserve: bool = True + self, + project_state: ProjectState, + preserve: bool = True, ) -> ProjectState: """ This is a workaround that allows to store ``linkcheck`` diff --git a/integreat_cms/cms/migrations/0051_rename_farsi_flag.py b/integreat_cms/cms/migrations/0051_rename_farsi_flag.py index adf53c701c..50b82766fc 100644 --- a/integreat_cms/cms/migrations/0051_rename_farsi_flag.py +++ b/integreat_cms/cms/migrations/0051_rename_farsi_flag.py @@ -12,18 +12,17 @@ def update_flag( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Migrate the flag name for Farsi :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ Language = apps.get_model("cms", "Language") Language.objects.filter(primary_country_code="fa").update(primary_country_code="fs") Language.objects.filter(secondary_country_code="fa").update( - secondary_country_code="fs" + secondary_country_code="fs", ) diff --git a/integreat_cms/cms/migrations/0053_alter_role_name.py b/integreat_cms/cms/migrations/0053_alter_role_name.py index 3bb77e9b07..4c35509aaa 100644 --- a/integreat_cms/cms/migrations/0053_alter_role_name.py +++ b/integreat_cms/cms/migrations/0053_alter_role_name.py @@ -16,13 +16,12 @@ def update_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Update permissions for service and management group :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ Group = apps.get_model("auth", "Group") Permission = apps.get_model("auth", "Permission") @@ -38,7 +37,7 @@ def update_roles( group.permissions.clear() # Set permissions group.permissions.add( - *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]) + *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]), ) # Create the new role Observer diff --git a/integreat_cms/cms/migrations/0055_track_region_deepl_api_usage.py b/integreat_cms/cms/migrations/0055_track_region_deepl_api_usage.py index c699379b74..f71da6ed3d 100644 --- a/integreat_cms/cms/migrations/0055_track_region_deepl_api_usage.py +++ b/integreat_cms/cms/migrations/0055_track_region_deepl_api_usage.py @@ -28,7 +28,8 @@ class Migration(migrations.Migration): model_name="region", name="deepl_budget_used", field=models.PositiveIntegerField( - default=0, verbose_name="used DeepL budget" + default=0, + verbose_name="used DeepL budget", ), ), migrations.AddField( diff --git a/integreat_cms/cms/migrations/0056_update_observer_permissions.py b/integreat_cms/cms/migrations/0056_update_observer_permissions.py index cb1121c3c8..0309007e9a 100644 --- a/integreat_cms/cms/migrations/0056_update_observer_permissions.py +++ b/integreat_cms/cms/migrations/0056_update_observer_permissions.py @@ -13,13 +13,12 @@ def update_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Update the role definitions :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ # We can't import the Person model directly as it may be a newer # version than this migration expects. We use the historical version. @@ -33,7 +32,7 @@ def update_roles( group.permissions.clear() # Set permissions group.permissions.add( - *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]) + *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]), ) diff --git a/integreat_cms/cms/migrations/0058_translations_management_settings.py b/integreat_cms/cms/migrations/0058_translations_management_settings.py index 89c2bc6fc3..d8476ea964 100644 --- a/integreat_cms/cms/migrations/0058_translations_management_settings.py +++ b/integreat_cms/cms/migrations/0058_translations_management_settings.py @@ -16,13 +16,12 @@ def update_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Update translation settings permissions :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ Group = apps.get_model("auth", "Group") Permission = apps.get_model("auth", "Permission") @@ -37,7 +36,7 @@ def update_roles( group.permissions.clear() # Set permissions group.permissions.add( - *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]) + *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]), ) diff --git a/integreat_cms/cms/migrations/0066_update_poi_translation_status.py b/integreat_cms/cms/migrations/0066_update_poi_translation_status.py index ee37379e6e..7b90d761e8 100644 --- a/integreat_cms/cms/migrations/0066_update_poi_translation_status.py +++ b/integreat_cms/cms/migrations/0066_update_poi_translation_status.py @@ -13,20 +13,20 @@ def update_poi_translation_status( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Update poi translation status to draft for pois without the default public translation :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ Region = apps.get_model("cms", "Region") POITranslation = apps.get_model("cms", "POITranslation") for region in Region.objects.filter(pois__isnull=False).distinct(): default_language = region.language_tree_nodes.values_list( - "language", flat=True + "language", + flat=True, ).first() POITranslation.objects.filter( poi__region=region, @@ -35,10 +35,8 @@ def update_poi_translation_status( poi__in=region.pois.filter( translations__language=default_language, translations__status=status.PUBLIC, - ) - ).update( - status=status.DRAFT - ) + ), + ).update(status=status.DRAFT) class Migration(migrations.Migration): diff --git a/integreat_cms/cms/migrations/0075_add_pushnotification_multiple_regions.py b/integreat_cms/cms/migrations/0075_add_pushnotification_multiple_regions.py index 83385117cf..2a46f62951 100644 --- a/integreat_cms/cms/migrations/0075_add_pushnotification_multiple_regions.py +++ b/integreat_cms/cms/migrations/0075_add_pushnotification_multiple_regions.py @@ -12,13 +12,12 @@ def forwards_func( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Adopting the old data when applying this migration :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ PushNotification = apps.get_model("cms", "PushNotification") for pn in PushNotification.objects.all(): @@ -27,13 +26,12 @@ def forwards_func( def reverse_func( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Reverting (most of the) newer data when reverting this migration :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ PushNotification = apps.get_model("cms", "PushNotification") for pn in PushNotification.objects.all(): diff --git a/integreat_cms/cms/migrations/0079_alter_page_mirrored_page.py b/integreat_cms/cms/migrations/0079_alter_page_mirrored_page.py index 6c6d59526a..113cf935a4 100644 --- a/integreat_cms/cms/migrations/0079_alter_page_mirrored_page.py +++ b/integreat_cms/cms/migrations/0079_alter_page_mirrored_page.py @@ -16,13 +16,12 @@ def update_mirrored_pages( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Set the field mirrored_page to None when a page is archived or belongs to an archived region. :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ Page = apps.get_model("cms", "Page") @@ -32,7 +31,8 @@ def update_mirrored_pages( pages_archived = [ Page.objects.filter( - tree_id=page.tree_id, lft__range=(page.lft, page.rgt - 1) + tree_id=page.tree_id, + lft__range=(page.lft, page.rgt - 1), ).order_by() for page in Page.objects.filter(explicitly_archived=True) ] diff --git a/integreat_cms/cms/migrations/0080_alter_poi_category.py b/integreat_cms/cms/migrations/0080_alter_poi_category.py index 20369a77d7..a40e99525b 100644 --- a/integreat_cms/cms/migrations/0080_alter_poi_category.py +++ b/integreat_cms/cms/migrations/0080_alter_poi_category.py @@ -15,7 +15,7 @@ def check_and_update_poicategory( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ This function checks whether the default POI category "Other" exists and creates it if not. diff --git a/integreat_cms/cms/migrations/0081_poi_appointment_url.py b/integreat_cms/cms/migrations/0081_poi_appointment_url.py index 0bece4584a..2fec2bc997 100644 --- a/integreat_cms/cms/migrations/0081_poi_appointment_url.py +++ b/integreat_cms/cms/migrations/0081_poi_appointment_url.py @@ -13,13 +13,12 @@ def set_default_appointment_only( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Set default value for appointmentOnly :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ POI = apps.get_model("cms", "POI") for obj in POI.objects.all(): diff --git a/integreat_cms/cms/migrations/0084_add_zammad_forms_as_offers.py b/integreat_cms/cms/migrations/0084_add_zammad_forms_as_offers.py index 32dfa68363..588b3d4f4b 100644 --- a/integreat_cms/cms/migrations/0084_add_zammad_forms_as_offers.py +++ b/integreat_cms/cms/migrations/0084_add_zammad_forms_as_offers.py @@ -38,7 +38,9 @@ class Migration(migrations.Migration): model_name="offertemplate", name="thumbnail", field=models.URLField( - blank=True, max_length=250, verbose_name="thumbnail URL" + blank=True, + max_length=250, + verbose_name="thumbnail URL", ), ), migrations.AlterField( diff --git a/integreat_cms/cms/migrations/0096_chat_beta_testing.py b/integreat_cms/cms/migrations/0096_chat_beta_testing.py index d48126a14b..1c209178aa 100644 --- a/integreat_cms/cms/migrations/0096_chat_beta_testing.py +++ b/integreat_cms/cms/migrations/0096_chat_beta_testing.py @@ -46,7 +46,8 @@ class Migration(migrations.Migration): ( "region", models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="cms.region" + on_delete=django.db.models.deletion.CASCADE, + to="cms.region", ), ), ], diff --git a/integreat_cms/cms/migrations/0098_add_contact.py b/integreat_cms/cms/migrations/0098_add_contact.py index 2b214f651c..f6e81410d9 100644 --- a/integreat_cms/cms/migrations/0098_add_contact.py +++ b/integreat_cms/cms/migrations/0098_add_contact.py @@ -10,12 +10,11 @@ def update_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Add permissions for managing external calendars :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ Group = apps.get_model("auth", "Group") Permission = apps.get_model("auth", "Permission") @@ -30,7 +29,7 @@ def update_roles( group.permissions.clear() # Set permissions group.permissions.add( - *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]) + *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]), ) @@ -61,13 +60,17 @@ class Migration(migrations.Migration): ( "email", models.EmailField( - blank=True, max_length=254, verbose_name="email address" + blank=True, + max_length=254, + verbose_name="email address", ), ), ( "phone_number", models.CharField( - blank=True, max_length=250, verbose_name="phone number" + blank=True, + max_length=250, + verbose_name="phone number", ), ), ( @@ -85,13 +88,15 @@ class Migration(migrations.Migration): ( "last_updated", models.DateTimeField( - auto_now=True, verbose_name="modification date" + auto_now=True, + verbose_name="modification date", ), ), ( "created_date", models.DateTimeField( - default=django.utils.timezone.now, verbose_name="creation date" + default=django.utils.timezone.now, + verbose_name="creation date", ), ), ( diff --git a/integreat_cms/cms/migrations/0101_external_calendar.py b/integreat_cms/cms/migrations/0101_external_calendar.py index 61b44ff8c5..e3a035613b 100644 --- a/integreat_cms/cms/migrations/0101_external_calendar.py +++ b/integreat_cms/cms/migrations/0101_external_calendar.py @@ -11,13 +11,12 @@ def update_roles( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Add permissions for managing external calendars :param apps: The configuration of installed applications - :param schema_editor: The database abstraction layer that creates actual SQL code """ Group = apps.get_model("auth", "Group") Permission = apps.get_model("auth", "Permission") @@ -32,7 +31,7 @@ def update_roles( group.permissions.clear() # Set permissions group.permissions.add( - *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]) + *Permission.objects.filter(codename__in=roles.PERMISSIONS[role_name]), ) @@ -70,7 +69,9 @@ class Migration(migrations.Migration): ( "name", models.CharField( - default="", max_length=255, verbose_name="calendar name" + default="", + max_length=255, + verbose_name="calendar name", ), ), ( @@ -98,7 +99,9 @@ class Migration(migrations.Migration): ( "errors", models.CharField( - blank=True, default="", verbose_name="import errors" + blank=True, + default="", + verbose_name="import errors", ), ), ], diff --git a/integreat_cms/cms/migrations/0104_userchat_language_userchat_region_and_more.py b/integreat_cms/cms/migrations/0104_userchat_language_userchat_region_and_more.py index d389e6b908..f5f735a686 100644 --- a/integreat_cms/cms/migrations/0104_userchat_language_userchat_region_and_more.py +++ b/integreat_cms/cms/migrations/0104_userchat_language_userchat_region_and_more.py @@ -1,6 +1,6 @@ # Generated by Django 4.2.13 on 2024-09-23 15:33 -from typing import Callable +from collections.abc import Callable import django.db.models.deletion from django.apps.registry import Apps @@ -8,7 +8,8 @@ def set_zammad_urls( - apps: Apps, schema_editor: Callable # pylint: disable=unused-argument + apps: Apps, + _schema_editor: Callable, ) -> None: """ Update empty Zammad URLs to Null diff --git a/integreat_cms/cms/migrations/0105_treebeard_models_add_deferrable_tree_constraints.py b/integreat_cms/cms/migrations/0105_treebeard_models_add_deferrable_tree_constraints.py index 22782fc8f6..26d4724500 100644 --- a/integreat_cms/cms/migrations/0105_treebeard_models_add_deferrable_tree_constraints.py +++ b/integreat_cms/cms/migrations/0105_treebeard_models_add_deferrable_tree_constraints.py @@ -16,7 +16,7 @@ def forwards_func( apps: Apps, - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Data migration step attempting to fix all trees @@ -26,8 +26,8 @@ def forwards_func( def reverse_func( - apps: Apps, # pylint: disable=unused-argument - schema_editor: BaseDatabaseSchemaEditor, # pylint: disable=unused-argument + _apps: Apps, + _schema_editor: BaseDatabaseSchemaEditor, ) -> None: """ Data migration when reverting this migration diff --git a/integreat_cms/cms/migrations/0108_rename_contact_title_to_point_of_contact_for.py b/integreat_cms/cms/migrations/0108_rename_contact_title_to_point_of_contact_for.py index 075c39bbb5..ff8d75e5a4 100644 --- a/integreat_cms/cms/migrations/0108_rename_contact_title_to_point_of_contact_for.py +++ b/integreat_cms/cms/migrations/0108_rename_contact_title_to_point_of_contact_for.py @@ -28,7 +28,9 @@ class Migration(migrations.Migration): model_name="contact", name="point_of_contact_for", field=models.CharField( - blank=True, max_length=200, verbose_name="point of contact for" + blank=True, + max_length=200, + verbose_name="point of contact for", ), ), migrations.AddConstraint( diff --git a/integreat_cms/cms/migrations/0109_custom_truncating_char_field.py b/integreat_cms/cms/migrations/0109_custom_truncating_char_field.py index 3e8a7dd215..0c9043927a 100644 --- a/integreat_cms/cms/migrations/0109_custom_truncating_char_field.py +++ b/integreat_cms/cms/migrations/0109_custom_truncating_char_field.py @@ -17,35 +17,41 @@ class Migration(migrations.Migration): model_name="contact", name="point_of_contact_for", field=integreat_cms.cms.models.fields.truncating_char_field.TruncatingCharField( - blank=True, max_length=200, verbose_name="point of contact for" + blank=True, + max_length=200, + verbose_name="point of contact for", ), ), migrations.AlterField( model_name="eventtranslation", name="title", field=integreat_cms.cms.models.fields.truncating_char_field.TruncatingCharField( - max_length=1024, verbose_name="title" + max_length=1024, + verbose_name="title", ), ), migrations.AlterField( model_name="imprintpagetranslation", name="title", field=integreat_cms.cms.models.fields.truncating_char_field.TruncatingCharField( - max_length=1024, verbose_name="title" + max_length=1024, + verbose_name="title", ), ), migrations.AlterField( model_name="pagetranslation", name="title", field=integreat_cms.cms.models.fields.truncating_char_field.TruncatingCharField( - max_length=1024, verbose_name="title" + max_length=1024, + verbose_name="title", ), ), migrations.AlterField( model_name="poitranslation", name="title", field=integreat_cms.cms.models.fields.truncating_char_field.TruncatingCharField( - max_length=1024, verbose_name="title" + max_length=1024, + verbose_name="title", ), ), ] diff --git a/integreat_cms/cms/migrations/0110_region_zammad_webhook_token_alter_region_zammad_url.py b/integreat_cms/cms/migrations/0110_region_zammad_webhook_token_alter_region_zammad_url.py index 60016f4994..a2b603c77f 100644 --- a/integreat_cms/cms/migrations/0110_region_zammad_webhook_token_alter_region_zammad_url.py +++ b/integreat_cms/cms/migrations/0110_region_zammad_webhook_token_alter_region_zammad_url.py @@ -1,14 +1,15 @@ # Generated by Django 4.2.13 on 2024-11-08 12:41 import uuid -from typing import Callable +from collections.abc import Callable from django.apps.registry import Apps from django.db import migrations, models def set_zammad_urls( - apps: Apps, schema_editor: Callable # pylint: disable=unused-argument + apps: Apps, + _schema_editor: Callable, ) -> None: """ Update empty Zammad URLs to Null diff --git a/integreat_cms/cms/models/abstract_base_model.py b/integreat_cms/cms/models/abstract_base_model.py index a13d9ec546..cae2487570 100644 --- a/integreat_cms/cms/models/abstract_base_model.py +++ b/integreat_cms/cms/models/abstract_base_model.py @@ -44,7 +44,7 @@ def __repr__(self) -> str: """ try: return self.get_repr() - except Exception as e: # pylint: disable=broad-except + except Exception as e: fallback_repr = f"<{type(self).__name__} (id: {self.id})>" # Skip logging if it's either a triggered SQL query or the id of the object is None and related objects do not exist yet if not ( diff --git a/integreat_cms/cms/models/abstract_content_model.py b/integreat_cms/cms/models/abstract_content_model.py index b531090780..f2f7bb3ba8 100644 --- a/integreat_cms/cms/models/abstract_content_model.py +++ b/integreat_cms/cms/models/abstract_content_model.py @@ -11,11 +11,14 @@ from django.utils.translation import gettext_lazy as _ if TYPE_CHECKING: - from typing import Any, Iterator + from collections.abc import Iterator + from typing import Any from .abstract_content_translation import AbstractContentTranslation from .languages.language import Language +import contextlib + from ..constants import status, translation_status from ..utils.content_edit_lock import get_locking_user from .abstract_base_model import AbstractBaseModel @@ -30,7 +33,9 @@ class ContentQuerySet(models.QuerySet): """ def prefetch_translations( - self, to_attr: str = "prefetched_translations", **filters: Any + self, + to_attr: str = "prefetched_translations", + **filters: Any, ) -> ContentQuerySet: r""" Get the queryset including the custom attribute ``to_attr`` which contains the latest @@ -50,7 +55,7 @@ def prefetch_translations( .distinct(foreign_field, "language_id") .select_related("language"), to_attr=to_attr, - ) + ), ) def prefetch_public_translations( @@ -63,7 +68,8 @@ def prefetch_public_translations( :return: The queryset of content objects """ return self.prefetch_translations( - to_attr="prefetched_public_translations", status=status.PUBLIC + to_attr="prefetched_public_translations", + status=status.PUBLIC, ) def prefetch_public_or_draft_translations( @@ -109,16 +115,18 @@ def prefetch_major_public_translations( class AbstractContentModel(AbstractBaseModel): - # pylint: disable=too-many-public-methods """ Abstract base class for all content models """ region = models.ForeignKey( - Region, on_delete=models.CASCADE, verbose_name=_("region") + Region, + on_delete=models.CASCADE, + verbose_name=_("region"), ) created_date = models.DateTimeField( - default=timezone.now, verbose_name=_("creation date") + default=timezone.now, + verbose_name=_("creation date"), ) #: Custom model manager for content objects @@ -180,7 +188,9 @@ def public_languages(self) -> list[Language]: ] def get_prefetched_translations_by_language_slug( - self, attr: str = "prefetched_translations", **filters: Any + self, + attr: str = "prefetched_translations", + **filters: Any, ) -> dict[str, AbstractContentTranslation]: r""" This method returns a mapping from language slugs to their latest translations of this object @@ -241,11 +251,13 @@ def prefetched_public_translations_by_language_slug( :obj:`None` if no translation exists """ return self.get_prefetched_translations_by_language_slug( - attr="prefetched_public_translations", status=status.PUBLIC + attr="prefetched_public_translations", + status=status.PUBLIC, ) def get_public_translation( - self, language_slug: str + self, + language_slug: str, ) -> AbstractContentTranslation | None: """ This function retrieves the newest public translation of a content object. @@ -254,14 +266,14 @@ def get_public_translation( :return: The public translation of a content object """ public_translation = self.prefetched_public_translations_by_language_slug.get( - language_slug + language_slug, ) # Check if fallback translation should be used if not public_translation and self.fallback_translations_enabled: # Get the fallback translation public_translation = ( self.prefetched_public_translations_by_language_slug.get( - self.region.default_language.slug + self.region.default_language.slug, ) ) if public_translation: @@ -274,14 +286,10 @@ def get_public_translation( language_slug ].language # Reset prefetched translations - public_translation.foreign_object.prefetched_public_translations_by_language_slug = ( - self.prefetched_public_translations_by_language_slug - ) + public_translation.foreign_object.prefetched_public_translations_by_language_slug = self.prefetched_public_translations_by_language_slug # Clear cached property in case url with different language was already calculated before - try: + with contextlib.suppress(AttributeError): del public_translation.url_prefix - except AttributeError: - pass return public_translation @cached_property @@ -300,7 +308,8 @@ def prefetched_public_or_draft_translations_by_language_slug( ) def get_public_or_draft_translation( - self, language_slug: str + self, + language_slug: str, ) -> AbstractContentTranslation | None: """ This function retrieves the newest public or draft translation of a content object. @@ -309,7 +318,7 @@ def get_public_or_draft_translation( :return: The public translation of a content object """ return self.prefetched_public_or_draft_translations_by_language_slug.get( - language_slug + language_slug, ) @cached_property @@ -329,7 +338,8 @@ def prefetched_major_public_translations_by_language_slug( ) def get_major_public_translation( - self, language_slug: str + self, + language_slug: str, ) -> AbstractContentTranslation | None: """ This function retrieves the newest major public translation of a content object. @@ -338,7 +348,7 @@ def get_major_public_translation( :return: The public translation of a content object """ return self.prefetched_major_public_translations_by_language_slug.get( - language_slug + language_slug, ) @cached_property @@ -357,7 +367,8 @@ def prefetched_major_translations_by_language_slug( ) def get_major_translation( - self, language_slug: str + self, + language_slug: str, ) -> AbstractContentTranslation | None: """ This function retrieves the newest major translation of a content object. @@ -387,10 +398,8 @@ def invalidate_cached_translations(self) -> None: "prefetched_public_translations_by_language_slug", "translation_states", ]: - try: + with contextlib.suppress(AttributeError): delattr(self, prefetched_attr) - except AttributeError: - pass @cached_property def backend_translation(self) -> Any: @@ -448,7 +457,7 @@ def get_translation_state(self, language_slug: str) -> str: if translation := self.get_translation(language_slug): return translation.translation_state if self.fallback_translations_enabled and self.get_translation( - self.region.default_language.slug + self.region.default_language.slug, ): return translation_status.FALLBACK return translation_status.MISSING diff --git a/integreat_cms/cms/models/abstract_content_translation.py b/integreat_cms/cms/models/abstract_content_translation.py index 58c01519e7..f297541694 100644 --- a/integreat_cms/cms/models/abstract_content_translation.py +++ b/integreat_cms/cms/models/abstract_content_translation.py @@ -39,7 +39,6 @@ class AbstractContentTranslation(AbstractBaseModel): - # pylint: disable=too-many-public-methods """ Data model representing a translation of some kind of content (e.g. pages or events) """ @@ -72,7 +71,7 @@ class AbstractContentTranslation(AbstractBaseModel): default=False, verbose_name=_("currently in translation"), help_text=_( - "Flag to indicate a translation is being updated by an external translator" + "Flag to indicate a translation is being updated by an external translator", ), ) machine_translated = models.BooleanField( @@ -85,7 +84,7 @@ class AbstractContentTranslation(AbstractBaseModel): default=False, verbose_name=_("minor edit"), help_text=_( - "Tick if this change does not require an update of translations in other languages." + "Tick if this change does not require an update of translations in other languages.", ), ) last_updated = models.DateTimeField( @@ -103,7 +102,7 @@ class AbstractContentTranslation(AbstractBaseModel): default=False, verbose_name=_("Automatic translation"), help_text=_( - "Tick if updating this content should automatically refresh or create its translations." + "Tick if updating this content should automatically refresh or create its translations.", ), ) #: The HIX score is ``None`` if not overwritten by a submodel @@ -150,7 +149,7 @@ def url_prefix(self) -> str: self.language.slug, self.url_infix, ], - ) + ), ) + "/" ) @@ -276,13 +275,13 @@ def sitemap_alternates(self) -> list[dict[str, str]]: if language == self.language: continue if other_translation := self.foreign_object.get_public_translation( - language.slug + language.slug, ): available_languages.append( { "location": f"{settings.WEBAPP_URL}{other_translation.get_absolute_url()}", "lang_slug": other_translation.language.slug, - } + }, ) return available_languages @@ -337,7 +336,7 @@ def public_or_draft_source_translation(self) -> AbstractContentTranslation | Non """ if self.source_language: return self.foreign_object.get_public_or_draft_translation( - self.source_language.slug + self.source_language.slug, ) return None @@ -354,7 +353,7 @@ def major_public_source_translation(self) -> AbstractContentTranslation | None: """ if self.source_language: return self.foreign_object.get_major_public_translation( - self.source_language.slug + self.source_language.slug, ) return None @@ -572,7 +571,7 @@ def link_title(self) -> Element | str: :return: The link content """ foreign_object = self.foreign_object - if icon := getattr(foreign_object, "icon", None): + if icon := getattr(foreign_object, "icon", None): # noqa: SIM102 if url := icon.thumbnail_url: img = make_icon(url) img.tail = self.title @@ -592,7 +591,8 @@ def get_all_used_slugs(self) -> Iterable[str]: return self.all_versions.values_list("slug", flat=True) def create_new_version_copy( - self, user: User | None = None + self, + user: User | None = None, ) -> AbstractContentTranslation: """ Create a new version by copying @@ -661,7 +661,7 @@ def save(self, *args: Any, **kwargs: Any) -> None: """ if self.read_only: raise RuntimeError( - "This object is read-only - changes cannot be saved to the database." + "This object is read-only - changes cannot be saved to the database.", ) if kwargs.pop("update_timestamp", True): self.last_updated = timezone.now() @@ -678,7 +678,7 @@ def cleanup_autosaves(self) -> None: try: second_last_manual_save = ( self.foreign_object.translations.filter(language=self.language).exclude( - status=status.AUTO_SAVE + status=status.AUTO_SAVE, ) )[1] @@ -687,7 +687,7 @@ def cleanup_autosaves(self) -> None: language=self.language, status=status.AUTO_SAVE, version__lt=second_last_manual_save.version, - ) + ), ) except IndexError: @@ -700,7 +700,7 @@ def cleanup_autosaves(self) -> None: logger.debug("Deleting autosaves: %r", delete_auto_saves) first_deleted_version = delete_auto_saves[-1].version self.foreign_object.translations.filter( - id__in=[t.id for t in delete_auto_saves] + id__in=[t.id for t in delete_auto_saves], ).delete() # Get all versions which have now outdated version numbers and lock the database rows @@ -715,7 +715,8 @@ def cleanup_autosaves(self) -> None: with disable_listeners(): # Make version numbers continuous for new_version, translation in enumerate( - remaining_versions, start=first_deleted_version + remaining_versions, + start=first_deleted_version, ): logger.debug("Fixing version %s → %s", translation.version, new_version) translation.version = new_version diff --git a/integreat_cms/cms/models/abstract_tree_node.py b/integreat_cms/cms/models/abstract_tree_node.py index 06cb7f6ef5..71675d561a 100644 --- a/integreat_cms/cms/models/abstract_tree_node.py +++ b/integreat_cms/cms/models/abstract_tree_node.py @@ -50,7 +50,7 @@ def create(self, *args, **kwargs): # type: ignore[no-untyped-def] "last-sibling", ) arguments = list(args) + [ - f"{k}={repr(v)}" for k, v in kwargs.items() if k not in mptt_props + f"{k}={v!r}" for k, v in kwargs.items() if k not in mptt_props ] BOLD = "\033[1m" @@ -59,20 +59,19 @@ def create(self, *args, **kwargs): # type: ignore[no-untyped-def] def b(string): # type: ignore[no-untyped-def] return f"{BOLD}{string}{RESET}" - print( + print( # noqa: T201 f""" -#### {BOLD}Don't use {obj.__class__.__name__}.objects.create().{RESET} To avoid collisions from manually setting {b('lft')}, {b('rgt')}, {b('tree_id')} and {b('depth')} please use one of the following: - {obj.__class__.__name__}{b('.add_root(')}{', '.join(arguments)}) - some_parent_node{b('.add_child(')}{', '.join(arguments)}) - some_sibling_node{BOLD}.add_sibling(pos={' | '.join([repr(p) for p in positions])},{RESET} {', '.join(arguments)}) +#### {BOLD}Don't use {obj.__class__.__name__}.objects.create().{RESET} To avoid collisions from manually setting {b("lft")}, {b("rgt")}, {b("tree_id")} and {b("depth")} please use one of the following: + {obj.__class__.__name__}{b(".add_root(")}{", ".join(arguments)}) + some_parent_node{b(".add_child(")}{", ".join(arguments)}) + some_sibling_node{BOLD}.add_sibling(pos={" | ".join([repr(p) for p in positions])},{RESET} {", ".join(arguments)}) More details at https://django-treebeard.readthedocs.io/en/latest/api.html#treebeard.models.Node.add_root - """ + """, ) return obj class AbstractTreeNode(NS_Node, AbstractBaseModel): - # pylint: disable=attribute-defined-outside-init """ Abstract data model representing a tree node within a region. """ @@ -265,7 +264,7 @@ def get_tree_max_depth(self, max_depth: int = 1) -> NS_NodeQuerySet: :return: This node including its descendants with relative max depth """ return self.__class__.get_tree(parent=self).filter( - depth__lte=self.depth + max_depth + depth__lte=self.depth + max_depth, ) def move(self, target: AbstractTreeNode, pos: str | None = None) -> None: @@ -356,6 +355,7 @@ class Meta: deferrable=Deferrable.DEFERRED, ), CheckConstraint( - check=Q(lft__lt=F("rgt")), name="%(class)s_check_rgt_greater_lft" + check=Q(lft__lt=F("rgt")), + name="%(class)s_check_rgt_greater_lft", ), ] diff --git a/integreat_cms/cms/models/chat/attachment_map.py b/integreat_cms/cms/models/chat/attachment_map.py index ea139a32ad..ac0071af5b 100644 --- a/integreat_cms/cms/models/chat/attachment_map.py +++ b/integreat_cms/cms/models/chat/attachment_map.py @@ -2,16 +2,12 @@ import hashlib import secrets -from typing import TYPE_CHECKING from django.db import models from django.utils.translation import gettext_lazy as _ from ..abstract_base_model import AbstractBaseModel -if TYPE_CHECKING: - from typing import Any - def generate_random_hash() -> str: """ @@ -28,10 +24,14 @@ class AttachmentMap(AbstractBaseModel): """ user_chat = models.ForeignKey( - "cms.UserChat", on_delete=models.CASCADE, related_name="attachments" + "cms.UserChat", + on_delete=models.CASCADE, + related_name="attachments", ) random_hash = models.CharField( - max_length=64, default=generate_random_hash, unique=True + max_length=64, + default=generate_random_hash, + unique=True, ) article_id = models.IntegerField() attachment_id = models.IntegerField() diff --git a/integreat_cms/cms/models/chat/chat_message.py b/integreat_cms/cms/models/chat/chat_message.py index 8a6df15736..022600e923 100644 --- a/integreat_cms/cms/models/chat/chat_message.py +++ b/integreat_cms/cms/models/chat/chat_message.py @@ -14,7 +14,6 @@ class ChatHistoryManager(models.Manager): - # pylint: disable=too-few-public-methods """ Custom manager for returning the chat history of the last x days (as configured in :attr:`~integreat_cms.core.settings.AUTHOR_CHAT_HISTORY_DAYS`) @@ -31,7 +30,7 @@ def get_queryset(self) -> QuerySet: .get_queryset() .filter( sent_datetime__gt=timezone.now() - - timezone.timedelta(days=settings.AUTHOR_CHAT_HISTORY_DAYS) + - timezone.timedelta(days=settings.AUTHOR_CHAT_HISTORY_DAYS), ) ) diff --git a/integreat_cms/cms/models/chat/user_chat.py b/integreat_cms/cms/models/chat/user_chat.py index 18ee5b3b84..fdbcac6c57 100644 --- a/integreat_cms/cms/models/chat/user_chat.py +++ b/integreat_cms/cms/models/chat/user_chat.py @@ -16,7 +16,6 @@ class UserChatManager(models.Manager): - # pylint: disable=too-few-public-methods """ custom manager providing function to get the current chat """ @@ -103,7 +102,7 @@ def record_hit(self) -> None: timestamps = self.most_recent_hits.split(",") if self.most_recent_hits else [] while len(timestamps) > settings.USER_CHAT_WINDOW_LIMIT: timestamps.pop(0) - self.most_recent_hits = ",".join(timestamps + [str(int(time.time()))]) + self.most_recent_hits = ",".join([*timestamps, str(int(time.time()))]) self.save() def ratelimit_exceeded(self) -> bool: diff --git a/integreat_cms/cms/models/contact/contact.py b/integreat_cms/cms/models/contact/contact.py index 5665dded01..65a1571772 100644 --- a/integreat_cms/cms/models/contact/contact.py +++ b/integreat_cms/cms/models/contact/contact.py @@ -1,8 +1,7 @@ from __future__ import annotations -from typing import List, TYPE_CHECKING +from typing import TYPE_CHECKING -from django.conf import settings from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector from django.db import models from django.db.models import Q @@ -19,12 +18,12 @@ from ..pages.page_translation import PageTranslation from ..pois.poi import POI from ..pois.poi_translation import POITranslation -from ..regions.region import Region if TYPE_CHECKING: from django.db.models.query import QuerySet from ..abstract_content_translation import AbstractContentTranslation + from ..regions.region import Region class Contact(AbstractBaseModel): @@ -33,7 +32,9 @@ class Contact(AbstractBaseModel): """ point_of_contact_for = TruncatingCharField( - max_length=200, blank=True, verbose_name=_("point of contact for") + max_length=200, + blank=True, + verbose_name=_("point of contact for"), ) name = models.CharField(max_length=200, blank=True, verbose_name=_("name")) location = models.ForeignKey( @@ -62,7 +63,8 @@ class Contact(AbstractBaseModel): verbose_name=_("modification date"), ) created_date = models.DateTimeField( - default=timezone.now, verbose_name=_("creation date") + default=timezone.now, + verbose_name=_("creation date"), ) @cached_property @@ -226,7 +228,7 @@ def referring_event_translations(self) -> QuerySet[EventTranslation]: ) @cached_property - def referring_objects(self) -> List[AbstractContentTranslation]: + def referring_objects(self) -> list[AbstractContentTranslation]: """ Returns a list of all objects linking to this contact. @@ -324,7 +326,7 @@ class Meta: condition=Q(point_of_contact_for=""), name="contact_singular_empty_point_of_contact_per_location", violation_error_message=_( - "Only one contact per location can have an empty point of contact." + "Only one contact per location can have an empty point of contact.", ), ), models.CheckConstraint( @@ -335,7 +337,7 @@ class Meta: | ~Q(website=""), name="contact_non_empty", violation_error_message=_( - "One of the following fields must be filled: point of contact for, name, e-mail, phone number, website." + "One of the following fields must be filled: point of contact for, name, e-mail, phone number, website.", ), ), ] diff --git a/integreat_cms/cms/models/events/event.py b/integreat_cms/cms/models/events/event.py index 26dd35ae96..6e09553369 100644 --- a/integreat_cms/cms/models/events/event.py +++ b/integreat_cms/cms/models/events/event.py @@ -62,7 +62,7 @@ def filter_upcoming(self, from_date: datetime | None = None) -> Self: | Q( recurrence_rule__isnull=False, recurrence_rule__recurrence_end_date__gte=from_date, - ) + ), ) def filter_completed(self, to_date: date | None = None) -> Self: @@ -82,7 +82,7 @@ def filter_completed(self, to_date: date | None = None) -> Self: | Q( recurrence_rule__isnull=False, recurrence_rule__recurrence_end_date__lt=to_date, - ) + ), ) diff --git a/integreat_cms/cms/models/events/event_translation.py b/integreat_cms/cms/models/events/event_translation.py index 688f9671ab..995f2104ce 100644 --- a/integreat_cms/cms/models/events/event_translation.py +++ b/integreat_cms/cms/models/events/event_translation.py @@ -112,7 +112,7 @@ def search(cls, region: Region, language_slug: str, query: str) -> QuerySet: .exclude(event__translations__language__slug=language_slug) ) queryset = cls.objects.filter( - Q(id__in=queryset) | Q(id__in=default_language_queryset) + Q(id__in=queryset) | Q(id__in=default_language_queryset), ) return queryset diff --git a/integreat_cms/cms/models/events/recurrence_rule.py b/integreat_cms/cms/models/events/recurrence_rule.py index ed6c39e132..1fdaa729e8 100644 --- a/integreat_cms/cms/models/events/recurrence_rule.py +++ b/integreat_cms/cms/models/events/recurrence_rule.py @@ -15,7 +15,7 @@ from ..abstract_base_model import AbstractBaseModel if TYPE_CHECKING: - from typing import Iterator + from collections.abc import Iterator class RecurrenceRule(AbstractBaseModel): @@ -43,7 +43,7 @@ class RecurrenceRule(AbstractBaseModel): blank=True, verbose_name=_("weekdays"), help_text=_( - "If the frequency is weekly, this field determines on which days the event takes place" + "If the frequency is weekly, this field determines on which days the event takes place", ), ) #: Manage choices in :mod:`~integreat_cms.cms.constants.weekdays` @@ -53,7 +53,7 @@ class RecurrenceRule(AbstractBaseModel): blank=True, verbose_name=_("weekday"), help_text=_( - "If the frequency is monthly, this field determines on which days the event takes place" + "If the frequency is monthly, this field determines on which days the event takes place", ), ) #: Manage choices in :mod:`~integreat_cms.cms.constants.weeks` @@ -63,7 +63,7 @@ class RecurrenceRule(AbstractBaseModel): blank=True, verbose_name=_("week"), help_text=_( - "If the frequency is monthly, this field determines on which week of the month the event takes place" + "If the frequency is monthly, this field determines on which week of the month the event takes place", ), ) recurrence_end_date = models.DateField( @@ -71,7 +71,7 @@ class RecurrenceRule(AbstractBaseModel): blank=True, verbose_name=_("recurrence end date"), help_text=_( - "If the recurrence is not for an indefinite period, this field contains the end date" + "If the recurrence is not for an indefinite period, this field contains the end date", ), ) @@ -133,14 +133,16 @@ def advance() -> Iterator[date]: if weekday < next_recurrence.weekday(): continue next_recurrence += timedelta( - days=weekday - next_recurrence.weekday() + days=weekday - next_recurrence.weekday(), ) yield next_recurrence # advance to the next monday next_recurrence += timedelta(days=7 - next_recurrence.weekday()) elif self.frequency == frequency.MONTHLY: next_recurrence = get_nth_weekday( - next_recurrence, self.weekday_for_monthly, self.week_for_monthly + next_recurrence, + self.weekday_for_monthly, + self.week_for_monthly, ) if next_recurrence < start_date: next_recurrence = get_nth_weekday( @@ -158,7 +160,7 @@ def advance() -> Iterator[date]: while True: try: next_recurrence = next_recurrence.replace( - year=next_recurrence.year + year_dif + year=next_recurrence.year + year_dif, ) break except ValueError: @@ -182,7 +184,8 @@ def __str__(self) -> str: :return: A readable string representation of the recurrence rule """ return gettext('Recurrence rule of "{}" ({})').format( - self.event.best_translation.title, self.get_frequency_display() + self.event.best_translation.title, + self.get_frequency_display(), ) def get_repr(self) -> str: @@ -215,13 +218,12 @@ def to_ical_rrule(self) -> rrule.rrule: self.event.start.tzinfo, ) - ical_rrule = rrule.rrule( + return rrule.rrule( getattr(rrule, self.frequency), dtstart=self.event.start, interval=self.interval, **kwargs, ) - return ical_rrule def to_ical_rrule_string(self) -> str: """ diff --git a/integreat_cms/cms/models/external_calendars/external_calendar.py b/integreat_cms/cms/models/external_calendars/external_calendar.py index a123943513..971974bba8 100644 --- a/integreat_cms/cms/models/external_calendars/external_calendar.py +++ b/integreat_cms/cms/models/external_calendars/external_calendar.py @@ -27,7 +27,7 @@ class ExternalCalendar(AbstractBaseModel): blank=True, default=settings.EXTERNAL_CALENDAR_CATEGORY, verbose_name=_( - "The category that events need to have to get imported (Leave blank to import all events)" + "The category that events need to have to get imported (Leave blank to import all events)", ), ) errors = models.CharField(verbose_name=_("import errors"), default="", blank=True) @@ -69,8 +69,8 @@ def load_ical(self) -> icalendar.Calendar: """ response = requests.get(self.url, timeout=60) if response.status_code != 200: - raise IOError( - f"Failed to load external calendar. Status code: {response.status_code}" + raise OSError( + f"Failed to load external calendar. Status code: {response.status_code}", ) return icalendar.Calendar.from_ical(response.content) diff --git a/integreat_cms/cms/models/feedback/event_list_feedback.py b/integreat_cms/cms/models/feedback/event_list_feedback.py index 77b4ea9dd6..3eb3e35afd 100644 --- a/integreat_cms/cms/models/feedback/event_list_feedback.py +++ b/integreat_cms/cms/models/feedback/event_list_feedback.py @@ -48,7 +48,9 @@ def related_feedback(self) -> QuerySet[EventListFeedback]: :return: The queryset of related feedback """ return EventListFeedback.objects.filter( - region=self.region, language=self.language, is_technical=self.is_technical + region=self.region, + language=self.language, + is_technical=self.is_technical, ) class Meta: diff --git a/integreat_cms/cms/models/feedback/imprint_page_feedback.py b/integreat_cms/cms/models/feedback/imprint_page_feedback.py index 2074563cd1..f9ab8a2321 100644 --- a/integreat_cms/cms/models/feedback/imprint_page_feedback.py +++ b/integreat_cms/cms/models/feedback/imprint_page_feedback.py @@ -26,11 +26,10 @@ def object_name(self) -> str: :return: The name of the object this feedback refers to """ try: - translation = ( + return ( self.region.imprint.get_translation(self.language.slug) or self.region.imprint.default_translation ) - return translation except ImprintPage.DoesNotExist: return _("Imprint") @@ -57,7 +56,9 @@ def related_feedback(self) -> QuerySet[ImprintPageFeedback]: :return: The queryset of related feedback """ return ImprintPageFeedback.objects.filter( - region=self.region, language=self.language, is_technical=self.is_technical + region=self.region, + language=self.language, + is_technical=self.is_technical, ) class Meta: diff --git a/integreat_cms/cms/models/feedback/map_feedback.py b/integreat_cms/cms/models/feedback/map_feedback.py index 46e5b49855..2b758a6f85 100644 --- a/integreat_cms/cms/models/feedback/map_feedback.py +++ b/integreat_cms/cms/models/feedback/map_feedback.py @@ -48,7 +48,9 @@ def related_feedback(self) -> QuerySet[MapFeedback]: :return: The queryset of related feedback """ return MapFeedback.objects.filter( - region=self.region, language=self.language, is_technical=self.is_technical + region=self.region, + language=self.language, + is_technical=self.is_technical, ) class Meta: diff --git a/integreat_cms/cms/models/feedback/offer_feedback.py b/integreat_cms/cms/models/feedback/offer_feedback.py index 775b962513..d42e51b0a8 100644 --- a/integreat_cms/cms/models/feedback/offer_feedback.py +++ b/integreat_cms/cms/models/feedback/offer_feedback.py @@ -52,7 +52,8 @@ def related_feedback(self) -> QuerySet[OfferFeedback]: :return: The queryset of related feedback """ return OfferFeedback.objects.filter( - offer=self.offer, is_technical=self.is_technical + offer=self.offer, + is_technical=self.is_technical, ) class Meta: diff --git a/integreat_cms/cms/models/feedback/offer_list_feedback.py b/integreat_cms/cms/models/feedback/offer_list_feedback.py index f980ac82f0..d78e210795 100644 --- a/integreat_cms/cms/models/feedback/offer_list_feedback.py +++ b/integreat_cms/cms/models/feedback/offer_list_feedback.py @@ -45,7 +45,9 @@ def related_feedback(self) -> QuerySet[OfferListFeedback]: :return: The queryset of related feedback """ return OfferListFeedback.objects.filter( - region=self.region, language=self.language, is_technical=self.is_technical + region=self.region, + language=self.language, + is_technical=self.is_technical, ) class Meta: diff --git a/integreat_cms/cms/models/feedback/region_feedback.py b/integreat_cms/cms/models/feedback/region_feedback.py index b5846f997d..74c6691ae4 100644 --- a/integreat_cms/cms/models/feedback/region_feedback.py +++ b/integreat_cms/cms/models/feedback/region_feedback.py @@ -48,7 +48,9 @@ def related_feedback(self) -> QuerySet[RegionFeedback]: :return: The queryset of related feedback """ return RegionFeedback.objects.filter( - region=self.region, language=self.language, is_technical=self.is_technical + region=self.region, + language=self.language, + is_technical=self.is_technical, ) class Meta: diff --git a/integreat_cms/cms/models/fields/truncating_char_field.py b/integreat_cms/cms/models/fields/truncating_char_field.py index 4fc7305ccc..a1afbe9a09 100644 --- a/integreat_cms/cms/models/fields/truncating_char_field.py +++ b/integreat_cms/cms/models/fields/truncating_char_field.py @@ -1,5 +1,4 @@ from django.db import models -from django.utils.translation import gettext_lazy as _ class TruncatingCharField(models.CharField): diff --git a/integreat_cms/cms/models/languages/language.py b/integreat_cms/cms/models/languages/language.py index d1ff5a1428..2a3d5d0c6d 100644 --- a/integreat_cms/cms/models/languages/language.py +++ b/integreat_cms/cms/models/languages/language.py @@ -34,7 +34,7 @@ class Language(AbstractBaseModel): validators=[MinLengthValidator(2)], verbose_name=_("Language Slug"), help_text=_( - "Unique string identifier used in URLs without spaces and special characters." + "Unique string identifier used in URLs without spaces and special characters.", ), ) #: The recommended minimum buffer for `bcp47 `__ is 35. @@ -49,7 +49,7 @@ class Language(AbstractBaseModel): help_text=__( _("Language identifier without spaces and special characters."), _( - "This field usually contains a combination of subtags from the IANA Subtag Registry." + "This field usually contains a combination of subtags from the IANA Subtag Registry.", ), ), ) @@ -101,7 +101,7 @@ class Language(AbstractBaseModel): blank=False, default="#000000", help_text=_( - "This color is used to represent the color label of the chosen language" + "This color is used to represent the color label of the chosen language", ), ) created_date = models.DateTimeField( @@ -126,7 +126,7 @@ class Language(AbstractBaseModel): blank=True, verbose_name=_("Social media title of the WebApp"), help_text=_( - "Displayed title of the WebApp in the search results and on social media pages (max 100 characters)." + "Displayed title of the WebApp in the search results and on social media pages (max 100 characters).", ), ) social_media_webapp_description = models.TextField( @@ -134,7 +134,7 @@ class Language(AbstractBaseModel): blank=True, verbose_name=_("Social media description"), help_text=_( - "Displayed description of the WebApp in the search results and on social media pages (max 200 characters)." + "Displayed description of the WebApp in the search results and on social media pages (max 200 characters).", ), ) message_content_not_available = models.CharField( @@ -142,10 +142,10 @@ class Language(AbstractBaseModel): blank=False, default="This page does not exist in the selected language. It is however available in these languages:", verbose_name=_( - '"This page does not exist in the selected language. It is however available in these languages:" in this language' + '"This page does not exist in the selected language. It is however available in these languages:" in this language', ), help_text=_( - "This is shown to the user when a page is not available in their language." + "This is shown to the user when a page is not available in their language.", ), ) message_partial_live_content_not_available = models.CharField( @@ -153,10 +153,10 @@ class Language(AbstractBaseModel): blank=False, default="Part of the page does not exist in the selected language. It is however available in these languages:", verbose_name=_( - '"Part of the page does not exist in the selected language. It is however available in these languages:" in this language' + '"Part of the page does not exist in the selected language. It is however available in these languages:" in this language', ), help_text=_( - "This is shown to the user when the mirrored part of a page is not available in their language." + "This is shown to the user when the mirrored part of a page is not available in their language.", ), ) @@ -177,7 +177,8 @@ def active_in_regions(self) -> QuerySet: :return: regions in which the language is active """ return Region.objects.filter( - language_tree_nodes__language=self, language_tree_nodes__active=True + language_tree_nodes__language=self, + language_tree_nodes__active=True, ) @cached_property diff --git a/integreat_cms/cms/models/languages/language_tree_node.py b/integreat_cms/cms/models/languages/language_tree_node.py index e43b1c42ae..67e0df6b85 100644 --- a/integreat_cms/cms/models/languages/language_tree_node.py +++ b/integreat_cms/cms/models/languages/language_tree_node.py @@ -36,7 +36,7 @@ class LanguageTreeNode(AbstractTreeNode): default=True, verbose_name=_("visible"), help_text=_( - "Defines whether the language is displayed to the users of the app" + "Defines whether the language is displayed to the users of the app", ), ) active = models.BooleanField( diff --git a/integreat_cms/cms/models/media/media_file.py b/integreat_cms/cms/models/media/media_file.py index a3fdb03b8a..e096c88a4a 100644 --- a/integreat_cms/cms/models/media/media_file.py +++ b/integreat_cms/cms/models/media/media_file.py @@ -60,7 +60,8 @@ def upload_path(instance: MediaFile, filename: str) -> str: def upload_path_thumbnail( - instance: MediaFile, filename: str # pylint: disable=unused-argument + instance: MediaFile, + _filename: str, ) -> str: """ This function derives the upload path of a thumbnail file from it's original file. @@ -72,7 +73,6 @@ def upload_path_thumbnail( will be stored as ``A_EOHRFQ2_thumbnail.jpg``, making it easier to examine these files on the file system. :param instance: The media library object - :param filename: The (unused) initial filename of thumbnail :return: The upload path of the thumbnail """ # Derive the thumbnail name from the original file name @@ -92,8 +92,8 @@ def file_size_limit(value: FieldFile) -> None: if value.size > settings.MEDIA_MAX_UPLOAD_SIZE: raise ValidationError( _("File too large. Size should not exceed {}.").format( - filesizeformat(settings.MEDIA_MAX_UPLOAD_SIZE) - ) + filesizeformat(settings.MEDIA_MAX_UPLOAD_SIZE), + ), ) @@ -110,8 +110,10 @@ def filter_unused(self) -> MediaFileQuerySet: """ urls = Url.objects.filter( url=Concat( - Value(settings.BASE_URL), Value(settings.MEDIA_URL), OuterRef("file") - ) + Value(settings.BASE_URL), + Value(settings.MEDIA_URL), + OuterRef("file"), + ), ) return self.annotate(is_embedded=Exists(urls)).filter( icon_organizations__isnull=True, @@ -166,7 +168,9 @@ class MediaFile(AbstractBaseModel): verbose_name=_("region"), ) alt_text = models.CharField( - max_length=512, blank=True, verbose_name=_("description") + max_length=512, + blank=True, + verbose_name=_("description"), ) uploaded_date = models.DateTimeField( auto_now_add=True, diff --git a/integreat_cms/cms/models/offers/offer_template.py b/integreat_cms/cms/models/offers/offer_template.py index 6f2c1df1b5..83387d93a7 100644 --- a/integreat_cms/cms/models/offers/offer_template.py +++ b/integreat_cms/cms/models/offers/offer_template.py @@ -28,7 +28,9 @@ class OfferTemplate(AbstractBaseModel): ), ) thumbnail = models.URLField( - blank=True, max_length=250, verbose_name=_("thumbnail URL") + blank=True, + max_length=250, + verbose_name=_("thumbnail URL"), ) url = models.URLField( blank=True, @@ -42,7 +44,8 @@ class OfferTemplate(AbstractBaseModel): blank=True, verbose_name=_("POST parameter"), help_text=__( - _("Additional POST data for retrieving the URL."), _("Specify as JSON.") + _("Additional POST data for retrieving the URL."), + _("Specify as JSON."), ), ) #: Manage choices in :mod:`~integreat_cms.cms.constants.postal_code` @@ -52,7 +55,7 @@ class OfferTemplate(AbstractBaseModel): default=postal_code.NONE, verbose_name=_("use postal code"), help_text=_( - "Whether and how to insert the postcode of the region into the URL or POST data" + "Whether and how to insert the postcode of the region into the URL or POST data", ), ) supported_by_app_in_content = models.BooleanField( @@ -60,7 +63,7 @@ class OfferTemplate(AbstractBaseModel): blank=True, verbose_name=_("supported by app in content"), help_text=_( - "Whether the Integreat app supports displaying offers from this provider in pages" + "Whether the Integreat app supports displaying offers from this provider in pages", ), ) is_zammad_form = models.BooleanField( diff --git a/integreat_cms/cms/models/pages/imprint_page_translation.py b/integreat_cms/cms/models/pages/imprint_page_translation.py index a69b1a44b1..34d1000f0e 100644 --- a/integreat_cms/cms/models/pages/imprint_page_translation.py +++ b/integreat_cms/cms/models/pages/imprint_page_translation.py @@ -1,7 +1,7 @@ from __future__ import annotations import logging -from typing import Iterable +from typing import TYPE_CHECKING from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation @@ -13,6 +13,9 @@ from .abstract_base_page_translation import AbstractBasePageTranslation +if TYPE_CHECKING: + from collections.abc import Iterable + logger = logging.getLogger(__name__) diff --git a/integreat_cms/cms/models/pages/page.py b/integreat_cms/cms/models/pages/page.py index 91725fb47d..ecc0359191 100644 --- a/integreat_cms/cms/models/pages/page.py +++ b/integreat_cms/cms/models/pages/page.py @@ -20,7 +20,8 @@ from .page_translation import PageTranslation if TYPE_CHECKING: - from typing import Any, Iterator + from collections.abc import Iterator + from typing import Any from django.db.models import QuerySet from django.db.models.base import ModelBase @@ -35,7 +36,9 @@ class PageQuerySet(NS_NodeQuerySet, ContentQuerySet): """ def cache_tree( - self, archived: bool | None = None, language_slug: str | None = None + self, + archived: bool | None = None, + language_slug: str | None = None, ) -> list[Page]: """ Caches a page tree queryset in a python data structure. @@ -49,7 +52,7 @@ def cache_tree( """ if language_slug is not None and archived is not False: raise ValueError( - "archived must be False in order to filter for public translations by language_slug" + "archived must be False in order to filter for public translations by language_slug", ) result: dict[int, Page] = {} skipped_pages: list[Page] = [] @@ -58,12 +61,10 @@ def cache_tree( .prefetch_public_translations() .order_by("tree_id", "lft") ): - # pylint: disable=protected-access page._cached_ancestors = [] page._cached_descendants = [] page._cached_children = [] # Determine whether the page should be included in the result - # pylint: disable=too-many-boolean-expressions if ( # If page is explicitly archived, include it only when archive is either True or None (page.explicitly_archived and archived is not False) @@ -85,12 +86,11 @@ def cache_tree( ): if page.parent_id in result: # Cache the page as child of the parent page - # pylint: disable=protected-access result[page.parent_id]._cached_children.append(page) result[page.parent_id]._cached_descendants.append(page) # Cache the parent page as ancestor of the current page page._cached_ancestors.extend( - result[page.parent_id]._cached_ancestors + result[page.parent_id]._cached_ancestors, ) page._cached_ancestors.append(result[page.parent_id]) # Cache the current page as descendant of the parent's page ancestors @@ -112,7 +112,6 @@ def cache_tree( class PageManager(models.Manager): - # pylint: disable=too-few-public-methods """ Custom manager for pages to inherit methods from both managers for tree nodes and content objects """ @@ -147,7 +146,7 @@ class Page(AbstractTreeNode, AbstractBasePage): related_name="mirroring_pages", verbose_name=_("mirrored page"), help_text=_( - "If the page embeds live content from another page, it is referenced here." + "If the page embeds live content from another page, it is referenced here.", ), ) mirrored_page_first = models.BooleanField( @@ -156,7 +155,7 @@ class Page(AbstractTreeNode, AbstractBasePage): blank=True, verbose_name=_("Position of mirrored page"), help_text=_( - "If a mirrored page is set, this field determines whether the live content is embedded before the content of this page or after." + "If a mirrored page is set, this field determines whether the live content is embedded before the content of this page or after.", ), ) authors = models.ManyToManyField( @@ -167,7 +166,7 @@ class Page(AbstractTreeNode, AbstractBasePage): help_text=__( _("A list of users who have the permission to edit this specific page."), _( - "Only has effect if these users do not have the permission to edit pages anyway." + "Only has effect if these users do not have the permission to edit pages anyway.", ), ), ) @@ -179,7 +178,7 @@ class Page(AbstractTreeNode, AbstractBasePage): help_text=__( _("A list of users who have the permission to publish this specific page."), _( - "Only has effect if these users do not have the permission to publish pages anyway." + "Only has effect if these users do not have the permission to publish pages anyway.", ), ), ) @@ -190,7 +189,7 @@ class Page(AbstractTreeNode, AbstractBasePage): on_delete=models.SET_NULL, verbose_name=_("responsible organization"), help_text=_( - "This allows all members of the organization to edit and publish this page." + "This allows all members of the organization to edit and publish this page.", ), ) api_token = models.CharField( @@ -209,7 +208,7 @@ class Page(AbstractTreeNode, AbstractBasePage): blank=True, verbose_name=_("page based offer"), help_text=_( - "Select an offer provider whose offers should be displayed on this page." + "Select an offer provider whose offers should be displayed on this page.", ), ) @@ -352,7 +351,8 @@ def restore(self) -> None: # Restore related link objects for child_page in self.get_non_archived_children(): for translation in child_page.translations.distinct( - "page__pk", "language__pk" + "page__pk", + "language__pk", ): # The post_save signal will create link objects from the content translation.save(update_timestamp=False) diff --git a/integreat_cms/cms/models/pages/page_translation.py b/integreat_cms/cms/models/pages/page_translation.py index e62f66839e..17d4806590 100644 --- a/integreat_cms/cms/models/pages/page_translation.py +++ b/integreat_cms/cms/models/pages/page_translation.py @@ -72,7 +72,7 @@ def ancestor_path(self) -> str: slugs = [] for ancestor in self.page.get_cached_ancestors(): if public_translation := ancestor.get_public_translation( - self.language.slug + self.language.slug, ): slugs.append(public_translation.slug) continue @@ -119,7 +119,8 @@ def short_url(self) -> str: """ return settings.SHORT_LINKS_URL + reverse( - "public:expand_page_translation_id", kwargs={"short_url_id": self.id} + "public:expand_page_translation_id", + kwargs={"short_url_id": self.id}, ) @cached_property @@ -130,11 +131,10 @@ def mirrored_translation_text(self) -> str: :return: The text """ - if self.page.mirrored_page: - if translation := self.page.get_mirrored_page_translation( - self.language.slug - ): - return translation.content + if self.page.mirrored_page and ( + translation := self.page.get_mirrored_page_translation(self.language.slug) + ): + return translation.content return "" @cached_property @@ -156,9 +156,7 @@ def mirrored_translation_text_or_fallback_message(self) -> str: return content # Get all translations of this page which have a corresponding translation of the mirrored page - languages = ( - self.page.mirrored_page.prefetched_major_public_translations_by_language_slug.keys() - ) + languages = self.page.mirrored_page.prefetched_major_public_translations_by_language_slug.keys() translations = [ self.page.get_public_translation(language_slug) for language_slug in languages @@ -193,7 +191,7 @@ def display_content(self) -> str: :return: Html text for the content of this translation """ - if self.content and not self.content.isspace() or self.page.mirrored_page: + if (self.content and not self.content.isspace()) or self.page.mirrored_page: return self.content fallback_translations = [ @@ -243,7 +241,7 @@ def combined_last_updated(self) -> datetime: :return: The last_updated date of this or the mirrored page translation """ mirrored_page_translation = self.page.get_mirrored_page_translation( - self.language.slug + self.language.slug, ) if ( not self.content @@ -292,7 +290,9 @@ def tags(self) -> list[Promise]: @classmethod def get_translations( - cls, region: Region, language: Language + cls, + region: Region, + language: Language, ) -> QuerySet[PageTranslation]: """ This function retrieves the most recent versions of all :class:`~integreat_cms.cms.models.pages.page_translation.PageTranslation` @@ -303,12 +303,14 @@ def get_translations( :return: A :class:`~django.db.models.query.QuerySet` of all page translations of a region in a specific language """ return cls.objects.filter(page__region=region, language=language).distinct( - "page" + "page", ) @classmethod def get_up_to_date_translations( - cls, region: Region, language: Language + cls, + region: Region, + language: Language, ) -> list[PageTranslation]: """ This function is similar to :func:`~integreat_cms.cms.models.pages.page_translation.PageTranslation.get_translations` but @@ -321,14 +323,17 @@ def get_up_to_date_translations( return [ t for t in cls.objects.filter( - page__region=region, language=language + page__region=region, + language=language, ).distinct("page") if t.is_up_to_date ] @classmethod def get_current_translations( - cls, region: Region, language: Language + cls, + region: Region, + language: Language, ) -> list[PageTranslation]: """ This function is similar to :func:`~integreat_cms.cms.models.pages.page_translation.PageTranslation.get_translations` but @@ -341,14 +346,17 @@ def get_current_translations( return [ t for t in cls.objects.filter( - page__region=region, language=language + page__region=region, + language=language, ).distinct("page") if t.currently_in_translation ] @classmethod def get_outdated_translations( - cls, region: Region, language: Language + cls, + region: Region, + language: Language, ) -> list[PageTranslation]: """ This function is similar to :func:`~integreat_cms.cms.models.pages.page_translation.PageTranslation.get_translations` but @@ -361,7 +369,8 @@ def get_outdated_translations( return [ t for t in cls.objects.filter( - page__region=region, language=language + page__region=region, + language=language, ).distinct("page") if t.is_outdated ] @@ -378,13 +387,13 @@ def path(self) -> SafeString: escape( ( ancestor.prefetched_public_translations_by_language_slug.get( - self.language.slug + self.language.slug, ) or ancestor.best_translation - ).title + ).title, ) for ancestor in self.page.get_cached_ancestors(include_self=True) - ] + ], ) # Add warning if page is archived if self.page.archived: diff --git a/integreat_cms/cms/models/pois/poi.py b/integreat_cms/cms/models/pois/poi.py index 04d7dd171c..cb60b6feb9 100644 --- a/integreat_cms/cms/models/pois/poi.py +++ b/integreat_cms/cms/models/pois/poi.py @@ -44,7 +44,8 @@ class POI(AbstractContentModel): """ address = models.CharField( - max_length=250, verbose_name=_("street and house number") + max_length=250, + verbose_name=_("street and house number"), ) postcode = models.CharField(max_length=10, verbose_name=_("postal code")) city = models.CharField(max_length=250, verbose_name=_("city")) @@ -87,7 +88,9 @@ class POI(AbstractContentModel): verbose_name=_("email address"), ) phone_number = models.CharField( - max_length=250, blank=True, verbose_name=_("phone number") + max_length=250, + blank=True, + verbose_name=_("phone number"), ) category = models.ForeignKey( POICategory, @@ -108,7 +111,7 @@ class POI(AbstractContentModel): blank=True, verbose_name=_("appointment link"), help_text=_( - "Link to an external website where an appointment for this location can be made." + "Link to an external website where an appointment for this location can be made.", ), ) opening_hours = models.JSONField( @@ -161,7 +164,7 @@ def delete(self, *args: list, **kwargs: dict) -> bool: was_successful = True else: logger.debug( - "Can't be deleted because this poi is used by an event or a contact" + "Can't be deleted because this poi is used by an event or a contact", ) return was_successful @@ -178,7 +181,7 @@ def archive(self) -> bool: was_successful = True else: logger.debug( - "Can't be archived because this poi is used by an event or a contact" + "Can't be archived because this poi is used by an event or a contact", ) return was_successful diff --git a/integreat_cms/cms/models/pois/poi_translation.py b/integreat_cms/cms/models/pois/poi_translation.py index ce40eaecae..c071097f61 100644 --- a/integreat_cms/cms/models/pois/poi_translation.py +++ b/integreat_cms/cms/models/pois/poi_translation.py @@ -47,7 +47,7 @@ class POITranslation(AbstractContentTranslation): help_text=__( _("Describe the location in one or two short sentences."), _( - "This text will be displayed in the Google search results below the title." + "This text will be displayed in the Google search results below the title.", ), ), ) @@ -105,7 +105,7 @@ def map_url(self) -> str: """ :return: the link to the POI on the Integreat map (if it exists), to google maps otherwise """ - if not self.poi.location_on_map and not self.status == status.DRAFT: + if not self.poi.location_on_map and self.status != status.DRAFT: return f"{settings.WEBAPP_URL}{self.get_absolute_url()}" return f"https://www.google.com/maps/search/?api=1&query={self.poi.address},{self.poi.city},{self.poi.country}" @@ -134,7 +134,7 @@ def search(cls, region: Region, language_slug: str, query: str) -> QuerySet: .exclude(poi__translations__language__slug=language_slug) ) queryset = cls.objects.filter( - Q(id__in=queryset) | Q(id__in=default_language_queryset) + Q(id__in=queryset) | Q(id__in=default_language_queryset), ) return queryset diff --git a/integreat_cms/cms/models/push_notifications/push_notification.py b/integreat_cms/cms/models/push_notifications/push_notification.py index b2663c4d6d..bf370bdd6c 100644 --- a/integreat_cms/cms/models/push_notifications/push_notification.py +++ b/integreat_cms/cms/models/push_notifications/push_notification.py @@ -93,7 +93,7 @@ def languages(self) -> QuerySet[Language]: translated into """ return Language.objects.filter( - push_notification_translations__push_notification=self + push_notification_translations__push_notification=self, ) @property @@ -162,7 +162,7 @@ def is_overdue(self) -> bool: return False retention_time = timezone.now() - timedelta( - hours=settings.FCM_NOTIFICATION_RETAIN_TIME_IN_HOURS + hours=settings.FCM_NOTIFICATION_RETAIN_TIME_IN_HOURS, ) return timezone.localtime(self.scheduled_send_date) <= retention_time diff --git a/integreat_cms/cms/models/push_notifications/push_notification_translation.py b/integreat_cms/cms/models/push_notifications/push_notification_translation.py index e4923df9ff..6beae43e48 100644 --- a/integreat_cms/cms/models/push_notifications/push_notification_translation.py +++ b/integreat_cms/cms/models/push_notifications/push_notification_translation.py @@ -47,7 +47,10 @@ class PushNotificationTranslation(AbstractBaseModel): @classmethod def search( - cls, region: Region, language_slug: str, query: str + cls, + region: Region, + language_slug: str, + query: str, ) -> QuerySet[PushNotificationTranslation]: """ Searches for all push notifications which match the given `query` in their title. @@ -87,9 +90,9 @@ def get_text(self) -> str: [ f"{translation.language.native_name}: {settings.WEBAPP_URL}{translation.get_absolute_url()}" for translation in self.push_notification.translations.exclude( - text="" + text="", ) - ] + ], ) return f"{self.language.message_content_not_available}\n{translations}" return self.text diff --git a/integreat_cms/cms/models/regions/region.py b/integreat_cms/cms/models/regions/region.py index 2b0864eeb1..779e5c948a 100644 --- a/integreat_cms/cms/models/regions/region.py +++ b/integreat_cms/cms/models/regions/region.py @@ -64,7 +64,6 @@ def format_mt_help_text(help_text: Promise) -> str: class RegionManager(models.Manager): - # pylint: disable=too-few-public-methods """ This manager annotates each region object with its language tree root node. This is done because it is required to calculate the region's @@ -81,7 +80,8 @@ def get_queryset(self) -> QuerySet: """ # Get model instead of importing it to avoid circular imports LanguageTreeNode = apps.get_model( - app_label="cms", model_name="LanguageTreeNode" + app_label="cms", + model_name="LanguageTreeNode", ) return ( super() @@ -91,13 +91,12 @@ def get_queryset(self) -> QuerySet: "language_tree_nodes", queryset=LanguageTreeNode.objects.all().select_related("language"), to_attr="prefetched_language_tree_nodes", - ) + ), ) ) class Region(AbstractBaseModel): - # pylint: disable=too-many-public-methods """ Data model representing region. """ @@ -110,7 +109,7 @@ class Region(AbstractBaseModel): blank=True, verbose_name=_("community identification number"), help_text=_( - "Number sequence for identifying politically independent administrative units" + "Number sequence for identifying politically independent administrative units", ), ) slug = models.SlugField( @@ -156,7 +155,7 @@ class Region(AbstractBaseModel): help_text=__( _("Enter parts of the name that should not affect sorting."), _( - "Use this field only if the prefix is not an available choice in the list of administrative divisions above." + "Use this field only if the prefix is not an available choice in the list of administrative divisions above.", ), ), ) @@ -217,7 +216,7 @@ class Region(AbstractBaseModel): max_length=10, verbose_name=_("postal code"), help_text=_( - "For districts, enter the postcode of the administrative headquarters." + "For districts, enter the postcode of the administrative headquarters.", ), ) @@ -248,7 +247,7 @@ class Region(AbstractBaseModel): default=False, verbose_name=_("activate SEO section"), help_text=_( - "Enable possibility to fill meta description for pages, events and locations" + "Enable possibility to fill meta description for pages, events and locations", ), ) matomo_id = models.PositiveSmallIntegerField( @@ -266,7 +265,7 @@ class Region(AbstractBaseModel): default="", verbose_name=_("Matomo authentication token"), help_text=_( - "The secret Matomo access token of the region is used to authenticate in API requests" + "The secret Matomo access token of the region is used to authenticate in API requests", ), ) @@ -274,7 +273,7 @@ class Region(AbstractBaseModel): default=False, verbose_name=_("activate page-specific permissions"), help_text=_( - "This allows individual users to be granted the right to edit or publish a specific page." + "This allows individual users to be granted the right to edit or publish a specific page.", ), ) @@ -291,7 +290,7 @@ class Region(AbstractBaseModel): default=True, verbose_name=_("activate author chat"), help_text=_( - "This gives all users of this region access to the cross-regional author chat." + "This gives all users of this region access to the cross-regional author chat.", ), ) @@ -300,10 +299,10 @@ class Region(AbstractBaseModel): verbose_name=_("include administrative division into name"), help_text=__( _( - "Determines whether the administrative division is displayed next to the region name." + "Determines whether the administrative division is displayed next to the region name.", ), _( - "Sorting is always based on the name, independently from the administrative division." + "Sorting is always based on the name, independently from the administrative division.", ), ), ) @@ -315,10 +314,10 @@ class Region(AbstractBaseModel): verbose_name=_("offers"), help_text=__( _( - "Integreat offers are extended features apart from pages and events and are usually offered by a third party." + "Integreat offers are extended features apart from pages and events and are usually offered by a third party.", ), _( - "In most cases, the url is an external API endpoint which the frontend apps can query and render the results inside the Integreat app." + "In most cases, the url is an external API endpoint which the frontend apps can query and render the results inside the Integreat app.", ), ), ) @@ -333,7 +332,7 @@ class Region(AbstractBaseModel): default=False, verbose_name=_("Enable external news"), help_text=_( - "Enable to display external articles in addition to local news managed by the CMS" + "Enable to display external articles in addition to local news managed by the CMS", ), ) @@ -341,7 +340,7 @@ class Region(AbstractBaseModel): default=True, verbose_name=_("Show content in default language as fallback"), help_text=_( - "Whether or not events and locations are shown in default language as fallback" + "Whether or not events and locations are shown in default language as fallback", ), ) @@ -349,7 +348,7 @@ class Region(AbstractBaseModel): default=False, verbose_name=_("Activate HIX analysis"), help_text=_( - "Allow users of this region to analyze understandability of text content via TextLab API." + "Allow users of this region to analyze understandability of text content via TextLab API.", ), ) @@ -357,7 +356,7 @@ class Region(AbstractBaseModel): default=False, verbose_name=_("activate automatic translations via SUMM.AI"), help_text=_( - "Whether automatic translations into Easy German with SUMM.AI are enabled" + "Whether automatic translations into Easy German with SUMM.AI are enabled", ), ) @@ -373,8 +372,8 @@ class Region(AbstractBaseModel): verbose_name=_("Add-on package for foreign languages booked"), help_text=format_mt_help_text( _( - "This makes {} translation credits available to the region in addition to the {} free ones." - ) + "This makes {} translation credits available to the region in addition to the {} free ones.", + ), ), ) @@ -422,7 +421,7 @@ class Region(AbstractBaseModel): default="", verbose_name=_("Zammad-URL"), help_text=_( - "URL pointing to this region's Zammad instance. Setting this enables Zammad form offers." + "URL pointing to this region's Zammad instance. Setting this enables Zammad form offers.", ), ) zammad_access_token = models.CharField( @@ -431,7 +430,7 @@ class Region(AbstractBaseModel): default="", verbose_name=_("Zammad access token"), help_text=_( - 'Access token for a Zammad user account. In Zammad, the account must be part of the "Agent" role and have full group permissions for the group:' + 'Access token for a Zammad user account. In Zammad, the account must be part of the "Agent" role and have full group permissions for the group:', ), ) zammad_webhook_token = models.UUIDField( @@ -440,7 +439,7 @@ class Region(AbstractBaseModel): default=uuid.uuid4, verbose_name=_("Token used by Zammad webhook"), help_text=_( - "Token used by Zammad webhooks to inform the Integreat CMS about changed tickets. The token has to be appended with a token= GET parameter to the webhook path." + "Token used by Zammad webhooks to inform the Integreat CMS about changed tickets. The token has to be appended with a token= GET parameter to the webhook path.", ), ) zammad_chat_handlers = models.CharField( @@ -449,7 +448,7 @@ class Region(AbstractBaseModel): default="", verbose_name=_("Zammad chat handlers"), help_text=_( - "Comma-separated email addresses of the accounts which should automatically be subscribed to new chat tickets. Note that these users must have full group permissions for the group:" + "Comma-separated email addresses of the accounts which should automatically be subscribed to new chat tickets. Note that these users must have full group permissions for the group:", ), ) @@ -459,7 +458,7 @@ class Region(AbstractBaseModel): validators=[MinValueValidator(0), MaxValueValidator(100)], verbose_name=_("Chat beta tester percentage"), help_text=_( - "Percentage of users selected as beta testers for the Integreat Chat feature" + "Percentage of users selected as beta testers for the Integreat Chat feature", ), ) @@ -479,7 +478,7 @@ def has_bounding_box(self) -> bool: self.latitude_min, self.longitude_max, self.latitude_max, - ] + ], ) @cached_property @@ -670,7 +669,10 @@ def statistics(self) -> MatomoApiClient: return MatomoApiClient(self) def get_language_or_404( - self, language_slug: str, only_active: bool = False, only_visible: bool = False + self, + language_slug: str, + only_active: bool = False, + only_visible: bool = False, ) -> Language: """ This class method returns the requested language of this region with optional filters ``active`` and ``visible`` @@ -685,16 +687,17 @@ def get_language_or_404( try: node = self.language_node_by_slug[language_slug] if only_active and not node.active: - raise KeyError( - f"Language {node.language} is not active in region {self}" + raise KeyError( # noqa: TRY301 + f"Language {node.language} is not active in region {self}", ) if only_visible and not node.visible: - raise KeyError( - f"Language {node.language} is not visible in region {self}" + raise KeyError( # noqa: TRY301 + f"Language {node.language} is not visible in region {self}", ) - return node.language except KeyError as e: raise Http404("No language matches the given query.") from e + else: + return node.language @property def explicitly_archived_ancestors_subquery(self) -> PageQuerySet: @@ -725,7 +728,7 @@ def archived_pages(self) -> PageQuerySet: id=models.Case( models.When( models.Exists( - self.explicitly_archived_ancestors_subquery.values("pk") + self.explicitly_archived_ancestors_subquery.values("pk"), ), then=models.F("pk"), ), @@ -734,7 +737,7 @@ def archived_pages(self) -> PageQuerySet: then=models.F("pk"), ), default=None, - ) + ), ) @cached_property @@ -751,7 +754,7 @@ def non_archived_pages(self) -> PageQuerySet: id=models.Case( models.When( models.Exists( - self.explicitly_archived_ancestors_subquery.values("pk") + self.explicitly_archived_ancestors_subquery.values("pk"), ), then=None, ), @@ -813,7 +816,8 @@ def get_root_pages(self) -> PageQuerySet: return Page.get_root_pages(region_slug=self.slug) def outdated_pages( - self, translation_ids: QuerySet | list | None = None + self, + translation_ids: QuerySet | list | None = None, ) -> QuerySet: """ Returns the outdated pages of this region. A page is outdated if it has not been updated in a configurable amount of time. @@ -831,10 +835,10 @@ def outdated_pages( translation_ids = self.latest_page_translations.values_list("id", flat=True) outdated_threshold_date = datetime.now() - relativedelta( - days=settings.OUTDATED_THRESHOLD_DAYS + days=settings.OUTDATED_THRESHOLD_DAYS, ) - outdated_pages = ( + return ( PageTranslation.objects.filter( language__slug=self.default_language.slug, id__in=translation_ids, @@ -845,8 +849,6 @@ def outdated_pages( .exclude(Q(content="") & Q(page__mirrored_page=None)) ) - return outdated_pages - @classmethod def search(cls, query: str) -> QuerySet[Region]: """ @@ -917,12 +919,13 @@ def last_content_update(self) -> datetime: :return: the last content update date """ min_date = django_timezone.make_aware( - django_timezone.datetime.min, django_timezone.get_default_timezone() + django_timezone.datetime.min, + django_timezone.get_default_timezone(), ) latest_page_update = ( self.pages.aggregate( - latest_update=models.Max("translations__last_updated") + latest_update=models.Max("translations__last_updated"), )["latest_update"] or min_date ) @@ -934,13 +937,13 @@ def last_content_update(self) -> datetime: ) latest_event_update = ( self.events.aggregate( - latest_update=models.Max("translations__last_updated") + latest_update=models.Max("translations__last_updated"), )["latest_update"] or min_date ) latest_imprint_update = ( self.imprint.translations.aggregate( - latest_update=models.Max("last_updated") + latest_update=models.Max("last_updated"), )["latest_update"] if self.imprint else None diff --git a/integreat_cms/cms/models/users/organization.py b/integreat_cms/cms/models/users/organization.py index 4633bc8cd6..b566c82b70 100644 --- a/integreat_cms/cms/models/users/organization.py +++ b/integreat_cms/cms/models/users/organization.py @@ -48,10 +48,13 @@ class Organization(AbstractBaseModel): ) region = models.ForeignKey( - Region, on_delete=models.CASCADE, verbose_name=_("region") + Region, + on_delete=models.CASCADE, + verbose_name=_("region"), ) created_date = models.DateTimeField( - default=timezone.now, verbose_name=_("creation date") + default=timezone.now, + verbose_name=_("creation date"), ) website = models.URLField(max_length=250, verbose_name=_("website")) @@ -141,7 +144,7 @@ def delete(self, *args: list, **kwargs: dict) -> bool: was_successful = True else: logger.debug( - "Can't be deleted because this organization is used by a poi, page or user" + "Can't be deleted because this organization is used by a poi, page or user", ) return was_successful @@ -156,7 +159,7 @@ def archive(self) -> bool: was_successful = True else: logger.debug( - "Can't be archived because this organization is used by a poi, page or user" + "Can't be archived because this organization is used by a poi, page or user", ) return was_successful diff --git a/integreat_cms/cms/models/users/user.py b/integreat_cms/cms/models/users/user.py index 851dbf00ff..7c4f73599f 100644 --- a/integreat_cms/cms/models/users/user.py +++ b/integreat_cms/cms/models/users/user.py @@ -49,8 +49,9 @@ def get_queryset(self) -> QuerySet: .get_queryset() .prefetch_related( models.Prefetch( - "regions", queryset=Region.objects.order_by("-last_updated") - ) + "regions", + queryset=Region.objects.order_by("-last_updated"), + ), ) ) @@ -59,13 +60,13 @@ def get_queryset(self) -> QuerySet: username={"verbose_name": _("username")}, is_active={ "help_text": _( - "Designates whether this account should be treated as active. Unselect this instead of deleting accounts." - ) + "Designates whether this account should be treated as active. Unselect this instead of deleting accounts.", + ), }, is_superuser={ "help_text": _( - "Designates that this account has all permissions without explicitly assigning them." - ) + "Designates that this account has all permissions without explicitly assigning them.", + ), }, ) class User(AbstractUser, AbstractBaseModel): @@ -88,7 +89,7 @@ class User(AbstractUser, AbstractBaseModel): related_name="members", verbose_name=_("organization"), help_text=_( - "This allows the user to edit and publish all pages for which the organisation is registered as the responsible organisation" + "This allows the user to edit and publish all pages for which the organisation is registered as the responsible organisation", ), ) chat_last_visited = models.DateTimeField( @@ -100,14 +101,14 @@ class User(AbstractUser, AbstractBaseModel): default=False, verbose_name=_("experienced user"), help_text=_( - "Enable this option to display additional features like XLIFF import/export, page filtering, mirrored pages, page-based permissions and status information for broken links" + "Enable this option to display additional features like XLIFF import/export, page filtering, mirrored pages, page-based permissions and status information for broken links", ), ) page_tree_tutorial_seen = models.BooleanField( default=False, verbose_name=_("Page tree tutorial seen"), help_text=_( - "Will be set to true once the user dismissed the page tree tutorial" + "Will be set to true once the user dismissed the page tree tutorial", ), ) distribute_sidebar_boxes = models.BooleanField( @@ -115,10 +116,10 @@ class User(AbstractUser, AbstractBaseModel): verbose_name=_("automatically distribute sidebar boxes"), help_text=__( _( - "Enable this option to automatically distribute the boxes in the sidebar of forms to make the best use of screen space." + "Enable this option to automatically distribute the boxes in the sidebar of forms to make the best use of screen space.", ), _( - "This only affects screen resolutions where the boxes are displayed in two columns." + "This only affects screen resolutions where the boxes are displayed in two columns.", ), ), ) @@ -134,7 +135,7 @@ class User(AbstractUser, AbstractBaseModel): default=False, verbose_name=_("Enable passwordless authentication"), help_text=_( - "Enable this option to activate the passwordless login routine for this account" + "Enable this option to activate the passwordless login routine for this account", ), ) webauthn_id = models.BinaryField(default=generate_user_handle) @@ -150,10 +151,9 @@ def role(self) -> Role | None: :return: The role of this user """ # Many-to-many relationships can only be used for objects that are already saved to the database - if self.id: - if groups := self.groups.all(): - # Assume users only have one group/role - return groups[0].role + if self.id and (groups := self.groups.all()): + # Assume users only have one group/role + return groups[0].role return None @cached_property @@ -220,11 +220,11 @@ def access_granted_pages(self, region: Region) -> QuerySet[Page]: Get a list of all pages the user has been given explicit rights to edit """ access_granted_pages = Page.objects.filter( - models.Q(authors=self) | models.Q(editors=self) + models.Q(authors=self) | models.Q(editors=self), ).filter(region=region) if self.organization: access_granted_pages = access_granted_pages.union( - Page.objects.filter(organization=self.organization) + Page.objects.filter(organization=self.organization), ) return access_granted_pages diff --git a/integreat_cms/cms/models/users/user_fido_key.py b/integreat_cms/cms/models/users/user_fido_key.py index 7ab4e7cd8b..fcc4710df2 100644 --- a/integreat_cms/cms/models/users/user_fido_key.py +++ b/integreat_cms/cms/models/users/user_fido_key.py @@ -20,7 +20,9 @@ class FidoKey(AbstractBaseModel): ) name = models.CharField(max_length=200, verbose_name=_("key name")) key_id = models.BinaryField( - max_length=255, null=False, verbose_name=_("WebAuthn ID") + max_length=255, + null=False, + verbose_name=_("WebAuthn ID"), ) public_key = models.BinaryField( max_length=255, diff --git a/integreat_cms/cms/rules.py b/integreat_cms/cms/rules.py index 6421564553..90552a5b3e 100644 --- a/integreat_cms/cms/rules.py +++ b/integreat_cms/cms/rules.py @@ -87,9 +87,13 @@ def can_publish_all_pages(user: User, page: Page | None) -> bool: :param page: The page parameter is used for the region check :return: Whether or not ``user`` can publish all pages """ - if not (user.is_superuser or user.is_staff): - if page and page.id and page.region not in user.regions.all(): - return False + if ( + not (user.is_superuser or user.is_staff) + and page + and page.id + and page.region not in user.regions.all() + ): + return False return user.has_perm("cms.publish_page") diff --git a/integreat_cms/cms/templatetags/base_filters.py b/integreat_cms/cms/templatetags/base_filters.py index d02f19f206..5a3146a57a 100644 --- a/integreat_cms/cms/templatetags/base_filters.py +++ b/integreat_cms/cms/templatetags/base_filters.py @@ -31,7 +31,8 @@ def in_list(value: str | None, comma_separated_list: SafeString) -> bool: @register.filter def get_private_member( - element: LanguageTreeNodeForm | PageForm, key: SafeString + element: LanguageTreeNodeForm | PageForm, + key: SafeString, ) -> BoundField: """ This filter returns a private member of an element @@ -55,6 +56,5 @@ def get_mt_visibility(region: Region, perms: PermWrapper) -> bool: return "cms.manage_translations" in perms and ( settings.DEEPL_ENABLED or settings.GOOGLE_TRANSLATE_ENABLED - or settings.SUMM_AI_ENABLED - and region.summ_ai_enabled + or (settings.SUMM_AI_ENABLED and region.summ_ai_enabled) ) diff --git a/integreat_cms/cms/templatetags/content_filters.py b/integreat_cms/cms/templatetags/content_filters.py index 50e384dfae..b8d0060f35 100644 --- a/integreat_cms/cms/templatetags/content_filters.py +++ b/integreat_cms/cms/templatetags/content_filters.py @@ -41,7 +41,8 @@ @register.simple_tag def get_translation( - instance: AbstractContentModel, language_slug: str + instance: AbstractContentModel, + language_slug: str, ) -> AbstractContentTranslation | None: """ This tag returns the most recent translation of the requested content object in the requested language. @@ -56,7 +57,8 @@ def get_translation( @register.simple_tag def get_public_translation( - instance: AbstractContentModel, language_slug: str + instance: AbstractContentModel, + language_slug: str, ) -> AbstractContentTranslation | None: """ This tag returns the most recent public translation of the requested content object in the requested language. @@ -111,7 +113,9 @@ def minor_edit_label(region: Region, language: Language) -> Promise: @register.simple_tag def minor_edit_help_text( - region: Region, language: Language, translation_form: CustomContentModelForm + region: Region, + language: Language, + translation_form: CustomContentModelForm, ) -> Promise: """ This tag returns the help text of the minor edit field of the given form @@ -127,7 +131,7 @@ def minor_edit_help_text( if language_node.is_root(): return translation_form["minor_edit"].help_text return _( - "Tick if this edit should not change the status of this translation and does not require an update of translations in other languages." + "Tick if this edit should not change the status of this translation and does not require an update of translations in other languages.", ) @@ -171,10 +175,11 @@ def remove(elements: list[Any], element: Any) -> list[Any]: # Copy input list to make sure it is not modified in place result = elements.copy() result.remove(element) - return result except ValueError: # If the element wasn't in the list to begin with, just return the input return elements + else: + return result @register.filter @@ -227,7 +232,8 @@ def is_empty(iterable: MultiValueDict) -> bool: @register.simple_tag def object_translation_has_view_perm( - user: SimpleLazyObject, obj: AbstractContentTranslation + user: SimpleLazyObject, + obj: AbstractContentTranslation, ) -> bool: """ This filter accepts any translation of Event, Page or Poi and returns diff --git a/integreat_cms/cms/templatetags/linkcheck_filters.py b/integreat_cms/cms/templatetags/linkcheck_filters.py index 1678ac71ed..68e5958460 100644 --- a/integreat_cms/cms/templatetags/linkcheck_filters.py +++ b/integreat_cms/cms/templatetags/linkcheck_filters.py @@ -6,7 +6,6 @@ from django.utils.safestring import mark_safe if TYPE_CHECKING: - from linkcheck.models import Url register = template.Library() diff --git a/integreat_cms/cms/templatetags/pdf_filters.py b/integreat_cms/cms/templatetags/pdf_filters.py index 6b7caaab3c..0c93859612 100644 --- a/integreat_cms/cms/templatetags/pdf_filters.py +++ b/integreat_cms/cms/templatetags/pdf_filters.py @@ -35,7 +35,6 @@ def pdf_strip_fontstyles(instance: str) -> str: @register.filter def pdf_truncate_links(page_content: str, max_chars: int) -> str: - # pylint: disable=unused-variable """ This tag returns the page content with truncated link texts. @@ -47,7 +46,7 @@ def pdf_truncate_links(page_content: str, max_chars: int) -> str: content = fromstring(page_content) except ParserError: return page_content - for elem, attrib, link, pos in content.iterlinks(): + for elem, _attrib, _link, _pos in content.iterlinks(): if elem.text: elem.text = " ".join( Truncator(word).chars(max_chars) for word in elem.text.split(" ") diff --git a/integreat_cms/cms/templatetags/poi_filters.py b/integreat_cms/cms/templatetags/poi_filters.py index 65e1d2751c..1303242a53 100644 --- a/integreat_cms/cms/templatetags/poi_filters.py +++ b/integreat_cms/cms/templatetags/poi_filters.py @@ -9,9 +9,10 @@ from django import template from django.template.loader import render_to_string from django.utils.html import escape -from django.utils.safestring import SafeString if TYPE_CHECKING: + from django.utils.safestring import SafeString + from ..models import Language, POI register = template.Library() @@ -46,6 +47,7 @@ def render_poi_address(poi: POI) -> SafeString: """ return escape( render_to_string( - "ajax_poi_form/_poi_address_container.html", {"poi": poi, "disabled": False} - ) + "ajax_poi_form/_poi_address_container.html", + {"poi": poi, "disabled": False}, + ), ) diff --git a/integreat_cms/cms/templatetags/push_notification_filters.py b/integreat_cms/cms/templatetags/push_notification_filters.py index a395f0c9a8..e9b33bc328 100644 --- a/integreat_cms/cms/templatetags/push_notification_filters.py +++ b/integreat_cms/cms/templatetags/push_notification_filters.py @@ -17,7 +17,8 @@ @register.simple_tag def get_translation( - push_notification: PushNotification, language_slug: str + push_notification: PushNotification, + language_slug: str, ) -> PushNotificationTranslation: """ This tag returns the most recent translation of the requested push notification in the requested language. diff --git a/integreat_cms/cms/templatetags/tagifnotempty.py b/integreat_cms/cms/templatetags/tagifnotempty.py index d275e898f8..645a8839ed 100644 --- a/integreat_cms/cms/templatetags/tagifnotempty.py +++ b/integreat_cms/cms/templatetags/tagifnotempty.py @@ -15,4 +15,4 @@ def tagifnotempty(value: SafeString, args: SafeString) -> str: :return: Correctly formatted properties """ value = value.strip() - return f'{str(args)}="{value}"' if value else value + return f'{args!s}="{value}"' if value else value diff --git a/integreat_cms/cms/urls/protected.py b/integreat_cms/cms/urls/protected.py index ad74ba5f3c..17ace7da16 100644 --- a/integreat_cms/cms/urls/protected.py +++ b/integreat_cms/cms/urls/protected.py @@ -98,7 +98,7 @@ media.delete_directory_ajax, name="mediacenter_delete_directory", ), - ] + ], ), ), path( @@ -135,7 +135,7 @@ media.get_file_usages_ajax, name="mediacenter_get_file_usages", ), - ] + ], ), ), path( @@ -148,7 +148,7 @@ media.get_unused_media_files_ajax, name="mediacenter_filter_unused_media_files", ), - ] + ], ), ), ] @@ -169,7 +169,7 @@ settings.TOTPRegisterView.as_view(), name="register_totp", ), - ] + ], ), ), path( @@ -181,7 +181,7 @@ settings.TOTPDeleteView.as_view(), name="delete_totp", ), - ] + ], ), ), path( @@ -208,10 +208,10 @@ settings.DeleteUserFidoKeyView.as_view(), name="delete_fido_key", ), - ] + ], ), ), - ] + ], ), ), ] @@ -251,7 +251,7 @@ region_condition.export_region_conditions, name="export_region_conditions", ), - ] + ], ), ), path( @@ -277,7 +277,7 @@ linkcheck.LinkcheckListView.as_view(), name="edit_url", ), - ] + ], ), ), path( @@ -285,7 +285,7 @@ linkcheck.LinkReplaceView.as_view(), name="search_and_replace_link", ), - ] + ], ), ), path( @@ -308,12 +308,14 @@ name="edit_region", ), path( - "delete/", regions.delete_region, name="delete_region" + "delete/", + regions.delete_region, + name="delete_region", ), - ] + ], ), ), - ] + ], ), ), path("media-library/", media.AdminMediaListView.as_view(), name="media_admin"), @@ -325,8 +327,8 @@ "", list_views.ModelListView.as_view( queryset=Language.objects.all().prefetch_related( - "language_tree_nodes" - ) + "language_tree_nodes", + ), ), name="languages", ), @@ -342,7 +344,7 @@ path( "edit/", form_views.CustomUpdateView.as_view( - form_class=LanguageForm + form_class=LanguageForm, ), name="edit_language", ), @@ -353,10 +355,10 @@ ), name="delete_language", ), - ] + ], ), ), - ] + ], ), ), path( @@ -380,10 +382,10 @@ users.resend_activation_link, name="resend_activation_link", ), - ] + ], ), ), - ] + ], ), ), path( @@ -417,10 +419,10 @@ poi_categories.POICategoryDeleteView.as_view(), name="delete_poicategory", ), - ] + ], ), ), - ] + ], ), ), path( @@ -443,10 +445,10 @@ roles.RoleFormView.as_view(), name="delete_role", ), - ] + ], ), ), - ] + ], ), ), path( @@ -488,7 +490,7 @@ feedback.delete_admin_feedback, name="delete_admin_feedback", ), - ] + ], ), ), path( @@ -498,7 +500,9 @@ path( "", list_views.ModelListView.as_view( - queryset=OfferTemplate.objects.all().prefetch_related("regions") + queryset=OfferTemplate.objects.all().prefetch_related( + "regions", + ), ), name="offertemplates", ), @@ -514,21 +518,22 @@ path( "edit/", form_views.CustomUpdateView.as_view( - form_class=OfferTemplateForm + form_class=OfferTemplateForm, ), name="edit_offertemplate", ), path( "delete/", delete_views.CustomDeleteView.as_view( - model=OfferTemplate, protect_manytomany="regions" + model=OfferTemplate, + protect_manytomany="regions", ), name="delete_offertemplate", ), - ] + ], ), ), - ] + ], ), ), path("", include(user_settings_urlpatterns)), @@ -551,7 +556,7 @@ chat.delete_chat_message, name="delete_chat_message", ), - ] + ], ), ), path( @@ -559,7 +564,7 @@ utils.search_content_ajax, name="search_content_ajax", ), - ] + ], ), ), path( @@ -590,7 +595,7 @@ dashboard.DashboardView.get_translation_coverage_context, name="get_translation_coverage_ajax", ), - ] + ], ), ), path("", include(media_ajax_urlpatterns)), @@ -617,7 +622,7 @@ pages.get_page_content_ajax, name="get_page_content_ajax", ), - ] + ], ), ), path( @@ -635,7 +640,7 @@ utils.build_json_for_machine_translation, name="word_count", ), - ] + ], ), ), path( @@ -656,7 +661,7 @@ pages.render_partial_page_tree_views, name="get_page_tree_ajax", ), - ] + ], ), ), path( @@ -666,19 +671,19 @@ path( "", include( - page_order_table_urlpatterns + page_order_table_urlpatterns, ), ), path( "parent-/", include( - page_order_table_urlpatterns + page_order_table_urlpatterns, ), ), - ] + ], ), ), - ] + ], ), ), path( @@ -695,7 +700,7 @@ chat.delete_chat_message, name="delete_chat_message", ), - ] + ], ), ), path( @@ -717,7 +722,7 @@ statistics.get_visits_per_language_ajax, name="statistics_visits_per_language", ), - ] + ], ), ), path( @@ -734,7 +739,7 @@ pages.revoke_page_permission_ajax, name="revoke_page_permission_ajax", ), - ] + ], ), ), path( @@ -751,7 +756,7 @@ utils.content_edit_lock_release, name="content_edit_lock_release", ), - ] + ], ), ), path( @@ -784,7 +789,7 @@ pois.get_address_from_coordinates, name="get_address_from_coordinates", ), - ] + ], ), ), path( @@ -829,7 +834,7 @@ linkcheck.LinkcheckListView.as_view(), name="edit_url", ), - ] + ], ), ), path( @@ -837,10 +842,10 @@ linkcheck.LinkReplaceView.as_view(), name="search_and_replace_link", ), - ] + ], ), ), - ] + ], ), ), path( @@ -883,10 +888,10 @@ external_calendars.delete_external_calendar, name="delete_external_calendar", ), - ] + ], ), ), - ] + ], ), ), path( @@ -920,7 +925,7 @@ path( "download/", pages.ExportXliffView.as_view( - prefetch_translations=True + prefetch_translations=True, ), name="download_xliff", ), @@ -1062,13 +1067,13 @@ pages.move_page, name="move_page", ), - ] + ], ), ), - ] + ], ), ), - ] + ], ), ), path( @@ -1118,7 +1123,7 @@ ], ), ), - ] + ], ), ), path( @@ -1126,7 +1131,7 @@ imprint.delete_imprint, name="delete_imprint", ), - ] + ], ), ), path( @@ -1146,7 +1151,7 @@ path( "archived/", organizations.OrganizationListView.as_view( - archived=True + archived=True, ), name="archived_organizations", ), @@ -1189,10 +1194,10 @@ organizations.restore, name="restore_organization", ), - ] + ], ), ), - ] + ], ), ), path( @@ -1222,21 +1227,22 @@ path( "machine-translate/", bulk_action_views.BulkMachineTranslationView.as_view( - model=Event, form=EventTranslationForm + model=Event, + form=EventTranslationForm, ), name="machine_translation_events", ), path( "bulk-archive/", bulk_action_views.BulkArchiveView.as_view( - model=Event + model=Event, ), name="bulk_archive_events", ), path( "bulk-restore/", bulk_action_views.BulkRestoreView.as_view( - model=Event + model=Event, ), name="bulk_restore_events", ), @@ -1300,13 +1306,13 @@ ], ), ), - ] + ], ), ), - ] + ], ), ), - ] + ], ), ), path( @@ -1336,21 +1342,22 @@ path( "machine-translate/", bulk_action_views.BulkMachineTranslationView.as_view( - model=POI, form=POITranslationForm + model=POI, + form=POITranslationForm, ), name="machine_translation_pois", ), path( "bulk-archive/", bulk_action_views.BulkArchiveView.as_view( - model=POI + model=POI, ), name="bulk_archive_pois", ), path( "bulk-restore/", bulk_action_views.BulkRestoreView.as_view( - model=POI + model=POI, ), name="bulk_restore_pois", ), @@ -1424,13 +1431,13 @@ ], ), ), - ] + ], ), ), - ] + ], ), ), - ] + ], ), ), path( @@ -1516,10 +1523,10 @@ contacts.delete_contact, name="delete_contact", ), - ] + ], ), ), - ] + ], ), ), path( @@ -1566,7 +1573,7 @@ feedback.export_region_feedback, name="export_region_feedback", ), - ] + ], ), ), path( @@ -1590,7 +1597,7 @@ path( "templates/", push_notifications.PushNotificationListView.as_view( - templates=True + templates=True, ), name="push_notifications_templates", ), @@ -1608,13 +1615,13 @@ push_notifications.PushNotificationFormView.as_view(), name="edit_push_notification", ), - ] + ], ), ), - ] + ], ), ), - ] + ], ), ), path( @@ -1658,7 +1665,7 @@ path( "edit/", language_tree.LanguageTreeNodeUpdateView.as_view( - form_class=LanguageTreeNodeForm + form_class=LanguageTreeNodeForm, ), name="edit_languagetreenode", ), @@ -1673,10 +1680,10 @@ language_tree.move_language_tree_node, name="move_languagetreenode", ), - ] + ], ), ), - ] + ], ), ), path("media-library/", media.MediaListView.as_view(), name="media"), @@ -1713,10 +1720,10 @@ users.resend_activation_link_region, name="resend_activation_link_region", ), - ] + ], ), ), - ] + ], ), ), path("", include(user_settings_urlpatterns)), @@ -1725,7 +1732,7 @@ release_notes.ReleaseNotesView.as_view(), name="release_notes", ), - ] + ], ), ), ] diff --git a/integreat_cms/cms/urls/public.py b/integreat_cms/cms/urls/public.py index 333f51798c..587a1774a0 100644 --- a/integreat_cms/cms/urls/public.py +++ b/integreat_cms/cms/urls/public.py @@ -54,7 +54,7 @@ imprint.expand_imprint_translation_id, name="expand_imprint_translation_id", ), - ] + ], ), ), path( @@ -69,7 +69,7 @@ path( "passwordless/", authentication.PasswordlessLoginView.as_view( - extra_context=auth_context + extra_context=auth_context, ), name="passwordless_login", ), @@ -97,10 +97,10 @@ authentication.WebAuthnVerifyView.as_view(), name="login_webauthn_verify", ), - ] + ], ), ), - ] + ], ), ), path( @@ -127,11 +127,11 @@ path( "//", authentication.PasswordResetConfirmView.as_view( - extra_context=auth_context + extra_context=auth_context, ), name="password_reset_confirm", ), - ] + ], ), ), path( @@ -147,7 +147,7 @@ path( "favicon.ico", RedirectView.as_view( - url=f"/static/logos/{settings.BRANDING}/{settings.BRANDING}-icon.svg" + url=f"/static/logos/{settings.BRANDING}/{settings.BRANDING}-icon.svg", ), ), ] diff --git a/integreat_cms/cms/utils/content_translation_utils.py b/integreat_cms/cms/utils/content_translation_utils.py index 7b4dc19e1e..1f4c91d6f4 100644 --- a/integreat_cms/cms/utils/content_translation_utils.py +++ b/integreat_cms/cms/utils/content_translation_utils.py @@ -23,7 +23,8 @@ def update_links_to( - content_translation: AbstractContentTranslation, user: User | None + content_translation: AbstractContentTranslation, + user: User | None, ) -> None: """ Updates all content translations with links that point to the given translation. @@ -33,7 +34,7 @@ def update_links_to( :param user: The user who should be responsible for updating the links """ for outdated_content_translation in get_referencing_translations( - content_translation + content_translation, ): # Assert that the related translation is not archived # Note that this should not be possible, since links to archived pages get deleted @@ -78,13 +79,17 @@ def get_referencing_translations( translation_slugs = set(content_translation.get_all_used_slugs()) translation_ids = set(content_translation.all_versions.values_list("id", flat=True)) logger.debug( - "Collecting links that contain %s or %s", translation_slugs, translation_ids + "Collecting links that contain %s or %s", + translation_slugs, + translation_ids, ) filter_query = reduce( - operator.or_, (Q(url__contains=slug) for slug in translation_slugs) + operator.or_, + (Q(url__contains=slug) for slug in translation_slugs), ) filter_query = filter_query | reduce( - operator.or_, (Q(url__contains=str(uid)) for uid in translation_ids) + operator.or_, + (Q(url__contains=str(uid)) for uid in translation_ids), ) urls = (url for url in Url.objects.filter(filter_query) if url.internal) diff --git a/integreat_cms/cms/utils/content_utils.py b/integreat_cms/cms/utils/content_utils.py index 1906a889ae..d3b345d7f9 100644 --- a/integreat_cms/cms/utils/content_utils.py +++ b/integreat_cms/cms/utils/content_utils.py @@ -180,7 +180,7 @@ def update_contacts(content: HtmlElement) -> None: contact_urls = [card.get("data-contact-url") for card in contact_cards] for contact_id, contact_url, contact_card in zip( - contact_ids, contact_urls, contact_cards + contact_ids, contact_urls, contact_cards, strict=True ): try: wanted_details = contact_url.split("details=", 1)[1].split(",") @@ -203,11 +203,10 @@ def fix_alt_texts(content: HtmlElement) -> None: # Remove host relative_url = urlparse(src).path # Remove media url prefix if exists - if relative_url.startswith(settings.MEDIA_URL): - relative_url = relative_url[len(settings.MEDIA_URL) :] + relative_url = relative_url.removeprefix(settings.MEDIA_URL) # Check whether media file exists in database media_file = MediaFile.objects.filter( - Q(file=relative_url) | Q(thumbnail=relative_url) + Q(file=relative_url) | Q(thumbnail=relative_url), ).first() # Replace alternative text if media_file and media_file.alt_text: @@ -232,7 +231,7 @@ def fix_notranslate(content: HtmlElement) -> None: { "translate": "no", "dir": "ltr", - } + }, ) diff --git a/integreat_cms/cms/utils/email_utils.py b/integreat_cms/cms/utils/email_utils.py index 0ecb60062e..7eb787c742 100644 --- a/integreat_cms/cms/utils/email_utils.py +++ b/integreat_cms/cms/utils/email_utils.py @@ -44,7 +44,7 @@ def send_mail( "COMPANY": settings.COMPANY, "BRANDING": settings.BRANDING, "BRANDING_TITLE": settings.BRANDING_TITLE, - } + }, ) # Assemble message body body = render_to_string( @@ -59,7 +59,7 @@ def send_mail( email.attach_alternative(html_message, "text/html") # Attach logo if image_path := finders.find( - f"logos/{settings.BRANDING}/{settings.BRANDING}-logo.png" + f"logos/{settings.BRANDING}/{settings.BRANDING}-logo.png", ): with open(image_path, mode="rb") as f: image = MIMEImage(f.read()) @@ -74,14 +74,14 @@ def send_mail( try: email.send() except BadHeaderError as e: - logger.error(e) + logger.exception("") raise RuntimeError(_("Malformed header data.")) from e except SMTPAuthenticationError as e: - logger.error(e) + logger.exception("") raise RuntimeError(_("Invalid email credentials.")) from e except SMTPException as e: - logger.error(e) - raise RuntimeError(_("An SMTP error occured.")) from e + logger.exception("") + raise RuntimeError(_("An SMTP error occurred.")) from e except ConnectionRefusedError as e: - logger.error(e) + logger.exception("") raise RuntimeError(_("The email server refused the connection.")) from e diff --git a/integreat_cms/cms/utils/external_calendar_utils.py b/integreat_cms/cms/utils/external_calendar_utils.py index 50c92f0aa1..eb2f5665df 100644 --- a/integreat_cms/cms/utils/external_calendar_utils.py +++ b/integreat_cms/cms/utils/external_calendar_utils.py @@ -6,19 +6,10 @@ import dataclasses import datetime -import logging -from typing import Any, Self +from typing import Any, Self, TYPE_CHECKING -import icalendar.cal from django.utils.translation import gettext as _ -from icalendar.prop import ( - vCategory, - vDDDTypes, - vFrequency, - vInt, - vRecur, - vWeekday, -) +from icalendar.prop import vCategory, vDDDTypes, vFrequency, vInt, vRecur, vWeekday from integreat_cms.cms.constants import frequency, status from integreat_cms.cms.constants.weekdays import RRULE_WEEKDAY_TO_WEEKDAY @@ -27,6 +18,11 @@ from integreat_cms.cms.models import EventTranslation, ExternalCalendar, RecurrenceRule from integreat_cms.cms.utils.content_utils import clean_content +if TYPE_CHECKING: + import logging + + import icalendar + @dataclasses.dataclass(frozen=True, kw_only=True) class ImportResult: @@ -39,7 +35,6 @@ class ImportResult: @dataclasses.dataclass(frozen=True, kw_only=True) class IcalEventData: - # pylint: disable=too-many-instance-attributes """ Holds data extracted from ical events """ @@ -73,7 +68,6 @@ def from_ical_event( :return: An instance of IcalEventData :raises ValueError: If the data are invalid """ - # pylint: disable=too-many-locals event_id = event.decoded("UID").decode("utf-8") title = event.decoded("SUMMARY").decode("utf-8") content = clean_content( @@ -121,7 +115,9 @@ def from_ical_event( recurrence_rule = None if "RRULE" in event: recurrence_rule = RecurrenceRuleData.from_ical_rrule( - recurrence_rule=event.decoded("RRULE"), start=start_date, logger=logger + recurrence_rule=event.decoded("RRULE"), + start=start_date, + logger=logger, ) return cls( @@ -188,7 +184,10 @@ class RecurrenceRuleData: @classmethod def from_ical_rrule( - cls, recurrence_rule: vRecur, start: datetime.date, logger: logging.Logger + cls, + recurrence_rule: vRecur, + start: datetime.date, + logger: logging.Logger, ) -> Self: """ Constructs this class from an ical recurrence rule. @@ -221,7 +220,7 @@ def pop_single_value(name: str, *, required: bool = False) -> Any: if by_day and len(by_day) == 1: if by_set_pos is not None and by_day[0].relative is not None: raise ValueError( - f"Conflicting `BYSETPOS` and `BYDAY`: {by_set_pos} and {by_day}" + f"Conflicting `BYSETPOS` and `BYDAY`: {by_set_pos} and {by_day}", ) by_set_pos = by_day[0].relative or by_set_pos by_day[0] = by_day[0].weekday @@ -231,31 +230,31 @@ def pop_single_value(name: str, *, required: bool = False) -> Any: updated_days.append(day.weekday) if day.relative is not None: raise ValueError( - f"Cannot support multiple days with frequency right now: {by_day}" + f"Cannot support multiple days with frequency right now: {by_day}", ) by_day = updated_days # WKST currently always has to be monday (or unset, because it defaults do monday) if (wkst := pop_single_value("WKST")) and wkst.lower() != "mo": raise ValueError( - f"Currently only recurrence rules with weeks starting on Monday are supported (attempted WKST: {wkst})" + f"Currently only recurrence rules with weeks starting on Monday are supported (attempted WKST: {wkst})", ) if ( by_month_day := pop_single_value("BYMONTHDAY") ) and by_month_day != start.day: raise ValueError( - f"Month day of recurrence rule does not match month day of event: {by_month_day} and {start.day}" + f"Month day of recurrence rule does not match month day of event: {by_month_day} and {start.day}", ) if (by_month := pop_single_value("BYMONTH")) and by_month != start.month: raise ValueError( - f"Month of recurrence rule does not match month of event: {by_month} and {start.month}" + f"Month of recurrence rule does not match month of event: {by_month} and {start.month}", ) if len(recurrence_rule) > 0: raise ValueError( - f"Recurrence rule contained unsupported attribute(s): {list(recurrence_rule.keys())}" + f"Recurrence rule contained unsupported attribute(s): {list(recurrence_rule.keys())}", ) return cls( @@ -352,7 +351,9 @@ def import_events(calendar: ExternalCalendar, logger: logging.Logger) -> ImportR def _import_events( - calendar: ExternalCalendar, errors: list[str], logger: logging.Logger + calendar: ExternalCalendar, + errors: list[str], + logger: logging.Logger, ) -> None: """ Imports events from this calendar and sets or clears the errors field of the calendar @@ -362,14 +363,14 @@ def _import_events( """ try: ical = calendar.load_ical() - except IOError as e: - logger.error("Could not import events from %s: %s", calendar, e) + except OSError: + logger.exception("Could not import events from %s", calendar) errors.append(_("Could not access the url of this external calendar")) return - except ValueError as e: - logger.error("Malformed calendar %s: %s", calendar, e) + except ValueError: + logger.exception("Malformed calendar %s", calendar) errors.append( - _("The data provided by the url of this external calendar is invalid") + _("The data provided by the url of this external calendar is invalid"), ) return @@ -379,21 +380,22 @@ def _import_events( if (event_uid := import_event(calendar, event, errors, logger)) is not None: calendar_events.add(event_uid) except KeyError as e: - logger.error( - "Could not import event because it does not have a required field: %s, missing field: %r", + logger.exception( + "Could not import event because it does not have a required field: %s, missing field", event, - e, ) errors.append( _( - "Could not import event because it is missing a required field: {}" - ).format(e) + "Could not import event because it is missing a required field: {}", + ).format(e), ) continue events_to_delete = calendar.events.exclude(external_event_id__in=calendar_events) logger.info( - "Deleting %s unused events: %r", events_to_delete.count(), events_to_delete + "Deleting %s unused events: %r", + events_to_delete.count(), + events_to_delete, ) events_to_delete.delete() @@ -404,7 +406,6 @@ def import_event( errors: list[str], logger: logging.Logger, ) -> str | None: - # pylint: disable=too-many-return-statements """ Imports an event from the external calendar @@ -419,10 +420,13 @@ def import_event( try: event_data = IcalEventData.from_ical_event( - event, language.slug, calendar.pk, logger + event, + language.slug, + calendar.pk, + logger, ) except ValueError as e: - logger.error("Could not import event: %r due to error: %s", event, e) + logger.exception("Could not import event: %r due to error", event) errors.append(_("Could not import '{}': {}").format(event.get("SUMMARY"), e)) try: return event.decoded("UID").decode("utf-8") @@ -464,22 +468,22 @@ def import_event( if not event_form.is_valid(): logger.error("Could not import event: %r", event_form.errors) errors.append( - _("Could not import '{}': {}").format(event_data.title, event_form.errors) + _("Could not import '{}': {}").format(event_data.title, event_form.errors), ) return event_data.event_id try: recurrence_rule_form_data = event_data.to_recurrence_rule_form_data() except ValueError as e: - logger.error( - "Could not import event due to unsupported recurrence rule: %r\n%s", - e, + logger.exception( + "Could not import event due to unsupported recurrence rule:\n%s", event_data, ) errors.append( _("Could not import '{}': Unsupported recurrence rule").format( - event_data.title, e - ) + event_data.title, + e, + ), ) return event_data.event_id recurrence_rule_form = RecurrenceRuleForm( @@ -489,12 +493,14 @@ def import_event( ) if recurrence_rule_form_data and not recurrence_rule_form.is_valid(): logger.error( - "Could not import recurrence rule: %r", recurrence_rule_form.errors + "Could not import recurrence rule: %r", + recurrence_rule_form.errors, ) errors.append( _("Could not import '{}': {}").format( - event_data.title, recurrence_rule_form.errors - ) + event_data.title, + recurrence_rule_form.errors, + ), ) return event_data.event_id @@ -518,8 +524,9 @@ def import_event( logger.error("Could not import event: %r", event_translation_form.errors) errors.append( _("Could not import '{}': {}").format( - event_data.title, event_translation_form.errors - ) + event_data.title, + event_translation_form.errors, + ), ) return event_data.event_id @@ -531,7 +538,12 @@ def import_event( or recurrence_rule_form.has_changed() ): event_translation = event_translation_form.save() - logger.success("Imported event %r, %r, %r", event, event_translation, event.recurrence_rule) # type: ignore[attr-defined] + logger.info( + "Imported event %r, %r, %r", + event, + event_translation, + event.recurrence_rule, + ) # type: ignore[attr-defined] else: logger.info("Event %r has not changed", event_translation_form.instance) diff --git a/integreat_cms/cms/utils/file_utils.py b/integreat_cms/cms/utils/file_utils.py index 9f3318b7ed..e8fd6c9df0 100644 --- a/integreat_cms/cms/utils/file_utils.py +++ b/integreat_cms/cms/utils/file_utils.py @@ -66,7 +66,7 @@ def extract_zip_archive( invalid_files = [ file_path for file_path in extracted_files - if not file_path.endswith(("/",) + tuple(allowed_file_extensions)) + if not file_path.endswith(("/", *allowed_file_extensions)) ] # Remove all invalid files from extracted files extracted_files = list(set(extracted_files) - set(invalid_files)) diff --git a/integreat_cms/cms/utils/internal_link_checker.py b/integreat_cms/cms/utils/internal_link_checker.py index 3a203c892c..d1a174c68d 100644 --- a/integreat_cms/cms/utils/internal_link_checker.py +++ b/integreat_cms/cms/utils/internal_link_checker.py @@ -46,7 +46,10 @@ def mark_invalid(url: Url, error_message: str = "") -> None: def check_imprint( - url: Url, path_components: list[str], region: Region, language: Language + url: Url, + path_components: list[str], + region: Region, + language: Language, ) -> bool: """ Check whether the imprint exists in the given region and language @@ -65,16 +68,20 @@ def check_imprint( mark_valid(url) else: logger.debug( - "Imprint of %r in %r does not exist or is not public", region, language + "Imprint of %r in %r does not exist or is not public", + region, + language, ) mark_invalid(url, _("Imprint does not exist or is not public in this language")) return url.status def check_news_link( - url: Url, path_components: list[str], region: Region, language: Language + url: Url, + path_components: list[str], + region: Region, + language: Language, ) -> bool | None: - # pylint: disable=too-many-branches """ Check whether the news exists in the given region @@ -86,7 +93,8 @@ def check_news_link( """ if len(path_components) == 1: mark_invalid( - url, _("News links require a subcategory (either 'local' or 'tu-news')") + url, + _("News links require a subcategory (either 'local' or 'tu-news')"), ) elif len(path_components) <= 3: if path_components[1] == "tu-news": @@ -96,19 +104,21 @@ def check_news_link( mark_valid(url) else: logger.debug( - "Skipping check of tü-news with id %r", path_components[2] + "Skipping check of tü-news with id %r", + path_components[2], ) else: logger.debug("tü-news are disabled in %r", region) mark_invalid(url, _("tü-news are disabled in this region.")) elif path_components[1] == "local": - if len(path_components) == 2: - mark_valid(url) - elif region.push_notifications.filter( - id=path_components[2], - sent_date__isnull=False, - translations__language=language, - ).exists(): + if ( + len(path_components) == 2 + or region.push_notifications.filter( + id=path_components[2], + sent_date__isnull=False, + translations__language=language, + ).exists() + ): mark_valid(url) else: logger.debug( @@ -165,7 +175,9 @@ def check_offer_link(url: Url, path_components: list[str], region: Region) -> bo def check_translation_link( - content_object: Event | (Page | POI), url: Url, language: Language + content_object: Event | (Page | POI), + url: Url, + language: Language, ) -> bool: """ Check whether the link of the given content object is valid @@ -179,7 +191,7 @@ def check_translation_link( mark_invalid(url, _("The link target is archived.")) elif translation := content_object.get_public_translation(language.slug): if translation.get_absolute_url().strip("/") != unquote(url.internal_url).strip( - "/" + "/", ): logger.debug( "%r has different URL (%r) than the checked URL (%r)", @@ -236,7 +248,8 @@ def check_object_link( language, ) mark_invalid( - url, _("The link target does not exist in this region and language.") + url, + _("The link target does not exist in this region and language."), ) elif len(objects) == 1: check_translation_link(objects[0], url, language) @@ -250,7 +263,8 @@ def check_object_link( objects, ) mark_invalid( - url, _("The link target is not unique in this region and language.") + url, + _("The link target is not unique in this region and language."), ) return url.status @@ -277,7 +291,10 @@ def check_event_or_location( """ if len(path_components) == 1: logger.debug( - "Link to %s list of %r in %r is valid", content_type, region, language + "Link to %s list of %r in %r is valid", + content_type, + region, + language, ) mark_valid(url) elif len(path_components) == 2: @@ -300,13 +317,15 @@ def check_event_or_location( def check_internal(url: Url) -> bool | None: # noqa: PLR0911 - # pylint: disable=too-many-return-statements, too-many-branches """ :param url: The internal URL to check :returns: The status of the URL """ logger.debug( - "Checking %r (type: %r, internal: %r)", url, url.type, url.internal_url + "Checking %r (type: %r, internal: %r)", + url, + url.type, + url.internal_url, ) if url.type == "empty" or url.internal_url == "/": @@ -334,7 +353,8 @@ def check_internal(url: Url) -> bool | None: # noqa: PLR0911 if not language_and_path: logger.debug( - "Link to category overview of %r in the default language is valid", region + "Link to category overview of %r in the default language is valid", + region, ) mark_valid(url) return url.status @@ -355,7 +375,9 @@ def check_internal(url: Url) -> bool | None: # noqa: PLR0911 try: language = region.get_language_or_404( - language_slug, only_active=True, only_visible=True + language_slug, + only_active=True, + only_visible=True, ) except Http404: logger.debug( @@ -363,7 +385,8 @@ def check_internal(url: Url) -> bool | None: # noqa: PLR0911 language_slug, ) mark_invalid( - url, _("This language does not exist or is not active and visible.") + url, + _("This language does not exist or is not active and visible."), ) return url.status @@ -378,16 +401,31 @@ def check_internal(url: Url) -> bool | None: # noqa: PLR0911 return check_imprint(url, path_components, region, language) if content_type == "events": return check_event_or_location( - "Event", region.events, url, path_components, region, language + "Event", + region.events, + url, + path_components, + region, + language, ) if content_type == "locations": return check_event_or_location( - "POI", region.pois, url, path_components, region, language + "POI", + region.pois, + url, + path_components, + region, + language, ) if content_type == "news": return check_news_link(url, path_components, region, language) if content_type == "offers": return check_offer_link(url, path_components, region) return check_object_link( - "Page", region.pages, path_components[-1], url, region, language + "Page", + region.pages, + path_components[-1], + url, + region, + language, ) diff --git a/integreat_cms/cms/utils/internal_link_utils.py b/integreat_cms/cms/utils/internal_link_utils.py index 5f85c54d9a..aa1ada5e79 100644 --- a/integreat_cms/cms/utils/internal_link_utils.py +++ b/integreat_cms/cms/utils/internal_link_utils.py @@ -19,8 +19,6 @@ ) if TYPE_CHECKING: - from typing import Optional - from lxml.html import Element from ..models.abstract_content_translation import AbstractContentTranslation @@ -29,9 +27,9 @@ def update_link( - link: Element, target_language_slug: str -) -> Optional[tuple[str, Element | str]]: - # pylint: disable=compare-to-zero + link: Element, + target_language_slug: str, +) -> tuple[str, Element | str] | None: """ Fixes the internal link, if it is broken. This includes: @@ -55,7 +53,7 @@ def update_link( return None if target_translation := source_translation.foreign_object.get_public_translation( - target_language_slug + target_language_slug, ): # Always use the full url, even if the url was previously a short url fixed_link = target_translation.full_url @@ -111,12 +109,16 @@ def get_public_translation_for_webapp_link( region_slug, language_slug, *path_parts = parts return get_public_translation_for_webapp_link_parts( - region_slug, language_slug, path_parts + region_slug, + language_slug, + path_parts, ) def get_public_translation_for_webapp_link_parts( - region_slug: str, language_slug: str, path_parts: list[str] + region_slug: str, + language_slug: str, + path_parts: list[str], ) -> AbstractContentTranslation | None: """ Calculates the content translation that corresponds to the given path, region slug and language slug. diff --git a/integreat_cms/cms/utils/linkcheck_utils.py b/integreat_cms/cms/utils/linkcheck_utils.py index cec22c2ec4..c773ee1362 100644 --- a/integreat_cms/cms/utils/linkcheck_utils.py +++ b/integreat_cms/cms/utils/linkcheck_utils.py @@ -3,7 +3,7 @@ import logging import time from collections import defaultdict -from typing import DefaultDict, TYPE_CHECKING +from typing import TYPE_CHECKING from django.conf import settings from django.db.models import Count, Prefetch, Q, QuerySet, Subquery @@ -58,13 +58,13 @@ def get_urls( "links", queryset=region_links, to_attr="region_links", - ) + ), ) # Annotate with number of links that are not ignored. # If there is any link that is not ignored, the url is also not ignored. urls = urls.annotate( - non_ignored_links=Count("links", filter=Q(links__ignore=False)) + non_ignored_links=Count("links", filter=Q(links__ignore=False)), ) # Filter out ignored URL types @@ -87,30 +87,30 @@ def get_region_links(region: Region) -> QuerySet: page__id__in=Subquery(region.non_archived_pages.values("pk")), ) .distinct("page__id", "language__id") - .values_list("pk", flat=True) + .values_list("pk", flat=True), ) latest_poitranslation_versions = Subquery( POITranslation.objects.filter(poi__region=region) .distinct("poi__id", "language__id") - .values_list("pk", flat=True) + .values_list("pk", flat=True), ) latest_eventtranslation_versions = Subquery( EventTranslation.objects.filter(event__region=region) .distinct("event__id", "language__id") - .values_list("pk", flat=True) + .values_list("pk", flat=True), ) latest_imprinttranslation_versions = Subquery( ImprintPageTranslation.objects.filter(page__region=region) .distinct("page__id", "language__id") - .values_list("pk", flat=True) + .values_list("pk", flat=True), ) organizations = Organization.objects.filter(region=region, archived=False) # Get all link objects of the requested region region_links = Link.objects.filter( - page_translation__id__in=latest_pagetranslation_versions + page_translation__id__in=latest_pagetranslation_versions, ).union( Link.objects.filter( - imprint_translation__id__in=latest_imprinttranslation_versions + imprint_translation__id__in=latest_imprinttranslation_versions, ), Link.objects.filter(event_translation__id__in=latest_eventtranslation_versions), Link.objects.filter(poi_translation__id__in=latest_poitranslation_versions), @@ -137,7 +137,6 @@ def filter_urls( url_filter: str | None = None, prefetch_region_links: bool = False, ) -> tuple[list[Url], dict[str, int]]: - # pylint: disable=too-many-branches """ Filter all urls of one region by the given category @@ -148,7 +147,8 @@ def filter_urls( :return: A tuple of the requested urls and a dict containing the counters of all remaining urls """ urls = get_urls( - region_slug=region_slug, prefetch_region_links=prefetch_region_links + region_slug=region_slug, + prefetch_region_links=prefetch_region_links, ) # Split url lists into their respective categories ignored_urls, valid_urls, invalid_urls, email_links, phone_links, unchecked_urls = ( @@ -172,7 +172,7 @@ def filter_urls( unchecked_urls.append(url) else: raise NotImplementedError( - f"Url {url!r} does not fit into any of the defined categories" + f"Url {url!r} does not fit into any of the defined categories", ) # Pass the number of urls to a dict which can be used as extra template context count_dict = { @@ -236,7 +236,10 @@ def replace_links( def find_target_url_per_content( - search: str, replace: str, region: Region | None, link_types: list[str] | None + search: str, + replace: str, + region: Region | None, + link_types: list[str] | None, ) -> dict[AbstractContentTranslation, dict[str, str]]: """ returns in which translation what URL must be replaced @@ -262,19 +265,19 @@ def find_target_url_per_content( link for link in links if link.url.type in link_types - or link.url.status is False - and "invalid" in link_types + or (link.url.status is False and "invalid" in link_types) ) if link_types else links ) - content_objects: DefaultDict[AbstractContentTranslation, dict[str, str]] = ( + content_objects: defaultdict[AbstractContentTranslation, dict[str, str]] = ( defaultdict(dict) ) for link in links_to_replace: content_objects[link.content_object][link.url.url] = link.url.url.replace( - search, replace + search, + replace, ) return content_objects diff --git a/integreat_cms/cms/utils/media_utils.py b/integreat_cms/cms/utils/media_utils.py index d1509cda99..c6359a4108 100644 --- a/integreat_cms/cms/utils/media_utils.py +++ b/integreat_cms/cms/utils/media_utils.py @@ -64,8 +64,8 @@ def generate_thumbnail( charset=None, ) logger.debug("Successfully generated thumbnail %r", thumbnail) + except OSError: + logger.exception("Thumbnail generation for %r failed", original_image) + return None + else: return thumbnail - except IOError as e: - logger.error("Thumbnail generation for %r failed", original_image) - logger.exception(e) - return None diff --git a/integreat_cms/cms/utils/mfa_utils.py b/integreat_cms/cms/utils/mfa_utils.py index 81eb4d6eb8..e8935a8faa 100644 --- a/integreat_cms/cms/utils/mfa_utils.py +++ b/integreat_cms/cms/utils/mfa_utils.py @@ -29,7 +29,7 @@ def generate_challenge(challenge_len: int) -> str: [ random.SystemRandom().choice(string.ascii_letters + string.digits) for i in range(challenge_len) - ] + ], ) @@ -41,6 +41,5 @@ def get_mfa_user(request: HttpRequest) -> User | None: :return: The user """ if "mfa_user_id" in request.session: - user = get_user_model().objects.get(id=request.session["mfa_user_id"]) - return user + return get_user_model().objects.get(id=request.session["mfa_user_id"]) return None diff --git a/integreat_cms/cms/utils/pdf_utils.py b/integreat_cms/cms/utils/pdf_utils.py index 1811d2315b..2487d6b2e9 100644 --- a/integreat_cms/cms/utils/pdf_utils.py +++ b/integreat_cms/cms/utils/pdf_utils.py @@ -34,9 +34,10 @@ def generate_pdf( - region: Region, language_slug: str, pages: PageQuerySet + region: Region, + language_slug: str, + pages: PageQuerySet, ) -> HttpResponseRedirect: - # pylint: disable=too-many-locals """ Function for handling a pdf export request for pages. The pages were either selected by cms user or by API request (see :func:`~integreat_cms.api.v3.pdf_export`) @@ -66,7 +67,8 @@ def generate_pdf( pdf_hash = hashlib.sha256(bytes(pdf_key_string, "utf-8")).hexdigest()[:10] if not (amount_pages := pages.count()): return HttpResponse( - _("No valid pages selected for PDF generation."), status=400 + _("No valid pages selected for PDF generation."), + status=400, ) if amount_pages == 1: # If pdf contains only one page, take its title as filename @@ -122,7 +124,6 @@ def generate_pdf( encoding="UTF-8", default_css=fixed_css, ) - # pylint: disable=no-member if pisa_status.err: logger.error( "The following PDF could not be rendered: %r, %r, %r", @@ -131,19 +132,19 @@ def generate_pdf( pages, ) return HttpResponse( - _("The PDF could not be successfully generated."), status=500 + _("The PDF could not be successfully generated."), + status=500, ) return redirect(pdf_storage.url(filename)) -def link_callback(uri: str, rel: str) -> str | None: # pylint: disable=unused-argument +def link_callback(uri: str, _rel: str) -> str | None: """ According to the xhtml2pdf documentation (see `Link callback `_, this function is necessary for resolving the Django static files references. It returns the absolute paths to the files on the file system. :param uri: URI that is generated by django template tag 'static' - :param rel: The relative path directory :return: The absolute path on the file system according to django's static file settings """ parsed_uri = urlparse(uri) @@ -159,12 +160,13 @@ def link_callback(uri: str, rel: str) -> str | None: # pylint: disable=unused-a if uri.startswith(settings.MEDIA_URL): # Get absolute path for media files path = unquote( - os.path.join(settings.MEDIA_ROOT, uri.replace(settings.MEDIA_URL, "")) + os.path.join(settings.MEDIA_ROOT, uri.replace(settings.MEDIA_URL, "")), ) # make sure that file exists if not os.path.isfile(path): logger.exception( - "The file %r was not found in the media directories.", path[:1024] + "The file %r was not found in the media directories.", + path[:1024], ) return None return path diff --git a/integreat_cms/cms/utils/repair_tree.py b/integreat_cms/cms/utils/repair_tree.py index 153ea2b3c2..adf9015037 100644 --- a/integreat_cms/cms/utils/repair_tree.py +++ b/integreat_cms/cms/utils/repair_tree.py @@ -14,7 +14,7 @@ from .tree_mutex import tree_mutex if TYPE_CHECKING: - from typing import Iterable + from collections.abc import Iterable from django.apps.registry import Apps from django.db.models import Model @@ -48,7 +48,7 @@ def repair_tree( Page.objects.get(id=single_id) except Page.DoesNotExist as e: raise ValueError( - f'The page with id "{single_id}" does not exist.' + f'The page with id "{single_id}" does not exist.', ) from e # All ids in page_id are valid, get them from mptt_fixer root_nodes = { @@ -75,7 +75,8 @@ def repair_tree( def print_changed_fields( - tree_node: ShadowInstance[Page], logging_name: str = __name__ + tree_node: ShadowInstance[Page], + logging_name: str = __name__, ) -> None: """ Utility function to print changed and unchanged attributes using a @@ -164,7 +165,8 @@ def get_fixed_tree_nodes(self) -> Iterable[ShadowInstance[Page]]: return self.fixed_nodes.values() def get_fixed_tree_of_page( - self, node_id: int | None = None + self, + node_id: int | None = None, ) -> Iterable[ShadowInstance[Page]]: """ Yield all nodes of the same page tree as the node specified by id in order (fixed). diff --git a/integreat_cms/cms/utils/shadow_instance.py b/integreat_cms/cms/utils/shadow_instance.py index 4c011291f5..586d08d41d 100644 --- a/integreat_cms/cms/utils/shadow_instance.py +++ b/integreat_cms/cms/utils/shadow_instance.py @@ -2,7 +2,8 @@ This module contains utilities to repair or detect inconsistencies in a tree """ -from typing import Any, Generic, Iterable, TypeVar +from collections.abc import Iterable +from typing import Any, Generic, TypeVar from django.db.models import Model diff --git a/integreat_cms/cms/utils/slug_utils.py b/integreat_cms/cms/utils/slug_utils.py index a181154c8c..a99f7eed8a 100644 --- a/integreat_cms/cms/utils/slug_utils.py +++ b/integreat_cms/cms/utils/slug_utils.py @@ -13,7 +13,6 @@ from django.utils.translation import gettext_lazy as _ if TYPE_CHECKING: - import sys from typing import Any, Literal, NotRequired, TypeAlias, TypedDict, Unpack from django.db.models import Manager @@ -22,7 +21,6 @@ from ..models import Language, Region from ..models.abstract_base_model import AbstractBaseModel - from ..models.abstract_content_model import AbstractContentModel logger = logging.getLogger(__name__) @@ -30,7 +28,12 @@ if TYPE_CHECKING: ForeignModelType: TypeAlias = Literal[ - "page", "event", "poi", "region", "organization", "offer-template" + "page", + "event", + "poi", + "region", + "organization", + "offer-template", ] class SlugKwargs(TypedDict): @@ -49,7 +52,8 @@ class SlugKwargs(TypedDict): def generate_unique_slug_helper( - form_object: ModelForm, foreign_model: ForeignModelType + form_object: ModelForm, + foreign_model: ForeignModelType, ) -> str: """ This function acts like an interface and extracts all parameters of the form_object before actually generating @@ -77,7 +81,7 @@ def generate_unique_slug_helper( "region": form_object.instance.foreign_object.region, "language": form_object.instance.language, "fallback": "title", - } + }, ) return generate_unique_slug(**kwargs) @@ -139,7 +143,7 @@ def generate_unique_slug(**kwargs: Unpack[SlugKwargs]) -> str: **{ foreign_model + "__region": region, "language": language, - } + }, ) # generate new slug while it is not unique @@ -153,7 +157,7 @@ def generate_unique_slug(**kwargs: Unpack[SlugKwargs]) -> str: **{ foreign_model: object_instance.foreign_object, "language": language, - } + }, ) else: # the current object is also allowed to have the same slug diff --git a/integreat_cms/cms/utils/stringify_list.py b/integreat_cms/cms/utils/stringify_list.py index 2c951a3c28..29fea5314a 100644 --- a/integreat_cms/cms/utils/stringify_list.py +++ b/integreat_cms/cms/utils/stringify_list.py @@ -4,7 +4,10 @@ def iter_to_string( - iterable: Iterable, *, quotation_char: str = '"', max_items: int = 5 + iterable: Iterable, + *, + quotation_char: str = '"', + max_items: int = 5, ) -> str: """ This is a helper function that turns a list into a string using different delimiters. diff --git a/integreat_cms/cms/utils/totp_utils.py b/integreat_cms/cms/utils/totp_utils.py index 063bb7924d..9876e82790 100644 --- a/integreat_cms/cms/utils/totp_utils.py +++ b/integreat_cms/cms/utils/totp_utils.py @@ -25,7 +25,8 @@ def generate_totp_qrcode(key: str, user: User) -> str: :return: The QR code as a base64-encoded string """ qr_string = pyotp.TOTP(key).provisioning_uri( - name=user.username, issuer_name=settings.BRANDING_TITLE + name=user.username, + issuer_name=settings.BRANDING_TITLE, ) img = qrcode.make(qr_string) @@ -34,12 +35,10 @@ def generate_totp_qrcode(key: str, user: User) -> str: img.save(img_byte_arr, format="PNG") img_byte_val = img_byte_arr.getvalue() - img_base64_string = "data:image/png;base64," + base64.b64encode( - img_byte_val + return "data:image/png;base64," + base64.b64encode( + img_byte_val, ).decode("utf-8") - return img_base64_string - def check_totp_code(given_totp_code: str, key: str) -> bool: r""" diff --git a/integreat_cms/cms/utils/tree_mutex.py b/integreat_cms/cms/utils/tree_mutex.py index 4d5c8f235e..3e42d93076 100644 --- a/integreat_cms/cms/utils/tree_mutex.py +++ b/integreat_cms/cms/utils/tree_mutex.py @@ -3,13 +3,17 @@ """ import functools +import logging import threading +from collections.abc import Callable, Generator from contextlib import contextmanager -from typing import Callable, Generator, ParamSpec, Type, TypeVar +from typing import ParamSpec, TypeVar from django.db import DEFAULT_DB_ALIAS, transaction from treebeard.models import Node +logger = logging.getLogger(__name__) + #: For how many seconds the lock persists, and the timeout for retrying to acquire it. LOCK_SECONDS = 10 #: How long to sleep between retries to acquire the lock. @@ -20,7 +24,6 @@ _LOCKS = {} -# pylint: disable=protected-access @contextmanager def monkeypatch_cursor_func( using: str = DEFAULT_DB_ALIAS, @@ -33,14 +36,17 @@ def monkeypatch_cursor_func( connection = transaction.get_connection(using=using) original_get_database_cursor = Node._get_database_cursor - def monkeypatched_get_cursor(cls: Type, action: str) -> None: + def monkeypatched_get_cursor(cls: type, action: str) -> None: """ A fake classmethod to overwrite :meth:`treebeard.models.Node._get_database_cursor` with. Gets the cursor for the currend django connection instead, allowing treebeard to be forced to use database transactions. """ - print( - f"someone is getting our monkeypatched db cursor ({using})! {cls}, {action}" + logger.debug( + "someone is getting our monkeypatched db cursor (%s)! %r, %s", + using, + cls, + action, ) return connection.cursor() @@ -87,10 +93,12 @@ def innermost_function(*args: P.args, **kwargs: P.kwargs) -> R: monkey patch :meth:`treebeard.models.Node._get_database_cursor` to get djangos db cursor and finally call the decorated ``func``. """ - with lock: - with transaction.atomic(using=DEFAULT_DB_ALIAS, durable=False): - with monkeypatch_cursor_func(using=DEFAULT_DB_ALIAS): - return func(*args, **kwargs) + with ( + lock, + transaction.atomic(using=DEFAULT_DB_ALIAS, durable=False), + monkeypatch_cursor_func(using=DEFAULT_DB_ALIAS), + ): + return func(*args, **kwargs) return innermost_function diff --git a/integreat_cms/cms/utils/welcome_mail_utils.py b/integreat_cms/cms/utils/welcome_mail_utils.py index cd111a4f90..1be9be0b07 100644 --- a/integreat_cms/cms/utils/welcome_mail_utils.py +++ b/integreat_cms/cms/utils/welcome_mail_utils.py @@ -50,7 +50,7 @@ def send_welcome_mail(request: HttpRequest, user: User, activation: bool) -> Non { "uid": uid, "token": token, - } + }, ) else: subject += _("Welcome") @@ -74,7 +74,8 @@ def send_welcome_mail(request: HttpRequest, user: User, activation: bool) -> Non messages.success( request, _("{} was successfully sent to user {}.").format( - debug_mail_type, user.full_user_name + debug_mail_type, + user.full_user_name, ), ) except RuntimeError as e: diff --git a/integreat_cms/cms/views/analytics/app_size_view.py b/integreat_cms/cms/views/analytics/app_size_view.py index 694315125b..ddf53aa911 100644 --- a/integreat_cms/cms/views/analytics/app_size_view.py +++ b/integreat_cms/cms/views/analytics/app_size_view.py @@ -28,8 +28,8 @@ def get_context_data(self, **kwargs: Any) -> dict: """ context = super().get_context_data(**kwargs) - # pylint: disable=fixme - # TODO: Implement correct calculation. + # TODO(ulliholtgrave): Implement correct calculation. + # https://github.com/digitalfabrik/integreat-cms/issues/3286 app_size_total = 0 context.update({"current_menu_item": "app_size", "app_size": app_size_total}) diff --git a/integreat_cms/cms/views/analytics/translation_coverage_view.py b/integreat_cms/cms/views/analytics/translation_coverage_view.py index 68e33b4802..c5a0999311 100644 --- a/integreat_cms/cms/views/analytics/translation_coverage_view.py +++ b/integreat_cms/cms/views/analytics/translation_coverage_view.py @@ -25,7 +25,6 @@ from django.db.models.query import QuerySet - from ...models import Language logger = logging.getLogger(__name__) @@ -74,7 +73,7 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "word_count": word_count, "total_outdated_words": sum(c[OUTDATED] for c in word_count.values()), "total_missing_words": sum(c[MISSING] for c in word_count.values()), - } + }, ) context.update(self.get_hix_context()) return context @@ -94,7 +93,7 @@ def get_hix_context(self) -> dict[str, QuerySet | int | float]: ) translations_under_hix_threshold = get_translation_under_hix_threshold( - self.request.region + self.request.region, ).count() total_count = get_translations_relevant_to_hix(self.request.region).count() diff --git a/integreat_cms/cms/views/authentication/account_activation_view.py b/integreat_cms/cms/views/authentication/account_activation_view.py index 188503dfdf..175c31bb53 100644 --- a/integreat_cms/cms/views/authentication/account_activation_view.py +++ b/integreat_cms/cms/views/authentication/account_activation_view.py @@ -64,7 +64,7 @@ def dispatch(self, *args: Any, **kwargs: Any) -> HttpResponse: _("This account activation link is invalid."), _("It may have already been used."), _( - "Please contact an administrator to request a new link to activate your account." + "Please contact an administrator to request a new link to activate your account.", ), ), ) diff --git a/integreat_cms/cms/views/authentication/password_reset_view.py b/integreat_cms/cms/views/authentication/password_reset_view.py index df14563f51..a54ad1a5bf 100644 --- a/integreat_cms/cms/views/authentication/password_reset_view.py +++ b/integreat_cms/cms/views/authentication/password_reset_view.py @@ -40,7 +40,9 @@ class PasswordResetView(auth_views.PasswordResetView): form_class = CustomPasswordResetForm def dispatch( - self, *args: HttpRequest, **kwargs: Any + self, + *args: HttpRequest, + **kwargs: Any, ) -> HttpResponseRedirect | TemplateResponse: r""" The view part of the view. Handles all HTTP methods equally. @@ -74,23 +76,24 @@ def form_valid(self, form: CustomPasswordResetForm) -> HttpResponseRedirect: self.request, __( _( - "We've emailed you instructions for setting your password, if an account exists with the email you entered." + "We've emailed you instructions for setting your password, if an account exists with the email you entered.", ), _("You should receive them shortly."), _( - "If you don’t receive an email, please make sure you’ve entered the address you registered with, and check your spam folder." + "If you don’t receive an email, please make sure you’ve entered the address you registered with, and check your spam folder.", ), ), ) - return response except RuntimeError as e: messages.error( self.request, __( _("An error occurred! Could not send {}.").format( - _("password reset email") + _("password reset email"), ), e, ), ) return redirect("public:password_reset") + else: + return response diff --git a/integreat_cms/cms/views/authentication/totp_login_view.py b/integreat_cms/cms/views/authentication/totp_login_view.py index 25fb76835e..f09e760e3a 100644 --- a/integreat_cms/cms/views/authentication/totp_login_view.py +++ b/integreat_cms/cms/views/authentication/totp_login_view.py @@ -83,7 +83,9 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: if check_totp_code(user_totp, self.user.totp_key): auth_login( - request, self.user, backend="django.contrib.auth.backends.ModelBackend" + request, + self.user, + backend="django.contrib.auth.backends.ModelBackend", ) return redirect("public:region_selection") diff --git a/integreat_cms/cms/views/authentication/webauthn/webauthn_assert_view.py b/integreat_cms/cms/views/authentication/webauthn/webauthn_assert_view.py index f505d795aa..04f3d71a57 100644 --- a/integreat_cms/cms/views/authentication/webauthn/webauthn_assert_view.py +++ b/integreat_cms/cms/views/authentication/webauthn/webauthn_assert_view.py @@ -36,12 +36,13 @@ def get(self, request: HttpRequest) -> HttpResponse: """ if not (user := get_mfa_user(request)): return JsonResponse( - {"success": False, "error": _("You need to log in first")}, status=403 + {"success": False, "error": _("You need to log in first")}, + status=403, ) if request.user.is_authenticated: return JsonResponse( - {"success": False, "error": _("You are already logged in.")} + {"success": False, "error": _("You are already logged in.")}, ) webauthn_challenge = generate_authentication_options( @@ -54,7 +55,7 @@ def get(self, request: HttpRequest) -> HttpResponse: request.session["challenge"] = bytes_to_base64url(webauthn_challenge.challenge) - # pylint: disable=http-response-with-content-type-json return HttpResponse( - options_to_json(webauthn_challenge), content_type="application/json" + options_to_json(webauthn_challenge), + content_type="application/json", ) diff --git a/integreat_cms/cms/views/authentication/webauthn/webauthn_verify_view.py b/integreat_cms/cms/views/authentication/webauthn/webauthn_verify_view.py index bafa94cdda..0e15380c7a 100644 --- a/integreat_cms/cms/views/authentication/webauthn/webauthn_verify_view.py +++ b/integreat_cms/cms/views/authentication/webauthn/webauthn_verify_view.py @@ -36,7 +36,8 @@ def post(self, request: HttpRequest) -> JsonResponse: """ if not (user := get_mfa_user(request)): return JsonResponse( - {"success": False, "error": _("You need to log in first")}, status=403 + {"success": False, "error": _("You need to log in first")}, + status=403, ) challenge = request.session["challenge"] @@ -48,7 +49,7 @@ def post(self, request: HttpRequest) -> JsonResponse: try: authentication_verification = verify_authentication_response( credential=parse_authentication_credential_json( - json.loads(request.body) + json.loads(request.body), ), expected_challenge=base64url_to_bytes(challenge), expected_rp_id=settings.HOSTNAME, @@ -56,10 +57,11 @@ def post(self, request: HttpRequest) -> JsonResponse: credential_public_key=key.public_key, credential_current_sign_count=key.sign_count, ) - except InvalidAuthenticationResponse as e: - logger.exception(e) + except InvalidAuthenticationResponse: + logger.exception("") return JsonResponse( - {"success": False, "error": "Authentication rejected"}, status=403 + {"success": False, "error": "Authentication rejected"}, + status=403, ) # Update counter. diff --git a/integreat_cms/cms/views/bulk_action_views.py b/integreat_cms/cms/views/bulk_action_views.py index a821b8b845..9898c580c0 100644 --- a/integreat_cms/cms/views/bulk_action_views.py +++ b/integreat_cms/cms/views/bulk_action_views.py @@ -130,7 +130,10 @@ class BulkMachineTranslationView(BulkActionView): form: ModelForm | None = None def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Translate multiple objects automatically @@ -150,27 +153,31 @@ def post( messages.error( request, _('Machine translations are disabled for language "{}"').format( - language_node + language_node, ), ) return super().post(request, *args, **kwargs) if not language_node.mt_provider.is_permitted( - request.region, request.user, self.form._meta.model + request.region, + request.user, + self.form._meta.model, ): messages.error( request, _( - "Machine translations are not allowed for the current user or content type" + "Machine translations are not allowed for the current user or content type", ).format(language_node), ) return super().post(request, *args, **kwargs) if language_node.mt_provider.bulk_only_for_staff and not request.user.is_staff: raise PermissionDenied( - f"Only staff users have the permission to bulk translate {self.form._meta.model._meta.model_name} via {language_node.mt_provider}" + f"Only staff users have the permission to bulk translate {self.form._meta.model._meta.model_name} via {language_node.mt_provider}", ) to_translate = language_node.mt_provider.is_needed( - request.region, self.get_queryset(), language_node + request.region, + self.get_queryset(), + language_node, ) if not to_translate: messages.error( @@ -223,7 +230,7 @@ def field_name(self) -> str: :raises NotImplementedError: If the ``field_name`` attribute is not implemented in the subclass """ raise NotImplementedError( - "Subclasses of BulkUpdateBooleanFieldView must provide a 'field_name' attribute" + "Subclasses of BulkUpdateBooleanFieldView must provide a 'field_name' attribute", ) @property @@ -234,11 +241,14 @@ def action(self) -> str: :raises NotImplementedError: If the ``action`` attribute is not implemented in the subclass """ raise NotImplementedError( - "Subclasses of BulkUpdateBooleanFieldView must provide an 'action' attribute" + "Subclasses of BulkUpdateBooleanFieldView must provide an 'action' attribute", ) def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Update the fields of the selected objects and redirect @@ -263,7 +273,8 @@ def post( messages.success( request, _("The selected {} were successfully {}").format( - self.model._meta.verbose_name_plural, self.action + self.model._meta.verbose_name_plural, + self.action, ), ) # Let the base view handle the redirect @@ -277,7 +288,10 @@ class BulkArchiveView(BulkActionView): @tree_mutex("page") def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Archive multiple objects @@ -374,7 +388,10 @@ class BulkRestoreView(BulkActionView): @tree_mutex("page") def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Restore multiple objects @@ -391,7 +408,7 @@ def post( for content_object in self.get_queryset(): if self.get_queryset().model is Page and content_object.implicitly_archived: restore_failed_because_parent_archived.append( - content_object.best_translation.title + content_object.best_translation.title, ) elif not content_object.archived: restore_unchanged.append(content_object.best_translation.title) @@ -460,7 +477,10 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: :return: The redirect """ change_publication_status( - request, self.get_queryset(), kwargs["language_slug"], status.PUBLIC + request, + self.get_queryset(), + kwargs["language_slug"], + status.PUBLIC, ) return super().post(request, *args, **kwargs) @@ -480,6 +500,9 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: :return: The redirect """ change_publication_status( - request, self.get_queryset(), kwargs["language_slug"], status.DRAFT + request, + self.get_queryset(), + kwargs["language_slug"], + status.DRAFT, ) return super().post(request, *args, **kwargs) diff --git a/integreat_cms/cms/views/chat/chat_actions.py b/integreat_cms/cms/views/chat/chat_actions.py index ac409d752a..12f5e07772 100644 --- a/integreat_cms/cms/views/chat/chat_actions.py +++ b/integreat_cms/cms/views/chat/chat_actions.py @@ -23,13 +23,12 @@ def send_chat_message( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> HttpResponse: """ Send chat message :param request: The current request - :param region_slug: The slug of the current region :return: A redirection to the :class:`~integreat_cms.cms.views.pages.page_tree_view.PageTreeView` """ chat_form = ChatMessageForm(data=request.POST, sender=request.user) @@ -60,14 +59,13 @@ def send_chat_message( @require_POST def delete_chat_message( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, message_id: int | None = None, ) -> JsonResponse: """ Delete chat message :param request: The current request - :param region_slug: The slug of the current region :param message_id: The id of the message :raises ~django.core.exceptions.PermissionDenied: If user does not have the permission to delete the specific message @@ -78,7 +76,7 @@ def delete_chat_message( if not request.user.has_perm("cms.delete_chat_message_object", message): # If the user is neither superuser or staff, nor the sender of the message, he cannot delete it raise PermissionDenied( - f"{request.user!r} does not have the permission to delete {message!r}" + f"{request.user!r} does not have the permission to delete {message!r}", ) message.delete() @@ -89,5 +87,5 @@ def delete_chat_message( "success": True, "status": "The chat message was successfully deleted.", "message": str(message), - } + }, ) diff --git a/integreat_cms/cms/views/contacts/contact_actions.py b/integreat_cms/cms/views/contacts/contact_actions.py index 0d4469c74a..69ef7d7896 100644 --- a/integreat_cms/cms/views/contacts/contact_actions.py +++ b/integreat_cms/cms/views/contacts/contact_actions.py @@ -15,7 +15,9 @@ @permission_required("cms.change_contact") def archive_contact( - request: HttpRequest, contact_id: int, region_slug: str + request: HttpRequest, + contact_id: int, + region_slug: str, ) -> HttpResponseRedirect: """ Method that archives a given contact @@ -26,7 +28,9 @@ def archive_contact( :return: A redirection to the :class:`~integreat_cms.cms.views.contacts.contact_list_view.ContactListView` """ to_be_archived_contact = get_object_or_404( - Contact, id=contact_id, location__region=request.region + Contact, + id=contact_id, + location__region=request.region, ) if not to_be_archived_contact.referring_objects: to_be_archived_contact.archive() @@ -51,7 +55,9 @@ def archive_contact( @permission_required("cms.delete_contact") def delete_contact( - request: HttpRequest, contact_id: int, region_slug: str + request: HttpRequest, + contact_id: int, + region_slug: str, ) -> HttpResponseRedirect: """ Delete given contact @@ -62,7 +68,9 @@ def delete_contact( :return: A redirection to the :class:`~integreat_cms.cms.views.contacts.contact_list_view.ContactListView` """ to_be_deleted_contact = get_object_or_404( - Contact, id=contact_id, location__region=request.region + Contact, + id=contact_id, + location__region=request.region, ) if not to_be_deleted_contact.referring_objects: to_be_deleted_contact.delete() @@ -87,7 +95,9 @@ def delete_contact( @permission_required("cms.change_contact") def restore_contact( - request: HttpRequest, contact_id: int, region_slug: str + request: HttpRequest, + contact_id: int, + region_slug: str, ) -> HttpResponseRedirect: """ Restore given contact @@ -98,7 +108,9 @@ def restore_contact( :return: A redirection to the :class:`~integreat_cms.cms.views.contacts.contact_list_view.ContactListView` """ to_be_restored_contact = get_object_or_404( - Contact, id=contact_id, location__region=request.region + Contact, + id=contact_id, + location__region=request.region, ) to_be_restored_contact.restore() @@ -117,7 +129,9 @@ def restore_contact( @permission_required("cms.change_contact") def copy_contact( - request: HttpRequest, contact_id: int, region_slug: str + request: HttpRequest, + contact_id: int, + region_slug: str, ) -> HttpResponseRedirect: """ Method that copies an existing contact @@ -128,12 +142,15 @@ def copy_contact( :return: A redirection to the :class:`~integreat_cms.cms.views.contacts.contact_list_view.ContactListView` """ to_be_copied_contact = get_object_or_404( - Contact, id=contact_id, location__region=request.region + Contact, + id=contact_id, + location__region=request.region, ) to_be_copied_contact.copy() messages.success( - request, _("Contact {0} was successfully copied").format(to_be_copied_contact) + request, + _("Contact {0} was successfully copied").format(to_be_copied_contact), ) return redirect( "contacts", diff --git a/integreat_cms/cms/views/contacts/contact_context_mixin.py b/integreat_cms/cms/views/contacts/contact_context_mixin.py index acf8123426..4e281a2db0 100644 --- a/integreat_cms/cms/views/contacts/contact_context_mixin.py +++ b/integreat_cms/cms/views/contacts/contact_context_mixin.py @@ -9,7 +9,6 @@ from typing import Any -# pylint:disable=too-few-public-methods class ContactContextMixin(ContextMixin): """ This mixin provides extra context for language tree views @@ -29,15 +28,15 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: { "current_menu_item": "contacts", "archive_dialog_title": _( - "Please confirm that you really want to archive this contact" + "Please confirm that you really want to archive this contact", ), "restore_dialog_title": _( - "Please confirm that you really want to restore this contact" + "Please confirm that you really want to restore this contact", ), "delete_dialog_title": _( - "Please confirm that you really want to delete this contact" + "Please confirm that you really want to delete this contact", ), - } + }, ) return context diff --git a/integreat_cms/cms/views/contacts/contact_form_view.py b/integreat_cms/cms/views/contacts/contact_form_view.py index 87cea1bf43..a348ef751e 100644 --- a/integreat_cms/cms/views/contacts/contact_form_view.py +++ b/integreat_cms/cms/views/contacts/contact_form_view.py @@ -11,7 +11,6 @@ from ...decorators import permission_required from ...forms import ContactForm from ...models import Contact, Event, Page, POI -from ...utils.translation_utils import gettext_many_lazy as __ from .contact_context_mixin import ContactContextMixin if TYPE_CHECKING: @@ -41,18 +40,21 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: """ region = request.region contact_instance = Contact.objects.filter( - id=kwargs.get("contact_id"), location__region=region + id=kwargs.get("contact_id"), + location__region=region, ).first() if contact_instance and contact_instance.archived: disabled = True messages.warning( - request, _("You cannot edit this contact because it is archived.") + request, + _("You cannot edit this contact because it is archived."), ) elif not request.user.has_perm("cms.change_contact"): disabled = True messages.warning( - request, _("You don't have the permission to edit contacts.") + request, + _("You don't have the permission to edit contacts."), ) else: disabled = False @@ -67,7 +69,7 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: _("This location is used for the contact.") if contact_instance else _( - "Select a location to use for your contact or create a new location. Only published locations can be set." + "Select a location to use for your contact or create a new location. Only published locations can be set.", ) ) @@ -75,7 +77,8 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: Page.objects.filter( id__in=( contact_instance.referring_page_translations.values_list( - "page_id", flat=True + "page_id", + flat=True, ) ), ) @@ -87,7 +90,8 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: POI.objects.filter( id__in=( contact_instance.referring_poi_translations.values_list( - "poi_id", flat=True + "poi_id", + flat=True, ) ), ) @@ -99,7 +103,8 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: Event.objects.filter( id__in=( contact_instance.referring_event_translations.values_list( - "event_id", flat=True + "event_id", + flat=True, ) ), ) @@ -134,7 +139,8 @@ def post(self, request: HttpRequest, **kwargs: Any) -> HttpResponse: region = request.region contact_instance = Contact.objects.filter( - id=kwargs.get("contact_id"), location__region=region + id=kwargs.get("contact_id"), + location__region=region, ).first() contact_form = ContactForm( data=request.POST, @@ -150,7 +156,7 @@ def post(self, request: HttpRequest, **kwargs: Any) -> HttpResponse: messages.success( request, _('Contact for "{}" was successfully created').format( - contact_form.instance + contact_form.instance, ), ) elif not contact_form.has_changed(): @@ -159,7 +165,7 @@ def post(self, request: HttpRequest, **kwargs: Any) -> HttpResponse: messages.success( request, _('Contact for "{}" was successfully saved').format( - contact_form.instance + contact_form.instance, ), ) return redirect( @@ -174,7 +180,7 @@ def post(self, request: HttpRequest, **kwargs: Any) -> HttpResponse: _("This location is used for the contact.") if contact_instance else _( - "Select a location to use for your contact or create a new location. Only published locations can be set." + "Select a location to use for your contact or create a new location. Only published locations can be set.", ) ) diff --git a/integreat_cms/cms/views/contacts/contact_list_view.py b/integreat_cms/cms/views/contacts/contact_list_view.py index 72cd84313d..9a96e642c6 100644 --- a/integreat_cms/cms/views/contacts/contact_list_view.py +++ b/integreat_cms/cms/views/contacts/contact_list_view.py @@ -6,7 +6,6 @@ from django.core.paginator import Paginator from django.shortcuts import render from django.utils.decorators import method_decorator -from django.utils.translation import gettext_lazy as _ from django.views.generic import TemplateView from ...decorators import permission_required @@ -40,11 +39,13 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: region = request.region contacts = Contact.objects.filter( - location__region=region, archived=self.archived + location__region=region, + archived=self.archived, ).select_related("location") archived_count = Contact.objects.filter( - location__region=region, archived=True + location__region=region, + archived=True, ).count() chunk_size = int(request.GET.get("size", settings.PER_PAGE)) diff --git a/integreat_cms/cms/views/content_version_view.py b/integreat_cms/cms/views/content_version_view.py index 3bf38471a0..576f7a4c73 100644 --- a/integreat_cms/cms/views/content_version_view.py +++ b/integreat_cms/cms/views/content_version_view.py @@ -187,7 +187,8 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context = super().get_context_data(**kwargs) api_version = next( - (t for t in self.translations if t.status == status.PUBLIC), None + (t for t in self.translations if t.status == status.PUBLIC), + None, ) try: @@ -206,20 +207,23 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "back_to_form_label": self.back_to_form_label, "slug_label": slug_label, "title_label": self.selected_version._meta.get_field( - "title" + "title", ).verbose_name, "content_label": self.selected_version._meta.get_field( - "content" + "content", ).verbose_name, "can_edit": self.has_change_permission(), "can_publish": self.has_publish_permission(), - } + }, ) return context def dispatch( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect | TemplateResponse: r""" Validate the versions view @@ -236,7 +240,8 @@ def dispatch( assert self.object self.language = self.request.region.get_language_or_404( - kwargs.get("language_slug"), only_active=True + kwargs.get("language_slug"), + only_active=True, ) self.translations = self.object.translations.filter(language=self.language) @@ -263,7 +268,10 @@ def dispatch( return super().dispatch(request, *args, **kwargs) def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Restore a previous revision of a page translation @@ -285,7 +293,7 @@ def post( messages.info( request, _( - "%s %s can not change its status as it was imported from an external calendar" + "%s %s can not change its status as it was imported from an external calendar", ) % ( self.model_name, @@ -311,7 +319,7 @@ def post( return redirect(self.versions_url) if desired_status not in dict(status.CHOICES): raise PermissionDenied( - f"{request.user!r} tried to restore {restored_version!r} of {self.object!r} with invalid status {desired_status!r}" + f"{request.user!r} tried to restore {restored_version!r} of {self.object!r} with invalid status {desired_status!r}", ) else: # If the current version should be rejected, return to the latest version that is neither an auto save nor in review @@ -351,7 +359,7 @@ def post( messages.error( request, _( - "This version is identical to the current version of this translation." + "This version is identical to the current version of this translation.", ), ) return redirect(self.versions_url) diff --git a/integreat_cms/cms/views/dashboard/dashboard_view.py b/integreat_cms/cms/views/dashboard/dashboard_view.py index faea6fd079..f2ee442648 100644 --- a/integreat_cms/cms/views/dashboard/dashboard_view.py +++ b/integreat_cms/cms/views/dashboard/dashboard_view.py @@ -53,10 +53,12 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: { "current_menu_item": "region_dashboard", "blog_url": settings.BLOG_URLS.get( - language_slug, settings.DEFAULT_BLOG_URL + language_slug, + settings.DEFAULT_BLOG_URL, ), "feed_url": settings.RSS_FEED_URLS.get( - language_slug, settings.DEFAULT_RSS_FEED_URL + language_slug, + settings.DEFAULT_RSS_FEED_URL, ), "broken_link_ajax": reverse( "get_broken_links_ajax", @@ -66,7 +68,7 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "get_translation_coverage_ajax", kwargs={"region_slug": self.request.region.slug}, ), - } + }, ) context.update(self.get_unreviewed_pages_context()) @@ -85,7 +87,8 @@ def get_latest_versions(self) -> list[int]: :return: The ids of the latest page translations of the current region """ latest_version_ids = self.request.region.latest_page_translations.values_list( - "pk", flat=True + "pk", + flat=True, ) return list(latest_version_ids) @@ -136,17 +139,19 @@ def get_unread_feedback_context(self) -> dict[str, QuerySet]: :return: Dictionary containing the context for unreviewed pages """ unread_feedback = Feedback.objects.filter( - read_by=None, archived=False, region=self.request.region + read_by=None, + archived=False, + region=self.request.region, ) return { "unread_feedback": unread_feedback, } + @staticmethod @json_response def get_broken_links_context( - # pylint: disable=no-self-argument request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + **kwargs: Any, ) -> JsonResponse: r""" Extend context by info on broken links @@ -154,7 +159,9 @@ def get_broken_links_context( :return: Dictionary containing the context for broken links """ invalid_urls = filter_urls( - request.region.slug, "invalid", prefetch_region_links=True + request.region.slug, + "invalid", + prefetch_region_links=True, )[0] invalid_url = invalid_urls[0] if invalid_urls else None @@ -180,7 +187,7 @@ def get_broken_links_context( "broken_links": len(invalid_urls), "relevant_translation": str(relevant_translation), "edit_url": f"{edit_url}" if len(edit_url) > 0 else "", - } + }, ) def get_low_hix_value_context(self) -> dict[str, list[PageTranslation]]: @@ -190,7 +197,7 @@ def get_low_hix_value_context(self) -> dict[str, list[PageTranslation]]: :return: Dictionary containing the context for pages with low hix value """ translations_under_hix_threshold = get_translation_under_hix_threshold( - self.request.region + self.request.region, ) return {"pages_under_hix_threshold": translations_under_hix_threshold} @@ -215,7 +222,7 @@ def get_outdated_pages_context( ) outdated_threshold_date = datetime.now() - relativedelta( - days=settings.OUTDATED_THRESHOLD_DAYS + days=settings.OUTDATED_THRESHOLD_DAYS, ) outdated_threshold_date_str = outdated_threshold_date.strftime("%Y-%m-%d") @@ -248,8 +255,8 @@ def get_drafted_pages( "single_drafted_page": single_drafted_page, } + @staticmethod @json_response - # pylint: disable=unused-argument, disable=no-self-argument def get_translation_coverage_context( request: HttpRequest, region_slug: str ) -> JsonResponse: diff --git a/integreat_cms/cms/views/dashboard/region_selection.py b/integreat_cms/cms/views/dashboard/region_selection.py index 33a65cb0ef..a86c46f06a 100644 --- a/integreat_cms/cms/views/dashboard/region_selection.py +++ b/integreat_cms/cms/views/dashboard/region_selection.py @@ -27,7 +27,10 @@ class RegionSelection(TemplateView): template_name = "dashboard/region_selection.html" def get( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Redirect to correct dashboard or render the region selection if dashboard cannot be automatically determined diff --git a/integreat_cms/cms/views/delete_views.py b/integreat_cms/cms/views/delete_views.py index 85c92dd28b..32714b70c0 100644 --- a/integreat_cms/cms/views/delete_views.py +++ b/integreat_cms/cms/views/delete_views.py @@ -71,7 +71,7 @@ def delete(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpRespons manytomany_manager = getattr(self.object, self.protect_manytomany) if manytomany_manager.exists(): raise IntegrityError( - f"The object {self.object!r} cannot be deleted because of the following many to many relationship: {manytomany_manager.all()}" + f"The object {self.object!r} cannot be deleted because of the following many to many relationship: {manytomany_manager.all()}", ) success_url = self.get_success_url() self.object.delete() @@ -79,7 +79,8 @@ def delete(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpRespons messages.success( request, _('{} "{}" was successfully deleted').format( - self.object._meta.verbose_name, self.object + self.object._meta.verbose_name, + self.object, ), ) return HttpResponseRedirect(success_url) diff --git a/integreat_cms/cms/views/error_handler/error_handler.py b/integreat_cms/cms/views/error_handler/error_handler.py index 6d0d5262df..9f5ee23fce 100644 --- a/integreat_cms/cms/views/error_handler/error_handler.py +++ b/integreat_cms/cms/views/error_handler/error_handler.py @@ -36,7 +36,7 @@ def render_error_template(context: dict[str, Any]) -> SafeString: "COMPANY_URL": settings.COMPANY_URL, "BRANDING": settings.BRANDING, "BRANDING_TITLE": settings.BRANDING_TITLE, - } + }, ) return render_to_string("error_handler/http_error.html", context) @@ -60,7 +60,8 @@ def handler400(request: HttpRequest, exception: BadRequest) -> HttpResponseBadRe def handler403( - request: HttpRequest, exception: PermissionDenied + request: HttpRequest, + exception: PermissionDenied, ) -> HttpResponseForbidden: """ Render a HTTP 403 Error code diff --git a/integreat_cms/cms/views/events/event_actions.py b/integreat_cms/cms/views/events/event_actions.py index 45aa2af057..5f0d8da1c6 100644 --- a/integreat_cms/cms/views/events/event_actions.py +++ b/integreat_cms/cms/views/events/event_actions.py @@ -28,7 +28,10 @@ @require_POST @permission_required("cms.change_event") def archive( - request: HttpRequest, event_id: int, region_slug: str, language_slug: str + request: HttpRequest, + event_id: int, + region_slug: str, + language_slug: str, ) -> HttpResponseRedirect: """ Set archived flag for an event @@ -59,7 +62,10 @@ def archive( @require_POST @permission_required("cms.change_event") def copy( - request: HttpRequest, event_id: int, region_slug: str, language_slug: str + request: HttpRequest, + event_id: int, + region_slug: str, + language_slug: str, ) -> HttpResponseRedirect: """ Duplicates the given event and all of its translations. @@ -81,21 +87,26 @@ def copy( _("Event couldn't be copied because it's from an external calendar"), ) return redirect( - "events", **{"region_slug": region_slug, "language_slug": language_slug} + "events", + **{"region_slug": region_slug, "language_slug": language_slug}, ) logger.debug("%r copied by %r", event, request.user) messages.success(request, _("Event was successfully copied")) return redirect( - "events", **{"region_slug": region_slug, "language_slug": language_slug} + "events", + **{"region_slug": region_slug, "language_slug": language_slug}, ) @require_POST @permission_required("cms.change_event") def restore( - request: HttpRequest, event_id: int, region_slug: str, language_slug: str + request: HttpRequest, + event_id: int, + region_slug: str, + language_slug: str, ) -> HttpResponseRedirect: """ Remove archived flag for an event @@ -126,7 +137,10 @@ def restore( @require_POST @permission_required("cms.delete_event") def delete( - request: HttpRequest, event_id: int, region_slug: str, language_slug: str + request: HttpRequest, + event_id: int, + region_slug: str, + language_slug: str, ) -> HttpResponseRedirect: """ Delete a single event @@ -159,13 +173,13 @@ def delete( @require_POST @permission_required("cms.view_event") def search_poi_ajax( - request: HttpRequest, region_slug: str # pylint: disable=unused-argument + request: HttpRequest, + region_slug: str, ) -> HttpResponse: """ AJAX endpoint for searching POIs :param request: Object representing the user call - :param region_slug: The current regions slug :return: The rendered template response """ data = json.loads(request.body.decode("utf-8")) diff --git a/integreat_cms/cms/views/events/event_context_mixin.py b/integreat_cms/cms/views/events/event_context_mixin.py index c9d6b4f914..8636ef13c2 100644 --- a/integreat_cms/cms/views/events/event_context_mixin.py +++ b/integreat_cms/cms/views/events/event_context_mixin.py @@ -13,7 +13,6 @@ class EventContextMixin(ContextMixin): - # pylint: disable=too-few-public-methods """ This mixin provides extra context for event views """ @@ -31,29 +30,29 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: { "current_menu_item": "events_form", "archive_dialog_title": _( - "Please confirm that you really want to archive this event" + "Please confirm that you really want to archive this event", ), "archive_dialog_text": _( - "All translations of this event will also be archived." + "All translations of this event will also be archived.", ), "restore_dialog_title": _( - "Please confirm that you really want to restore this event" + "Please confirm that you really want to restore this event", ), "restore_dialog_text": _( - "All translations of this event will also be restored." + "All translations of this event will also be restored.", ), "delete_dialog_title": _( - "Please confirm that you really want to delete this event" + "Please confirm that you really want to delete this event", ), "delete_dialog_text": _( - "All translations of this event will also be deleted." + "All translations of this event will also be deleted.", ), "help_text": _( - "Create an event location or start typing the name of an existing location. Only published locations can be set as event venues." + "Create an event location or start typing the name of an existing location. Only published locations can be set as event venues.", ), "cannot_copy_title": _( - "An event from an external calendar can't be copied." + "An event from an external calendar can't be copied.", ), - } + }, ) return context diff --git a/integreat_cms/cms/views/events/event_form_view.py b/integreat_cms/cms/views/events/event_form_view.py index 57dba4b493..2af03bb003 100644 --- a/integreat_cms/cms/views/events/event_form_view.py +++ b/integreat_cms/cms/views/events/event_form_view.py @@ -32,7 +32,10 @@ @method_decorator(permission_required("cms.view_event"), name="dispatch") @method_decorator(permission_required("cms.change_event"), name="post") class EventFormView( - TemplateView, EventContextMixin, MediaContextMixin, ContentEditLockMixin + TemplateView, + EventContextMixin, + MediaContextMixin, + ContentEditLockMixin, ): """ Class for rendering the events form @@ -56,42 +59,45 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: """ region = request.region language = region.get_language_or_404( - kwargs.get("language_slug"), only_active=True + kwargs.get("language_slug"), + only_active=True, ) # get event and event translation objects if they exist, otherwise objects are None event_instance = region.events.filter(id=kwargs.get("event_id")).first() event_translation_instance = language.event_translations.filter( - event=event_instance + event=event_instance, ).first() recurrence_rule_instance = RecurrenceRule.objects.filter( - event=event_instance + event=event_instance, ).first() # Make form disabled if event is archived or user doesn't have the permission to edit the event if event_instance and event_instance.archived: disabled = True messages.warning( - request, _("You cannot edit this event because it is archived.") + request, + _("You cannot edit this event because it is archived."), ) elif event_instance and event_instance.external_calendar: disabled = True messages.warning( request, _( - "You cannot edit this event because it was imported from an external calendar." + "You cannot edit this event because it was imported from an external calendar.", ), ) elif not request.user.has_perm("cms.change_event"): disabled = True messages.warning( - request, _("You don't have the permission to edit events.") + request, + _("You don't have the permission to edit events."), ) elif not request.user.has_perm("cms.publish_event"): disabled = False messages.warning( request, _( - "You don't have the permission to publish events, but you can propose changes and submit them for review instead." + "You don't have the permission to publish events, but you can propose changes and submit them for review instead.", ), ) else: @@ -108,7 +114,8 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: disabled=disabled, ) recurrence_rule_form = RecurrenceRuleForm( - instance=recurrence_rule_instance, disabled=disabled + instance=recurrence_rule_instance, + disabled=disabled, ) url_link = f"{settings.WEBAPP_URL}/{region.slug}/{language.slug}/{event_translation_form.instance.url_infix}/" @@ -135,7 +142,6 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ) def post(self, request: HttpRequest, **kwargs: Any) -> HttpResponse: - # pylint: disable=too-many-locals,too-many-branches r""" Save event and ender event form for HTTP POST requests @@ -151,10 +157,11 @@ def post(self, request: HttpRequest, **kwargs: Any) -> HttpResponse: event_instance = Event.objects.filter(id=kwargs.get("event_id")).first() recurrence_rule_instance = RecurrenceRule.objects.filter( - event=event_instance + event=event_instance, ).first() event_translation_instance = EventTranslation.objects.filter( - event=event_instance, language=language + event=event_instance, + language=language, ).first() event_form = EventForm( @@ -212,7 +219,7 @@ def post(self, request: HttpRequest, **kwargs: Any) -> HttpResponse: status.PUBLIC, ] and not request.user.has_perm("cms.publish_event"): raise PermissionDenied( - f"{request.user!r} does not have the permission 'cms.publish_event'" + f"{request.user!r} does not have the permission 'cms.publish_event'", ) # Save forms if event_form.cleaned_data.get("is_recurring"): @@ -242,7 +249,7 @@ def post(self, request: HttpRequest, **kwargs: Any) -> HttpResponse: node.language for node in language_tree_node.get_descendants() ] event_translation_form.instance.event.translations.filter( - language__in=languages + language__in=languages, ).update(status=status.DRAFT) elif ( @@ -250,12 +257,14 @@ def post(self, request: HttpRequest, **kwargs: Any) -> HttpResponse: and event_translation_form.instance.minor_edit ): event_translation_form.instance.event.translations.filter( - language=language + language=language, ).update(status=status.PUBLIC) # Show a message that the slug was changed if it was not unique if user_slug and user_slug != event_translation_form.cleaned_data["slug"]: other_translation = EventTranslation.objects.filter( - event__region=region, slug=user_slug, language=language + event__region=region, + slug=user_slug, + language=language, ).first() other_translation_link = other_translation.backend_edit_link message = _( @@ -282,7 +291,7 @@ def post(self, request: HttpRequest, **kwargs: Any) -> HttpResponse: messages.success( request, _('Event "{}" was successfully created').format( - event_translation_form.instance + event_translation_form.instance, ), ) elif ( diff --git a/integreat_cms/cms/views/events/event_list_view.py b/integreat_cms/cms/views/events/event_list_view.py index f315eb1a70..b4ad1b1360 100644 --- a/integreat_cms/cms/views/events/event_list_view.py +++ b/integreat_cms/cms/views/events/event_list_view.py @@ -72,7 +72,8 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: if not request.user.has_perm("cms.change_event"): messages.warning( - request, _("You don't have the permission to edit or create events.") + request, + _("You don't have the permission to edit or create events."), ) # all events of the current region in the current language diff --git a/integreat_cms/cms/views/events/event_version_view.py b/integreat_cms/cms/views/events/event_version_view.py index 66892e467e..01e6adb14c 100644 --- a/integreat_cms/cms/views/events/event_version_view.py +++ b/integreat_cms/cms/views/events/event_version_view.py @@ -8,7 +8,6 @@ class EventVersionView(EventContextMixin, ContentVersionView): - # pylint: disable=too-many-ancestors """ View for browsing the event versions and restoring old event versions """ diff --git a/integreat_cms/cms/views/external_calendars/external_calendar_actions.py b/integreat_cms/cms/views/external_calendars/external_calendar_actions.py index f20b7248d6..6d05fe5a93 100644 --- a/integreat_cms/cms/views/external_calendars/external_calendar_actions.py +++ b/integreat_cms/cms/views/external_calendars/external_calendar_actions.py @@ -14,7 +14,9 @@ @require_POST @permission_required("cms.delete_externalcalendar") def delete_external_calendar( - request: HttpRequest, calendar_id: int, region_slug: str + request: HttpRequest, + calendar_id: int, + region_slug: str, ) -> HttpResponseRedirect: """ Delete external calendar diff --git a/integreat_cms/cms/views/external_calendars/external_calendar_form_view.py b/integreat_cms/cms/views/external_calendars/external_calendar_form_view.py index 558f6fbadf..41f580f809 100644 --- a/integreat_cms/cms/views/external_calendars/external_calendar_form_view.py +++ b/integreat_cms/cms/views/external_calendars/external_calendar_form_view.py @@ -45,10 +45,11 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: """ region = request.region external_calendar_instance = region.external_calendars.filter( - id=kwargs.get("calendar_id") + id=kwargs.get("calendar_id"), ).first() external_calendar_form = ExternalCalendarForm( - instance=external_calendar_instance, user=None + instance=external_calendar_instance, + user=None, ) return render( request, @@ -57,7 +58,7 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: "external_calendar_form": external_calendar_form, "current_menu_item": "external_calendar_list", "delete_dialog_title": _( - "Please confirm that you really want to delete this external calendar" + "Please confirm that you really want to delete this external calendar", ), }, ) @@ -73,10 +74,12 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: """ region = request.region external_calendar_instance = region.external_calendars.filter( - id=kwargs.get("calendar_id") + id=kwargs.get("calendar_id"), ).first() external_calendar_form = ExternalCalendarForm( - data=request.POST, instance=external_calendar_instance, user=request.user + data=request.POST, + instance=external_calendar_instance, + user=request.user, ) if not external_calendar_form.is_valid(): external_calendar_form.add_error_messages(request) @@ -93,14 +96,14 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.success( request, _('External calendar "{}" was successfully created').format( - external_calendar_form.instance + external_calendar_form.instance, ), ) else: messages.success( request, _('External calendar "{}" was successfully saved').format( - external_calendar_form.instance + external_calendar_form.instance, ), ) @@ -123,7 +126,9 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ) def show_import_messages( - self, external_calendar_instance: ExternalCalendar, import_result: ImportResult + self, + external_calendar_instance: ExternalCalendar, + import_result: ImportResult, ) -> None: """ This function shows the message after the import has been done @@ -138,7 +143,7 @@ def show_import_messages( messages.error( self.request, _( - "No events have been successfully imported from this external calendar" + "No events have been successfully imported from this external calendar", ), ) elif number_of_successes > 0 and number_of_errors > 0: @@ -146,12 +151,12 @@ def show_import_messages( self.request, _( _( - "Could not import %d events of this external calendar (out of %d total). See the status for more information" + "Could not import %d events of this external calendar (out of %d total). See the status for more information", ) % ( number_of_errors, number_of_errors + number_of_successes, - ) + ), ), ) else: diff --git a/integreat_cms/cms/views/external_calendars/external_calendar_list_view.py b/integreat_cms/cms/views/external_calendars/external_calendar_list_view.py index f4202c3fa0..2970d976b3 100644 --- a/integreat_cms/cms/views/external_calendars/external_calendar_list_view.py +++ b/integreat_cms/cms/views/external_calendars/external_calendar_list_view.py @@ -32,8 +32,8 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "current_menu_item": "external_calendar_list", "external_calendars": self.request.region.external_calendars.all(), "delete_dialog_title": _( - "Please confirm that you really want to delete this calendar" + "Please confirm that you really want to delete this calendar", ), - } + }, ) return context diff --git a/integreat_cms/cms/views/feedback/admin_feedback_actions.py b/integreat_cms/cms/views/feedback/admin_feedback_actions.py index f9e9c7dcd1..52de0c25a5 100644 --- a/integreat_cms/cms/views/feedback/admin_feedback_actions.py +++ b/integreat_cms/cms/views/feedback/admin_feedback_actions.py @@ -65,7 +65,9 @@ def mark_admin_feedback_as_unread(request: HttpRequest) -> HttpResponseRedirect: selected_feedback.update(read_by=None) logger.debug( - "Feedback objects %r marked as unread by %r", selected_ids, request.user + "Feedback objects %r marked as unread by %r", + selected_ids, + request.user, ) messages.success(request, _("Feedback was successfully marked as unread")) @@ -84,7 +86,7 @@ def archive_admin_feedback(request: HttpRequest) -> HttpResponseRedirect: selected_ids = request.POST.getlist("selected_ids[]") Feedback.objects.filter(id__in=selected_ids, is_technical=True).update( - archived=True + archived=True, ) invalidate_model(Feedback) @@ -106,7 +108,7 @@ def restore_admin_feedback(request: HttpRequest) -> HttpResponseRedirect: selected_ids = request.POST.getlist("selected_ids[]") Feedback.objects.filter(id__in=selected_ids, is_technical=True).update( - archived=False + archived=False, ) invalidate_model(Feedback) diff --git a/integreat_cms/cms/views/feedback/admin_feedback_list_view.py b/integreat_cms/cms/views/feedback/admin_feedback_list_view.py index 1a47d66670..05a63796e3 100644 --- a/integreat_cms/cms/views/feedback/admin_feedback_list_view.py +++ b/integreat_cms/cms/views/feedback/admin_feedback_list_view.py @@ -55,7 +55,8 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: """ admin_feedback = Feedback.objects.filter( - is_technical=True, archived=self.archived + is_technical=True, + archived=self.archived, ) # Filter pages according to given filters, if any @@ -76,7 +77,8 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: "current_menu_item": "admin_feedback", "admin_feedback": admin_feedback_chunk, "archived_count": Feedback.objects.filter( - is_technical=True, archived=True + is_technical=True, + archived=True, ).count(), "filter_form": filter_form, "search_query": query, diff --git a/integreat_cms/cms/views/feedback/feedback_resource.py b/integreat_cms/cms/views/feedback/feedback_resource.py index 92220236ea..7cea9a1d09 100644 --- a/integreat_cms/cms/views/feedback/feedback_resource.py +++ b/integreat_cms/cms/views/feedback/feedback_resource.py @@ -8,7 +8,6 @@ class FeedbackResource(resources.ModelResource): - # pylint: disable=too-few-public-methods """ This is the Resource class that connects to the django-import-export library """ @@ -31,7 +30,8 @@ class FeedbackResource(resources.ModelResource): rating = fields.Field(column_name=_("Rating"), attribute="get_rating_display") read_by_username = fields.Field( - column_name=_("Read by"), attribute="read_by__full_user_name" + column_name=_("Read by"), + attribute="read_by__full_user_name", ) comment = fields.Field(column_name=_("Comment"), attribute="comment") @@ -43,7 +43,6 @@ class FeedbackResource(resources.ModelResource): ) def get_instance(self, *args: Any, **kwargs: Any) -> Any: - # pylint: disable=useless-parent-delegation """ See :meth:`import_export.resources.Resource.get_instance` """ diff --git a/integreat_cms/cms/views/feedback/region_feedback_actions.py b/integreat_cms/cms/views/feedback/region_feedback_actions.py index 0c9cf60f7b..48fe09b925 100644 --- a/integreat_cms/cms/views/feedback/region_feedback_actions.py +++ b/integreat_cms/cms/views/feedback/region_feedback_actions.py @@ -30,7 +30,8 @@ @require_POST @permission_required("cms.change_feedback") def mark_region_feedback_as_read( - request: HttpRequest, region_slug: str + request: HttpRequest, + region_slug: str, ) -> HttpResponseRedirect: """ Set read flag for a list of feedback items @@ -44,7 +45,9 @@ def mark_region_feedback_as_read( selected_ids = request.POST.getlist("selected_ids[]") selected_feedback = Feedback.objects.filter( - id__in=selected_ids, region=region, is_technical=False + id__in=selected_ids, + region=region, + is_technical=False, ) for feedback in selected_feedback: invalidate_obj(feedback) @@ -65,7 +68,8 @@ def mark_region_feedback_as_read( @require_POST @permission_required("cms.change_feedback") def mark_region_feedback_as_unread( - request: HttpRequest, region_slug: str + request: HttpRequest, + region_slug: str, ) -> HttpResponseRedirect: """ Unset read flag for a list of feedback items @@ -79,7 +83,9 @@ def mark_region_feedback_as_unread( selected_ids = request.POST.getlist("selected_ids[]") selected_feedback = Feedback.objects.filter( - id__in=selected_ids, region=region, is_technical=False + id__in=selected_ids, + region=region, + is_technical=False, ) for feedback in selected_feedback: invalidate_obj(feedback) @@ -100,7 +106,8 @@ def mark_region_feedback_as_unread( @require_POST @permission_required("cms.change_feedback") def archive_region_feedback( - request: HttpRequest, region_slug: str + request: HttpRequest, + region_slug: str, ) -> HttpResponseRedirect: """ Archive a list of feedback items @@ -114,7 +121,9 @@ def archive_region_feedback( selected_ids = request.POST.getlist("selected_ids[]") Feedback.objects.filter( - id__in=selected_ids, region=region, is_technical=False + id__in=selected_ids, + region=region, + is_technical=False, ).update(archived=True) invalidate_model(Feedback) @@ -127,7 +136,8 @@ def archive_region_feedback( @require_POST @permission_required("cms.change_feedback") def restore_region_feedback( - request: HttpRequest, region_slug: str + request: HttpRequest, + region_slug: str, ) -> HttpResponseRedirect: """ Restore a list of feedback items @@ -141,7 +151,9 @@ def restore_region_feedback( selected_ids = request.POST.getlist("selected_ids[]") Feedback.objects.filter( - id__in=selected_ids, region=region, is_technical=False + id__in=selected_ids, + region=region, + is_technical=False, ).update(archived=False) invalidate_model(Feedback) @@ -154,7 +166,8 @@ def restore_region_feedback( @require_POST @permission_required("cms.delete_feedback") def delete_region_feedback( - request: HttpRequest, region_slug: str + request: HttpRequest, + region_slug: str, ) -> HttpResponseRedirect: """ Delete a list of feedback items @@ -168,7 +181,9 @@ def delete_region_feedback( selected_ids = request.POST.getlist("selected_ids[]") Feedback.objects.filter( - id__in=selected_ids, region=region, is_technical=False + id__in=selected_ids, + region=region, + is_technical=False, ).delete() logger.info("Feedback objects %r deleted by %r", selected_ids, request.user) @@ -181,20 +196,21 @@ def delete_region_feedback( @permission_required("cms.view_feedback") def export_region_feedback( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, file_format: str, ) -> HttpResponse: """ Export a list of feedback items :param request: Object representing the user call - :param region_slug: The slug of the current region :param file_format: The export format :return: Response with file """ selected_ids = request.POST.getlist("selected_ids[]") selected_feedback = Feedback.objects.filter( - id__in=selected_ids, region=request.region, is_technical=False + id__in=selected_ids, + region=request.region, + is_technical=False, ) resource = FeedbackResource() diff --git a/integreat_cms/cms/views/feedback/region_feedback_list_view.py b/integreat_cms/cms/views/feedback/region_feedback_list_view.py index 28c144fa6f..382451c5f7 100644 --- a/integreat_cms/cms/views/feedback/region_feedback_list_view.py +++ b/integreat_cms/cms/views/feedback/region_feedback_list_view.py @@ -57,7 +57,9 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: region = request.region region_feedback = Feedback.objects.filter( - region=region, is_technical=False, archived=self.archived + region=region, + is_technical=False, + archived=self.archived, ) filter_form = RegionFeedbackFilterForm(data=request.GET) @@ -77,7 +79,9 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: "current_menu_item": "region_feedback", "region_feedback": region_feedback_chunk, "archived_count": Feedback.objects.filter( - region=region, is_technical=False, archived=True + region=region, + is_technical=False, + archived=True, ).count(), "filter_form": filter_form, "search_query": query, diff --git a/integreat_cms/cms/views/form_views.py b/integreat_cms/cms/views/form_views.py index 6e3c90ba99..2217aeb432 100644 --- a/integreat_cms/cms/views/form_views.py +++ b/integreat_cms/cms/views/form_views.py @@ -32,7 +32,6 @@ class CustomModelFormMixin( ModelConfirmationContextMixin, MediaContextMixin, ): - # pylint: disable=too-many-ancestors """ This mixin handles error messages in form views of subclasses of :class:`~integreat_cms.cms.forms.custom_model_form.CustomModelForm` @@ -117,14 +116,16 @@ def form_valid(self, form: CustomModelForm) -> HttpResponseRedirect: messages.success( self.request, _('{} "{}" was successfully saved').format( - self.object._meta.verbose_name, self.object + self.object._meta.verbose_name, + self.object, ), ) else: messages.success( self.request, _('{} "{}" was successfully created').format( - form.instance._meta.verbose_name, form.instance + form.instance._meta.verbose_name, + form.instance, ), ) return super().form_valid(form) diff --git a/integreat_cms/cms/views/imprint/imprint_actions.py b/integreat_cms/cms/views/imprint/imprint_actions.py index 01be0fd93b..188451d92e 100644 --- a/integreat_cms/cms/views/imprint/imprint_actions.py +++ b/integreat_cms/cms/views/imprint/imprint_actions.py @@ -58,7 +58,8 @@ def delete_imprint(request: HttpRequest, region_slug: str) -> HttpResponseRedire def expand_imprint_translation_id( - request: HttpRequest, imprint_translation_id: int + request: HttpRequest, + imprint_translation_id: int, ) -> HttpResponseRedirect | HttpResponseNotFound: """ Searches for an imprint translation with corresponding ID and redirects browser to web app @@ -69,7 +70,7 @@ def expand_imprint_translation_id( """ imprint_translation = ImprintPageTranslation.objects.get( - id=imprint_translation_id + id=imprint_translation_id, ).public_version if imprint_translation and not imprint_translation.page.archived: diff --git a/integreat_cms/cms/views/imprint/imprint_context_mixin.py b/integreat_cms/cms/views/imprint/imprint_context_mixin.py index 1dc86cb1f8..e4452035ab 100644 --- a/integreat_cms/cms/views/imprint/imprint_context_mixin.py +++ b/integreat_cms/cms/views/imprint/imprint_context_mixin.py @@ -13,7 +13,6 @@ class ImprintContextMixin(ContextMixin): - # pylint: disable=too-few-public-methods """ This mixin provides extra context for imprint views """ @@ -31,6 +30,6 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: { "current_menu_item": "imprint", "IMPRINT_SLUG": settings.IMPRINT_SLUG, - } + }, ) return context diff --git a/integreat_cms/cms/views/imprint/imprint_form_view.py b/integreat_cms/cms/views/imprint/imprint_form_view.py index c02d133183..8a9ea5f236 100644 --- a/integreat_cms/cms/views/imprint/imprint_form_view.py +++ b/integreat_cms/cms/views/imprint/imprint_form_view.py @@ -110,7 +110,7 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ) message = __( _( - "This is not the most recent public revision of this translation." + "This is not the most recent public revision of this translation.", ), _( "Instead, revision {} is shown in the apps.", @@ -131,11 +131,13 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: if not request.user.has_perm("cms.change_imprintpage"): disabled = True messages.warning( - request, _("You don't have the permission to edit the imprint.") + request, + _("You don't have the permission to edit the imprint."), ) imprint_translation_form = ImprintTranslationForm( - instance=imprint_translation, disabled=disabled + instance=imprint_translation, + disabled=disabled, ) # If the imprint does not exist yet, create the key manually @@ -155,7 +157,9 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: # Languages for tab view "languages": region.active_languages if imprint else [language], "side_by_side_language_options": self.get_side_by_side_language_options( - region, language, imprint + region, + language, + imprint, ), "translation_states": imprint.translation_states if imprint else [], "lock_key": edit_lock_key, @@ -184,7 +188,8 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: region = request.region language = region.get_language_or_404( - kwargs.get("language_slug"), only_active=True + kwargs.get("language_slug"), + only_active=True, ) try: @@ -259,7 +264,9 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: region.active_languages if imprint_instance else [language] ), "side_by_side_language_options": self.get_side_by_side_language_options( - region, language, imprint_instance + region, + language, + imprint_instance, ), "translation_states": ( imprint_instance.translation_states if imprint_instance else [] @@ -278,7 +285,9 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: @staticmethod def get_side_by_side_language_options( - region: Region, language: Language, imprint: ImprintPage | None + region: Region, + language: Language, + imprint: ImprintPage | None, ) -> list[dict[str, Any]]: """ This is a helper function to generate the side-by-side language options for both the get and post requests. @@ -304,6 +313,6 @@ def get_side_by_side_language_options( ), "selected": language_node.language == language, "disabled": not source_translation.exists(), - } + }, ) return side_by_side_language_options diff --git a/integreat_cms/cms/views/imprint/imprint_sbs_view.py b/integreat_cms/cms/views/imprint/imprint_sbs_view.py index 556b850a04..51895a6e51 100644 --- a/integreat_cms/cms/views/imprint/imprint_sbs_view.py +++ b/integreat_cms/cms/views/imprint/imprint_sbs_view.py @@ -51,8 +51,8 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: kwargs={ "region_slug": kwargs["region_slug"], }, - ) - } + ), + }, ) return context @@ -80,12 +80,13 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: if not request.user.has_perm("cms.change_imprintpage"): disabled = True messages.warning( - request, _("You don't have the permission to edit the imprint.") + request, + _("You don't have the permission to edit the imprint."), ) target_language = Language.objects.get(slug=kwargs.get("language_slug")) source_language_node = region.language_tree_nodes.get( - language=target_language + language=target_language, ).parent if source_language_node: @@ -94,7 +95,7 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.error( request, _( - "You cannot use the side-by-side-view for the region's default language (in this case {default_language})." + "You cannot use the side-by-side-view for the region's default language (in this case {default_language}).", ).format(default_language=target_language.translated_name), ) return redirect( @@ -112,7 +113,7 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.error( request, _( - "You cannot use the side-by-side-view if the source translation (in this case {source_language}) does not exist." + "You cannot use the side-by-side-view if the source translation (in this case {source_language}) does not exist.", ).format(source_language=source_language.translated_name), ) return redirect( @@ -124,11 +125,14 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ) imprint_translation_form = ImprintTranslationForm( - instance=target_imprint_translation, disabled=disabled + instance=target_imprint_translation, + disabled=disabled, ) old_translation_content = get_old_source_content( - imprint, source_language_node.language, target_language + imprint, + source_language_node.language, + target_language, ) return render( @@ -165,18 +169,18 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: target_language = Language.objects.get(slug=kwargs.get("language_slug")) source_language_node = region.language_tree_nodes.get( - language=target_language + language=target_language, ).parent if source_language_node: source_imprint_translation = imprint.get_translation( - source_language_node.language.slug + source_language_node.language.slug, ) else: messages.error( request, _( - "You cannot use the side-by-side-view for the region's default language (in this case {default_language})." + "You cannot use the side-by-side-view for the region's default language (in this case {default_language}).", ).format(default_language=target_language.translated_name), ) return redirect( @@ -193,7 +197,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.error( request, _( - "You cannot use the side-by-side-view if the source translation (in this case {source_language}) does not exist." + "You cannot use the side-by-side-view if the source translation (in this case {source_language}) does not exist.", ).format(source_language=source_language_node.language.translated_name), ) return redirect( @@ -234,7 +238,9 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ) old_translation_content = get_old_source_content( - imprint, source_language_node.language, target_language + imprint, + source_language_node.language, + target_language, ) return render( @@ -251,7 +257,9 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: def get_old_source_content( - imprint: ImprintPage, source_language: Language, target_language: Language + imprint: ImprintPage, + source_language: Language, + target_language: Language, ) -> str: """ This function returns the content of the source language translation that was up to date when the latest (no minor edit) @@ -264,16 +272,20 @@ def get_old_source_content( """ # For the text diff, use the latest source translation that was created before the latest no minor edit target translation - if major_target_imprint_translation := imprint.translations.filter( - language__slug=target_language.slug, minor_edit=False - ).first(): - if source_previous_translation := ( + if ( + major_target_imprint_translation := imprint.translations.filter( + language__slug=target_language.slug, + minor_edit=False, + ).first() + ) and ( + source_previous_translation := ( imprint.translations.filter( language=source_language, last_updated__lte=major_target_imprint_translation.last_updated, ) .order_by("-last_updated") .first() - ): - return source_previous_translation.content + ) + ): + return source_previous_translation.content return "" diff --git a/integreat_cms/cms/views/imprint/imprint_version_view.py b/integreat_cms/cms/views/imprint/imprint_version_view.py index e211b30262..179a7e4e37 100644 --- a/integreat_cms/cms/views/imprint/imprint_version_view.py +++ b/integreat_cms/cms/views/imprint/imprint_version_view.py @@ -15,7 +15,6 @@ class ImprintVersionView(ImprintContextMixin, ContentVersionView): - # pylint: disable=too-many-ancestors """ View for browsing the imprint versions and restoring old imprint versions """ diff --git a/integreat_cms/cms/views/language_tree/language_tree_actions.py b/integreat_cms/cms/views/language_tree/language_tree_actions.py index b2dcb65a81..d7f69b1bf6 100644 --- a/integreat_cms/cms/views/language_tree/language_tree_actions.py +++ b/integreat_cms/cms/views/language_tree/language_tree_actions.py @@ -61,7 +61,7 @@ def move_language_tree_node( try: if target.depth == 1 and target_position in [position.LEFT, position.RIGHT]: - raise InvalidPosition(_("A region can only have one root language.")) + raise InvalidPosition(_("A region can only have one root language.")) # noqa: TRY301 language_tree_node.move(target, target_position) # Call the save method on the (reloaded) node in order to trigger possible signal handlers etc. # (The move()-method executes raw sql which might cause problems if the instance isn't fetched again) @@ -71,7 +71,7 @@ def move_language_tree_node( messages.success( request, _('The language tree node "{}" was successfully moved.').format( - language_tree_node.translated_name + language_tree_node.translated_name, ), ) logger.debug( @@ -83,7 +83,7 @@ def move_language_tree_node( ) except (ValueError, InvalidPosition, InvalidMoveToDescendant) as e: messages.error(request, e) - logger.exception(e) + logger.exception("") return redirect("languagetreenodes", **{"region_slug": region_slug}) @@ -92,7 +92,9 @@ def move_language_tree_node( @permission_required("cms.delete_languagetreenode") @tree_mutex("languagetreenode") def delete_language_tree_node( - request: HttpRequest, region_slug: str, pk: int + request: HttpRequest, + region_slug: str, + pk: int, ) -> HttpResponseRedirect: """ Deletes the language node of distinct region @@ -128,7 +130,7 @@ def delete_language_tree_node( messages.error( request, _( - 'The language tree node "{}" cannot be deleted because it is the source language of other language(s).' + 'The language tree node "{}" cannot be deleted because it is the source language of other language(s).', ).format(language_node.translated_name), ) return redirect("languagetreenodes", **{"region_slug": region_slug}) @@ -138,7 +140,7 @@ def delete_language_tree_node( messages.success( request, _( - 'The language tree node "{}" and all corresponding translations were successfully deleted.' + 'The language tree node "{}" and all corresponding translations were successfully deleted.', ).format(language_node.translated_name), ) return redirect("languagetreenodes", **{"region_slug": region_slug}) diff --git a/integreat_cms/cms/views/language_tree/language_tree_bulk_actions.py b/integreat_cms/cms/views/language_tree/language_tree_bulk_actions.py index def175a980..f2eecd6447 100644 --- a/integreat_cms/cms/views/language_tree/language_tree_bulk_actions.py +++ b/integreat_cms/cms/views/language_tree/language_tree_bulk_actions.py @@ -47,7 +47,7 @@ def field_name(self) -> str: :raises NotImplementedError: If the ``field_name`` attribute is not implemented in the subclass """ raise NotImplementedError( - "Subclasses of LanguageTreeBulkActionView must provide a 'field_name' attribute" + "Subclasses of LanguageTreeBulkActionView must provide a 'field_name' attribute", ) @property @@ -58,12 +58,15 @@ def action(self) -> str: :raises NotImplementedError: If the ``action`` attribute is not implemented in the subclass """ raise NotImplementedError( - "Subclasses of LanguageTreeBulkActionView must provide an 'action' attribute" + "Subclasses of LanguageTreeBulkActionView must provide an 'action' attribute", ) @tree_mutex("languagetreenode") def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Execute bulk action for language tree node and flush the cache @@ -89,7 +92,6 @@ def post( class BulkMakeVisibleView(LanguageTreeBulkActionView): - # pylint: disable=too-many-ancestors """ Bulk action for making multiple language tree nodes visible at once """ @@ -102,7 +104,6 @@ class BulkMakeVisibleView(LanguageTreeBulkActionView): class BulkHideView(BulkMakeVisibleView): - # pylint: disable=too-many-ancestors """ Bulk action for hiding multiple language tree nodes at once """ @@ -115,7 +116,6 @@ class BulkHideView(BulkMakeVisibleView): class BulkActivateView(LanguageTreeBulkActionView): - # pylint: disable=too-many-ancestors """ Bulk action for activating multiple language tree nodes at once """ @@ -127,7 +127,10 @@ class BulkActivateView(LanguageTreeBulkActionView): action = _("activated") def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: for language_tree_node in self.get_queryset(): models = [PageTranslation, EventTranslation, POITranslation] @@ -148,7 +151,6 @@ def post( class BulkDisableView(BulkActivateView): - # pylint: disable=too-many-ancestors """ Bulk action for disabling multiple language tree nodes at once """ diff --git a/integreat_cms/cms/views/language_tree/language_tree_context_mixin.py b/integreat_cms/cms/views/language_tree/language_tree_context_mixin.py index f4cfda0049..14ec7fbb17 100644 --- a/integreat_cms/cms/views/language_tree/language_tree_context_mixin.py +++ b/integreat_cms/cms/views/language_tree/language_tree_context_mixin.py @@ -13,7 +13,6 @@ class LanguageTreeContextMixin(ContextMixin): - # pylint: disable=too-few-public-methods """ This mixin provides extra context for language tree views """ @@ -30,11 +29,11 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context.update( { "delete_dialog_title": _( - "Please confirm that you really want to delete this language node" + "Please confirm that you really want to delete this language node", ), "delete_dialog_text": _( - "All translations for pages, locations, events and push notifications of this language will also be deleted." + "All translations for pages, locations, events and push notifications of this language will also be deleted.", ), - } + }, ) return context diff --git a/integreat_cms/cms/views/language_tree/language_tree_view.py b/integreat_cms/cms/views/language_tree/language_tree_view.py index ded40db617..4b9631a838 100644 --- a/integreat_cms/cms/views/language_tree/language_tree_view.py +++ b/integreat_cms/cms/views/language_tree/language_tree_view.py @@ -14,7 +14,6 @@ class LanguageTreeView(LanguageTreeContextMixin, ModelListView): - # pylint: disable=too-many-ancestors """ View for rendering the language tree view. This view is available in regions. diff --git a/integreat_cms/cms/views/linkcheck/link_replace_view.py b/integreat_cms/cms/views/linkcheck/link_replace_view.py index 12a1cc9982..83cc306655 100644 --- a/integreat_cms/cms/views/linkcheck/link_replace_view.py +++ b/integreat_cms/cms/views/linkcheck/link_replace_view.py @@ -39,7 +39,8 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: :return: The rendered template response """ form = LinkReplaceForm( - region=self.request.region, initial={"link_types": ["internal"]} + region=self.request.region, + initial={"link_types": ["internal"]}, ) return render( diff --git a/integreat_cms/cms/views/linkcheck/linkcheck_list_view.py b/integreat_cms/cms/views/linkcheck/linkcheck_list_view.py index 0d0489a9cf..89aca02588 100644 --- a/integreat_cms/cms/views/linkcheck/linkcheck_list_view.py +++ b/integreat_cms/cms/views/linkcheck/linkcheck_list_view.py @@ -105,7 +105,9 @@ def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpRespo region = request.region.slug if request.region else None try: self.instance = get_urls( - region, url_ids=[edit_url_id], prefetch_region_links=True + region, + url_ids=[edit_url_id], + prefetch_region_links=True, )[0] except IndexError as e: raise Http404("This URL does not exist") from e @@ -129,10 +131,10 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> TemplateRespon """ try: return super().get(request, *args, **kwargs) - except Http404 as e: + except Http404: # If already the last page was requested, raise the error if request.GET.get("page") == "last": - raise e + raise # If the page does not exist, use the last page as fallback logger.debug("Redirecting to last page because response was 404") params = {"page": "last"} @@ -141,7 +143,6 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> TemplateRespon return redirect(f"{request.path}?{urlencode(params)}") def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: - # pylint: disable-msg=too-many-branches, too-many-locals r""" Applies selected action for selected urls @@ -176,7 +177,8 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.success(request, _("Email link was successfully replaced")) elif new_url.startswith("tel:"): messages.success( - request, _("Phone number link was successfully replaced") + request, + _("Phone number link was successfully replaced"), ) else: messages.success(request, _("URL was successfully replaced")) @@ -207,7 +209,8 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ) Link.objects.filter(id__in=link_ids).update(ignore=True) messages.success( - request, _("Links were successfully marked as verified") + request, + _("Links were successfully marked as verified"), ) elif action == "unignore": for url in selected_urls: diff --git a/integreat_cms/cms/views/list_views.py b/integreat_cms/cms/views/list_views.py index 4f9f7612cf..b7d3bd39f1 100644 --- a/integreat_cms/cms/views/list_views.py +++ b/integreat_cms/cms/views/list_views.py @@ -28,7 +28,6 @@ class ModelListView( ModelConfirmationContextMixin, ListView, ): - # pylint: disable=too-many-ancestors """ Render some list of objects, set by ``self.model`` or ``self.queryset``. """ diff --git a/integreat_cms/cms/views/media/media_actions.py b/integreat_cms/cms/views/media/media_actions.py index d1d0137479..498a9b8a01 100644 --- a/integreat_cms/cms/views/media/media_actions.py +++ b/integreat_cms/cms/views/media/media_actions.py @@ -38,13 +38,12 @@ @json_response def get_directory_path_ajax( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> JsonResponse: """ View provides the frontend with the current directory path for the breadcrumbs. :param request: The current request - :param region_slug: The slug of the current region :return: JSON response which indicates error or success """ region = request.region @@ -69,13 +68,12 @@ def get_directory_path_ajax( @json_response def get_directory_content_ajax( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> JsonResponse: """ View provides the frontend with the content of a directory via AJAX. :param request: The current request - :param region_slug: The slug of the current region :return: JSON response which indicates error or success """ region = request.region @@ -94,7 +92,8 @@ def get_directory_content_ajax( Q(parent_directory=directory), ) directories = Directory.objects.filter( - Q(region=region) | Q(region__isnull=True, is_hidden=False), parent=directory + Q(region=region) | Q(region__isnull=True, is_hidden=False), + parent=directory, ) result = [d.serialize() for d in list(directories) + list(media_files)] @@ -107,13 +106,12 @@ def get_directory_content_ajax( @permission_required("cms.view_mediafile") def get_query_search_results_ajax( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> JsonResponse: """ View to search the media library :param request: The current request - :param region_slug: The slug of the current region :return: JSON response with the search result """ query = request.GET.get("query") @@ -132,13 +130,12 @@ def get_query_search_results_ajax( @permission_required("cms.view_mediafile") def get_file_usages_ajax( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> JsonResponse: """ View to search unused media files :param request: The current request - :param region_slug: The slug of the current region :return: JSON response with the search result """ @@ -157,18 +154,17 @@ def get_file_usages_ajax( @permission_required("cms.view_mediafile") def get_unused_media_files_ajax( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> JsonResponse: """ View to search unused media files :param request: The current request - :param region_slug: The slug of the current region :return: JSON response with the search result """ unused_media_files = MediaFile.objects.filter( - Q(region=request.region) | Q(region__isnull=True, is_hidden=False) + Q(region=request.region) | Q(region__isnull=True, is_hidden=False), ).filter_unused() result = [d.serialize() for d in unused_media_files] @@ -181,13 +177,12 @@ def get_unused_media_files_ajax( @json_response def upload_file_ajax( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> JsonResponse: """ View to create a file via an AJAX upload. :param request: The current request - :param region_slug: The slug of the current region :return: JSON response which indicates error or success """ region = request.region @@ -214,12 +209,12 @@ def upload_file_ajax( { "type": "success", "text": _('File "{}" was uploaded successfully').format( - media_file.name + media_file.name, ), - } + }, ], "file": media_file.serialize(), - } + }, ) @@ -228,19 +223,19 @@ def upload_file_ajax( @json_response def edit_file_ajax( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> JsonResponse: """ View provides the edit of a file via AJAX. :param request: The current request - :param region_slug: The slug of the current region :return: JSON response which indicates error or success """ region = request.region media_file = get_object_or_404( - MediaFile.objects.filter(region=region), id=request.POST.get("id") + MediaFile.objects.filter(region=region), + id=request.POST.get("id"), ) media_file_form = MediaFileForm(data=request.POST, instance=media_file) @@ -260,10 +255,10 @@ def edit_file_ajax( { "type": "info", "text": _("No changes detected"), - } + }, ], "file": media_file.serialize(), - } + }, ) # Save form @@ -275,12 +270,12 @@ def edit_file_ajax( { "type": "success", "text": _('File "{}" was saved successfully').format( - media_file.name + media_file.name, ), - } + }, ], "file": media_file.serialize(), - } + }, ) @@ -289,23 +284,26 @@ def edit_file_ajax( @json_response def replace_file_ajax( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> JsonResponse: """ View provides the replacement of a file via AJAX. :param request: The current request - :param region_slug: The slug of the current region :return: JSON response which indicates error or success """ region = request.region media_file = get_object_or_404( - MediaFile.objects.filter(region=region), id=request.POST.get("id") + MediaFile.objects.filter(region=region), + id=request.POST.get("id"), ) media_file_form = ReplaceMediaFileForm( - user=request.user, data=request.POST, instance=media_file, files=request.FILES + user=request.user, + data=request.POST, + instance=media_file, + files=request.FILES, ) if not media_file_form.is_valid(): @@ -326,12 +324,12 @@ def replace_file_ajax( { "type": "success", "text": _('File "{}" was saved successfully').format( - media_file.name + media_file.name, ), - } + }, ], "file": media_file.serialize(), - } + }, ) @@ -340,19 +338,19 @@ def replace_file_ajax( @json_response def delete_file_ajax( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> JsonResponse: """ View to delete a file via an AJAX call. :param request: The current request - :param region_slug: The slug of the current region :return: JSON response which indicates error or success """ region = request.region media_file = get_object_or_404( - MediaFile.objects.filter(region=region), id=request.POST.get("id") + MediaFile.objects.filter(region=region), + id=request.POST.get("id"), ) # Check if the media file is in use @@ -363,9 +361,9 @@ def delete_file_ajax( { "type": "warning", "text": _( - 'File "{}" cannot be deleted because it is used as an icon or content.' + 'File "{}" cannot be deleted because it is used as an icon or content.', ).format(media_file.name), - } + }, ], }, status=400, @@ -383,12 +381,12 @@ def delete_file_ajax( { "type": "success", "text": _('File "{}" was successfully deleted').format( - media_file.name + media_file.name, ), - } + }, ], "file": media_file.serialize(), - } + }, ) @@ -397,13 +395,12 @@ def delete_file_ajax( @json_response def create_directory_ajax( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> JsonResponse: """ View provides the frontend with the option to create a directory via AJAX. :param request: The current request - :param region_slug: The slug of the current region :return: JSON response which indicates error or success """ region = request.region @@ -430,12 +427,12 @@ def create_directory_ajax( { "type": "success", "text": _('Directory "{}" was created successfully').format( - directory.name + directory.name, ), - } + }, ], "directory": directory.serialize(), - } + }, ) @@ -444,19 +441,19 @@ def create_directory_ajax( @json_response def edit_directory_ajax( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> JsonResponse: """ View provides the frontend with the option to modify a directory via AJAX. :param request: The current request - :param region_slug: The slug of the current region :return: JSON response which indicates error or success """ region = request.region directory = get_object_or_404( - Directory.objects.filter(region=region), id=request.POST.get("id") + Directory.objects.filter(region=region), + id=request.POST.get("id"), ) directory_form = DirectoryForm(data=request.POST, instance=directory) @@ -474,7 +471,7 @@ def edit_directory_ajax( { "messages": [{"type": "info", "text": _("No changes detected")}], "directory": directory.serialize(), - } + }, ) # Save form @@ -486,12 +483,12 @@ def edit_directory_ajax( { "type": "success", "text": _('Directory "{}" was saved successfully').format( - directory.name + directory.name, ), - } + }, ], "directory": directory.serialize(), - } + }, ) @@ -500,19 +497,19 @@ def edit_directory_ajax( @json_response def delete_directory_ajax( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> JsonResponse: """ View provides the frontend with the option to delete a directory via AJAX. :param request: The current request - :param region_slug: The slug of the current region :return: JSON response which indicates error or success """ region = request.region directory = get_object_or_404( - Directory.objects.filter(region=region), id=request.POST.get("id") + Directory.objects.filter(region=region), + id=request.POST.get("id"), ) serialized = directory.serialize() @@ -525,9 +522,9 @@ def delete_directory_ajax( { "type": "warning", "text": _( - 'Directory "{}" cannot be deleted because it is not empty' + 'Directory "{}" cannot be deleted because it is not empty', ).format(directory.name), - } + }, ], }, status=400, @@ -539,12 +536,12 @@ def delete_directory_ajax( { "type": "success", "text": _('Directory "{}" was successfully deleted').format( - directory.name + directory.name, ), - } + }, ], "directory": serialized, - } + }, ) @@ -554,20 +551,20 @@ def delete_directory_ajax( @json_response def move_file_ajax( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> JsonResponse: """ This view provides the frontend with the option to move files via AJAX. :param request: The current request - :param region_slug: The slug of the current region :return: JSON response which indicates error or success """ region = request.region media_file = get_object_or_404( - MediaFile.objects.filter(region=region), id=request.POST.get("mediafile_id") + MediaFile.objects.filter(region=region), + id=request.POST.get("mediafile_id"), ) media_move_form = MediaMoveForm(data=request.POST, instance=media_file) @@ -584,7 +581,7 @@ def move_file_ajax( return JsonResponse( { "messages": [{"type": "info", "text": _("No changes detected")}], - } + }, ) # Save form @@ -596,9 +593,9 @@ def move_file_ajax( { "type": "success", "text": _( - 'File "{}" was moved successfully into directory "{}"' + 'File "{}" was moved successfully into directory "{}"', ).format(media_file.name, media_file.parent_directory or "Home"), - } + }, ], - } + }, ) diff --git a/integreat_cms/cms/views/media/media_context_mixin.py b/integreat_cms/cms/views/media/media_context_mixin.py index 98abd4c346..ba135f568b 100644 --- a/integreat_cms/cms/views/media/media_context_mixin.py +++ b/integreat_cms/cms/views/media/media_context_mixin.py @@ -13,7 +13,6 @@ class MediaContextMixin(ContextMixin): - # pylint: disable=too-few-public-methods """ This mixin provides context data required by the the media library. """ @@ -51,7 +50,7 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "btn_change_directory": _("Save changes"), "btn_delete_directory": _("Delete directory"), "btn_delete_empty_directory": _( - "You can only delete empty directories" + "You can only delete empty directories", ), "btn_back": _("Back"), "btn_close": _("Close detail view"), @@ -81,17 +80,17 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "text_enter_directory_name": _("Enter directory name here"), "text_file_readonly": _("This file is read-only and cannot be edited."), "text_dir_readonly": _( - "This directory is read-only and cannot be edited." + "This directory is read-only and cannot be edited.", ), "text_only_image": _("Only images can be selected as an icon."), "text_file_delete_confirm": _( - "Please confirm that you really want to delete this file" + "Please confirm that you really want to delete this file", ), "text_dir_delete_confirm": _( - "Please confirm that you really want to delete this directory" + "Please confirm that you really want to delete this directory", ), "text_error_invalid_file_type": _( - "This file type is not supported. Supported types are:" + "This file type is not supported. Supported types are:", ), "text_error": ( _("An error has occurred.") + " " + _("Please try again later.") @@ -102,7 +101,7 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: + _("Please try again later.") ), "text_allowed_media_types": ", ".join( - map(str, dict(allowed_media.UPLOAD_CHOICES).values()) + map(str, dict(allowed_media.UPLOAD_CHOICES).values()), ), }, "mediaTypes": { @@ -144,7 +143,8 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "deleteFile": reverse("mediacenter_delete_file", kwargs=kwargs), "replaceFile": reverse("mediacenter_replace_file", kwargs=kwargs), "filterUnusedMediaFiles": reverse( - "mediacenter_filter_unused_media_files", kwargs=kwargs + "mediacenter_filter_unused_media_files", + kwargs=kwargs, ), } diff --git a/integreat_cms/cms/views/mixins.py b/integreat_cms/cms/views/mixins.py index 784c2ccc24..01b54ea1f6 100644 --- a/integreat_cms/cms/views/mixins.py +++ b/integreat_cms/cms/views/mixins.py @@ -54,7 +54,6 @@ def template_name(self) -> str: class ModelConfirmationContextMixin(ContextMixin): - # pylint: disable=too-few-public-methods """ A mixin that can be used to inject confirmation text into a template of a model (e.g. list or form) """ @@ -71,16 +70,15 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context.update( { "delete_dialog_title": _( - "Please confirm that you really want to delete this {}" + "Please confirm that you really want to delete this {}", ).format(self.model._meta.verbose_name), "delete_dialog_text": _("This cannot be reversed."), - } + }, ) return context class ContentEditLockMixin(ContextMixin): - # pylint: disable=too-few-public-methods """ A mixin that provides some variables required for the content edit lock """ @@ -105,14 +103,13 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "region_slug": kwargs["region_slug"], "language_slug": kwargs["language_slug"], }, - ) - } + ), + }, ) return context class MachineTranslationContextMixin(ContextMixin): - # pylint: disable=too-few-public-methods """ This mixin provides extra context for machine translation options """ @@ -131,7 +128,9 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context["MT_PROVIDER"] = language_node.mt_provider context["MT_PERMITTED"] = ( MachineTranslationProvider.is_permitted( - self.request.region, self.request.user, self.translation_model + self.request.region, + self.request.user, + self.translation_model, ) and not language_node.is_root() and not ( diff --git a/integreat_cms/cms/views/organizations/organization_actions.py b/integreat_cms/cms/views/organizations/organization_actions.py index ef8973d0cc..f64d257659 100644 --- a/integreat_cms/cms/views/organizations/organization_actions.py +++ b/integreat_cms/cms/views/organizations/organization_actions.py @@ -23,7 +23,9 @@ @require_POST @permission_required("cms.change_organization") def archive( - request: HttpRequest, region_slug: str, organization_id: int + request: HttpRequest, + region_slug: str, + organization_id: int, ) -> HttpResponseRedirect: """ Set archived flag for an organization @@ -52,7 +54,9 @@ def archive( @require_POST @permission_required("cms.change_organization") def restore( - request: HttpRequest, region_slug: str, organization_id: int + request: HttpRequest, + region_slug: str, + organization_id: int, ) -> HttpResponseRedirect: """ Remove archived flag for an organization @@ -76,7 +80,9 @@ def restore( @require_POST @permission_required("cms.delete_organization") def delete( - request: HttpRequest, region_slug: str, organization_id: int + request: HttpRequest, + region_slug: str, + organization_id: int, ) -> HttpResponseRedirect: """ Delete a single organization diff --git a/integreat_cms/cms/views/organizations/organization_bulk_actions.py b/integreat_cms/cms/views/organizations/organization_bulk_actions.py index 4689222a87..d440f95fdf 100644 --- a/integreat_cms/cms/views/organizations/organization_bulk_actions.py +++ b/integreat_cms/cms/views/organizations/organization_bulk_actions.py @@ -4,7 +4,6 @@ from typing import TYPE_CHECKING from django.contrib import messages -from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from integreat_cms.cms.utils.stringify_list import iter_to_string @@ -16,7 +15,6 @@ from typing import Any from django.http import HttpRequest, HttpResponse - from django.http.response import HttpResponseRedirect logger = logging.getLogger(__name__) diff --git a/integreat_cms/cms/views/organizations/organization_content_mixin.py b/integreat_cms/cms/views/organizations/organization_content_mixin.py index c83825a5cb..ef53cdd5bb 100644 --- a/integreat_cms/cms/views/organizations/organization_content_mixin.py +++ b/integreat_cms/cms/views/organizations/organization_content_mixin.py @@ -13,7 +13,6 @@ class OrganizationContextMixin(ContextMixin): - # pylint: disable=too-few-public-methods """ This mixin provides extra context for organization views """ @@ -31,26 +30,26 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: { "current_menu_item": "organization_form", "archive_dialog_title": _( - "Please confirm that you really want to archive this organization" + "Please confirm that you really want to archive this organization", ), "archive_dialog_text": _( - "Archiving this organization removes it from all users and content objects that use it" + "Archiving this organization removes it from all users and content objects that use it", ), "restore_dialog_title": _( - "Please confirm that you really want to restore this organization" + "Please confirm that you really want to restore this organization", ), "delete_dialog_title": _( - "Please confirm that you really want to delete this organization" + "Please confirm that you really want to delete this organization", ), "delete_dialog_text": _( - "Deleting this organization removes it from all users and content objects that use it" + "Deleting this organization removes it from all users and content objects that use it", ), "cannot_archive_title": _( - "You cannot archive an organization which is used by a poi, page or user. \nThis also involves archived pages and pois" + "You cannot archive an organization which is used by a poi, page or user. \nThis also involves archived pages and pois", ), "cannot_delete_title": _( - "You cannot delete an organization which is used by a poi, page or user. \nThis also involves archived pages and pois" + "You cannot delete an organization which is used by a poi, page or user. \nThis also involves archived pages and pois", ), - } + }, ) return context diff --git a/integreat_cms/cms/views/organizations/organization_form_view.py b/integreat_cms/cms/views/organizations/organization_form_view.py index e01e9cf8f4..8f00bf5550 100644 --- a/integreat_cms/cms/views/organizations/organization_form_view.py +++ b/integreat_cms/cms/views/organizations/organization_form_view.py @@ -46,18 +46,20 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: # get organization objects if it exists, otherwise objects are None organization_instance = region.organizations.filter( - id=kwargs.get("organization_id") + id=kwargs.get("organization_id"), ).first() if organization_instance and organization_instance.archived: disabled = True messages.warning( - request, _("You cannot edit this organization because it is archived.") + request, + _("You cannot edit this organization because it is archived."), ) elif not request.user.has_perm("cms.change_organization"): disabled = True messages.warning( - request, _("You don't have the permission to edit organizations.") + request, + _("You don't have the permission to edit organizations."), ) else: disabled = False @@ -88,7 +90,7 @@ def post(self, request: HttpRequest, **kwargs: Any) -> HttpResponse: region = request.region organization_instance = Organization.objects.filter( - id=kwargs.get("organization_id") + id=kwargs.get("organization_id"), ).first() if organization_instance and organization_instance.archived: @@ -116,7 +118,7 @@ def post(self, request: HttpRequest, **kwargs: Any) -> HttpResponse: messages.success( request, _('Organization "{}" was successfully created').format( - organization_form.instance + organization_form.instance, ), ) return redirect( @@ -130,7 +132,7 @@ def post(self, request: HttpRequest, **kwargs: Any) -> HttpResponse: messages.success( request, _('Organization "{}" was successfully saved').format( - organization_form.instance + organization_form.instance, ), ) else: diff --git a/integreat_cms/cms/views/organizations/organization_list_view.py b/integreat_cms/cms/views/organizations/organization_list_view.py index 5678fc3c4c..9bbff6a861 100644 --- a/integreat_cms/cms/views/organizations/organization_list_view.py +++ b/integreat_cms/cms/views/organizations/organization_list_view.py @@ -45,11 +45,13 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: region = request.region organizations = Organization.objects.filter( - region=region, archived=self.archived + region=region, + archived=self.archived, ) archived_count = Organization.objects.filter( - region=region, archived=True + region=region, + archived=True, ).count() chunk_size = int(request.GET.get("size", settings.PER_PAGE)) diff --git a/integreat_cms/cms/views/pages/page_actions.py b/integreat_cms/cms/views/pages/page_actions.py index b563dd7716..9a827c18e5 100644 --- a/integreat_cms/cms/views/pages/page_actions.py +++ b/integreat_cms/cms/views/pages/page_actions.py @@ -49,7 +49,10 @@ @require_POST @tree_mutex("page") def archive_page( - request: HttpRequest, page_id: int, region_slug: str, language_slug: str + request: HttpRequest, + page_id: int, + region_slug: str, + language_slug: str, ) -> HttpResponseRedirect: """ Archive page object @@ -67,14 +70,14 @@ def archive_page( if not request.user.has_perm("cms.change_page_object", page): raise PermissionDenied( - f"{request.user!r} does not have the permission to archive {page!r}" + f"{request.user!r} does not have the permission to archive {page!r}", ) if page.mirroring_pages.exists(): messages.error( request, _( - "This page cannot be archived because it was embedded as live content from another page." + "This page cannot be archived because it was embedded as live content from another page.", ), ) else: @@ -95,7 +98,10 @@ def archive_page( @require_POST @tree_mutex("page") def restore_page( - request: HttpRequest, page_id: int, region_slug: str, language_slug: str + request: HttpRequest, + page_id: int, + region_slug: str, + language_slug: str, ) -> HttpResponseRedirect: """ Restore page object (set ``archived=False``) @@ -114,7 +120,7 @@ def restore_page( if not request.user.has_perm("cms.change_page_object", page): raise PermissionDenied( - f"{request.user!r} does not have the permission to restore {page!r}" + f"{request.user!r} does not have the permission to restore {page!r}", ) page.restore() @@ -130,7 +136,7 @@ def restore_page( _("Page was successfully restored.") + " " + _( - "However, it is still archived because one of its parent pages is archived." + "However, it is still archived because one of its parent pages is archived.", ), ) return redirect( @@ -157,7 +163,7 @@ def restore_page( def preview_page_ajax( request: HttpRequest, page_id: int, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, ) -> JsonResponse: """ @@ -165,7 +171,6 @@ def preview_page_ajax( :param request: The current request :param page_id: The id of the page which should be viewed - :param region_slug: The slug of the current region :param language_slug: The slug of the current language :raises ~django.http.Http404: HTTP status 404 if page translation does not exist @@ -190,7 +195,7 @@ def preview_page_ajax( if page_translation else False ), - } + }, ) raise Http404("Translation of the given page could not be found") @@ -199,7 +204,7 @@ def preview_page_ajax( @json_response def get_page_content_ajax( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, page_id: int, ) -> JsonResponse: @@ -207,7 +212,6 @@ def get_page_content_ajax( Get content of a page translation based on language slug :param request: The current request - :param region_slug: The slug of the current region :param language_slug: The slug of the current language :param page_id: The id of the page which should be viewed :raises ~django.http.Http404: HTTP status 404 if page translation does not exist @@ -225,7 +229,10 @@ def get_page_content_ajax( @permission_required("cms.delete_page") @tree_mutex("page") def delete_page( - request: HttpRequest, page_id: int, region_slug: str, language_slug: str + request: HttpRequest, + page_id: int, + region_slug: str, + language_slug: str, ) -> HttpResponseRedirect: """ Delete page object @@ -246,7 +253,7 @@ def delete_page( messages.error( request, _( - "This page cannot be deleted because it was embedded as live content from another page." + "This page cannot be deleted because it was embedded as live content from another page.", ), ) else: @@ -264,7 +271,8 @@ def delete_page( def expand_page_translation_id( - request: HttpRequest, short_url_id: int + request: HttpRequest, + short_url_id: int, ) -> HttpResponseRedirect: """ Searches for a page translation with corresponding ID and redirects browser to web app @@ -286,7 +294,7 @@ def expand_page_translation_id( @json_response def cancel_translation_process_ajax( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, page_id: int, ) -> JsonResponse: @@ -294,7 +302,6 @@ def cancel_translation_process_ajax( This view is called for manually unsetting the translation process :param request: ajax request - :param region_slug: The slug of the current region :param language_slug: The slug of the current language :param page_id: The id of the requested page :return: on success returns language of updated translation @@ -305,8 +312,8 @@ def cancel_translation_process_ajax( return JsonResponse( { "error": _( - 'Page "{}" does not have a translation for language "{}"' - ).format(page, language_slug) + 'Page "{}" does not have a translation for language "{}"', + ).format(page, language_slug), }, status=404, ) @@ -319,18 +326,20 @@ def cancel_translation_process_ajax( return JsonResponse( { "success": _( - 'Cancelled translation process for page "{}" and language "{}"' + 'Cancelled translation process for page "{}" and language "{}"', ).format(page, page_translation.language), "languageSlug": page_translation.language.slug, "translationState": translation_state, - } + }, ) @require_POST @permission_required("cms.change_page") def upload_xliff( - request: HttpRequest, region_slug: str, language_slug: str + request: HttpRequest, + region_slug: str, + language_slug: str, ) -> HttpResponseRedirect: """ Upload and import an XLIFF file @@ -354,7 +363,7 @@ def upload_xliff( messages.error( request, _('File "{}" is neither a ZIP archive nor an XLIFF file.').format( - upload_file.name + upload_file.name, ), ) logger.warning( @@ -373,7 +382,8 @@ def upload_xliff( if upload_file.name.endswith(".zip"): # Extract zip archive xliff_paths_tmp, invalid_file_paths = extract_zip_archive( - os.path.join(upload_dir, upload_file.name), upload_dir + os.path.join(upload_dir, upload_file.name), + upload_dir, ) # Append contents of zip archive to total list of xliff files xliff_paths += xliff_paths_tmp @@ -381,14 +391,14 @@ def upload_xliff( messages.error( request, _('The ZIP archive "{}" does not contain XLIFF files.').format( - upload_file.name + upload_file.name, ), ) elif invalid_file_paths: messages.warning( request, _( - 'The ZIP archive "{}" contains the following invalid files: "{}"' + 'The ZIP archive "{}" contains the following invalid files: "{}"', ).format(upload_file.name, '", "'.join(invalid_file_paths)), ) else: @@ -475,14 +485,14 @@ def move_page( repair_tree([page_id, target_id], commit=True) try: page.save() - except IntegrityError as e: + except IntegrityError: messages.error( request, _( - "Error while saving page. Someone else probably moved another page at the same time." + "Error while saving page. Someone else probably moved another page at the same time.", ), ) - logger.exception(e) + logger.exception("") logger.debug( "%r moved to %r of %r by %r", page, @@ -493,7 +503,7 @@ def move_page( messages.success( request, _('The page "{page}" was successfully moved.').format( - page=page.best_translation.title + page=page.best_translation.title, ), ) except ( @@ -502,7 +512,7 @@ def move_page( InvalidMoveToDescendant, ) as e: messages.error(request, e) - logger.exception(e) + logger.exception("") return redirect( "pages", @@ -516,7 +526,7 @@ def move_page( @permission_required("cms.view_page") def get_page_order_table_ajax( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, parent_id: int | None = None, page_id: int | None = None, ) -> HttpResponse: @@ -525,7 +535,6 @@ def get_page_order_table_ajax( This is used in the page form to change the order of a page relative to its siblings. :param request: The current request - :param region_slug: The slug of the current region :param parent_id: The id of the parent page to which the order table should be returned :param page_id: The id of the page of the current page form :return: The rendered page order table @@ -565,14 +574,13 @@ def get_page_order_table_ajax( @permission_required("cms.view_page") def render_mirrored_page_field( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, ) -> HttpResponse: """ Retrieve the rendered mirrored page field template :param request: The current request - :param region_slug: The slug of the current region :param language_slug: The slug of the current language :return: The rendered mirrored page field """ @@ -602,7 +610,7 @@ def render_mirrored_page_field( def refresh_date( request: HttpRequest, page_id: int, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, ) -> HttpResponseRedirect: """ @@ -610,7 +618,6 @@ def refresh_date( :param request: The current request :param page_id: The id of the page of the current page form - :param region_slug: The slug of the current region :param language_slug: The slug of the current language :raises ~django.core.exceptions.PermissionDenied: If the user does not have the permission to refresh page dates @@ -621,19 +628,19 @@ def refresh_date( if not request.user.has_perm("cms.change_page_object", page): raise PermissionDenied( - f"{request.user!r} does not have the permission mark {page!r} as up-to-date" + f"{request.user!r} does not have the permission mark {page!r} as up-to-date", ) # Consider only the last version of each translation page_translations = page.translations.filter( - language__in=region.active_languages + language__in=region.active_languages, ).distinct("page__pk", "language__pk") # Sort page translations according to the position of their languages in the # language tree to ensure that the translations are not considered outdated. page_translations = sorted( page_translations, key=lambda page_translation: region.active_languages.index( - page_translation.language + page_translation.language, ), ) translations_to_update = [ diff --git a/integreat_cms/cms/views/pages/page_bulk_actions.py b/integreat_cms/cms/views/pages/page_bulk_actions.py index 69b5814c5c..d8bb1f9a15 100644 --- a/integreat_cms/cms/views/pages/page_bulk_actions.py +++ b/integreat_cms/cms/views/pages/page_bulk_actions.py @@ -51,7 +51,10 @@ class GeneratePdfView(PageBulkActionMixin, BulkActionView): @method_decorator(never_cache) def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Apply the bulk action on every item in the queryset and redirect @@ -80,7 +83,10 @@ class ExportXliffView(PageBulkActionMixin, BulkActionView): require_change_permission = False def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Function for handling a XLIFF export request for pages. @@ -107,11 +113,11 @@ def post( message = __( ( _( - "XLIFF file with published pages only for translation to {} successfully created." + "XLIFF file with published pages only for translation to {} successfully created.", ) if self.only_public else _( - "XLIFF file with unpublished and published pages for translation to {} successfully created." + "XLIFF file with unpublished and published pages for translation to {} successfully created.", ) ).format(target_language), _( @@ -161,12 +167,15 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ] if xliff_file_url := pages_to_xliff_file( - request, self.get_queryset(), target_languages, only_public=False + request, + self.get_queryset(), + target_languages, + only_public=False, ): # Insert link with automatic download into success message message = __( _( - "XLIFF file for translation to selected languages successfully created." + "XLIFF file for translation to selected languages successfully created.", ), _( "If the download does not start automatically, please click here.", @@ -211,8 +220,8 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: for content_object in self.get_queryset(): if ( - not content_object.get_translation_state(language_slug) - == translation_status.IN_TRANSLATION + content_object.get_translation_state(language_slug) + != translation_status.IN_TRANSLATION ): not_in_translation.append(content_object.best_translation.title) else: diff --git a/integreat_cms/cms/views/pages/page_context_mixin.py b/integreat_cms/cms/views/pages/page_context_mixin.py index f70c8bcabc..fc0532ab4d 100644 --- a/integreat_cms/cms/views/pages/page_context_mixin.py +++ b/integreat_cms/cms/views/pages/page_context_mixin.py @@ -16,7 +16,6 @@ class PageContextMixin(ContextMixin): - # pylint: disable=too-few-public-methods """ This mixin provides extra context for page views """ @@ -35,28 +34,28 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "current_menu_item": "pages", "translation_status": translation_status, "archive_dialog_title": _( - "Please confirm that you really want to archive this page" + "Please confirm that you really want to archive this page", ), "archive_dialog_text": __( _("All subpages of this page are automatically archived as well."), _("This affects all translations."), ), "restore_dialog_title": _( - "Please confirm that you really want to restore this page" + "Please confirm that you really want to restore this page", ), "restore_dialog_text": __( _( - "All subpages of this page that were not already archived before this page are also restored." + "All subpages of this page that were not already archived before this page are also restored.", ), _("Please check the subpages after the process is complete."), _("This affects all translations."), ), "delete_dialog_title": _( - "Please confirm that you really want to delete this page" + "Please confirm that you really want to delete this page", ), "delete_dialog_text": _( - "All translations of this page will also be deleted." + "All translations of this page will also be deleted.", ), - } + }, ) return context diff --git a/integreat_cms/cms/views/pages/page_form_view.py b/integreat_cms/cms/views/pages/page_form_view.py index 795f2f56ec..0acc78db20 100644 --- a/integreat_cms/cms/views/pages/page_form_view.py +++ b/integreat_cms/cms/views/pages/page_form_view.py @@ -44,7 +44,10 @@ @method_decorator(permission_required("cms.view_page"), name="dispatch") class PageFormView( - TemplateView, PageContextMixin, MediaContextMixin, ContentEditLockMixin + TemplateView, + PageContextMixin, + MediaContextMixin, + ContentEditLockMixin, ): """ View for the page form and page translation form @@ -60,7 +63,6 @@ class PageFormView( back_url_name: str | None = "pages" def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: - # pylint: disable=too-many-locals, too-many-branches r""" Render :class:`~integreat_cms.cms.forms.pages.page_form.PageForm` and :class:`~integreat_cms.cms.forms.pages.page_translation_form.PageTranslationForm` @@ -74,7 +76,8 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: region = request.region language = region.get_language_or_404( - kwargs.get("language_slug"), only_active=True + kwargs.get("language_slug"), + only_active=True, ) # Get page and translation objects if they exist @@ -92,14 +95,15 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: if page.explicitly_archived: disabled = True messages.warning( - request, _("You cannot edit this page because it is archived.") + request, + _("You cannot edit this page because it is archived."), ) elif page.implicitly_archived: disabled = True messages.warning( request, _( - "You cannot edit this page, because one of its parent pages is archived and therefore, this page is archived as well." + "You cannot edit this page, because one of its parent pages is archived and therefore, this page is archived as well.", ), ) # If the page is not public, check whether we need additional messages to prevent confusion @@ -125,7 +129,7 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: message = __( status_message, _( - "You can {action} these changes in the version overview." + "You can {action} these changes in the version overview.", ).format(action=action), ) status_message = translate_link( @@ -153,7 +157,7 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: }, ) message = _( - "Currently, version {} of this page is displayed in the app." + "Currently, version {} of this page is displayed in the app.", ).format(public_translation.version) messages.info( request, @@ -178,7 +182,7 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.warning( request, _( - "You don't have the permission to publish this page, but you can propose changes and submit them for review instead." + "You don't have the permission to publish this page, but you can propose changes and submit them for review instead.", ), ) @@ -204,7 +208,9 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: # Pass side by side language options side_by_side_language_options = self.get_side_by_side_language_options( - region, language, page + region, + language, + page, ) # Pass siblings to template to enable rendering of page order table @@ -246,9 +252,11 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: @tree_mutex("page") def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: - # pylint: disable=too-many-statements, too-many-locals, too-many-branches r""" Submit :class:`~integreat_cms.cms.forms.pages.page_form.PageForm` and :class:`~integreat_cms.cms.forms.pages.page_translation_form.PageTranslationForm` and save :class:`~integreat_cms.cms.models.pages.page.Page` @@ -266,14 +274,15 @@ def post( region = request.region language = region.get_language_or_404( - kwargs.get("language_slug"), only_active=True + kwargs.get("language_slug"), + only_active=True, ) page_instance = region.pages.filter(id=kwargs.get("page_id")).first() if not request.user.has_perm("cms.change_page_object", page_instance): raise PermissionDenied( - f"{request.user!r} does not have the permission to edit {page_instance!r}" + f"{request.user!r} does not have the permission to edit {page_instance!r}", ) page_translation_instance = PageTranslation.objects.filter( @@ -314,14 +323,15 @@ def post( page_form.add_error_messages(request) page_translation_form.add_error_messages(request) elif not request.user.has_perm( - "cms.publish_page_object", page_form.instance + "cms.publish_page_object", + page_form.instance, ) and ( page_translation_form.cleaned_data.get("status") in [status.DRAFT, status.PUBLIC] ): # Raise PermissionDenied if user wants to publish page but doesn't have the permission raise PermissionDenied( - f"{request.user!r} does not have the permission to publish {page_form.instance!r}" + f"{request.user!r} does not have the permission to publish {page_form.instance!r}", ) elif ( page_translation_form.instance.status == status.AUTO_SAVE @@ -339,13 +349,15 @@ def post( page_translation_form.instance.page = page_form.save() # Save page translation form page_translation_instance = page_translation_form.save( - foreign_form_changed=page_form.has_changed() + foreign_form_changed=page_form.has_changed(), ) # Show a message that the slug was changed if it was not unique if user_slug and user_slug != page_translation_form.cleaned_data["slug"]: other_translation = PageTranslation.objects.filter( - page__region=region, slug=user_slug, language=language + page__region=region, + slug=user_slug, + language=language, ).first() other_translation_link = reverse( "page_versions", @@ -382,7 +394,8 @@ def post( node.language for node in language_tree_node.get_descendants() ] page_translation_form.instance.page.translations.filter( - language__in=languages, status=status.PUBLIC + language__in=languages, + status=status.PUBLIC, ).update(status=status.DRAFT) # If this is the first version and the minor edit checkbox is checked, remove it if ( @@ -394,7 +407,7 @@ def post( messages.info( request, _( - 'The "minor edit" option was disabled because the first version can never be a minor edit.' + 'The "minor edit" option was disabled because the first version can never be a minor edit.', ), ) # If this is not an autosave, clean up previous auto saves @@ -407,14 +420,14 @@ def post( messages.success( request, _( - 'Page "{}" was successfully created and submitted for approval' + 'Page "{}" was successfully created and submitted for approval', ).format(page_translation_form.instance.title), ) else: messages.success( request, _('Page "{}" was successfully created').format( - page_translation_form.instance.title + page_translation_form.instance.title, ), ) elif ( @@ -442,7 +455,7 @@ def post( else: siblings = region.get_root_pages() siblings = siblings.filter( - explicitly_archived=page_form.instance.explicitly_archived + explicitly_archived=page_form.instance.explicitly_archived, ) return render( @@ -458,7 +471,9 @@ def post( # Languages for tab view "languages": region.active_languages if page_instance else [language], "side_by_side_language_options": self.get_side_by_side_language_options( - region, language, page_instance + region, + language, + page_instance, ), "right_to_left": ( language.text_direction == text_directions.RIGHT_TO_LEFT @@ -471,7 +486,9 @@ def post( @staticmethod def get_side_by_side_language_options( - region: Region, language: Language, page: Page | None + region: Region, + language: Language, + page: Page | None, ) -> list[dict[str, Any]]: """ This is a helper function to generate the side-by-side language options for both the get and post requests. @@ -486,7 +503,7 @@ def get_side_by_side_language_options( for language_node in filter(lambda n: n.active, region.language_tree): if not language_node.is_root(): source_language = region.language_node_by_id.get( - language_node.parent_id + language_node.parent_id, ).language side_by_side_language_options.append( { @@ -497,6 +514,6 @@ def get_side_by_side_language_options( ), "selected": language_node.language == language, "disabled": not page or source_language not in page.languages, - } + }, ) return side_by_side_language_options diff --git a/integreat_cms/cms/views/pages/page_permission_actions.py b/integreat_cms/cms/views/pages/page_permission_actions.py index bb370acbc1..8d2b9a84ee 100644 --- a/integreat_cms/cms/views/pages/page_permission_actions.py +++ b/integreat_cms/cms/views/pages/page_permission_actions.py @@ -23,13 +23,13 @@ @permission_required("cms.change_page") @permission_required("cms.grant_page_permissions") def grant_page_permission_ajax( - request: HttpRequest, region_slug: str # pylint: disable=unused-argument + request: HttpRequest, + **kwargs: Any, ) -> HttpResponse: """ Grant a user editing or publishing permissions on a specific page object :param request: The current request - :param region_slug: The slug of the current region :raises ~django.core.exceptions.PermissionDenied: If page permissions are disabled for this region or the user does not have the permission to grant page permissions @@ -49,8 +49,8 @@ def grant_page_permission_ajax( ensure_user_has_correct_permissions(request, page, user) permission_message = permission.grant_permission(user, page) - except PermissionDenied as e: - logger.exception(e) + except PermissionDenied: + logger.exception("") permission_message = PermissionMessage( "An error has occurred. Please contact an administrator.", MessageLevel.ERROR, @@ -64,13 +64,13 @@ def grant_page_permission_ajax( @permission_required("cms.change_page") @permission_required("cms.grant_page_permissions") def revoke_page_permission_ajax( - request: HttpRequest, region_slug: str # pylint: disable=unused-argument + request: HttpRequest, + **kwargs: Any, ) -> HttpResponse: """ Remove a page permission for a given user and page :param request: The current request - :param region_slug: The slug of the current region :raises ~django.core.exceptions.PermissionDenied: If page permissions are disabled for this region or the user does not have the permission to revoke page permissions @@ -90,8 +90,7 @@ def revoke_page_permission_ajax( ensure_user_has_correct_permissions(request, page, user) permission_message = permission.revoke_permission(user, page) - except PermissionDenied as e: - logger.exception(e) + except PermissionDenied: permission_message = PermissionMessage( _("An error has occurred. Please contact an administrator."), MessageLevel.ERROR, @@ -180,7 +179,7 @@ def grant_permission(self, user: User, page: Page) -> PermissionMessage: return PermissionMessage( _( "Page-specific permissions cannot be granted to users " - "who do not have permission to view pages (e.g event managers)." + "who do not have permission to view pages (e.g event managers).", ), MessageLevel.WARNING, ) @@ -212,7 +211,7 @@ def build_grant_message(self, user: User, page: Page) -> PermissionMessage: """ if user.has_perm(self.permission, page): message = _( - 'Information: The user "{}" has this permission already.' + 'Information: The user "{}" has this permission already.', ).format(user.full_user_name) level_tag = MessageLevel.INFO else: @@ -231,7 +230,7 @@ def build_revoke_message(self, user: User, page: Page) -> PermissionMessage: """ if user.has_perm(self.permission, page): message = self.revoke_with_no_effect_success_message.format( - user.full_user_name + user.full_user_name, ) level_tag = MessageLevel.INFO else: @@ -270,7 +269,7 @@ def __init__(self) -> None: revoke_success_message = _('Success: The user "{}" cannot edit this page anymore.') revoke_with_no_effect_success_message = _( 'Information: The user "{}" has been removed from the authors of ' - "this page, but has the implicit permission to edit this page anyway." + "this page, but has the implicit permission to edit this page anyway.", ) def save_grant_permission(self, user: User, page: Page) -> None: @@ -293,11 +292,11 @@ def __init__(self) -> None: grant_success_message = _('Success: The user "{}" can now publish this page.') revoke_success_message = _( - 'Success: The user "{}" cannot publish this page anymore.' + 'Success: The user "{}" cannot publish this page anymore.', ) revoke_with_no_effect_success_message = _( 'Information: The user "{}" has been removed from the editors of ' - "this page, but has the implicit permission to publish this page anyway." + "this page, but has the implicit permission to publish this page anyway.", ) def save_grant_permission(self, user: User, page: Page) -> None: @@ -311,7 +310,9 @@ def save_revoke_permission(self, user: User, page: Page) -> None: def __render_response( - request: HttpRequest, page: Page, permission_message: PermissionMessage + request: HttpRequest, + page: Page, + permission_message: PermissionMessage, ) -> HttpResponse: return render( request, @@ -368,7 +369,9 @@ def ensure_page_specific_permissions_enabled(region: Region) -> None: def ensure_user_has_correct_permissions( - request: HttpRequest, page: Page, user: User + request: HttpRequest, + page: Page, + user: User, ) -> None: """ Ensure the user has correct permissions diff --git a/integreat_cms/cms/views/pages/page_sbs_view.py b/integreat_cms/cms/views/pages/page_sbs_view.py index 4d32fd954a..172ed9edb4 100644 --- a/integreat_cms/cms/views/pages/page_sbs_view.py +++ b/integreat_cms/cms/views/pages/page_sbs_view.py @@ -30,7 +30,10 @@ @method_decorator(permission_required("cms.view_page"), name="dispatch") class PageSideBySideView( - TemplateView, PageContextMixin, MediaContextMixin, ContentEditLockMixin + TemplateView, + PageContextMixin, + MediaContextMixin, + ContentEditLockMixin, ): """ View for the page side by side form @@ -42,7 +45,10 @@ class PageSideBySideView( back_url_name: str | None = "pages" def get( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponse | HttpResponseRedirect: r""" Render :class:`~integreat_cms.cms.forms.pages.page_translation_form.PageTranslationForm` on the side by side view @@ -58,14 +64,14 @@ def get( target_language = Language.objects.get(slug=kwargs.get("language_slug")) if source_language_node := region.language_tree_nodes.get( - language=target_language + language=target_language, ).parent: source_language = source_language_node.language else: messages.error( request, _( - "You cannot use the side-by-side-view for the region's default language (in this case {default_language})." + "You cannot use the side-by-side-view for the region's default language (in this case {default_language}).", ).format(default_language=target_language.translated_name), ) return redirect( @@ -84,7 +90,7 @@ def get( messages.error( request, _( - "You cannot use the side-by-side-view if the source translation (in this case {source_language}) does not exist." + "You cannot use the side-by-side-view if the source translation (in this case {source_language}) does not exist.", ).format(source_language=source_language.translated_name), ) return redirect( @@ -111,7 +117,9 @@ def get( ) old_translation_content = get_old_source_content( - page, source_language, target_language + page, + source_language, + target_language, ) return render( @@ -144,23 +152,23 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: if not request.user.has_perm("cms.change_page_object", page): raise PermissionDenied( - f"{request.user!r} does not have the permission to edit {page!r}" + f"{request.user!r} does not have the permission to edit {page!r}", ) target_language = Language.objects.get(slug=kwargs.get("language_slug")) source_language_node = region.language_tree_nodes.get( - language=target_language + language=target_language, ).parent if source_language_node: source_page_translation = page.get_translation( - source_language_node.language.slug + source_language_node.language.slug, ) else: messages.error( request, _( - "You cannot use the side-by-side-view for the region's default language (in this case {default_language})." + "You cannot use the side-by-side-view for the region's default language (in this case {default_language}).", ).format(default_language=target_language.translated_name), ) return redirect( @@ -178,7 +186,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.error( request, _( - "You cannot use the side-by-side-view if the source translation (in this case {source_language}) does not exist." + "You cannot use the side-by-side-view if the source translation (in this case {source_language}) does not exist.", ).format(source_language=source_language_node.language.translated_name), ) return redirect( @@ -213,7 +221,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ): # Raise PermissionDenied if user wants to publish page but doesn't have the permission raise PermissionDenied( - f"{request.user!r} does not have the permission to publish {page!r}" + f"{request.user!r} does not have the permission to publish {page!r}", ) else: # Save form @@ -222,7 +230,9 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: page_translation_form.add_success_message(request) old_translation_content = get_old_source_content( - page, source_language_node.language, target_language + page, + source_language_node.language, + target_language, ) return render( @@ -239,7 +249,9 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: def get_old_source_content( - page: Page, source_language: Language, target_language: Language + page: Page, + source_language: Language, + target_language: Language, ) -> str: """ This function returns the content of the source language translation that was up to date when the latest (no minor edit) @@ -250,16 +262,20 @@ def get_old_source_content( :param target_language: The target language of the page :return: The content of the translation """ - if major_target_page_translation := page.translations.filter( - language__slug=target_language.slug, minor_edit=False - ).first(): - if source_previous_translation := ( + if ( + major_target_page_translation := page.translations.filter( + language__slug=target_language.slug, + minor_edit=False, + ).first() + ) and ( + source_previous_translation := ( page.translations.filter( language=source_language, last_updated__lte=major_target_page_translation.last_updated, ) .order_by("-last_updated") .first() - ): - return source_previous_translation.content + ) + ): + return source_previous_translation.content return "" diff --git a/integreat_cms/cms/views/pages/page_tree_view.py b/integreat_cms/cms/views/pages/page_tree_view.py index 19430164dc..d46159a7b7 100644 --- a/integreat_cms/cms/views/pages/page_tree_view.py +++ b/integreat_cms/cms/views/pages/page_tree_view.py @@ -110,7 +110,8 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ) else: messages.warning( - request, _("You don't have the permission to edit or create pages.") + request, + _("You don't have the permission to edit or create pages."), ) # Initialize page filter form diff --git a/integreat_cms/cms/views/pages/page_version_view.py b/integreat_cms/cms/views/pages/page_version_view.py index f2612c4257..425ac39040 100644 --- a/integreat_cms/cms/views/pages/page_version_view.py +++ b/integreat_cms/cms/views/pages/page_version_view.py @@ -8,7 +8,6 @@ class PageVersionView(PageContextMixin, ContentVersionView): - # pylint: disable=too-many-ancestors """ View for browsing the page versions and restoring old page versions """ diff --git a/integreat_cms/cms/views/pages/page_xliff_import_view.py b/integreat_cms/cms/views/pages/page_xliff_import_view.py index 52969068c5..511eb9b38c 100644 --- a/integreat_cms/cms/views/pages/page_xliff_import_view.py +++ b/integreat_cms/cms/views/pages/page_xliff_import_view.py @@ -57,10 +57,11 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "current_menu_item": "pages", "upload_dir": os.path.basename(self.xliff_dir), "translation_diffs": get_xliff_import_diff( - self.request, self.xliff_dir + self.request, + self.xliff_dir, ), "language": self.language, - } + }, ) return context @@ -78,11 +79,13 @@ def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpRespo if TYPE_CHECKING: assert self.region self.language = self.region.get_language_or_404( - kwargs.get("language_slug"), only_active=True + kwargs.get("language_slug"), + only_active=True, ) # Get directory path of the uploaded XLIFF files self.xliff_dir = os.path.join( - settings.XLIFF_UPLOAD_DIR, str(kwargs.get("xliff_dir")) + settings.XLIFF_UPLOAD_DIR, + str(kwargs.get("xliff_dir")), ) if not os.path.isdir(self.xliff_dir): @@ -100,7 +103,10 @@ def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpRespo return super().dispatch(request, *args, **kwargs) def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Confirm the xliff import diff --git a/integreat_cms/cms/views/pages/partial_page_tree_view.py b/integreat_cms/cms/views/pages/partial_page_tree_view.py index af1a6cad73..7d358d253d 100644 --- a/integreat_cms/cms/views/pages/partial_page_tree_view.py +++ b/integreat_cms/cms/views/pages/partial_page_tree_view.py @@ -17,14 +17,13 @@ @require_POST def render_partial_page_tree_views( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, ) -> JsonResponse: r""" Retrieve the rendered subtree of a given root page :param request: The current request - :param region_slug: The slug of the current region :param language_slug: The slug of the current language :return: The rendered template responses """ @@ -69,7 +68,7 @@ def render_partial_page_tree_views( "parent_id": parent.id, }, request, - ) + ), ) return JsonResponse({"data": sub_trees}) diff --git a/integreat_cms/cms/views/poi_categories/poi_category_form_view.py b/integreat_cms/cms/views/poi_categories/poi_category_form_view.py index 465e4d8289..61fb8901ec 100644 --- a/integreat_cms/cms/views/poi_categories/poi_category_form_view.py +++ b/integreat_cms/cms/views/poi_categories/poi_category_form_view.py @@ -86,7 +86,7 @@ def get_formset(self) -> BaseInlinePOICategoryTranslationFormSet: # If the formset is bound to a POICategory instance, only pass languages which do not yet have a translation if self.object: languages = languages.exclude( - id__in=self.object.translations.values_list("language__id", flat=True) + id__in=self.object.translations.values_list("language__id", flat=True), ) # Get te formset class POICategoryTranslationFormSet = poi_category_translation_formset_factory() @@ -111,7 +111,10 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: return context def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Check whether the formset is valid and delegate to the respective function @@ -191,7 +194,8 @@ def form_valid(self, form: POICategoryForm) -> HttpResponseRedirect: messages.success( self.request, _('{} "{}" was successfully created').format( - self.object._meta.verbose_name, self.object + self.object._meta.verbose_name, + self.object, ), ) return super().form_valid(form) @@ -207,7 +211,10 @@ class POICategoryUpdateView(POICategoryMixin, UpdateView): """ def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Get the current :class:`~integreat_cms.cms.models.poi_categories.poi_category.POICategory` @@ -243,7 +250,8 @@ def form_valid(self, form: POICategoryForm) -> HttpResponseRedirect: messages.success( self.request, _('{} "{}" was successfully saved').format( - self.object._meta.verbose_name, self.object + self.object._meta.verbose_name, + self.object, ), ) return super().form_valid(form) @@ -259,7 +267,10 @@ class POICategoryDeleteView(CustomDeleteView): model = POICategory def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Attempt to delete the :class:`~integreat_cms.cms.models.poi_categories.poi_category.POICategory` @@ -277,7 +288,7 @@ def post( messages.error( request, _( - "You cannot delete a location category that is used in at least one location." + "You cannot delete a location category that is used in at least one location.", ), ) return HttpResponseRedirect(reverse("poicategories")) diff --git a/integreat_cms/cms/views/pois/poi_actions.py b/integreat_cms/cms/views/pois/poi_actions.py index ca250f957f..839411cac4 100644 --- a/integreat_cms/cms/views/pois/poi_actions.py +++ b/integreat_cms/cms/views/pois/poi_actions.py @@ -29,7 +29,10 @@ @require_POST @permission_required("cms.change_poi") def archive_poi( - request: HttpRequest, poi_id: int, region_slug: str, language_slug: str + request: HttpRequest, + poi_id: int, + region_slug: str, + language_slug: str, ) -> HttpResponseRedirect: """ Archive POI object @@ -63,7 +66,10 @@ def archive_poi( @require_POST @permission_required("cms.change_poi") def restore_poi( - request: HttpRequest, poi_id: int, region_slug: str, language_slug: str + request: HttpRequest, + poi_id: int, + region_slug: str, + language_slug: str, ) -> HttpResponseRedirect: """ Restore POI object (set ``archived=False``) @@ -93,7 +99,10 @@ def restore_poi( @require_POST @permission_required("cms.delete_poi") def delete_poi( - request: HttpRequest, poi_id: int, region_slug: str, language_slug: str + request: HttpRequest, + poi_id: int, + region_slug: str, + language_slug: str, ) -> HttpResponseRedirect: """ Delete POI object @@ -129,7 +138,7 @@ def delete_poi( def view_poi( request: HttpRequest, poi_id: int, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, ) -> HttpResponse: """ @@ -137,7 +146,6 @@ def view_poi( :param request: The current request :param poi_id: The id of the POI which should be viewed - :param region_slug: The slug of the current region :param language_slug: The slug of the current language :raises ~django.http.Http404: If user no translation exists for the requested POI and language @@ -156,13 +164,13 @@ def view_poi( @require_POST @permission_required("cms.view_poi") def auto_complete_address( - request: HttpRequest, region_slug: str # pylint: disable=unused-argument + request: HttpRequest, + region_slug: str, ) -> JsonResponse: """ Autocomplete location address and coordinates :param request: The current request - :param region_slug: The slug of the current region :raises ~django.http.Http404: If no location was found for the given address :return: The address and coordinates of the location @@ -198,7 +206,7 @@ def auto_complete_address( "country": address.get("country"), "longitude": result.longitude, "latitude": result.latitude, - } + }, ) @@ -206,13 +214,13 @@ def auto_complete_address( @require_POST @permission_required("cms.view_poi") def get_address_from_coordinates( - request: HttpRequest, region_slug: str # pylint: disable=unused-argument + request: HttpRequest, + region_slug: str, ) -> JsonResponse: """ Derive address from the coordinates (map pin position) :param request: The current request - :param region_slug: The slug of the current region :raises ~django.http.Http404: If no address was found for the given coordinates :return: The address of the location @@ -225,7 +233,8 @@ def get_address_from_coordinates( nominatim_api_client = NominatimApiClient() result = nominatim_api_client.get_address( - data.get("latitude"), data.get("longitude") + data.get("latitude"), + data.get("longitude"), ) if not result: @@ -242,5 +251,5 @@ def get_address_from_coordinates( or address.get("town") or address.get("village"), "country": address.get("country"), - } + }, ) diff --git a/integreat_cms/cms/views/pois/poi_context_mixin.py b/integreat_cms/cms/views/pois/poi_context_mixin.py index 3cf323f407..f1101b5e00 100644 --- a/integreat_cms/cms/views/pois/poi_context_mixin.py +++ b/integreat_cms/cms/views/pois/poi_context_mixin.py @@ -16,7 +16,6 @@ class POIContextMixin(ContextMixin): - # pylint: disable=too-few-public-methods """ This mixin provides extra context for language tree views """ @@ -39,7 +38,7 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "translations": { "weekdays": dict(weekdays.CHOICES), "openingHoursLabel": POI._meta.get_field( - "opening_hours" + "opening_hours", ).verbose_name.title(), "editWeekdayLabel": _("Edit opening hours for this weekday"), "editAllLabel": _("Edit all opening hours"), @@ -51,7 +50,7 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "allDayLabel": _("Open around the clock"), "appointmentOnlyLabel": _("By prior appointment only"), "selectText": _( - "Select the days for which the times selected below should apply" + "Select the days for which the times selected below should apply", ), "saveText": _("Save"), "cancelText": _("Cancel"), @@ -59,21 +58,21 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "removeTimeSlotText": _("Remove this time slot"), "errorOnlyLastSlotEmpty": _("Only the last time slot may be empty"), "errorLastSlotRequired": _( - "If the location is neither closed nor open all day, you have to specify at least one time slot" + "If the location is neither closed nor open all day, you have to specify at least one time slot", ), "errorClosingTimeMissing": _("Closing time is missing"), "errorOpeningTimeMissing": _("Opening time is missing"), "errorClosingTimeEarlier": _( - "Closing time is earlier than the opening time" + "Closing time is earlier than the opening time", ), "errorClosingTimeIdentical": _( - "Closing time is identical with the opening time" + "Closing time is identical with the opening time", ), "errorOpeningTimeEarlier": _( - "Opening time is earlier than the closing time of the previous slot" + "Opening time is earlier than the closing time of the previous slot", ), "errorOpeningTimeIdentical": _( - "Opening time is identical with the closing time of the previous slot" + "Opening time is identical with the closing time of the previous slot", ), }, "canChangeLocation": self.request.user.has_perm("cms.change_poi"), @@ -83,30 +82,30 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "current_menu_item": "pois", "translation_status": translation_status, "archive_dialog_title": _( - "Please confirm that you really want to archive this location" + "Please confirm that you really want to archive this location", ), "archive_dialog_text": _( - "All translations of this location will also be archived." + "All translations of this location will also be archived.", ), "restore_dialog_title": _( - "Please confirm that you really want to restore this location" + "Please confirm that you really want to restore this location", ), "restore_dialog_text": _( - "All translations of this location will also be restored." + "All translations of this location will also be restored.", ), "delete_dialog_title": _( - "Please confirm that you really want to delete this location" + "Please confirm that you really want to delete this location", ), "delete_dialog_text": _( - "All translations of this location will also be deleted." + "All translations of this location will also be deleted.", ), "cannot_archive_title": _( - "You cannot archive a location which is used by an event or a contact. \nThis also involves archived events and contacts" + "You cannot archive a location which is used by an event or a contact. \nThis also involves archived events and contacts", ), "cannot_delete_title": _( - "You cannot delete a location which is used by an event or a contact. \nThis also involves archived events and contacts" + "You cannot delete a location which is used by an event or a contact. \nThis also involves archived events and contacts", ), "opening_hour_config_data": opening_hour_config_data, - } + }, ) return context diff --git a/integreat_cms/cms/views/pois/poi_form_ajax_view.py b/integreat_cms/cms/views/pois/poi_form_ajax_view.py index 818505afe3..ab1ff707e3 100644 --- a/integreat_cms/cms/views/pois/poi_form_ajax_view.py +++ b/integreat_cms/cms/views/pois/poi_form_ajax_view.py @@ -96,7 +96,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: return JsonResponse( data={ "success": False, - } + }, ) poi_translation_form.instance.poi = poi_form.save() diff --git a/integreat_cms/cms/views/pois/poi_form_view.py b/integreat_cms/cms/views/pois/poi_form_view.py index 407f47d967..05f41a7b65 100644 --- a/integreat_cms/cms/views/pois/poi_form_view.py +++ b/integreat_cms/cms/views/pois/poi_form_view.py @@ -35,7 +35,10 @@ @method_decorator(permission_required("cms.view_poi"), name="dispatch") @method_decorator(permission_required("cms.change_poi"), name="post") class POIFormView( - TemplateView, POIContextMixin, MediaContextMixin, ContentEditLockMixin + TemplateView, + POIContextMixin, + MediaContextMixin, + ContentEditLockMixin, ): """ View for editing POIs @@ -69,12 +72,14 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: if poi and poi.archived: disabled = True messages.warning( - request, _("You cannot edit this location because it is archived.") + request, + _("You cannot edit this location because it is archived."), ) elif not request.user.has_perm("cms.change_poi"): disabled = True messages.warning( - request, _("You don't have the permission to edit locations.") + request, + _("You don't have the permission to edit locations."), ) else: disabled = False @@ -114,7 +119,6 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ) def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: - # pylint: disable=too-many-locals r""" Submit :class:`~integreat_cms.cms.forms.pois.poi_form.POIForm` and :class:`~integreat_cms.cms.forms.pois.poi_translation_form.POITranslationForm` and save :class:`~integreat_cms.cms.models.pois.poi.POI` and @@ -181,7 +185,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: # Save forms poi_translation_form.instance.poi = poi_form.save() poi_translation_instance = poi_translation_form.save( - foreign_form_changed=poi_form.has_changed() + foreign_form_changed=poi_form.has_changed(), ) # If any source translation changes to draft, set all depending translations/versions to draft @@ -191,20 +195,22 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: node.language for node in language_tree_node.get_descendants() ] poi_translation_form.instance.poi.translations.filter( - language__in=languages + language__in=languages, ).update(status=status.DRAFT) elif ( poi_translation_form.instance.status == status.PUBLIC and poi_translation_form.instance.minor_edit ): poi_translation_form.instance.poi.translations.filter( - language=language + language=language, ).update(status=status.PUBLIC) # Show a message that the slug was changed if it was not unique if user_slug and user_slug != poi_translation_form.cleaned_data["slug"]: other_translation = POITranslation.objects.filter( - poi__region=region, slug=user_slug, language=language + poi__region=region, + slug=user_slug, + language=language, ).first() other_translation_link = other_translation.backend_edit_link message = _( @@ -231,7 +237,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.success( request, _('Location "{}" was successfully created').format( - poi_translation_form.instance + poi_translation_form.instance, ), ) elif not poi_form.has_changed() and not poi_translation_form.has_changed(): @@ -245,7 +251,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: request, __( _( - "The distance between the manually entered coordinates and the coordinates of the address is {}km." + "The distance between the manually entered coordinates and the coordinates of the address is {}km.", ).format(poi_form.nominatim_distance_delta), _("Please make sure the entered values are correct."), ), diff --git a/integreat_cms/cms/views/pois/poi_list_view.py b/integreat_cms/cms/views/pois/poi_list_view.py index 43522b6397..5fb8a0c3ca 100644 --- a/integreat_cms/cms/views/pois/poi_list_view.py +++ b/integreat_cms/cms/views/pois/poi_list_view.py @@ -51,7 +51,6 @@ def template_name(self) -> str: return self.template_archived if self.archived else self.template def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: - # pylint: disable=too-many-locals r""" Render POI list @@ -79,7 +78,7 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.error( request, _( - "Please create at least one language node before creating locations." + "Please create at least one language node before creating locations.", ), ) return redirect( @@ -91,7 +90,8 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: if not request.user.has_perm("cms.change_poi"): messages.warning( - request, _("You don't have the permission to edit or create locations.") + request, + _("You don't have the permission to edit or create locations."), ) pois = region.pois.filter(archived=self.archived) @@ -102,14 +102,15 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: if search_form.is_valid(): query = search_form.cleaned_data["query"] poi_keys = POITranslation.search(region, language_slug, query).values( - "poi__pk" + "poi__pk", ) pois = pois.filter(pk__in=poi_keys) chunk_size = int(request.GET.get("size", settings.PER_PAGE)) # for consistent pagination querysets should be ordered paginator = Paginator( - pois.prefetch_translations().order_by("region__slug"), chunk_size + pois.prefetch_translations().order_by("region__slug"), + chunk_size, ) chunk = request.GET.get("page") poi_chunk = paginator.get_page(chunk) diff --git a/integreat_cms/cms/views/pois/poi_version_view.py b/integreat_cms/cms/views/pois/poi_version_view.py index 85e6f99abd..b694cd9e07 100644 --- a/integreat_cms/cms/views/pois/poi_version_view.py +++ b/integreat_cms/cms/views/pois/poi_version_view.py @@ -8,7 +8,6 @@ class POIVersionView(POIContextMixin, ContentVersionView): - # pylint: disable=too-many-ancestors """ View for browsing the POI versions and restoring old POI versions """ diff --git a/integreat_cms/cms/views/push_notifications/push_notification_form_view.py b/integreat_cms/cms/views/push_notifications/push_notification_form_view.py index f299cb0f34..a453c1ce9f 100644 --- a/integreat_cms/cms/views/push_notifications/push_notification_form_view.py +++ b/integreat_cms/cms/views/push_notifications/push_notification_form_view.py @@ -49,7 +49,6 @@ class PushNotificationFormView(TemplateView): } def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: - # pylint: disable=too-many-locals r""" Open form for creating or editing a push notification @@ -76,7 +75,8 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: return redirect("dashboard", **{"region_slug": region.slug}) language = region.get_language_or_404( - kwargs.get("language_slug"), only_active=True + kwargs.get("language_slug"), + only_active=True, ) push_notification_id = kwargs.get("push_notification_id") @@ -95,10 +95,10 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: request, __( _( - "This news has already been sent as push notification to mobile devices." + "This news has already been sent as push notification to mobile devices.", ), _( - 'Subsequent changes are displayed in the "News" area of the app, but have no effect on the push notification sent.' + 'Subsequent changes are displayed in the "News" area of the app, but have no effect on the push notification sent.', ), ), ) @@ -107,12 +107,12 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: __( _("You are not allowed to edit this news."), _( - "This news belongs to regions you don't have access to: {}." + "This news belongs to regions you don't have access to: {}.", ).format(", ".join(map(str, details["other_regions"]))), _( - "Please ask a person responsible for all regions or contact an administrator." + "Please ask a person responsible for all regions or contact an administrator.", ), - ) + ), ) messages.warning( request, @@ -183,7 +183,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: language = Language.objects.get(slug=kwargs.get("language_slug")) push_notification_instance = PushNotification.objects.filter( - id=kwargs.get("push_notification_id") + id=kwargs.get("push_notification_id"), ).first() if not request.user.has_perm("cms.change_pushnotification"): @@ -195,7 +195,9 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: raise PermissionDenied details = extract_pn_details( - request, push_notification_instance, sort_for_region=region + request, + push_notification_instance, + sort_for_region=region, ) pn_form = PushNotificationForm( @@ -239,7 +241,8 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.success( request, _('Template "{}" was successfully {}').format( - pn_form.instance.template_name, action + pn_form.instance.template_name, + action, ), ) else: @@ -273,7 +276,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: success = send_pn(request, pn_form) else: raise NotImplementedError( - "One of the following keys is required in POST data: 'submit_draft', 'submit_update', 'create_from_template', 'submit_schedule', 'submit_send'" + "One of the following keys is required in POST data: 'submit_draft', 'submit_update', 'create_from_template', 'submit_schedule', 'submit_send'", ) if success: @@ -317,11 +320,11 @@ def validate_forms( if details["disable_edit"]: not_accessible_regions_warning = __( _( - "This news is also assigned to regions you don't have access to: {}." + "This news is also assigned to regions you don't have access to: {}.", ).format(", ".join(map(str, details["other_regions"]))), _("Thus you cannot edit or delete this news."), _( - "Please ask the one responsible for all of these regions or contact an administrator." + "Please ask the one responsible for all of these regions or contact an administrator.", ), ) messages.error( @@ -367,7 +370,9 @@ def validate_forms( def save_forms( - instance: PushNotification, pn_form: PushNotificationForm, pnt_formset: Any + instance: PushNotification, + pn_form: PushNotificationForm, + pnt_formset: Any, ) -> None: """ Saves the forms @@ -383,7 +388,8 @@ def save_forms( def create_from_template( - request: HttpRequest, pn_form: PushNotificationForm + request: HttpRequest, + pn_form: PushNotificationForm, ) -> PushNotification | None: """ Create a push notification from a template @@ -415,7 +421,8 @@ def create_from_template( request, __( _('News "{}" was successfully created from template "{}".').format( - new_push_notification, pn_form.instance.template_name + new_push_notification, + pn_form.instance.template_name, ), _("In the next step, the news can now be sent."), ), @@ -424,9 +431,10 @@ def create_from_template( def send_pn( - request: HttpRequest, pn_form: PushNotificationForm, schedule: bool = False + request: HttpRequest, + pn_form: PushNotificationForm, + schedule: bool = False, ) -> bool: - # pylint: disable=too-many-return-statements """ Send (or schedule) a push notification @@ -449,22 +457,22 @@ def send_pn( messages.error( request, _('News "{}" was already sent on {}').format( - pn_form.instance, localize(localtime(pn_form.instance.sent_date)) + pn_form.instance, + localize(localtime(pn_form.instance.sent_date)), ), ) return False try: push_sender = FirebaseApiClient(pn_form.instance) - except ImproperlyConfigured as e: - logger.error( - "News could not be sent due to a configuration error: %s", - e, + except ImproperlyConfigured: + logger.exception( + "News could not be sent due to a configuration error", ) messages.error( request, _('News "{}" could not be sent due to a configuration error.').format( - pn_form.instance + pn_form.instance, ), ) return False @@ -472,7 +480,7 @@ def send_pn( messages.error( request, _('News "{}" cannot be sent because required texts are missing').format( - pn_form.instance + pn_form.instance, ), ) return False @@ -481,7 +489,7 @@ def send_pn( messages.error( request, _('News "{}" cannot be scheduled because the date is missing').format( - pn_form.instance + pn_form.instance, ), ) return False @@ -508,7 +516,8 @@ def send_pn( pn_form.instance.draft = False pn_form.instance.save() messages.success( - request, _('News "{}" was successfully sent').format(pn_form.instance) + request, + _('News "{}" was successfully sent').format(pn_form.instance), ) return True @@ -536,7 +545,8 @@ def extract_pn_details( ) other_regions = list(set(all_regions).difference(request.available_regions)) all_languages = Language.objects.filter( - language_tree_nodes__region__in=all_regions, language_tree_nodes__active=True + language_tree_nodes__region__in=all_regions, + language_tree_nodes__active=True, ).distinct() if sort_for_region is not None: @@ -546,7 +556,7 @@ def extract_pn_details( key=lambda x: act.index(x) if x in act else 1, ) disable_edit = bool( - not request.user.is_superuser and not request.user.is_staff and other_regions + not request.user.is_superuser and not request.user.is_staff and other_regions, ) return { "all_regions": all_regions, diff --git a/integreat_cms/cms/views/push_notifications/push_notification_list_view.py b/integreat_cms/cms/views/push_notifications/push_notification_list_view.py index a75d8414e6..c23ae5b8e0 100644 --- a/integreat_cms/cms/views/push_notifications/push_notification_list_view.py +++ b/integreat_cms/cms/views/push_notifications/push_notification_list_view.py @@ -51,7 +51,6 @@ def template_name(self) -> str: return self.template_templates if self.templates else self.template def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: - # pylint: disable=too-many-locals r""" Create a list that shows existing push notifications and translations @@ -93,7 +92,7 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.error( request, _( - "Please create at least one language node before creating push notifications." + "Please create at least one language node before creating push notifications.", ), ) return redirect( @@ -104,7 +103,7 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ) push_notifications = region.push_notifications.filter( - is_template=self.templates + is_template=self.templates, ) query = None @@ -113,10 +112,12 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: if search_form.is_valid(): query = search_form.cleaned_data["query"] push_notification_keys = PushNotificationTranslation.search( - region, language_slug, query + region, + language_slug, + query, ).values("push_notification__pk") push_notifications = push_notifications.filter( - pk__in=push_notification_keys + pk__in=push_notification_keys, ) chunk_size = int(request.GET.get("size", settings.PER_PAGE)) diff --git a/integreat_cms/cms/views/region_condition/region_condition_actions.py b/integreat_cms/cms/views/region_condition/region_condition_actions.py index 04c130af90..b9bf6299f1 100644 --- a/integreat_cms/cms/views/region_condition/region_condition_actions.py +++ b/integreat_cms/cms/views/region_condition/region_condition_actions.py @@ -42,8 +42,8 @@ class RegionConditionResource(resources.ModelResource): num_pages_with_missing_or_outdated_translation = fields.Field( column_name=_( - "Number of pages with at least one missing or outdated translation" - ) + "Number of pages with at least one missing or outdated translation", + ), ) num_outdated_pages = fields.Field(column_name=_("Number of outdated pages")) @@ -97,7 +97,9 @@ def has_bad_translation(page: Page) -> bool: ) pages = region.get_pages( - archived=False, prefetch_translations=True, prefetch_major_translations=True + archived=False, + prefetch_translations=True, + prefetch_major_translations=True, ) return sum(1 for page in pages if has_bad_translation(page)) @@ -118,7 +120,6 @@ def dehydrate_num_languages_besides_root_language(region: Region) -> int: return len(region.active_languages_without_default_language) def get_instance(self, *args: Any, **kwargs: Any) -> Any: - # pylint: disable=useless-parent-delegation """ See :meth:`import_export.resources.Resource.get_instance` """ @@ -142,7 +143,6 @@ def save_instance(self, *args: Any, **kwargs: Any) -> Any: """ return super().save_instance(*args, **kwargs) - # pylint:disable=too-few-public-methods class Meta: """ Metaclass of the region status resource @@ -166,8 +166,8 @@ def export_region_conditions(request: HttpRequest, file_format: str) -> HttpResp resource = RegionConditionResource() dataset = resource.export( queryset=Region.objects.filter( - Q(status=region_status.ACTIVE) | Q(status=region_status.HIDDEN) - ).order_by("name") + Q(status=region_status.ACTIVE) | Q(status=region_status.HIDDEN), + ).order_by("name"), ) supported_file_formats = (f.title for f in format_registry.formats()) @@ -177,7 +177,7 @@ def export_region_conditions(request: HttpRequest, file_format: str) -> HttpResp blob = getattr(dataset, file_format) mime = magic.from_buffer(blob, mime=True) response = HttpResponse(blob, content_type=mime) - filename = f'region conditions summary {datetime.now().strftime("%Y-%m-%d %H:%M")}.{file_format}' + filename = f"region conditions summary {datetime.now().strftime('%Y-%m-%d %H:%M')}.{file_format}" response["Content-Disposition"] = f'attachment; filename="{filename}"' return response diff --git a/integreat_cms/cms/views/regions/region_actions.py b/integreat_cms/cms/views/regions/region_actions.py index dde1908597..95b765ae74 100644 --- a/integreat_cms/cms/views/regions/region_actions.py +++ b/integreat_cms/cms/views/regions/region_actions.py @@ -29,7 +29,9 @@ @require_POST @permission_required("cms.delete_region") def delete_region( - request: HttpRequest, *args: Any, **kwargs: Any + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" This view deletes a region. All content is cascade deleted. Region users, who are not assigned to any other region, @@ -45,7 +47,8 @@ def delete_region( # Check whether region can be safely deleted mirrored_pages = Page.objects.filter( - region=region, mirroring_pages__isnull=False + region=region, + mirroring_pages__isnull=False, ).distinct() if len(mirrored_pages) > 0: messages.error( @@ -53,7 +56,7 @@ def delete_region( format_html( "{}
    {}
", _( - "Region could not be deleted, because the following pages are mirrored in other regions:" + "Region could not be deleted, because the following pages are mirrored in other regions:", ), format_html_join( "\n", @@ -114,7 +117,9 @@ def delete_region( # Get orphan users who aren't superuser or staff and don't have a region assigned # (Creating users with these combination is impossible, so they were region users of the deleted region before) orphan_users = get_user_model().objects.filter( - is_superuser=False, is_staff=False, regions=None + is_superuser=False, + is_staff=False, + regions=None, ) if orphan_users.exists(): logger.info( diff --git a/integreat_cms/cms/views/regions/region_update_view.py b/integreat_cms/cms/views/regions/region_update_view.py index 5fcad3df12..d2a661ab1f 100644 --- a/integreat_cms/cms/views/regions/region_update_view.py +++ b/integreat_cms/cms/views/regions/region_update_view.py @@ -42,13 +42,16 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> TemplateRespon messages.warning( request, _( - "Locations are enabled but the bounding box coordinates are incomplete." + "Locations are enabled but the bounding box coordinates are incomplete.", ), ) return response def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Updates region and removes mirrored pages from all pages of the region when it gets archived diff --git a/integreat_cms/cms/views/release_notes/release_notes_context_mixin.py b/integreat_cms/cms/views/release_notes/release_notes_context_mixin.py index ed169d350a..245e3ac437 100644 --- a/integreat_cms/cms/views/release_notes/release_notes_context_mixin.py +++ b/integreat_cms/cms/views/release_notes/release_notes_context_mixin.py @@ -48,7 +48,8 @@ def get_release_notes(self) -> dict[str, dict[str, dict[str, str]]]: return { year.name: self.get_versions(year) for year in sorted( - Path(settings.RELEASE_NOTES_DIRS).iterdir(), reverse=True + Path(settings.RELEASE_NOTES_DIRS).iterdir(), + reverse=True, )[: self.slice] } @@ -73,7 +74,7 @@ def get_entries(self, version: Path | PosixPath) -> dict[str, str]: # type: ign with ExitStack() as stack: return { note.stem: yaml.safe_load( - stack.enter_context(open(note, encoding="UTF-8")) + stack.enter_context(open(note, encoding="UTF-8")), )[get_language_from_request(self.request)] for note in natsorted(version.iterdir()) } diff --git a/integreat_cms/cms/views/roles/role_form_view.py b/integreat_cms/cms/views/roles/role_form_view.py index 030909b84d..9754ae11a0 100644 --- a/integreat_cms/cms/views/roles/role_form_view.py +++ b/integreat_cms/cms/views/roles/role_form_view.py @@ -75,7 +75,8 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ) role_form = RoleForm(data=request.POST, instance=role_instance) group_form = GroupForm( - data=request.POST, instance=getattr(role_instance, "group", None) + data=request.POST, + instance=getattr(role_instance, "group", None), ) if not role_form.is_valid() or not group_form.is_valid(): diff --git a/integreat_cms/cms/views/settings/dismiss_tutorial_view.py b/integreat_cms/cms/views/settings/dismiss_tutorial_view.py index e59073e19f..bb26d0ec98 100644 --- a/integreat_cms/cms/views/settings/dismiss_tutorial_view.py +++ b/integreat_cms/cms/views/settings/dismiss_tutorial_view.py @@ -33,7 +33,8 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> JsonResponse: request.user.page_tree_tutorial_seen = True else: return JsonResponse( - {"error": f"Tutorial '{tutorial_slug}' not found."}, status=404 + {"error": f"Tutorial '{tutorial_slug}' not found."}, + status=404, ) request.user.save() diff --git a/integreat_cms/cms/views/settings/totp_delete_view.py b/integreat_cms/cms/views/settings/totp_delete_view.py index b5e945761b..e54d266dab 100644 --- a/integreat_cms/cms/views/settings/totp_delete_view.py +++ b/integreat_cms/cms/views/settings/totp_delete_view.py @@ -60,7 +60,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.success( request, _( - "You have successfully disconnected your account from your authenticator app." + "You have successfully disconnected your account from your authenticator app.", ), ) kwargs = ( diff --git a/integreat_cms/cms/views/settings/totp_register_view.py b/integreat_cms/cms/views/settings/totp_register_view.py index 15697a8efd..96f5fdc763 100644 --- a/integreat_cms/cms/views/settings/totp_register_view.py +++ b/integreat_cms/cms/views/settings/totp_register_view.py @@ -42,7 +42,7 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.error( request, _( - "You have already registered a TOTP key. Please disconnect your app before adding a new one." + "You have already registered a TOTP key. Please disconnect your app before adding a new one.", ), ) kwargs = {"region_slug": request.region.slug} if request.region else {} @@ -72,10 +72,12 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: user.totp_key = key user.save() logger.info( - "Account %r added TOTP as a authentication method.", request.user + "Account %r added TOTP as a authentication method.", + request.user, ) messages.success( - request, _("The TOTP Authenticator has been added successfully.") + request, + _("The TOTP Authenticator has been added successfully."), ) # Clear session variable del request.session["new_totp_key"] diff --git a/integreat_cms/cms/views/settings/user_settings_view.py b/integreat_cms/cms/views/settings/user_settings_view.py index 8c8e515d06..4dee2fd0e3 100644 --- a/integreat_cms/cms/views/settings/user_settings_view.py +++ b/integreat_cms/cms/views/settings/user_settings_view.py @@ -42,14 +42,17 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "user_email_form": UserEmailForm(instance=self.request.user), "user_password_form": UserPasswordForm(instance=self.request.user), "user_preferences_form": UserPreferencesForm( - instance=self.request.user + instance=self.request.user, ), - } + }, ) return context def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Submit :class:`~integreat_cms.cms.forms.users.user_email_form.UserEmailForm` and @@ -105,7 +108,8 @@ def post( elif request.POST.get("submit_form") == "preferences_form": user_preferences_form = UserPreferencesForm( - data=request.POST, instance=user + data=request.POST, + instance=user, ) if not user_preferences_form.is_valid(): user_preferences_form.add_error_messages(request) diff --git a/integreat_cms/cms/views/settings/webauthn/delete_user_fido_key_view.py b/integreat_cms/cms/views/settings/webauthn/delete_user_fido_key_view.py index 5433149f2c..f33119417f 100644 --- a/integreat_cms/cms/views/settings/webauthn/delete_user_fido_key_view.py +++ b/integreat_cms/cms/views/settings/webauthn/delete_user_fido_key_view.py @@ -49,7 +49,7 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.warning( request, _( - "This is your last key, once removed you will be able to log in without a second factor." + "This is your last key, once removed you will be able to log in without a second factor.", ), ) else: @@ -57,10 +57,10 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: request, __( _( - "Once you remove the key you will need to use one of the other available keys to log into your account." + "Once you remove the key you will need to use one of the other available keys to log into your account.", ), _( - "Please make sure that you have at least one extra key available to log in before removing this key." + "Please make sure that you have at least one extra key available to log in before removing this key.", ), ), ) @@ -84,7 +84,7 @@ def post(self, request: HttpRequest, **kwargs: Any) -> HttpResponseRedirect: messages.success( request, _('The 2-factor authentication key "{}" was successfully deleted').format( - key.name + key.name, ), ) key.delete() diff --git a/integreat_cms/cms/views/settings/webauthn/get_mfa_challenge_view.py b/integreat_cms/cms/views/settings/webauthn/get_mfa_challenge_view.py index 79eec498c9..41f472d130 100644 --- a/integreat_cms/cms/views/settings/webauthn/get_mfa_challenge_view.py +++ b/integreat_cms/cms/views/settings/webauthn/get_mfa_challenge_view.py @@ -50,9 +50,9 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: user_display_name=user.first_name + " " + user.last_name, ) request.session["mfa_registration_challenge"] = bytes_to_base64url( - make_credential_options.challenge + make_credential_options.challenge, ) - # pylint: disable=http-response-with-content-type-json return HttpResponse( - options_to_json(make_credential_options), content_type="application/json" + options_to_json(make_credential_options), + content_type="application/json", ) diff --git a/integreat_cms/cms/views/settings/webauthn/register_user_fido_key_view.py b/integreat_cms/cms/views/settings/webauthn/register_user_fido_key_view.py index 04328be101..672a3f739d 100644 --- a/integreat_cms/cms/views/settings/webauthn/register_user_fido_key_view.py +++ b/integreat_cms/cms/views/settings/webauthn/register_user_fido_key_view.py @@ -57,22 +57,22 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: expected_rp_id=settings.HOSTNAME, expected_origin=settings.BASE_URL, expected_challenge=base64url_to_bytes( - request.session["mfa_registration_challenge"] + request.session["mfa_registration_challenge"], ), ) existing_key = request.user.fido_keys.filter(name=json_data["name"]) if existing_key.exists(): return JsonResponse( - {"success": False, "error": _("This key name has already been used")} + {"success": False, "error": _("This key name has already been used")}, ) existing_key = request.user.fido_keys.filter( - key_id=webauthn_registration_response.credential_id + key_id=webauthn_registration_response.credential_id, ) if existing_key.exists(): return JsonResponse( - {"success": False, "error": _("You already registered this key")} + {"success": False, "error": _("You already registered this key")}, ) new_key = FidoKey( @@ -86,7 +86,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.success( request, _( - 'The 2-factor authentication key "{}" was successfully registered.' + 'The 2-factor authentication key "{}" was successfully registered.', ).format(new_key.name), ) # Determine success url based on current region diff --git a/integreat_cms/cms/views/statistics/statistics_actions.py b/integreat_cms/cms/views/statistics/statistics_actions.py index 8a1214dfe2..6117507b67 100644 --- a/integreat_cms/cms/views/statistics/statistics_actions.py +++ b/integreat_cms/cms/views/statistics/statistics_actions.py @@ -4,7 +4,6 @@ from __future__ import annotations -import asyncio import logging from datetime import date, timedelta from typing import TYPE_CHECKING @@ -24,13 +23,13 @@ @permission_required("cms.view_statistics") def get_total_visits_ajax( - request: HttpRequest, region_slug: str # pylint: disable=unused-argument + request: HttpRequest, + region_slug: str, ) -> JsonResponse: """ Aggregates the total API hits of the last 14 days and renders a Widget for the Dashboard. :param request: The current request - :param region_slug: The slug of the current region :return: A JSON with all API-Hits of the last 2 weeks """ @@ -38,7 +37,8 @@ def get_total_visits_ajax( if not region.statistics_enabled: return JsonResponse( - {"error": "Statistics are not enabled for this region."}, status=500 + {"error": "Statistics are not enabled for this region."}, + status=500, ) start_date = date.today() - timedelta(days=15) @@ -46,29 +46,32 @@ def get_total_visits_ajax( try: result = region.statistics.get_total_visits( - start_date=start_date, end_date=end_date + start_date=start_date, + end_date=end_date, ) return JsonResponse(result) - except asyncio.TimeoutError: + except TimeoutError: return JsonResponse( - {"error": "Timeout during request to Matomo API"}, status=504 + {"error": "Timeout during request to Matomo API"}, + status=504, ) - except MatomoException as e: - logger.exception(e) + except MatomoException: + logger.exception("") return JsonResponse( - {"error": "The request to the Matomo API failed."}, status=500 + {"error": "The request to the Matomo API failed."}, + status=500, ) @require_POST def get_visits_per_language_ajax( - request: HttpRequest, region_slug: str # pylint: disable=unused-argument + request: HttpRequest, + region_slug: str, ) -> JsonResponse: """ Ajax method to request the app hits for a certain timerange distinguished by languages. :param request: The current request - :param region_slug: The slug of the current region :return: A JSON with all API-Hits of the requested time period """ @@ -76,7 +79,8 @@ def get_visits_per_language_ajax( if not region.statistics_enabled: return JsonResponse( - {"error": "Statistics are not enabled for this region."}, status=500 + {"error": "Statistics are not enabled for this region."}, + status=500, ) statistics_form = StatisticsFilterForm(data=request.POST) @@ -94,12 +98,14 @@ def get_visits_per_language_ajax( period=statistics_form.cleaned_data["period"], ) return JsonResponse(result, safe=False) - except asyncio.TimeoutError: + except TimeoutError: return JsonResponse( - {"error": "Timeout during request to Matomo API"}, status=504 + {"error": "Timeout during request to Matomo API"}, + status=504, ) - except MatomoException as e: - logger.exception(e) + except MatomoException: + logger.exception("") return JsonResponse( - {"error": "The request to the Matomo API failed."}, status=500 + {"error": "The request to the Matomo API failed."}, + status=500, ) diff --git a/integreat_cms/cms/views/statistics/statistics_view.py b/integreat_cms/cms/views/statistics/statistics_view.py index 6b6fd47aac..ea9bdbc8bf 100644 --- a/integreat_cms/cms/views/statistics/statistics_view.py +++ b/integreat_cms/cms/views/statistics/statistics_view.py @@ -34,7 +34,10 @@ class AnalyticsView(TemplateView): extra_context = {"current_menu_item": "statistics"} def get( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Render statistics of access numbers tracked by Matomo diff --git a/integreat_cms/cms/views/translations/translations_management_view.py b/integreat_cms/cms/views/translations/translations_management_view.py index bff618e4f9..656047bbd4 100644 --- a/integreat_cms/cms/views/translations/translations_management_view.py +++ b/integreat_cms/cms/views/translations/translations_management_view.py @@ -52,23 +52,23 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: for content_type in content_types: content_name = content_type._meta.verbose_name_plural.title() word_count[content_name] = Counter() - # pylint: disable=unused-variable - for status, name in CHOICES: + for status, _name in CHOICES: word_count[content_name][status] = 0 contents = ( region.get_pages(prefetch_translations=True) if content_type == Page else content_type.objects.filter( - region=region, archived=False + region=region, + archived=False, ).prefetch_translations() ) for content in contents: if latest_version := content.get_translation( - region.default_language.slug + region.default_language.slug, ): word_count[content_name][latest_version.status] += len( - latest_version.content.split() + latest_version.content.split(), ) context = super().get_context_data(**kwargs) @@ -81,7 +81,7 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: "total_autosave_words": sum( c["AUTO_SAVE"] for c in word_count.values() ), - } + }, ) return context @@ -108,7 +108,10 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: @transaction.atomic def post( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponseRedirect: r""" Submit :class:`~integreat_cms.cms.forms.translations.translations_management_form.TranslationsManagementForm` objects. diff --git a/integreat_cms/cms/views/users/region_user_actions.py b/integreat_cms/cms/views/users/region_user_actions.py index 45301d9f50..47193cff74 100644 --- a/integreat_cms/cms/views/users/region_user_actions.py +++ b/integreat_cms/cms/views/users/region_user_actions.py @@ -25,14 +25,13 @@ @permission_required("cms.delete_user") def delete_region_user( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, user_id: int, ) -> HttpResponseRedirect: """ This view deletes a region user :param request: The current request - :param region_slug: The slug of the current region :param user_id: The id of the user which should be deleted :return: A redirection to region user list """ @@ -62,7 +61,7 @@ def delete_region_user( messages.success( request, _('Account "{}" was successfully removed from this region.').format( - user.full_user_name + user.full_user_name, ), ) @@ -73,14 +72,13 @@ def delete_region_user( @permission_required("cms.change_user") def resend_activation_link_region( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, user_id: int, ) -> HttpResponseRedirect: """ Resends an activation link to a region user :param request: The current request - :param region_slug: The slug of the current region :param user_id: users id to send the activation link :return: A redirection to region user list """ diff --git a/integreat_cms/cms/views/users/region_user_form_view.py b/integreat_cms/cms/views/users/region_user_form_view.py index b84393e9d5..ee230f6420 100644 --- a/integreat_cms/cms/views/users/region_user_form_view.py +++ b/integreat_cms/cms/views/users/region_user_form_view.py @@ -79,7 +79,9 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: user_instance = region.region_users.filter(id=kwargs.get("user_id")).first() region_user_form = RegionUserForm( - region=region, data=request.POST, instance=user_instance + region=region, + data=request.POST, + instance=user_instance, ) if not region_user_form.is_valid(): @@ -102,7 +104,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.success( request, _('Account "{}" was successfully created.').format( - region_user_form.instance.full_user_name + region_user_form.instance.full_user_name, ), ) else: @@ -110,7 +112,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.success( request, _('Account "{}" was successfully saved.').format( - region_user_form.instance.full_user_name + region_user_form.instance.full_user_name, ), ) return redirect( diff --git a/integreat_cms/cms/views/users/user_actions.py b/integreat_cms/cms/views/users/user_actions.py index b101123d51..c07aa25286 100644 --- a/integreat_cms/cms/views/users/user_actions.py +++ b/integreat_cms/cms/views/users/user_actions.py @@ -41,7 +41,8 @@ def delete_user(request: HttpRequest, user_id: int) -> HttpResponseRedirect: user.delete() logger.info("%r deleted %r", request.user, user) messages.success( - request, _('Account "{}" was successfully deleted.').format(user.full_user_name) + request, + _('Account "{}" was successfully deleted.').format(user.full_user_name), ) return redirect("users") diff --git a/integreat_cms/cms/views/users/user_form_view.py b/integreat_cms/cms/views/users/user_form_view.py index 73581ba105..67404db404 100644 --- a/integreat_cms/cms/views/users/user_form_view.py +++ b/integreat_cms/cms/views/users/user_form_view.py @@ -87,7 +87,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.error( request, _( - "An account has to be either staff/superuser or needs to be restricted to at least one region." + "An account has to be either staff/superuser or needs to be restricted to at least one region.", ), ) elif not request.user.is_superuser and "is_superuser" in user_form.changed_data: @@ -118,7 +118,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.success( request, _('Account "{}" was successfully created.').format( - user_form.instance.full_user_name + user_form.instance.full_user_name, ), ) else: @@ -126,7 +126,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: messages.success( request, _('Account "{}" was successfully saved.').format( - user_form.instance.full_user_name + user_form.instance.full_user_name, ), ) return redirect( diff --git a/integreat_cms/cms/views/users/user_list_view.py b/integreat_cms/cms/views/users/user_list_view.py index e747513836..7d7ef4db60 100644 --- a/integreat_cms/cms/views/users/user_list_view.py +++ b/integreat_cms/cms/views/users/user_list_view.py @@ -50,8 +50,8 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: .objects.select_related("organization") .annotate( has_fido_keys=models.Exists( - FidoKey.objects.filter(user=models.OuterRef("pk")) - ) + FidoKey.objects.filter(user=models.OuterRef("pk")), + ), ) .prefetch_related("groups__role") .order_by("username") diff --git a/integreat_cms/cms/views/utils/contact_utils.py b/integreat_cms/cms/views/utils/contact_utils.py index be103ead84..634cd0276c 100644 --- a/integreat_cms/cms/views/utils/contact_utils.py +++ b/integreat_cms/cms/views/utils/contact_utils.py @@ -13,11 +13,8 @@ from ...models import Contact if TYPE_CHECKING: - from typing import Any, Literal - from django.http import HttpRequest - from ...models.abstract_content_translation import AbstractContentTranslation logger = logging.getLogger(__name__) @@ -37,7 +34,6 @@ def search_contact_ajax( :raises ~django.core.exceptions.PermissionDenied: If the user has no permission to the object type :return: Json object containing all matching elements, of shape {title: str, url: str, type: str} """ - # pylint: disable=unused-argument body = json.loads(request.body.decode("utf-8")) if (query := body["query_string"]) is None: @@ -47,7 +43,8 @@ def search_contact_ajax( if not request.user.has_perm("cms.view_contact"): raise PermissionDenied - assert request.region is not None + if request.region is None: + raise ValueError("Expected a region to be provided") results = Contact.search(request.region, query)[:MAX_RESULT_COUNT] return JsonResponse( @@ -59,14 +56,16 @@ def search_contact_ajax( "details": result.available_details, } for result in results - ] - } + ], + }, ) @permission_required("cms.view_contact") def get_contact( - request: HttpRequest, contact_id: int, region_slug: str | None = None + request: HttpRequest, + contact_id: int, + region_slug: str | None = None, ) -> str: """ Retrieves the rendered HTML representation of a contact. @@ -76,7 +75,6 @@ def get_contact( :param region_slug: The slug of the current region :return: HTML representation of the requested contact """ - # pylint: disable=unused-argument contact = get_object_or_404(Contact, pk=contact_id) wanted = request.GET.get("details", "").split(",") @@ -87,7 +85,9 @@ def get_contact( @permission_required("cms.view_contact") def get_contact_raw( - request: HttpRequest, contact_id: int, region_slug: str | None = None + request: HttpRequest, + contact_id: int, + region_slug: str | None = None, ) -> str: """ Retrieves the short representation of a single contact, and returns it @@ -98,7 +98,6 @@ def get_contact_raw( :param region_slug: The slug of the current region :return: Short representation of the requested contact """ - # pylint: disable=unused-argument contact = get_object_or_404(Contact, pk=contact_id) return JsonResponse( { diff --git a/integreat_cms/cms/views/utils/content_edit_lock.py b/integreat_cms/cms/views/utils/content_edit_lock.py index 3b7d8386aa..9175cf41bb 100644 --- a/integreat_cms/cms/views/utils/content_edit_lock.py +++ b/integreat_cms/cms/views/utils/content_edit_lock.py @@ -18,7 +18,7 @@ @require_POST def content_edit_lock_heartbeat( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> JsonResponse: """ This function handles heartbeat requests. @@ -26,7 +26,6 @@ def content_edit_lock_heartbeat( who is editing some content. :param request: The current request - :param region_slug: The slug of the current region, unused :return: Json object containing `success: true` if the lock could be acquired """ body = json.loads(request.body.decode("utf-8")) @@ -47,20 +46,19 @@ def content_edit_lock_heartbeat( if TYPE_CHECKING: assert locking_user return JsonResponse( - {"success": success, "lockingUser": locking_user.full_user_name} + {"success": success, "lockingUser": locking_user.full_user_name}, ) @require_POST def content_edit_lock_release( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, ) -> JsonResponse: """ This function handles unlock requests :param request: The current request - :param region_slug: The slug of the current region, unused :return: Json object containing `success: true` if the content object could be unlocked """ body = json.loads(request.POST.get("body")) diff --git a/integreat_cms/cms/views/utils/hix.py b/integreat_cms/cms/views/utils/hix.py index 21906074a5..d0c8cbe1ba 100644 --- a/integreat_cms/cms/views/utils/hix.py +++ b/integreat_cms/cms/views/utils/hix.py @@ -18,18 +18,19 @@ from lxml.html import fromstring, tostring from integreat_cms.cms.models.pages.page_translation import PageTranslation -from integreat_cms.cms.models.regions.region import Region from integreat_cms.cms.utils.round_hix_score import round_hix_score from ....api.decorators import json_response from ....textlab_api.textlab_api_client import TextlabClient, TextlabResult if TYPE_CHECKING: - from typing import Final + from typing import Any, Final from django.db.models.query import QuerySet from django.http import HttpRequest + from integreat_cms.cms.models.regions.region import Region + logger = logging.getLogger(__name__) MAX_TEXT_LENGTH: Final[int] = 100_000 @@ -75,7 +76,8 @@ def lookup_hix_score_helper(text: str) -> TextlabResult: try: return TextlabClient( - settings.TEXTLAB_API_USERNAME, settings.TEXTLAB_API_KEY + settings.TEXTLAB_API_USERNAME, + settings.TEXTLAB_API_KEY, ).benchmark(normalized_text) except (URLError, OSError) as e: logger.warning("HIX benchmark API call failed: %r", e) @@ -99,13 +101,13 @@ def lookup_hix_score(text: str) -> TextlabResult | None: @require_POST @json_response def get_hix_score( - request: HttpRequest, region_slug: str # pylint: disable=unused-argument + request: HttpRequest, + **kwargs: Any, ) -> JsonResponse: """ Calculates the hix score for the param 'text' in the request body and returns it. :param request: The request - :param region_slug: The slug of the current region :return: A json response, of {"score": value} in case of success """ # Don't pass texts larger than 100kb to the api in order to avoid being vulnerable to dos attacks @@ -124,7 +126,7 @@ def get_hix_score( { "score": round_hix_score(response.get("score")), "feedback": response.get("feedback"), - } + }, ) return JsonResponse({"error": "Could not retrieve hix score"}) @@ -145,7 +147,7 @@ def get_translations_relevant_to_hix(region: Region) -> QuerySet: language__slug__in=settings.TEXTLAB_API_LANGUAGES, page__in=region.get_pages().filter(hix_ignore=False), ).distinct("page_id", "language_id") - ) + ), ).order_by("hix_score") @@ -177,5 +179,5 @@ def get_translation_over_hix_threshold( :param region: The region for which to get all of the translations """ return get_translations_relevant_to_hix(region=region).filter( - hix_score__gte=ACTUAL_RAW_HIX_SCORE_THRESHOLD + hix_score__gte=ACTUAL_RAW_HIX_SCORE_THRESHOLD, ) diff --git a/integreat_cms/cms/views/utils/machine_translations.py b/integreat_cms/cms/views/utils/machine_translations.py index a46fc86c0c..5cdc77be58 100644 --- a/integreat_cms/cms/views/utils/machine_translations.py +++ b/integreat_cms/cms/views/utils/machine_translations.py @@ -23,16 +23,14 @@ def build_json_for_machine_translation( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, model_type: Literal["page", "event", "poi"], ) -> JsonResponse: - # pylint: disable=too-many-locals """ This function collects the hix score and the amount of words per content entry from the source :param request: The current request - :param region_slug: slug of the according region :param language_slug: The slug of the current language :param model_type: The according model to the different content types :return: A dictionary that contains the data for the machine translation popup (page id, title, amount of words and optional hix value) @@ -50,7 +48,8 @@ def build_json_for_machine_translation( logger.warning("Malformed request body! %r", e) selected_content_ids = [] selected_content = model_types[model_type].objects.filter( - region=request.region, id__in=selected_content_ids + region=request.region, + id__in=selected_content_ids, ) filtered_content = {} @@ -69,7 +68,9 @@ def build_json_for_machine_translation( ) if source_translation and check_hix_score( - request, source_translation, show_message=False + request, + source_translation, + show_message=False, ): content_data = { "id": content.id, diff --git a/integreat_cms/cms/views/utils/publication_status.py b/integreat_cms/cms/views/utils/publication_status.py index 9ee81188f8..d7ad348e19 100644 --- a/integreat_cms/cms/views/utils/publication_status.py +++ b/integreat_cms/cms/views/utils/publication_status.py @@ -7,7 +7,6 @@ from typing import TYPE_CHECKING from django.contrib import messages -from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from ...constants import status @@ -50,7 +49,7 @@ def change_publication_status( translation.version += 1 if desired_status == status.DRAFT: translation.all_versions.filter(status=status.PUBLIC).update( - status=status.DRAFT + status=status.DRAFT, ) translation.save() successful.append(translation.title) diff --git a/integreat_cms/cms/views/utils/search_content_ajax.py b/integreat_cms/cms/views/utils/search_content_ajax.py index 23c9c98c29..737315c15f 100644 --- a/integreat_cms/cms/views/utils/search_content_ajax.py +++ b/integreat_cms/cms/views/utils/search_content_ajax.py @@ -50,7 +50,7 @@ def format_object_translation( """ if object_translation.language.slug != target_language_slug: object_translation = object_translation.foreign_object.get_public_translation( - target_language_slug + target_language_slug, ) if isinstance(object_translation.link_title, str): html_title = object_translation.link_title @@ -73,16 +73,14 @@ def format_object_translation( @require_POST def search_content_ajax( request: HttpRequest, - region_slug: str | None = None, # pylint: disable=unused-argument + region_slug: str | None = None, language_slug: str | None = None, ) -> JsonResponse: - # pylint: disable=too-many-branches,too-many-statements """Searches all pois, events and pages for the current region and returns all that match the search query. Results which match the query in the title or slug get ranked higher than results which only match through their text content. :param request: The current request - :param region_slug: The slug of the current region :param language_slug: language slug :type language_slug: str @@ -132,7 +130,7 @@ def search_content_ajax( "type": "feedback", } for feedback in Feedback.search(region, query).filter( - archived=archived_flag + archived=archived_flag, ) ) @@ -150,7 +148,7 @@ def search_content_ajax( or query.lower() in page_translation.title.lower() ): results.append( - format_object_translation(page_translation, "page", language_slug) + format_object_translation(page_translation, "page", language_slug), ) if "poi" in object_types: @@ -182,7 +180,9 @@ def search_content_ajax( "type": "push_notification", } for push_notification in PushNotificationTranslation.search( - region, language_slug, query + region, + language_slug, + query, ) ) diff --git a/integreat_cms/cms/views/utils/slugify_ajax.py b/integreat_cms/cms/views/utils/slugify_ajax.py index 26ca62e70c..90f659f6be 100644 --- a/integreat_cms/cms/views/utils/slugify_ajax.py +++ b/integreat_cms/cms/views/utils/slugify_ajax.py @@ -20,14 +20,13 @@ def slugify_ajax( request: HttpRequest, - region_slug: str, # pylint: disable=unused-argument + region_slug: str, language_slug: str, model_type: Literal["page", "event", "poi"], ) -> JsonResponse: """checks the current user input for title and generates unique slug for permalink :param request: The current request - :param region_slug: region identifier :param language_slug: language slug :param model_type: The type of model to generate a unique slug for, one of `event|page|poi` :return: unique translation slug @@ -51,7 +50,7 @@ def slugify_ajax( manager = managers[model_type].objects object_instance = manager.filter( - **{model_type: model_id, "language": language} + **{model_type: model_id, "language": language}, ).first() if not ( diff --git a/integreat_cms/cms/views/utils/translation_coverage.py b/integreat_cms/cms/views/utils/translation_coverage.py index bed69df5ef..3e824467d6 100644 --- a/integreat_cms/cms/views/utils/translation_coverage.py +++ b/integreat_cms/cms/views/utils/translation_coverage.py @@ -1,5 +1,4 @@ from collections import Counter -from typing import Dict, Tuple from ...constants.translation_status import MISSING, OUTDATED, UP_TO_DATE from ...models import Language, Region @@ -7,7 +6,7 @@ def get_translation_and_word_count( region: Region, -) -> Tuple[Dict[Language, Counter], Dict[Language, Counter]]: +) -> tuple[dict[Language, Counter], dict[Language, Counter]]: """ This function counts the translations and words of a region diff --git a/integreat_cms/core/apps.py b/integreat_cms/core/apps.py index 91f440eb2d..372350a03a 100644 --- a/integreat_cms/core/apps.py +++ b/integreat_cms/core/apps.py @@ -35,20 +35,22 @@ class CoreConfig(AppConfig): test_external_apis: bool = False def ready(self) -> None: - # pylint: disable=unused-import,import-outside-toplevel # Implicitly connect signal handlers decorated with @receiver. - from . import signals + from . import signals # noqa: F401 # Add SUCCESS (25) as custom log level logging.addLevelName(SUCCESS, "SUCCESS") def success( - self: logging.Logger, message: str, *args: Any, **kwargs: Any + self: logging.Logger, + message: str, + *args: Any, + **kwargs: Any, ) -> None: if self.isEnabledFor(SUCCESS): self._log(SUCCESS, message, args, **kwargs) - setattr(logging.getLoggerClass(), "success", success) + logging.getLoggerClass().success = success # type: ignore[attr-defined] # Determine whether the availability of external APIs should be checked self.test_external_apis = ( diff --git a/integreat_cms/core/circleci_settings.py b/integreat_cms/core/circleci_settings.py index abb9a2badc..7414ad31aa 100644 --- a/integreat_cms/core/circleci_settings.py +++ b/integreat_cms/core/circleci_settings.py @@ -5,8 +5,6 @@ For the full list of settings and their values, see :doc:`django:ref/settings`. """ -# pylint: disable=wildcard-import -# pylint: disable=unused-wildcard-import from __future__ import annotations import sys @@ -14,7 +12,7 @@ from .settings import * #: Set a dummy secret key for CircleCI build even if it's not in debug mode -SECRET_KEY = "dummy" +SECRET_KEY = "dummy" # noqa: S105 #: Set dummy credentials path to test push notifications FCM_CREDENTIALS = "dummy" #: Enable manually because existing setting derives from the unset env var diff --git a/integreat_cms/core/docker_settings.py b/integreat_cms/core/docker_settings.py index 9744360a56..646b360208 100644 --- a/integreat_cms/core/docker_settings.py +++ b/integreat_cms/core/docker_settings.py @@ -8,8 +8,6 @@ For the full list of settings and their values, see :doc:`django:ref/settings`. """ -# pylint: disable=wildcard-import -# pylint: disable=unused-wildcard-import from __future__ import annotations from .settings import * @@ -24,5 +22,5 @@ "PASSWORD": "password", "HOST": "localhost", "PORT": "5433", - } + }, } diff --git a/integreat_cms/core/logging_formatter.py b/integreat_cms/core/logging_formatter.py index 474a2cac37..ecfeb5128e 100644 --- a/integreat_cms/core/logging_formatter.py +++ b/integreat_cms/core/logging_formatter.py @@ -39,7 +39,6 @@ def format(self, record: LogRecord) -> str: # Make level name bold fmt = self._fmt.replace("{levelname}", "\x1b[1m{levelname}" + color) # Make entire line colored - # pylint: disable=protected-access self._style._fmt = color + fmt + "\x1b[0m" return super().format(record) diff --git a/integreat_cms/core/management/commands/drop_expired_user_accounts.py b/integreat_cms/core/management/commands/drop_expired_user_accounts.py index cb0208488d..0614aa6cfd 100644 --- a/integreat_cms/core/management/commands/drop_expired_user_accounts.py +++ b/integreat_cms/core/management/commands/drop_expired_user_accounts.py @@ -32,12 +32,12 @@ def handle(self, *args: Any, **options: Any) -> None: # Fetch users who have not been activated and whose creation date is before the cutoff date users_to_delete = User.objects.filter( - is_active=False, date_joined__lte=cutoff_date + is_active=False, + date_joined__lte=cutoff_date, ) num_users_to_delete = users_to_delete.count() - # pylint: disable=consider-using-assignment-expr if not num_users_to_delete: logger.info("No inactive users were found who need to be deleted.") return diff --git a/integreat_cms/core/management/commands/duplicate_pages.py b/integreat_cms/core/management/commands/duplicate_pages.py index 9c11139dd2..3e49bf26cb 100644 --- a/integreat_cms/core/management/commands/duplicate_pages.py +++ b/integreat_cms/core/management/commands/duplicate_pages.py @@ -36,7 +36,6 @@ def duplicate_page(old_page: Page, new_parent: Page | None = None) -> Page: logger.debug("Old translations: %r", translations) new_page = copy.deepcopy(old_page) new_page.id = None - # pylint: disable=protected-access new_page._state.adding = True new_page = ( new_parent.add_child(instance=new_page) @@ -48,7 +47,7 @@ def duplicate_page(old_page: Page, new_parent: Page | None = None) -> Page: new_page.parent = new_page.get_parent(update=True) new_page.save() for translation in translations: - rand_str = "".join(random.choices(string.printable, k=5)) + rand_str = "".join(random.choices(string.printable, k=5)) # noqa: S311 new_title = translation.title.split(" - ")[0] + f" - {rand_str}" translation.id = None translation.page = new_page @@ -60,7 +59,9 @@ def duplicate_page(old_page: Page, new_parent: Page | None = None) -> Page: def duplicate_pages( - region: Region, old_parent: Page | None = None, new_parent: Page | None = None + region: Region, + old_parent: Page | None = None, + new_parent: Page | None = None, ) -> None: """ Duplicate pages recursively for the given region @@ -95,7 +96,6 @@ def add_arguments(self, parser: CommandParser) -> None: parser.add_argument("region_slug", help="The slug of the region") def handle(self, *args: Any, region_slug: str, **options: Any) -> None: - # pylint: disable=arguments-differ r""" Try to run the command @@ -110,7 +110,7 @@ def handle(self, *args: Any, region_slug: str, **options: Any) -> None: region = Region.objects.get(slug=region_slug) except Region.DoesNotExist as e: raise CommandError( - f'Region with slug "{region_slug}" does not exist.' + f'Region with slug "{region_slug}" does not exist.', ) from e logger.info("Duplicating pages for %r", region) diff --git a/integreat_cms/core/management/commands/find_large_files.py b/integreat_cms/core/management/commands/find_large_files.py index a4da9b5d25..baa31b9c92 100644 --- a/integreat_cms/core/management/commands/find_large_files.py +++ b/integreat_cms/core/management/commands/find_large_files.py @@ -44,7 +44,6 @@ def add_arguments(self, parser: CommandParser) -> None: ) def handle(self, *args: Any, limit: int, threshold: int, **options: Any) -> None: - # pylint: disable=arguments-differ r""" Try to run the command @@ -63,10 +62,12 @@ def handle(self, *args: Any, limit: int, threshold: int, **options: Any) -> None raise CommandError("Please select a limit smaller than 100.") threshold_bytes = threshold * 1024 * 1024 logger.info( - "Searching the largest %s media with more than %sMiB...", limit, threshold + "Searching the largest %s media with more than %sMiB...", + limit, + threshold, ) if queryset := MediaFile.objects.filter(file_size__gt=threshold_bytes).order_by( - "-file_size" + "-file_size", )[:limit]: file_info = [ (filesizeformat(file.file_size), file.name, file.region or "global") diff --git a/integreat_cms/core/management/commands/find_missing_versions.py b/integreat_cms/core/management/commands/find_missing_versions.py index a2635d8ade..b49ede00d0 100644 --- a/integreat_cms/core/management/commands/find_missing_versions.py +++ b/integreat_cms/core/management/commands/find_missing_versions.py @@ -35,7 +35,7 @@ def get_model(model_str: str) -> ModelBase: return MODELS[model_str] except KeyError as e: raise ArgumentTypeError( - "Invalid model (must be either page, event or poi)" + "Invalid model (must be either page, event or poi)", ) from e @@ -59,7 +59,6 @@ def add_arguments(self, parser: CommandParser) -> None: ) def handle(self, *args: Any, model: ModelBase, **options: Any) -> None: - # pylint: disable=arguments-differ r""" Try to run the command @@ -69,14 +68,16 @@ def handle(self, *args: Any, model: ModelBase, **options: Any) -> None: """ self.set_logging_stream() logger.info( - "Checking the model %s for version inconsistencies...", model.__name__ + "Checking the model %s for version inconsistencies...", + model.__name__, ) success = True for latest_version in model.objects.distinct( - f"{model.foreign_field()}__pk", "language__pk" + f"{model.foreign_field()}__pk", + "language__pk", ): num = latest_version.foreign_object.translations.filter( - language=latest_version.language + language=latest_version.language, ).count() if latest_version.version != num: logger.error( diff --git a/integreat_cms/core/management/commands/fix_internal_links.py b/integreat_cms/core/management/commands/fix_internal_links.py index 1713be41b4..6936962392 100644 --- a/integreat_cms/core/management/commands/fix_internal_links.py +++ b/integreat_cms/core/management/commands/fix_internal_links.py @@ -4,7 +4,7 @@ import time from collections import defaultdict from functools import partial -from typing import DefaultDict, TYPE_CHECKING +from typing import TYPE_CHECKING from urllib.parse import unquote from django.contrib.auth import get_user_model @@ -14,7 +14,6 @@ from lxml.html import rewrite_links from ....cms.models import Region -from ....cms.models.abstract_content_translation import AbstractContentTranslation from ....cms.utils import internal_link_utils from ....cms.utils.link_utils import fix_content_link_encoding from ....cms.utils.linkcheck_utils import get_region_links @@ -26,6 +25,7 @@ from django.core.management.base import CommandParser from ....cms.models import User + from ....cms.models.abstract_content_translation import AbstractContentTranslation logger = logging.getLogger(__name__) @@ -76,7 +76,8 @@ def add_arguments(self, parser: CommandParser) -> None: :param parser: The argument parser """ parser.add_argument( - "--region-slug", help="Only fix links in the region with this slug" + "--region-slug", + help="Only fix links in the region with this slug", ) parser.add_argument("--username", help="The username of the creator") parser.add_argument( @@ -86,9 +87,13 @@ def add_arguments(self, parser: CommandParser) -> None: ) def handle( - self, *args: Any, region_slug: str, username: str, commit: bool, **options: Any + self, + *args: Any, + region_slug: str, + username: str, + commit: bool, + **options: Any, ) -> None: - # pylint: disable=arguments-differ, too-many-locals r""" Try to run the command @@ -104,7 +109,7 @@ def handle( region = get_region(region_slug) if region_slug else None user = get_user(username) if username else None - translation_updates: DefaultDict[AbstractContentTranslation, dict[str, str]] = ( + translation_updates: defaultdict[AbstractContentTranslation, dict[str, str]] = ( defaultdict(dict) ) @@ -117,7 +122,7 @@ def handle( if not url.internal: continue source_translation = internal_link_utils.get_public_translation_for_link( - url.url + url.url, ) if not source_translation: continue @@ -128,7 +133,7 @@ def handle( if target_language_slug != source_translation.language.slug: target_translation = ( source_translation.foreign_object.get_public_translation( - target_language_slug + target_language_slug, ) ) @@ -136,7 +141,10 @@ def handle( source_url = unquote(url.url) if target_url.strip("/") != source_url.strip("/"): logger.debug( - "%r: %r -> %r", link.content_object, source_url, target_url + "%r: %r -> %r", + link.content_object, + source_url, + target_url, ) translation_updates[link.content_object][source_url] = target_url @@ -171,10 +179,13 @@ def replace_links_of_translation( """ new_translation = translation.create_new_version_copy(user) new_translation.content = fix_content_link_encoding( - rewrite_links(new_translation.content, partial(replace_link_helper, rules)) + rewrite_links(new_translation.content, partial(replace_link_helper, rules)), ) logger.debug( - "Replacing %r link(s) in %r: %r", len(rules), new_translation, rules.keys() + "Replacing %r link(s) in %r: %r", + len(rules), + new_translation, + rules.keys(), ) if commit: translation.links.all().delete() diff --git a/integreat_cms/core/management/commands/hix_bulk.py b/integreat_cms/core/management/commands/hix_bulk.py index a24f838a62..abe6193c4c 100644 --- a/integreat_cms/core/management/commands/hix_bulk.py +++ b/integreat_cms/core/management/commands/hix_bulk.py @@ -66,7 +66,6 @@ def add_arguments(self, parser: CommandParser) -> None: ) def handle(self, *args: Any, region_slugs: list[str], **options: Any) -> None: - # pylint: disable=arguments-differ r""" Try to run the command diff --git a/integreat_cms/core/management/commands/import_pois_from_csv.py b/integreat_cms/core/management/commands/import_pois_from_csv.py index e5619d31c5..8c1e02ad92 100644 --- a/integreat_cms/core/management/commands/import_pois_from_csv.py +++ b/integreat_cms/core/management/commands/import_pois_from_csv.py @@ -50,7 +50,7 @@ def get_or_create_default_category(self, default_language: Language) -> POICateg """ if not ( default_category := POICategory.objects.filter( - icon=poicategory.OTHER + icon=poicategory.OTHER, ).first() ): default_category = POICategory.objects.create( @@ -65,7 +65,9 @@ def get_or_create_default_category(self, default_language: Language) -> POICateg return default_category def get_category( - self, category_name: str, default_language: Language + self, + category_name: str, + default_language: Language, ) -> POICategory: """ Get a POI category object from the category's name @@ -75,7 +77,7 @@ def get_category( :returns: The given POI category """ if category_translation := POICategoryTranslation.objects.filter( - name=category_name + name=category_name, ).first(): return category_translation.category return self.get_or_create_default_category(default_language) @@ -163,7 +165,8 @@ def add_arguments(self, parser: CommandParser) -> None: """ parser.add_argument("csv_filename", help="The source CSV file to import from") parser.add_argument( - "region_slug", help="Import the POI objects into this region" + "region_slug", + help="Import the POI objects into this region", ) parser.add_argument("username", help="The username of the creator") @@ -175,7 +178,6 @@ def handle( username: str, **options: Any, ) -> None: - # pylint: disable=arguments-differ r""" Try to run the command @@ -192,14 +194,14 @@ def handle( region = Region.objects.get(slug=region_slug) except Region.DoesNotExist as e: raise CommandError( - f'Region with slug "{region_slug}" does not exist.' + f'Region with slug "{region_slug}" does not exist.', ) from e try: user = get_user_model().objects.get(username=username) except get_user_model().DoesNotExist as e: raise CommandError( - f'User with username "{username}" does not exist.' + f'User with username "{username}" does not exist.', ) from e with open(csv_filename, newline="", encoding="utf-8") as csv_file: @@ -220,7 +222,8 @@ def handle( "opening_hours": json.dumps(self.get_opening_hours(poi)), "temporarily_closed": strtobool(poi["temporarily_closed"]), "category": self.get_category( - poi["category"], region.default_language + poi["category"], + region.default_language, ).id, "website": poi["website"], "appointment_url": poi["appointment_url"], @@ -251,7 +254,7 @@ def handle( "\n\t• " + "\n\t• ".join( m["text"] for m in poi_form.get_error_messages() - ) + ), ) if not poi_translation_form.is_valid(): raise CommandError( @@ -259,7 +262,7 @@ def handle( + "\n\t• ".join( m["text"] for m in poi_translation_form.get_error_messages() - ) + ), ) # Save forms poi_translation_form.instance.poi = poi_form.save() diff --git a/integreat_cms/core/management/commands/repair_tree.py b/integreat_cms/core/management/commands/repair_tree.py index 901c4c4512..eea62e25aa 100644 --- a/integreat_cms/core/management/commands/repair_tree.py +++ b/integreat_cms/core/management/commands/repair_tree.py @@ -1,12 +1,13 @@ from __future__ import annotations import logging -from typing import Iterable, TYPE_CHECKING +from typing import TYPE_CHECKING from ....cms.utils.repair_tree import repair_tree from ..log_command import LogCommand if TYPE_CHECKING: + from collections.abc import Iterable from typing import Any from django.core.management.base import CommandParser @@ -42,7 +43,6 @@ def add_arguments(self, parser: CommandParser) -> None: def handle( self, *args: Any, page_id: Iterable[int], commit: bool, **options: Any ) -> None: - # pylint: disable=arguments-differ """ Try to run the command """ diff --git a/integreat_cms/core/management/commands/replace_links.py b/integreat_cms/core/management/commands/replace_links.py index a846fac6be..355d7b060e 100644 --- a/integreat_cms/core/management/commands/replace_links.py +++ b/integreat_cms/core/management/commands/replace_links.py @@ -34,7 +34,8 @@ def add_arguments(self, parser: CommandParser) -> None: parser.add_argument("search", help="The (partial) URL to search") parser.add_argument("replace", help="The (partial) URL to replace") parser.add_argument( - "--region-slug", help="Only replace links in the region with this slug" + "--region-slug", + help="Only replace links in the region with this slug", ) parser.add_argument("--username", help="The username of the creator") parser.add_argument( @@ -53,7 +54,6 @@ def handle( commit: bool, **options: Any, ) -> None: - # pylint: disable=arguments-differ r""" Try to run the command @@ -73,7 +73,7 @@ def handle( region = Region.objects.get(slug=region_slug) except Region.DoesNotExist as e: raise CommandError( - f'Region with slug "{region_slug}" does not exist.' + f'Region with slug "{region_slug}" does not exist.', ) from e else: region = None @@ -82,7 +82,7 @@ def handle( user = get_user_model().objects.get(username=username) except get_user_model().DoesNotExist as e: raise CommandError( - f'User with username "{username}" does not exist.' + f'User with username "{username}" does not exist.', ) from e else: user = None diff --git a/integreat_cms/core/management/commands/reset_mt_budget.py b/integreat_cms/core/management/commands/reset_mt_budget.py index 0501372a0d..bf151eb833 100644 --- a/integreat_cms/core/management/commands/reset_mt_budget.py +++ b/integreat_cms/core/management/commands/reset_mt_budget.py @@ -37,7 +37,6 @@ def add_arguments(self, parser: CommandParser) -> None: ) def handle(self, *args: Any, force: bool, **options: Any) -> None: - # pylint: disable=arguments-differ """ Try to run the command """ @@ -57,7 +56,7 @@ def handle(self, *args: Any, force: bool, **options: Any) -> None: ) else: raise CommandError( - "It is not the 1st day of the month. If you want to reset MT budget despite that, run the command with --force" + "It is not the 1st day of the month. If you want to reset MT budget despite that, run the command with --force", ) if not (regions := Region.objects.filter(mt_renewal_month=current_month)): diff --git a/integreat_cms/core/management/commands/send_push_notifications.py b/integreat_cms/core/management/commands/send_push_notifications.py index ceeb15fc14..3f5be8a670 100644 --- a/integreat_cms/core/management/commands/send_push_notifications.py +++ b/integreat_cms/core/management/commands/send_push_notifications.py @@ -48,7 +48,7 @@ def handle(self, *args: Any, **options: Any) -> None: ) except Region.DoesNotExist as e: raise CommandError( - f"The system runs with DEBUG=True but the region with TEST_REGION_SLUG={settings.TEST_REGION_SLUG} does not exist." + f"The system runs with DEBUG=True but the region with TEST_REGION_SLUG={settings.TEST_REGION_SLUG} does not exist.", ) from e retain_time = settings.FCM_NOTIFICATION_RETAIN_TIME_IN_HOURS @@ -70,7 +70,7 @@ def handle(self, *args: Any, **options: Any) -> None: ) pending_push_notifications = list(failed_push_notifications) + list( - scheduled_push_notifications + scheduled_push_notifications, ) if total := len(pending_push_notifications): @@ -82,11 +82,14 @@ def handle(self, *args: Any, **options: Any) -> None: ) else: logger.info( - "There are currently no push notifications scheduled to be sent." + "There are currently no push notifications scheduled to be sent.", ) def send_push_notification( - self, counter: int, total: int, push_notification: PushNotification + self, + counter: int, + total: int, + push_notification: PushNotification, ) -> None: """ Sends a push notification @@ -120,11 +123,10 @@ def send_push_notification( total, push_notification, ) - except ImproperlyConfigured as e: - logger.error( - "Push notification %d/%d %r could not be sent due to a configuration error: %s", + except ImproperlyConfigured: + logger.exception( + "Push notification %d/%d %r could not be sent due to a configuration error", counter, total, push_notification, - e, ) diff --git a/integreat_cms/core/management/commands/summ_ai_bulk.py b/integreat_cms/core/management/commands/summ_ai_bulk.py index 015e2545bc..439e0c424a 100644 --- a/integreat_cms/core/management/commands/summ_ai_bulk.py +++ b/integreat_cms/core/management/commands/summ_ai_bulk.py @@ -42,7 +42,6 @@ def summ_ai_bulk(region: Region, username: str, initial: bool = True) -> None: # Fake request object to simulate real usage request = AsyncRequestFactory().get(f"/{region.slug}") request.session = "session" - # pylint: disable=protected-access request._messages = FallbackStorage(request) request.region = region request.user = user @@ -50,25 +49,26 @@ def summ_ai_bulk(region: Region, username: str, initial: bool = True) -> None: api_client = SummAiApiClient(request, PageTranslationForm) # Translate all pages for page in region.pages.filter(explicitly_archived=False).cache_tree( - archived=False + archived=False, ): try: if initial and ( target := page.get_translation( - settings.SUMM_AI_EASY_GERMAN_LANGUAGE_SLUG + settings.SUMM_AI_EASY_GERMAN_LANGUAGE_SLUG, ) ): logger.debug("[bot] Translation %r already exists, skipping", target) continue logger.info("[bot] Translating page %r", page) api_client.translate_object( - page, settings.SUMM_AI_EASY_GERMAN_LANGUAGE_SLUG + page, + settings.SUMM_AI_EASY_GERMAN_LANGUAGE_SLUG, ) source = page.get_translation(settings.SUMM_AI_GERMAN_LANGUAGE_SLUG) if source and source.content.strip(): time.sleep(30) - except Exception as e: # pylint: disable=broad-exception-caught - logger.exception(e) + except Exception: + logger.exception("") time.sleep(30) logger.info( "Successfully translated %r into Easy German", @@ -98,9 +98,13 @@ def add_arguments(self, parser: CommandParser) -> None: ) def handle( - self, *args: Any, region_slug: str, username: str, initial: bool, **options: Any + self, + *args: Any, + region_slug: str, + username: str, + initial: bool, + **options: Any, ) -> None: - # pylint: disable=arguments-differ r""" Try to run the command @@ -118,17 +122,17 @@ def handle( region = Region.objects.get(slug=region_slug) except Region.DoesNotExist as e: raise CommandError( - f'Region with slug "{region_slug}" does not exist.' + f'Region with slug "{region_slug}" does not exist.', ) from e if not region.summ_ai_enabled: raise CommandError(f'SUMM.AI API is disabled in "{region}".') if settings.SUMM_AI_TEST_MODE: logger.info( - "SUMM.AI API is enabled, but in test mode. No credits get charged, but only a dummy text is returned." + "SUMM.AI API is enabled, but in test mode. No credits get charged, but only a dummy text is returned.", ) elif settings.DEBUG: logger.info( - "SUMM.AI API is enabled, but in debug mode. Text is really translated and credits get charged, but user is 'testumgebung'" + "SUMM.AI API is enabled, but in debug mode. Text is really translated and credits get charged, but user is 'testumgebung'", ) summ_ai_bulk(region, username, initial) logger.success("Successfully translated region %r into Easy German", region) # type: ignore[attr-defined] diff --git a/integreat_cms/core/management/debug_command.py b/integreat_cms/core/management/debug_command.py index ec58dbbf48..9d92f6043a 100644 --- a/integreat_cms/core/management/debug_command.py +++ b/integreat_cms/core/management/debug_command.py @@ -36,5 +36,5 @@ def handle(self, *args: Any, **options: Any) -> None: The actual logic of the command. Subclasses must implement this method. """ raise NotImplementedError( - "subclasses of BaseCommand must provide a handle() method" + "subclasses of BaseCommand must provide a handle() method", ) diff --git a/integreat_cms/core/management/log_command.py b/integreat_cms/core/management/log_command.py index 52f5ddd659..20b78f8952 100644 --- a/integreat_cms/core/management/log_command.py +++ b/integreat_cms/core/management/log_command.py @@ -22,7 +22,7 @@ def handle(self, *args: Any, **options: Any) -> None: The actual logic of the command. Subclasses must implement this method. """ raise NotImplementedError( - "subclasses of BaseCommand must provide a handle() method" + "subclasses of BaseCommand must provide a handle() method", ) def set_logging_stream(self) -> None: diff --git a/integreat_cms/core/middleware/access_control_middleware.py b/integreat_cms/core/middleware/access_control_middleware.py index 995b02a620..00e60a9311 100644 --- a/integreat_cms/core/middleware/access_control_middleware.py +++ b/integreat_cms/core/middleware/access_control_middleware.py @@ -17,7 +17,6 @@ class AccessControlMiddleware: - # pylint: disable=too-few-public-methods """ Middleware class that performs a basic access control. For urls that are whitelisted (see :attr:`~integreat_cms.core.middleware.access_control_middleware.AccessControlMiddleware.whitelist`), no additional @@ -71,7 +70,7 @@ def __call__(self, request: HttpRequest) -> Any: repr(request.region) if request.region else "the staff area" ) raise PermissionDenied( - f"{request.user!r} does not have the permission to access {requested_area}" + f"{request.user!r} does not have the permission to access {requested_area}", ) # Continue with the request return self.get_response(request) diff --git a/integreat_cms/core/middleware/region_middleware.py b/integreat_cms/core/middleware/region_middleware.py index 238c7b628c..e0e3207b6a 100644 --- a/integreat_cms/core/middleware/region_middleware.py +++ b/integreat_cms/core/middleware/region_middleware.py @@ -52,7 +52,8 @@ def __call__(self, request: HttpRequest) -> Any: request.region = self.get_current_region(request) request.available_regions = self.get_available_regions(request, user_regions) request.quick_access_regions = self.get_quick_access_regions( - request, user_regions + request, + user_regions, ) return self.get_response(request) @@ -91,7 +92,8 @@ def get_available_regions(request: HttpRequest, user_regions: QuerySet) -> Query @staticmethod def get_quick_access_regions( - request: HttpRequest, user_regions: QuerySet + request: HttpRequest, + user_regions: QuerySet, ) -> list[Region]: """ This method returns the regions that are available for quick access in this request. @@ -109,7 +111,7 @@ def get_quick_access_regions( user_regions if (request.user.is_superuser or request.user.is_staff) and len(user_regions) > 0 - else request.available_regions + else request.available_regions, ) if request.region in quick_access_regions: quick_access_regions.remove(request.region) diff --git a/integreat_cms/core/middleware/timezone_middleware.py b/integreat_cms/core/middleware/timezone_middleware.py index 2becac2432..a61fa5605d 100644 --- a/integreat_cms/core/middleware/timezone_middleware.py +++ b/integreat_cms/core/middleware/timezone_middleware.py @@ -13,7 +13,6 @@ class TimezoneMiddleware: - # pylint: disable=too-few-public-methods """ Middleware class that sets the current time zone like specified in settings.py """ diff --git a/integreat_cms/core/settings.py b/integreat_cms/core/settings.py index 074f5e2601..d9541653a7 100644 --- a/integreat_cms/core/settings.py +++ b/integreat_cms/core/settings.py @@ -40,23 +40,26 @@ #: The URL to our webapp. This is used for urls in the ``sitemap.xml`` #: (see :mod:`~integreat_cms.sitemap` for more information). WEBAPP_URL: Final[str] = os.environ.get( - "INTEGREAT_CMS_WEBAPP_URL", "https://integreat.app" + "INTEGREAT_CMS_WEBAPP_URL", + "https://integreat.app", ) #: The URL to a domain that handles short links. #: This is currently the same as `BASE_URL` but will change in the future. SHORT_LINKS_URL: Final[str] = os.environ.get( - "INTEGREAT_CMS_SHORT_LINKS_URL", "http://localhost:8000" + "INTEGREAT_CMS_SHORT_LINKS_URL", + "http://localhost:8000", ) #: The URL to the Matomo statistics server. MATOMO_URL: Final[str] = os.environ.get( - "INTEGREAT_CMS_MATOMO_URL", "https://statistics.integreat-app.de" + "INTEGREAT_CMS_MATOMO_URL", + "https://statistics.integreat-app.de", ) #: Enable tracking of API requests in Matomo MATOMO_TRACKING: Final[bool] = bool( - strtobool(os.environ.get("INTEGREAT_CMS_MATOMO_TRACKING", "False")) + strtobool(os.environ.get("INTEGREAT_CMS_MATOMO_TRACKING", "False")), ) #: The slug for the legal notice (see e.g. :class:`~integreat_cms.cms.models.pages.imprint_page_translation.ImprintPageTranslation`) @@ -90,7 +93,8 @@ #: URL to the Integreat Website WEBSITE_URL: Final[str] = os.environ.get( - "INTEGREAT_CMS_WEBSITE_URL", "https://integreat-app.de" + "INTEGREAT_CMS_WEBSITE_URL", + "https://integreat-app.de", ) #: An alias of :attr:`~integreat_cms.core.settings.WEBAPP_URL`. @@ -109,7 +113,8 @@ #: URL to the Integreat wiki WIKI_URL: Final[str] = os.environ.get( - "INTEGREAT_CMS_WIKI_URL", "https://wiki.integreat-app.de" + "INTEGREAT_CMS_WIKI_URL", + "https://wiki.integreat-app.de", ) #: RSS feed URLs to the Integreat blog @@ -129,17 +134,19 @@ #: The maximum duration of an event MAX_EVENT_DURATION: Final[int] = int( - os.environ.get("INTEGREAT_CMS_MAX_EVENT_DURATION", 28) + os.environ.get("INTEGREAT_CMS_MAX_EVENT_DURATION", 28), ) #: The company operating this CMS COMPANY: Final[str] = os.environ.get( - "INTEGREAT_CMS_COMPANY", "Tür an Tür – Digitalfabrik gGmbH" + "INTEGREAT_CMS_COMPANY", + "Tür an Tür – Digitalfabrik gGmbH", ) #: The URL to the company's website COMPANY_URL: Final[str] = os.environ.get( - "INTEGREAT_CMS_COMPANY_URL", "https://tuerantuer.de/digitalfabrik/" + "INTEGREAT_CMS_COMPANY_URL", + "https://tuerantuer.de/digitalfabrik/", ) #: The available inbuilt brandings of the CMS @@ -153,10 +160,9 @@ #: The branding of the CMS BRANDING: Final[str] = os.environ.get("INTEGREAT_CMS_BRANDING", "integreat") -# pylint: disable=consider-using-assignment-expr if BRANDING not in AVAILABLE_BRANDINGS: raise ImproperlyConfigured( - f"The branding {BRANDING!r} is not supported, must be one of {list(AVAILABLE_BRANDINGS)}." + f"The branding {BRANDING!r} is not supported, must be one of {list(AVAILABLE_BRANDINGS)}.", ) #: The readable title of the branding @@ -178,7 +184,7 @@ #: The default timeout in seconds for retrieving external APIs etc. DEFAULT_REQUEST_TIMEOUT: Final[int] = int( - os.environ.get("INTEGREAT_CMS_DEFAULT_REQUEST_TIMEOUT", 10) + os.environ.get("INTEGREAT_CMS_DEFAULT_REQUEST_TIMEOUT", 10), ) #: Where release notes are stored @@ -186,14 +192,15 @@ #: Custom path for additional local translation files CUSTOM_LOCALE_PATH: Final[str] = os.environ.get( - "INTEGREAT_CMS_CUSTOM_LOCALE_PATH", "/etc/integreat-cms/locale" + "INTEGREAT_CMS_CUSTOM_LOCALE_PATH", + "/etc/integreat-cms/locale", ) #: The number of regions that are available via the dropdown NUM_REGIONS_QUICK_ACCESS: Final[int] = 15 BACKGROUND_TASKS_ENABLED = bool( - strtobool(os.environ.get("INTEGREAT_CMS_BACKGROUND_TASKS_ENABLED", "True")) + strtobool(os.environ.get("INTEGREAT_CMS_BACKGROUND_TASKS_ENABLED", "True")), ) #: The tag that events from external calendars need to get imported @@ -231,15 +238,14 @@ #: The interval at which scheduled push notifications are sent out #: Must be <= 60 and a divisor of 60 FCM_SCHEDULE_INTERVAL_MINUTES: Final[int] = int( - os.environ.get("INTEGREAT_CMS_FCM_SCHEDULE_INTERVAL_MINUTES", 60) + os.environ.get("INTEGREAT_CMS_FCM_SCHEDULE_INTERVAL_MINUTES", 60), ) -assert ( - not 60 % FCM_SCHEDULE_INTERVAL_MINUTES -), "Interval must be <= 60 and a divisor of 60" +if 60 % FCM_SCHEDULE_INTERVAL_MINUTES: + raise ValueError("Interval must be <= 60 and a divisor of 60") #: Duration (in hours) that we retain pending push notifications for retry attempts before discarding them FCM_NOTIFICATION_RETAIN_TIME_IN_HOURS: Final[int] = int( - os.environ.get("INTEGREAT_CMS_NOTIFICATION_RETAIN_TIME_IN_HOURS", 24) + os.environ.get("INTEGREAT_CMS_NOTIFICATION_RETAIN_TIME_IN_HOURS", 24), ) ########### @@ -262,13 +268,14 @@ #: Whether or not the Nominatim API for OpenStreetMap queries is enabled. #: This is used to automatically derive coordinates from addresses. NOMINATIM_API_ENABLED: Final[bool] = bool( - strtobool(os.environ.get("INTEGREAT_CMS_NOMINATIM_API_ENABLED", "True")) + strtobool(os.environ.get("INTEGREAT_CMS_NOMINATIM_API_ENABLED", "True")), ) #: The URL to our Nominatim API. #: This is used to automatically derive coordinates from addresses. NOMINATIM_API_URL: Final[str] = os.environ.get( - "INTEGREAT_CMS_NOMINATIM_API_URL", "http://nominatim.maps.tuerantuer.org/nominatim/" + "INTEGREAT_CMS_NOMINATIM_API_URL", + "http://nominatim.maps.tuerantuer.org/nominatim/", ) @@ -278,7 +285,8 @@ #: URL to the Textlab API TEXTLAB_API_URL: Final[str] = os.environ.get( - "INTEGREAT_CMS_TEXTLAB_API_URL", "https://textlab.online/api/" + "INTEGREAT_CMS_TEXTLAB_API_URL", + "https://textlab.online/api/", ) #: Key for the Textlab API @@ -290,7 +298,8 @@ #: Username for the Textlab API TEXTLAB_API_USERNAME: Final[str] = os.environ.get( - "INTEGREAT_CMS_TEXTLAB_API_USERNAME", "Integreat" + "INTEGREAT_CMS_TEXTLAB_API_USERNAME", + "Integreat", ) #: Which language slugs are allowed for the Textlab API @@ -301,22 +310,22 @@ #: How many seconds we should wait between the requests in the bulk management command TEXTLAB_API_BULK_WAITING_TIME: Final[float] = float( - os.environ.get("INTEGREAT_CMS_TEXTLAB_API_BULK_WAITING_TIME", 0.5) + os.environ.get("INTEGREAT_CMS_TEXTLAB_API_BULK_WAITING_TIME", 0.5), ) #: How many seconds we should wait after finishing a region in the bulk management command TEXTLAB_API_BULK_COOL_DOWN_PERIOD: Final[float] = float( - os.environ.get("INTEGREAT_CMS_TEXTLAB_API_BULK_COOL_DOWN_PERIOD", 60) + os.environ.get("INTEGREAT_CMS_TEXTLAB_API_BULK_COOL_DOWN_PERIOD", 60), ) #: Which text type / benchmark id to default to TEXTLAB_API_DEFAULT_BENCHMARK_ID: Final[int] = int( - os.environ.get("INTEGREAT_CMS_TEXTLAB_API_DEFAULT_BENCHMARK_ID", 420) + os.environ.get("INTEGREAT_CMS_TEXTLAB_API_DEFAULT_BENCHMARK_ID", 420), ) #: The minimum HIX score required for machine translation HIX_REQUIRED_FOR_MT: Final[float] = float( - os.environ.get("INTEGREAT_CMS_HIX_REQUIRED_FOR_MT", 15.0) + os.environ.get("INTEGREAT_CMS_HIX_REQUIRED_FOR_MT", 15.0), ) @@ -396,7 +405,7 @@ #: Check whether redis is activated REDIS_CACHE: Final[bool] = bool( - strtobool(os.environ.get("INTEGREAT_CMS_REDIS_CACHE", "False")) + strtobool(os.environ.get("INTEGREAT_CMS_REDIS_CACHE", "False")), ) # Install cacheops only if redis cache is available @@ -472,11 +481,12 @@ "NAME": os.environ.get("INTEGREAT_CMS_DB_NAME", "integreat"), "USER": os.environ.get("INTEGREAT_CMS_DB_USER", "integreat"), "PASSWORD": os.environ.get( - "INTEGREAT_CMS_DB_PASSWORD", "password" if DEBUG else "" + "INTEGREAT_CMS_DB_PASSWORD", + "password" if DEBUG else "", ), "HOST": os.environ.get("INTEGREAT_CMS_DB_HOST", "localhost"), "PORT": os.environ.get("INTEGREAT_CMS_DB_PORT", "5432"), - } + }, } #: Default primary key field type to use for models that don’t have a field with @@ -561,7 +571,7 @@ #: (see :setting:`django:AUTH_PASSWORD_VALIDATORS` and :ref:`django:password-validation`) AUTH_PASSWORD_VALIDATORS: Final[list[dict[str, str]]] = [ { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, @@ -590,12 +600,14 @@ #: The log level for dependencies DEPS_LOG_LEVEL: Final[str] = os.environ.get( - "INTEGREAT_CMS_DEPS_LOG_LEVEL", "INFO" if DEBUG else "WARN" + "INTEGREAT_CMS_DEPS_LOG_LEVEL", + "INFO" if DEBUG else "WARN", ) #: The file path of the logfile. Needs to be writable by the application. LOGFILE: Final[str] = os.environ.get( - "INTEGREAT_CMS_LOGFILE", os.path.join(BASE_DIR, "integreat-cms.log") + "INTEGREAT_CMS_LOGFILE", + os.path.join(BASE_DIR, "integreat-cms.log"), ) #: A custom message store for logging (see :setting:`django:MESSAGE_STORAGE`) @@ -603,7 +615,7 @@ #: Whether to log all entries from the messages framework MESSAGE_LOGGING_ENABLED: bool = bool( - strtobool(os.environ.get("INTEGREAT_CMS_MESSAGE_LOGGING_ENABLED", str(DEBUG))) + strtobool(os.environ.get("INTEGREAT_CMS_MESSAGE_LOGGING_ENABLED", str(DEBUG))), ) #: Logging configuration dictionary (see :setting:`django:LOGGING`) @@ -785,13 +797,15 @@ #: Default email address to use for various automated correspondence from the site manager(s) #: (see :setting:`django:DEFAULT_FROM_EMAIL`) DEFAULT_FROM_EMAIL: Final[str] = os.environ.get( - "INTEGREAT_CMS_SERVER_EMAIL", "keineantwort@integreat-app.de" + "INTEGREAT_CMS_SERVER_EMAIL", + "keineantwort@integreat-app.de", ) #: The email address that error messages come from, such as those sent to :attr:`~integreat_cms.core.settings.ADMINS`. #: (see :setting:`django:SERVER_EMAIL`) SERVER_EMAIL: Final[str] = os.environ.get( - "INTEGREAT_CMS_SERVER_EMAIL", "keineantwort@integreat-app.de" + "INTEGREAT_CMS_SERVER_EMAIL", + "keineantwort@integreat-app.de", ) #: A list of all the people who get code error notifications. When :attr:`~integreat_cms.core.settings.DEBUG` is ``False``, @@ -804,13 +818,14 @@ #: Password to use for the SMTP server defined in :attr:`~integreat_cms.core.settings.EMAIL_HOST` #: (see :setting:`django:EMAIL_HOST_PASSWORD`). If empty, Django won’t attempt authentication. EMAIL_HOST_PASSWORD: Final[str | None] = os.environ.get( - "INTEGREAT_CMS_EMAIL_HOST_PASSWORD" + "INTEGREAT_CMS_EMAIL_HOST_PASSWORD", ) #: Username to use for the SMTP server defined in :attr:`~integreat_cms.core.settings.EMAIL_HOST` #: (see :setting:`django:EMAIL_HOST_USER`). If empty, Django won’t attempt authentication. EMAIL_HOST_USER: Final[str] = os.environ.get( - "INTEGREAT_CMS_EMAIL_HOST_USER", SERVER_EMAIL + "INTEGREAT_CMS_EMAIL_HOST_USER", + SERVER_EMAIL, ) #: Port to use for the SMTP server defined in :attr:`~integreat_cms.core.settings.EMAIL_HOST` @@ -821,14 +836,14 @@ #: This is used for explicit TLS connections, generally on port 587. #: (see :setting:`django:EMAIL_USE_TLS`) EMAIL_USE_TLS: Final[bool] = bool( - strtobool(os.environ.get("INTEGREAT_CMS_EMAIL_USE_TLS", "True")) + strtobool(os.environ.get("INTEGREAT_CMS_EMAIL_USE_TLS", "True")), ) #: Whether to use an implicit TLS (secure) connection when talking to the SMTP server. #: In most email documentation this type of TLS connection is referred to as SSL. It is generally used on port 465. #: (see :setting:`django:EMAIL_USE_SSL`) EMAIL_USE_SSL: Final[bool] = bool( - strtobool(os.environ.get("INTEGREAT_CMS_EMAIL_USE_SSL", "False")) + strtobool(os.environ.get("INTEGREAT_CMS_EMAIL_USE_SSL", "False")), ) @@ -855,7 +870,8 @@ ( language.strip() for language in os.environ.get( - "INTEGREAT_CMS_LANGUAGES", "\n".join(DEFAULT_LANGUAGES) + "INTEGREAT_CMS_LANGUAGES", + "\n".join(DEFAULT_LANGUAGES), ).splitlines() ), ) @@ -875,7 +891,8 @@ #: A string representing the time zone that is used for rendering CURRENT_TIME_ZONE: Final[str] = os.environ.get( - "INTEGREAT_CMS_CURRENT_TIME_ZONE", "Europe/Berlin" + "INTEGREAT_CMS_CURRENT_TIME_ZONE", + "Europe/Berlin", ) #: A boolean that specifies whether Django’s translation system should be enabled @@ -909,7 +926,7 @@ #: A floating point that specifies the percentage of MT_CREDITS_FREE used as a soft margin MT_SOFT_MARGIN_FRACTION: Final[float] = float( - os.environ.get("INTEGREAT_CMS_MT_SOFT_MARGIN", 0.01) + os.environ.get("INTEGREAT_CMS_MT_SOFT_MARGIN", 0.01), ) #: The actual number of words which are used as soft margin @@ -928,7 +945,7 @@ #: Whether to enable deepl glossaries DEEPL_GLOSSARIES_ENABLED: Final[bool] = strtobool( - os.environ.get("INTEGREAT_CMS_DEEPL_GLOSSARIES_ENABLED", "False") + os.environ.get("INTEGREAT_CMS_DEEPL_GLOSSARIES_ENABLED", "False"), ) #: Whether automatic translations via DeepL are enabled. @@ -942,12 +959,13 @@ #: Version of Google Translate, either Basic or Advanced GOOGLE_TRANSLATE_VERSION: str = os.environ.get( - "INTEGREAT_CMS_GOOGLE_TRANSLATE_VERSION", "Basic" + "INTEGREAT_CMS_GOOGLE_TRANSLATE_VERSION", + "Basic", ) #: Path to the saved credential json file GOOGLE_APPLICATION_CREDENTIALS: str | None = os.environ.get( - "INTEGREAT_CMS_GOOGLE_CREDENTIALS" + "INTEGREAT_CMS_GOOGLE_CREDENTIALS", ) #: Google project id @@ -955,12 +973,13 @@ #: Location for google translate. Default to "global". This must be non-global for custom glossaries. GOOGLE_TRANSLATE_LOCATION: str = os.environ.get( - "INTEGREAT_CMS_GOOGLE_TRANSLATE_LOCATION", "global" + "INTEGREAT_CMS_GOOGLE_TRANSLATE_LOCATION", + "global", ) #: This is ``True`` when both the credentials and project id are provided GOOGLE_TRANSLATE_ENABLED: bool = bool(GOOGLE_APPLICATION_CREDENTIALS) and bool( - GOOGLE_PROJECT_ID + GOOGLE_PROJECT_ID, ) #: `parent` parameter for Google Translate, consists of project id and location @@ -975,7 +994,8 @@ #: The URL to our SUMM.AI API for automatic translations from German into Easy German SUMM_AI_API_URL: Final[str] = os.environ.get( - "INTEGREAT_CMS_SUMM_AI_API_URL", "https://backend.summ-ai.com/translate/v1/" + "INTEGREAT_CMS_SUMM_AI_API_URL", + "https://backend.summ-ai.com/translate/v1/", ) #: Authentication token for SUMM.AI, @@ -988,7 +1008,7 @@ #: Whether requests to the SUMM.AI are done with the ``is_test`` flag SUMM_AI_TEST_MODE: Final[bool] = strtobool( - os.environ.get("INTEGREAT_CMS_SUMM_AI_TEST_MODE", str(DEBUG)) + os.environ.get("INTEGREAT_CMS_SUMM_AI_TEST_MODE", str(DEBUG)), ) #: The timeout in minutes for requests to the SUMM.AI API @@ -996,12 +1016,12 @@ #: The limit for "Too many requests". SUMM_AI_MAX_CONCURRENT_REQUESTS = int( - os.environ.get("INTEGREAT_CMS_SUMM_AI_MAX_CONCURRENT_REQUESTS", 20) + os.environ.get("INTEGREAT_CMS_SUMM_AI_MAX_CONCURRENT_REQUESTS", 20), ) #: Waiting time after "Too many requests" response was sent SUMM_AI_RATE_LIMIT_COOLDOWN = float( - os.environ.get("INTEGREAT_CMS_SUMM_AI_RATE_LIMIT_COOLDOWN", 30) + os.environ.get("INTEGREAT_CMS_SUMM_AI_RATE_LIMIT_COOLDOWN", 30), ) #: Maximum amount of retries before giving up @@ -1010,17 +1030,20 @@ #: The language slugs for German SUMM_AI_GERMAN_LANGUAGE_SLUG: Final[str] = os.environ.get( - "INTEGREAT_CMS_SUMM_AI_GERMAN_LANGUAGE_SLUG", "de" + "INTEGREAT_CMS_SUMM_AI_GERMAN_LANGUAGE_SLUG", + "de", ) #: The language slug for Easy German SUMM_AI_EASY_GERMAN_LANGUAGE_SLUG: Final[str] = os.environ.get( - "INTEGREAT_CMS_SUMM_AI_EASY_GERMAN_LANGUAGE_SLUG", "de-si" + "INTEGREAT_CMS_SUMM_AI_EASY_GERMAN_LANGUAGE_SLUG", + "de-si", ) #: The separator which is used to split compound words, e.g. Bundes-Kanzler (hyphen) or Bundes·kanzler (interpunct) SUMM_AI_SEPARATOR: Final[str] = os.environ.get( - "INTEGREAT_CMS_SUMM_AI_SEPARATOR", "hyphen" + "INTEGREAT_CMS_SUMM_AI_SEPARATOR", + "hyphen", ) #: All plain text fields of the content models which should be translated @@ -1037,14 +1060,15 @@ #: Value of the ``is_initial`` flag SUMM_AI_IS_INITIAL: Final[bool] = bool( - strtobool(os.environ.get("INTEGREAT_CMS_SUMM_AI_IS_INITIAL", "True")) + strtobool(os.environ.get("INTEGREAT_CMS_SUMM_AI_IS_INITIAL", "True")), ) # Slugs of regions that prefer Plain German over Easy German in the management command SUMM_AI_PLAIN_GERMAN_REGIONS: Final[list[str]] = [ x.strip() for x in os.environ.get( - "INTEGREAT_CMS_SUMM_AI_PLAIN_GERMAN_REGIONS", "" + "INTEGREAT_CMS_SUMM_AI_PLAIN_GERMAN_REGIONS", + "", ).splitlines() ] @@ -1084,7 +1108,8 @@ #: Absolute filesystem path to the directory that will hold user-uploaded files (see :setting:`django:MEDIA_ROOT`) MEDIA_ROOT: Final[str] = os.environ.get( - "INTEGREAT_CMS_MEDIA_ROOT", os.path.join(BASE_DIR, "media") + "INTEGREAT_CMS_MEDIA_ROOT", + os.path.join(BASE_DIR, "media"), ) #: The maximum size of media images in pixels (larger images will automatically be resized) @@ -1098,12 +1123,12 @@ #: Enables the possibility to upload further file formats (e.g. DOC, GIF). LEGACY_FILE_UPLOAD: Final[bool] = bool( - strtobool(os.environ.get("INTEGREAT_CMS_LEGACY_FILE_UPLOAD", "False")) + strtobool(os.environ.get("INTEGREAT_CMS_LEGACY_FILE_UPLOAD", "False")), ) #: The maximum size of media files in bytes MEDIA_MAX_UPLOAD_SIZE: Final[int] = int( - os.environ.get("INTEGREAT_CMS_MEDIA_MAX_UPLOAD_SIZE", 3 * 1024 * 1024) + os.environ.get("INTEGREAT_CMS_MEDIA_MAX_UPLOAD_SIZE", 3 * 1024 * 1024), ) @@ -1171,7 +1196,7 @@ #: Disable linkcheck listeners e.g. when the fixtures are loaded LINKCHECK_DISABLE_LISTENERS: bool = bool( - strtobool(os.environ.get("INTEGREAT_CMS_LINKCHECK_DISABLE_LISTENERS", "False")) + strtobool(os.environ.get("INTEGREAT_CMS_LINKCHECK_DISABLE_LISTENERS", "False")), ) #: The maximum length of URLs which can be checked. Longer URLs will be silently ignored. @@ -1181,7 +1206,8 @@ LINKCHECK_IGNORED_URL_TYPES: Final[list[str]] = [ url_type.strip() for url_type in os.environ.get( - "INTEGREAT_CMS_LINKCHECK_IGNORED_URL_TYPES", "" + "INTEGREAT_CMS_LINKCHECK_IGNORED_URL_TYPES", + "", ).splitlines() if url_type ] @@ -1197,7 +1223,7 @@ #: #: $ INTEGREAT_CMS_LINKCHECK_COMMAND_RUNNING=1 integreat-cms-cli findlinks LINKCHECK_COMMAND_RUNNING: Final[bool] = bool( - strtobool(os.environ.get("INTEGREAT_CMS_LINKCHECK_COMMAND_RUNNING", "False")) + strtobool(os.environ.get("INTEGREAT_CMS_LINKCHECK_COMMAND_RUNNING", "False")), ) @@ -1226,7 +1252,7 @@ "DEFAULT": { "BUNDLE_DIR_NAME": "", "STATS_FILE": os.path.join(BASE_DIR, "webpack-stats.json"), - } + }, } @@ -1259,7 +1285,8 @@ #: The directory where PDF files are stored PDF_ROOT: Final[str] = os.environ.get( - "INTEGREAT_CMS_PDF_ROOT", os.path.join(BASE_DIR, "pdf") + "INTEGREAT_CMS_PDF_ROOT", + os.path.join(BASE_DIR, "pdf"), ) #: The URL path where PDF files are served for download @@ -1268,7 +1295,8 @@ #: Slugs of languages for which PDF export should be deactivated PDF_DEACTIVATED_LANGUAGES: Final[str | list[str]] = os.environ.get( - "INTEGREAT_CMS_PDF_DEACTIVATED_LANGUAGES", [] + "INTEGREAT_CMS_PDF_DEACTIVATED_LANGUAGES", + [], ) @@ -1286,7 +1314,8 @@ #: The xliff version to be used for exports XLIFF_EXPORT_VERSION: Final[str] = os.environ.get( - "INTEGREAT_CMS_XLIFF_EXPORT_VERSION", "xliff-1.2" + "INTEGREAT_CMS_XLIFF_EXPORT_VERSION", + "xliff-1.2", ) #: The default fields to be used for the XLIFF serialization @@ -1297,7 +1326,8 @@ #: The directory where xliff files are stored XLIFF_ROOT: Final[str] = os.environ.get( - "INTEGREAT_CMS_XLIFF_ROOT", os.path.join(BASE_DIR, "xliff") + "INTEGREAT_CMS_XLIFF_ROOT", + os.path.join(BASE_DIR, "xliff"), ) #: The directory to which xliff files should be uploaded (this should not be reachable by the webserver) @@ -1340,12 +1370,14 @@ #: Integreat Chat (app) backend server domain INTEGREAT_CHAT_BACK_END_DOMAIN = os.environ.get( - "INTEGREAT_CMS_INTEGREAT_CHAT_BACK_END_DOMAIN", "igchat-inference.tuerantuer.org" + "INTEGREAT_CMS_INTEGREAT_CHAT_BACK_END_DOMAIN", + "igchat-inference.tuerantuer.org", ) #: Integreat Chat (app) backend timeout INTEGREAT_CHAT_BACK_END_TIMEOUT = os.environ.get( - "INTEGREAT_CMS_INTEGREAT_CHAT_BACK_END_TIMEOUT", 300 + "INTEGREAT_CMS_INTEGREAT_CHAT_BACK_END_TIMEOUT", + 300, ) ########## diff --git a/integreat_cms/core/signals/auth_signals.py b/integreat_cms/core/signals/auth_signals.py index 4846252b6b..86559a337f 100644 --- a/integreat_cms/core/signals/auth_signals.py +++ b/integreat_cms/core/signals/auth_signals.py @@ -27,7 +27,7 @@ @receiver(user_logged_in) def user_logged_in_callback( - sender: ModelBase, # pylint: disable=unused-argument + sender: ModelBase, # noqa: ARG001 request: HttpRequest, user: User, **kwargs: Any, @@ -35,7 +35,6 @@ def user_logged_in_callback( r""" Log a successful login event - :param sender: The class of the user that just logged in. :param request: The current request :param user: The user instance that just logged in. :param \**kwargs: The supplied keyword arguments @@ -46,7 +45,7 @@ def user_logged_in_callback( @receiver(user_logged_out) def user_logged_out_callback( - sender: ModelBase, # pylint: disable=unused-argument + sender: ModelBase, # noqa: ARG001 request: HttpRequest, user: User, **kwargs: Any, @@ -54,7 +53,6 @@ def user_logged_out_callback( r""" Log a logout event - :param sender: The class of the user that just logged out or ``None`` if the user was not authenticated. :param request: The current request :param user: The user instance that just logged out or ``None`` if the user was not authenticated. :param \**kwargs: The supplied keyword arguments @@ -65,7 +63,7 @@ def user_logged_out_callback( @receiver(user_login_failed) def user_login_failed_callback( - sender: ModelBase, # pylint: disable=unused-argument + sender: ModelBase, # noqa: ARG001 credentials: dict[str, str], request: HttpRequest, **kwargs: Any, @@ -73,7 +71,6 @@ def user_login_failed_callback( r""" Log a failed login event - :param sender: The name of the module used for authentication. :param credentials: A dictionary of keyword arguments containing the user credentials that were passed to :func:`~django.contrib.auth.authenticate`. Credentials matching a set of ‘sensitive’ patterns, (including password) will not be sent in the clear as part of the signal. diff --git a/integreat_cms/core/signals/feedback_signals.py b/integreat_cms/core/signals/feedback_signals.py index aaffb9757e..6b31bb5242 100644 --- a/integreat_cms/core/signals/feedback_signals.py +++ b/integreat_cms/core/signals/feedback_signals.py @@ -33,12 +33,12 @@ @receiver(post_delete, sender=Feedback) def feedback_delete_handler( - sender: ModelBase, **kwargs: Any # pylint: disable=unused-argument + sender: ModelBase, # noqa: ARG001 + **kwargs: Any, ) -> None: r""" Invalidate feedback cache after feedback deletion - :param sender: The class of the feedback that was deleted :param \**kwargs: The supplied keyword arguments """ if kwargs.get("instance"): @@ -57,12 +57,12 @@ def feedback_delete_handler( @receiver(post_save, sender=SearchResultFeedback) @disable_for_loaddata def feedback_create_handler( - sender: ModelBase, **kwargs: Any # pylint: disable=unused-argument + sender: ModelBase, # noqa: ARG001 + **kwargs: Any, ) -> None: r""" Invalidate feedback cache after feedback creation - :param sender: The class of the feedback that was deleted :param \**kwargs: The supplied keyword arguments """ if (instance := kwargs.get("instance")) and ( diff --git a/integreat_cms/core/sphinx_settings.py b/integreat_cms/core/sphinx_settings.py index ad301b75f8..96f533566d 100644 --- a/integreat_cms/core/sphinx_settings.py +++ b/integreat_cms/core/sphinx_settings.py @@ -7,14 +7,12 @@ For the full list of settings and their values, see :doc:`django:ref/settings`. """ -# pylint: disable=wildcard-import -# pylint: disable=unused-wildcard-import from __future__ import annotations from .settings import * #: Set a dummy secret key for documentation build even if it's not in debug mode -SECRET_KEY = "dummy" +SECRET_KEY = "dummy" # noqa: S105 #: A boolean that specifies whether Django’s translation system should be enabled #: (see :setting:`django:USE_I18N` and :doc:`django:topics/i18n/index`) diff --git a/integreat_cms/core/urls.py b/integreat_cms/core/urls.py index 5c53ec635e..8552ebbd34 100644 --- a/integreat_cms/core/urls.py +++ b/integreat_cms/core/urls.py @@ -39,7 +39,7 @@ ( "django.conf.urls.i18n", "i18n", - ) + ), ), ), ] @@ -64,7 +64,7 @@ ( static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT), "media_files", - ) + ), ), ), path( @@ -73,7 +73,7 @@ ( static(settings.PDF_URL, document_root=settings.PDF_ROOT), "pdf_files", - ) + ), ), ), path( @@ -82,7 +82,7 @@ ( static(settings.XLIFF_URL, document_root=settings.XLIFF_DOWNLOAD_DIR), "xliff_files", - ) + ), ), ), ] diff --git a/integreat_cms/core/utils/decorators.py b/integreat_cms/core/utils/decorators.py index 11b9036842..1a3574cf44 100644 --- a/integreat_cms/core/utils/decorators.py +++ b/integreat_cms/core/utils/decorators.py @@ -3,7 +3,8 @@ """ import functools -from typing import Any, Callable +from collections.abc import Callable +from typing import Any def disable_for_loaddata(function: Callable) -> Callable: diff --git a/integreat_cms/core/utils/machine_translation_api_client.py b/integreat_cms/core/utils/machine_translation_api_client.py index 58bdcde3a2..53f71a71df 100644 --- a/integreat_cms/core/utils/machine_translation_api_client.py +++ b/integreat_cms/core/utils/machine_translation_api_client.py @@ -57,7 +57,9 @@ def __init__(self, request: HttpRequest, form_class: ModelFormMetaclass) -> None @abstractmethod def translate_queryset( - self, queryset: QuerySet[Event | Page | POI], language_slug: str + self, + queryset: QuerySet[Event | Page | POI], + language_slug: str, ) -> None: """ Translate a given queryset into one specific language. diff --git a/integreat_cms/core/utils/machine_translation_provider.py b/integreat_cms/core/utils/machine_translation_provider.py index 78c848c330..d093ef8435 100644 --- a/integreat_cms/core/utils/machine_translation_provider.py +++ b/integreat_cms/core/utils/machine_translation_provider.py @@ -61,7 +61,6 @@ class MachineTranslationProvider(metaclass=MachineTranslationProviderType): @classmethod def is_enabled(cls, region: Region, language: Language) -> bool: - # pylint: disable=too-many-return-statements """ Whether this provider is enabled for a given region and language. Call this from the parent class. @@ -96,7 +95,9 @@ def is_enabled(cls, region: Region, language: Language) -> bool: if not language_node.machine_translation_enabled: logger.debug( - "Machine translations are disabled for %r in %r.", language, region + "Machine translations are disabled for %r in %r.", + language, + region, ) return False @@ -139,7 +140,9 @@ def is_enabled(cls, region: Region, language: Language) -> bool: @staticmethod def is_permitted( - region: Region, user: SimpleLazyObject, content_type: ModelBase + region: Region, + user: SimpleLazyObject, + content_type: ModelBase, ) -> bool: """ Checks if a machine translation is permitted, i.e. if for the @@ -185,7 +188,9 @@ def is_permitted( return True def is_needed( - self, queryset: QuerySet[Event | Page | POI], target_language: Language + self, + queryset: QuerySet[Event | Page | POI], + target_language: Language, ) -> list[Event | Page | POI]: """ Checks if a machine translation is needed, thus checking if the @@ -200,7 +205,7 @@ def is_needed( to_translate = [] for content_object in queryset: existing_target_translation = content_object.get_translation( - target_language.slug + target_language.slug, ) if ( existing_target_translation diff --git a/integreat_cms/core/utils/word_count.py b/integreat_cms/core/utils/word_count.py index 6997947561..d64c01c143 100644 --- a/integreat_cms/core/utils/word_count.py +++ b/integreat_cms/core/utils/word_count.py @@ -6,7 +6,6 @@ from django.utils.html import strip_tags if TYPE_CHECKING: - from ...cms.models import EventTranslation, PageTranslation, POITranslation diff --git a/integreat_cms/deepl_api/apps.py b/integreat_cms/deepl_api/apps.py index 0dcbf4c6c5..23a3549761 100644 --- a/integreat_cms/deepl_api/apps.py +++ b/integreat_cms/deepl_api/apps.py @@ -40,7 +40,9 @@ class DeepLApiClientConfig(AppConfig): supported_glossaries: dict[tuple[str, str], GlossaryInfo] = {} def get_glossary( - self, source_language: str, target_language: str + self, + source_language: str, + target_language: str, ) -> GlossaryInfo | None: """ Looks up a glossary for the specified source language and target language pair. @@ -78,11 +80,10 @@ def ready(self) -> None: self.init_supported_glossaries(deepl_translator) self.assert_usage_limit_not_reached(deepl_translator) - except (DeepLException, AssertionError) as e: - logger.error(e) - logger.error( + except (DeepLException, ValueError): + logger.exception( "DeepL API is unavailable. You won't be able to " - "create and update machine translations." + "create and update machine translations.", ) else: logger.info("DeepL API is disabled.") @@ -101,7 +102,8 @@ def init_supported_source_languages(self, translator: Translator) -> None: "Supported source languages by DeepL: %r", self.supported_source_languages, ) - assert self.supported_source_languages + if not self.supported_source_languages: + raise ValueError def init_supported_target_languages(self, translator: Translator) -> None: """ @@ -117,7 +119,8 @@ def init_supported_target_languages(self, translator: Translator) -> None: "Supported target languages by DeepL: %r", self.supported_target_languages, ) - assert self.supported_target_languages + if not self.supported_target_languages: + raise ValueError def init_supported_glossaries(self, translator: Translator) -> None: """ @@ -136,7 +139,8 @@ def init_supported_glossaries(self, translator: Translator) -> None: if glossary.ready } logger.debug( - "Supported glossaries by DeepL: %s", list(self.supported_glossaries.keys()) + "Supported glossaries by DeepL: %s", + list(self.supported_glossaries.keys()), ) @staticmethod @@ -149,7 +153,6 @@ def assert_usage_limit_not_reached(translator: Translator) -> None: usage = translator.get_usage() if usage.any_limit_reached: logger.warning("DeepL API translation limit reached") - # pylint: disable=protected-access logger.info( "DeepL API is available at: %r (character usage: %s of %s)", translator._server_url, diff --git a/integreat_cms/deepl_api/deepl_api_client.py b/integreat_cms/deepl_api/deepl_api_client.py index ee12bf3985..8a425de765 100644 --- a/integreat_cms/deepl_api/deepl_api_client.py +++ b/integreat_cms/deepl_api/deepl_api_client.py @@ -17,7 +17,6 @@ from ..core.utils.machine_translation_api_client import MachineTranslationApiClient from ..core.utils.machine_translation_provider import MachineTranslationProvider from ..textlab_api.utils import check_hix_score -from .apps import DeepLApiClientConfig if TYPE_CHECKING: from django.forms.models import ModelFormMetaclass @@ -28,6 +27,8 @@ from integreat_cms.cms.models.pages.page import Page from integreat_cms.cms.models.pois.poi import POI + from .apps import DeepLApiClientConfig + logger = logging.getLogger(__name__) @@ -46,15 +47,18 @@ def __init__(self, request: HttpRequest, form_class: ModelFormMetaclass) -> None """ super().__init__(request, form_class) if not MachineTranslationProvider.is_permitted( - request.region, request.user, form_class._meta.model + request.region, + request.user, + form_class._meta.model, ): raise RuntimeError( - f'Machine translations are disabled for content type "{form_class._meta.model}" and {request.user!r}.' + f'Machine translations are disabled for content type "{form_class._meta.model}" and {request.user!r}.', ) if not settings.DEEPL_ENABLED: raise RuntimeError("DeepL is disabled globally.") self.translator = deepl.Translator( - auth_key=settings.DEEPL_AUTH_KEY, server_url=settings.DEEPL_API_URL + auth_key=settings.DEEPL_AUTH_KEY, + server_url=settings.DEEPL_API_URL, ) @staticmethod @@ -72,9 +76,10 @@ def get_target_language_key(target_language: Language) -> str: return "" def translate_queryset( - self, queryset: list[Event] | (list[Page] | list[POI]), language_slug: str + self, + queryset: list[Event] | (list[Page] | list[POI]), + language_slug: str, ) -> None: - # pylint: disable=too-many-locals, too-many-branches, too-many-statements """ This function translates a content queryset via DeepL @@ -105,24 +110,26 @@ def translate_queryset( for content_object in queryset: source_translation = content_object.get_translation( - source_language.slug + source_language.slug, ) if not source_translation: failed_changes_because_no_source_translation.append( - content_object.best_translation.title + content_object.best_translation.title, ) continue if not check_hix_score( - self.request, source_translation, show_message=False + self.request, + source_translation, + show_message=False, ): failed_changes_because_insufficient_hix_score.append( - source_translation.title + source_translation.title, ) continue existing_target_translation = content_object.get_translation( - target_language.slug + target_language.slug, ) # Before translating, check if translation would exceed usage limit @@ -132,7 +139,7 @@ def translate_queryset( ) = self.check_usage(region, source_translation) if translation_exceeds_limit: failed_changes_because_exceeds_limit.append( - source_translation.title + source_translation.title, ) continue @@ -145,12 +152,14 @@ def translate_queryset( for attr in self.translatable_attributes: # Only translate existing, non-empty attributes if hasattr(source_translation, attr) and getattr( - source_translation, attr + source_translation, + attr, ): try: # data has to be unescaped for DeepL to recognize Umlaute glossary = deepl_config.get_glossary( - source_language.slug, target_language_key + source_language.slug, + target_language_key, ) logger.debug("Used glossary for translation: %s", glossary) data[attr] = self.translator.translate_text( @@ -160,14 +169,14 @@ def translate_queryset( tag_handling="html", glossary=glossary, ) - except DeepLException as e: + except DeepLException: messages.error( self.request, _( - "A problem with DeepL API has occurred. Please contact an administrator." + "A problem with DeepL API has occurred. Please contact an administrator.", ), ) - logger.error(e) + logger.exception("") return content_translation_form = self.form_class( @@ -186,11 +195,11 @@ def translate_queryset( if existing_target_translation: if settings.REDIS_CACHE: existing_target_translation.all_versions.invalidated_update( - currently_in_translation=False + currently_in_translation=False, ) else: existing_target_translation.all_versions.update( - currently_in_translation=False + currently_in_translation=False, ) logger.debug( @@ -244,7 +253,7 @@ def translate_queryset( model_name=model_name, model_name_plural=model_name_plural, object_names=iter_to_string( - failed_changes_because_no_source_translation + failed_changes_because_no_source_translation, ), ), ) @@ -261,7 +270,7 @@ def translate_queryset( model_name_plural=model_name_plural, remaining_budget=region.mt_budget_remaining, object_names=iter_to_string( - failed_changes_because_exceeds_limit + failed_changes_because_exceeds_limit, ), ), ) @@ -278,7 +287,7 @@ def translate_queryset( model_name_plural=model_name_plural, min_required=settings.HIX_REQUIRED_FOR_MT, object_names=iter_to_string( - failed_changes_because_insufficient_hix_score + failed_changes_because_insufficient_hix_score, ), ), ) diff --git a/integreat_cms/firebase_api/apps.py b/integreat_cms/firebase_api/apps.py index 355f1ed51f..bee385b011 100644 --- a/integreat_cms/firebase_api/apps.py +++ b/integreat_cms/firebase_api/apps.py @@ -40,7 +40,7 @@ def ready(self) -> None: elif settings.DEBUG: logger.info( "Firebase Cloud Messaging is enabled, but in debug mode. " - "Push notifications are really sent, but only to the test region." + "Push notifications are really sent, but only to the test region.", ) else: logger.info("Firebase Cloud Messaging is enabled in production mode.") diff --git a/integreat_cms/firebase_api/firebase_api_client.py b/integreat_cms/firebase_api/firebase_api_client.py index ae7b3745c0..1c58b3e0fd 100644 --- a/integreat_cms/firebase_api/firebase_api_client.py +++ b/integreat_cms/firebase_api/firebase_api_client.py @@ -57,7 +57,7 @@ def __init__(self, push_notification: PushNotification) -> None: self.regions = [Region.objects.get(slug=settings.TEST_REGION_SLUG)] except Region.DoesNotExist as e: raise ImproperlyConfigured( - f"The system runs with DEBUG=True but the region with TEST_REGION_SLUG={settings.TEST_REGION_SLUG} does not exist." + f"The system runs with DEBUG=True but the region with TEST_REGION_SLUG={settings.TEST_REGION_SLUG} does not exist.", ) from e else: self.regions = push_notification.regions.all() @@ -67,12 +67,12 @@ def load_secondary_pnts(self) -> None: Load push notification translations in other languages """ secondary_pnts = PushNotificationTranslation.objects.filter( - push_notification=self.push_notification + push_notification=self.push_notification, ).exclude(id=self.primary_pnt.id) for secondary_pnt in secondary_pnts: if ( not secondary_pnt.title - and pnt_const.USE_MAIN_LANGUAGE == self.push_notification.mode + and self.push_notification.mode == pnt_const.USE_MAIN_LANGUAGE ): secondary_pnt.title = self.primary_pnt.title secondary_pnt.text = self.primary_pnt.text @@ -88,7 +88,8 @@ def is_valid(self) -> bool: """ if not self.prepared_pnts: logger.debug( - "%r does not have a default translation", self.push_notification + "%r does not have a default translation", + self.push_notification, ) return False for pnt in self.prepared_pnts: @@ -122,14 +123,14 @@ def send_pn(self, pnt: PushNotificationTranslation, region: Region) -> bool: "payload": { "aps": { "category": "NEW_MESSAGE_CATEGORY", - } + }, }, }, "android": { "ttl": "86400s", }, "fcm_options": { - "analytics_label": f"{region.slug}-{pnt.language.slug}" + "analytics_label": f"{region.slug}-{pnt.language.slug}", }, }, } @@ -154,10 +155,10 @@ def send_pn(self, pnt: PushNotificationTranslation, region: Region) -> bool: pnt, response.json(), ) - return False - except RequestException as e: - logger.error(e) - return False + except RequestException: + logger.exception("") + + return False def send_all(self) -> bool: """ @@ -168,7 +169,9 @@ def send_all(self) -> bool: status = True for pnt in self.prepared_pnts: for region in self.regions: - if pnt.language in region.active_languages: - if not self.send_pn(pnt, region): - status = False + if pnt.language in region.active_languages and not self.send_pn( + pnt, + region, + ): + status = False return status diff --git a/integreat_cms/firebase_api/firebase_data_client.py b/integreat_cms/firebase_api/firebase_data_client.py index 17844f43c5..870c3f7792 100644 --- a/integreat_cms/firebase_api/firebase_data_client.py +++ b/integreat_cms/firebase_api/firebase_data_client.py @@ -1,6 +1,5 @@ import logging from datetime import date -from typing import Dict, List, Union import requests from django.conf import settings @@ -12,7 +11,6 @@ class FirebaseDataClient: - # pylint: disable=too-few-public-methods """ A client for interacting with Firebase Cloud Messaging Data API. @@ -28,14 +26,14 @@ def __init__(self) -> None: """ if not settings.FCM_ENABLED: raise ImproperlyConfigured( - "Push notifications are disabled, so are the analytics" + "Push notifications are disabled, so are the analytics", ) self.endpoint_url = settings.FCM_DATA_URL def fetch_notification_statistics( self, - ) -> List[Dict[str, Union[str, int]]]: + ) -> list[dict[str, str | int]]: """ Fetches messaging statistics from the Firebase API and calculates the counts of notifications sent per date, per region, and per language within the returned timespan. @@ -72,7 +70,9 @@ def fetch_notification_statistics( date_info = item.get("date") date_obj = date( - date_info.get("year"), date_info.get("month"), date_info.get("day") + date_info.get("year"), + date_info.get("month"), + date_info.get("day"), ) count_accepted = int(item["data"]["countNotificationsAccepted"]) @@ -84,7 +84,7 @@ def fetch_notification_statistics( "region": region, "language_slug": language, "count": count_accepted, - } + }, ) return statistics_list diff --git a/integreat_cms/firebase_api/firebase_security_service.py b/integreat_cms/firebase_api/firebase_security_service.py index 7106323e52..9d7a8cb3a9 100644 --- a/integreat_cms/firebase_api/firebase_security_service.py +++ b/integreat_cms/firebase_api/firebase_security_service.py @@ -17,7 +17,7 @@ def get_messaging_access_token() -> str: """ return FirebaseSecurityService._get_access_token( - "https://www.googleapis.com/auth/firebase.messaging" + "https://www.googleapis.com/auth/firebase.messaging", ) @staticmethod @@ -29,7 +29,7 @@ def get_data_access_token() -> str: """ return FirebaseSecurityService._get_access_token( - "https://www.googleapis.com/auth/cloud-platform" + "https://www.googleapis.com/auth/cloud-platform", ) @staticmethod diff --git a/integreat_cms/google_translate_api/apps.py b/integreat_cms/google_translate_api/apps.py index 8ac4167470..2212e801e8 100644 --- a/integreat_cms/google_translate_api/apps.py +++ b/integreat_cms/google_translate_api/apps.py @@ -12,7 +12,10 @@ from django.apps import AppConfig from django.conf import settings from django.utils.translation import gettext_lazy as _ -from google.cloud import translate_v2, translate_v3 # type: ignore[attr-defined] +from google.cloud import ( # type: ignore[attr-defined] + translate_v2, + translate_v3, +) from google.oauth2 import service_account if TYPE_CHECKING: @@ -60,13 +63,13 @@ def ready_v3(self, credentials: Credentials) -> None: """ logger.info(translate_v3) google_translator_v3 = translate_v3.TranslationServiceClient( - credentials=credentials + credentials=credentials, ) parent = settings.GOOGLE_PARENT_PARAM supported_languages = google_translator_v3.get_supported_languages( - parent=parent + parent=parent, ).languages self.supported_source_languages = [] @@ -87,32 +90,33 @@ def ready(self) -> None: if settings.GOOGLE_TRANSLATE_ENABLED: try: credentials = service_account.Credentials.from_service_account_file( - settings.GOOGLE_APPLICATION_CREDENTIALS + settings.GOOGLE_APPLICATION_CREDENTIALS, ) if settings.GOOGLE_TRANSLATE_VERSION == "Advanced": self.ready_v3(credentials) else: self.ready_v2(credentials) - assert ( - self.supported_source_languages - ), "No supported source languages by Google Translate" + if not self.supported_source_languages: + raise ValueError( # noqa: TRY301 + "No supported source languages by Google Translate", + ) logger.debug( "Supported source languages by Google Translate: %r", self.supported_source_languages, ) - assert ( - self.supported_source_languages - ), "No supported target languages by Google Translate" + if not self.supported_source_languages: + raise ValueError( # noqa: TRY301 + "No supported target languages by Google Translate", + ) logger.debug( "Supported target languages by Google Translate: %r", self.supported_target_languages, ) logger.info("Google Translate API is enabled.") - except Exception as e: # pylint: disable=broad-except - logger.error(e) - logger.error("Google translate is not available.") + except Exception: + logger.exception("Google translate is not available.") else: logger.info("Google Translate API is disabled.") diff --git a/integreat_cms/google_translate_api/google_translate_api_client.py b/integreat_cms/google_translate_api/google_translate_api_client.py index f153f7e87f..c89b31bb6e 100644 --- a/integreat_cms/google_translate_api/google_translate_api_client.py +++ b/integreat_cms/google_translate_api/google_translate_api_client.py @@ -9,7 +9,10 @@ from django.db import transaction from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy -from google.cloud import translate_v2, translate_v3 # type: ignore[attr-defined] +from google.cloud import ( # type: ignore[attr-defined] + translate_v2, + translate_v3, +) from google.oauth2 import service_account from ..cms.utils.stringify_list import iter_to_string @@ -44,28 +47,29 @@ def __init__(self, request: HttpRequest, form_class: ModelFormMetaclass) -> None """ super().__init__(request, form_class) if not MachineTranslationProvider.is_permitted( - request.region, request.user, form_class._meta.model + request.region, + request.user, + form_class._meta.model, ): raise RuntimeError( - f'Machine translations are disabled for content type "{form_class._meta.model}" and {request.user!r}.' + f'Machine translations are disabled for content type "{form_class._meta.model}" and {request.user!r}.', ) if not settings.GOOGLE_TRANSLATE_ENABLED: raise RuntimeError("Google translate is disabled globally.") try: credentials = service_account.Credentials.from_service_account_file( - settings.GOOGLE_APPLICATION_CREDENTIALS + settings.GOOGLE_APPLICATION_CREDENTIALS, ) if settings.GOOGLE_TRANSLATE_VERSION == "Advanced": self.translator_v3 = translate_v3.TranslationServiceClient( - credentials=credentials + credentials=credentials, ) else: self.translator_v2 = translate_v2.Client(credentials=credentials) - except Exception as e: # pylint: disable=broad-except - logger.error(e) - logger.error( - "Google translate is not available. Please check the credentials file." + except Exception: + logger.exception( + "Google translate is not available. Please check the credentials file.", ) @staticmethod @@ -83,9 +87,10 @@ def get_target_language_key(target_language: Language) -> str: return "" def translate_queryset( # noqa: PLR0915 - self, queryset: list[Event] | (list[Page] | list[POI]), language_slug: str + self, + queryset: list[Event] | (list[Page] | list[POI]), + language_slug: str, ) -> None: - # pylint: disable=too-many-locals, too-many-branches, too-many-statements """ This function translates a content queryset via Google Translate @@ -116,24 +121,26 @@ def translate_queryset( # noqa: PLR0915 for content_object in queryset: source_translation = content_object.get_translation( - source_language.slug + source_language.slug, ) if not source_translation: failed_changes_because_no_source_translation.append( - content_object.best_translation.title + content_object.best_translation.title, ) continue if not check_hix_score( - self.request, source_translation, show_message=False + self.request, + source_translation, + show_message=False, ): failed_changes_because_insufficient_hix_score.append( - source_translation.title + source_translation.title, ) continue existing_target_translation = content_object.get_translation( - target_language.slug + target_language.slug, ) # Before translating, check if translation would exceed usage limit @@ -143,7 +150,7 @@ def translate_queryset( # noqa: PLR0915 ) = self.check_usage(region, source_translation) if translation_exceeds_limit: failed_changes_because_exceeds_limit.append( - source_translation.title + source_translation.title, ) continue @@ -156,7 +163,8 @@ def translate_queryset( # noqa: PLR0915 for attr in self.translatable_attributes: # Only translate existing, non-empty attributes if hasattr(source_translation, attr) and getattr( - source_translation, attr + source_translation, + attr, ): try: # data has to be unescaped to recognize Umlaute @@ -185,9 +193,9 @@ def translate_queryset( # noqa: PLR0915 source_language=source_language.slug, format_=format_, )[0]["translatedText"] - except Exception as e: # pylint: disable=broad-except + except Exception: google_translate_api_error = True - logger.error(e) + logger.exception("") break content_translation_form = self.form_class( @@ -206,11 +214,11 @@ def translate_queryset( # noqa: PLR0915 if existing_target_translation: if settings.REDIS_CACHE: existing_target_translation.all_versions.invalidated_update( - currently_in_translation=False + currently_in_translation=False, ) else: existing_target_translation.all_versions.update( - currently_in_translation=False + currently_in_translation=False, ) logger.debug( @@ -264,7 +272,7 @@ def translate_queryset( # noqa: PLR0915 model_name=model_name, model_name_plural=model_name_plural, object_names=iter_to_string( - failed_changes_because_no_source_translation + failed_changes_because_no_source_translation, ), ), ) @@ -281,7 +289,7 @@ def translate_queryset( # noqa: PLR0915 model_name_plural=model_name_plural, remaining_budget=region.mt_budget_remaining, object_names=iter_to_string( - failed_changes_because_exceeds_limit + failed_changes_because_exceeds_limit, ), ), ) @@ -298,7 +306,7 @@ def translate_queryset( # noqa: PLR0915 model_name_plural=model_name_plural, min_required=settings.HIX_REQUIRED_FOR_MT, object_names=iter_to_string( - failed_changes_because_insufficient_hix_score + failed_changes_because_insufficient_hix_score, ), ), ) @@ -321,6 +329,6 @@ def translate_queryset( # noqa: PLR0915 messages.error( self.request, _( - "A problem with Google Translate API has occurred. Please contact an administrator." + "A problem with Google Translate API has occurred. Please contact an administrator.", ), ) diff --git a/integreat_cms/gvz_api/apps.py b/integreat_cms/gvz_api/apps.py index 0e9c31fc19..9023396c61 100644 --- a/integreat_cms/gvz_api/apps.py +++ b/integreat_cms/gvz_api/apps.py @@ -43,19 +43,19 @@ def ready(self) -> None: try: response = requests.get(f"{settings.GVZ_API_URL}/api/", timeout=3) # Require the response to return a valid JSON, otherwise it's probably an error - assert json.loads(response.text) + if not json.loads(response.text): + raise ValueError # noqa: TRY301 logger.info("GVZ API is available at: %r", settings.GVZ_API_URL) self.api_available = True except ( json.decoder.JSONDecodeError, requests.exceptions.RequestException, requests.exceptions.Timeout, - AssertionError, - ) as e: - logger.error(e) - logger.error( + ValueError, + ): + logger.exception( "GVZ API is unavailable. You won't be able to " - "automatically import region coordinates and aliases." + "automatically import region coordinates and aliases.", ) else: logger.info("GVZ API is disabled") diff --git a/integreat_cms/gvz_api/utils.py b/integreat_cms/gvz_api/utils.py index d473586b5d..59ced747ef 100644 --- a/integreat_cms/gvz_api/utils.py +++ b/integreat_cms/gvz_api/utils.py @@ -29,7 +29,9 @@ class GvzApiWrapper: """ def search( - self, region_name: str | None, division_category: int | None + self, + region_name: str | None, + division_category: int | None, ) -> list[dict[str, Any]]: """ Search for a region and return candidates @@ -39,11 +41,10 @@ def search( :return: JSON search results defined in the GVZ API """ logger.debug("Searching for %r", region_name) - regions = requests.get( + return requests.get( f"{self.api_url}/api/administrative_divisions/?search={region_name}&division_category={division_category}", timeout=settings.DEFAULT_REQUEST_TIMEOUT, ).json()["results"] - return regions def get_details(self, ags: str) -> dict | None: """ @@ -82,7 +83,8 @@ def get_child_coordinates(self, child_urls: list) -> dict: result = {} for url in child_urls: response = requests.get( - url, timeout=settings.DEFAULT_REQUEST_TIMEOUT + url, + timeout=settings.DEFAULT_REQUEST_TIMEOUT, ).json() result[response["name"]] = { "longitude": response["longitude"], @@ -125,7 +127,9 @@ def translate_division_category(division_type: str | None) -> int | None: return result def best_match( - self, region_name: str | None, division_type: str | None + self, + region_name: str | None, + division_type: str | None, ) -> dict[str, Any] | None: """ Tries to find the correct region id (single search hit) @@ -137,7 +141,8 @@ def best_match( # First: let's try normal search. If there is only one result, then # everything is good. results = self.search( - region_name, self.translate_division_category(division_type) + region_name, + self.translate_division_category(division_type), ) if len(results) == 1: diff --git a/integreat_cms/integreat-cms-cli b/integreat_cms/integreat-cms-cli index 50d77e5d97..4871afdfdd 100755 --- a/integreat_cms/integreat-cms-cli +++ b/integreat_cms/integreat-cms-cli @@ -31,14 +31,12 @@ def main() -> None: read_config() try: - # pylint: disable=import-outside-toplevel from django.core.management import execute_from_command_line except ImportError: # The above import may fail for some other reason. Ensure that the # issue is really that Django is missing to avoid masking other # exceptions on Python 2. try: - # pylint: disable=import-outside-toplevel,unused-import import django except ImportError as e: raise ImportError( diff --git a/integreat_cms/integreat_celery/celery.py b/integreat_cms/integreat_celery/celery.py index 2606380c4d..ea3779dda2 100644 --- a/integreat_cms/integreat_celery/celery.py +++ b/integreat_cms/integreat_celery/celery.py @@ -1,5 +1,23 @@ """ Celery worker + +Usage example: + +.. code-block:: python + + @app.task + def wrapper_create_statistics(): + print("create statistics") + + + @app.on_after_configure.connect + def setup_periodic_tasks(sender, **kwargs): + sender.add_periodic_task( + 84600, + wrapper_create_statistics.s(), + name="wrapper_create_statistics", + ) + """ from __future__ import annotations @@ -27,15 +45,6 @@ app.autodiscover_tasks() -# @app.task -# def wrapper_create_statistics(): -# """ -# Periodic task to generate region statistics -# """ -# print("create statistics") -# - - @app.task def wrapper_import_events_from_external_calendars() -> None: """ diff --git a/integreat_cms/locale/de/LC_MESSAGES/django.po b/integreat_cms/locale/de/LC_MESSAGES/django.po index 58f51fe54c..29e45bf536 100644 --- a/integreat_cms/locale/de/LC_MESSAGES/django.po +++ b/integreat_cms/locale/de/LC_MESSAGES/django.po @@ -144,11 +144,11 @@ msgstr "PPT-Dokument" msgid "PPTX document" msgstr "PPTX-Dokument" -#: cms/constants/calendar.py +#: cms/constants/calendar_filters.py msgid "Event not from an external calendar" msgstr "Veranstaltung kommt nicht aus einem externen Kalender" -#: cms/constants/calendar.py +#: cms/constants/calendar_filters.py msgid "Event from an external calendar" msgstr "Veranstaltung kommt aus einem externen Kalender" @@ -8961,7 +8961,7 @@ msgid "Invalid email credentials." msgstr "Ungültige E-Mail-Anmeldedaten." #: cms/utils/email_utils.py -msgid "An SMTP error occured." +msgid "An SMTP error occurred." msgstr "Ein SMTP-Fehler ist aufgetreten." #: cms/utils/email_utils.py diff --git a/integreat_cms/matomo_api/matomo_api_client.py b/integreat_cms/matomo_api/matomo_api_client.py index e0af86421f..2c9d3d54c8 100644 --- a/integreat_cms/matomo_api/matomo_api_client.py +++ b/integreat_cms/matomo_api/matomo_api_client.py @@ -15,7 +15,6 @@ from ..cms.constants import language_color, matomo_periods if TYPE_CHECKING: - import sys from asyncio import AbstractEventLoop from collections.abc import KeysView from typing import Any, TypeGuard @@ -64,7 +63,9 @@ def __init__(self, region: Region) -> None: self.languages = region.active_languages async def fetch( - self, session: ClientSession, **kwargs: Any + self, + session: ClientSession, + **kwargs: Any, ) -> dict[str, Any] | list[int]: r""" Uses :meth:`aiohttp.ClientSession.get` to perform an asynchronous GET request to the Matomo API. @@ -106,7 +107,7 @@ def mask_token_auth(req_url: str) -> str: return response_data except aiohttp.ClientError as e: raise MatomoException( - f"An error occurred {mask_token_auth(str(e))}" + f"An error occurred {mask_token_auth(str(e))}", ) from None async def get_matomo_id_async(self, **query_params: Any) -> list[int]: @@ -144,7 +145,7 @@ def get_matomo_id(self, token_auth: str) -> int: self.get_matomo_id_async( token_auth=token_auth, method="SitesManager.getSitesIdWithAtLeastViewAccess", - ) + ), ) try: @@ -152,11 +153,12 @@ def get_matomo_id(self, token_auth: str) -> int: except IndexError as e: # If no id is returned, there is no user with the given access token raise MatomoException( - f"The access token for {self.region_name} is not correct." + f"The access token for {self.region_name} is not correct.", ) from e async def get_total_visits_async( - self, query_params: dict[str, str | int | None] + self, + query_params: dict[str, str | int | None], ) -> dict[str, Any]: """ Async wrapper to fetch the total visits with :mod:`aiohttp`. @@ -179,7 +181,10 @@ async def get_total_visits_async( return result def get_total_visits( - self, start_date: date, end_date: date, period: str = matomo_periods.DAY + self, + start_date: date, + end_date: date, + period: str = matomo_periods.DAY, ) -> dict[str, Any]: """ Returns the total calls within a time range for all languages. @@ -219,7 +224,7 @@ def get_total_visits( "backgroundColor": language_color.TOTAL_ACCESS, "borderColor": language_color.TOTAL_ACCESS, "data": list(dataset.values()), - } + }, ], }, } @@ -255,7 +260,7 @@ async def get_visits_per_language_async( session, **query_params, segment=f"pageUrl=@/{language.slug}/wp-json/extensions/v3/,pageUrl=@/api/v3/{self.region_slug}/{language.slug}/", - ) + ), ) for language in languages ] @@ -267,15 +272,17 @@ async def get_visits_per_language_async( **query_params, segment="pageUrl=@/wp-json/extensions/v3/pages,pageUrl=$/pages/", ), - ) + ), ) # Create separate task to gather WebApp download hits tasks.append( loop.create_task( self.fetch( - session, **query_params, segment="pageUrl=@/children/?depth" + session, + **query_params, + segment="pageUrl=@/children/?depth", ), - ) + ), ) # Create task for all downloads tasks.append( @@ -283,8 +290,8 @@ async def get_visits_per_language_async( self.fetch( session, **query_params, - ) - ) + ), + ), ) # Wait for all tasks to finish and collect the results # (the results are sorted in the order the tasks were created) @@ -293,7 +300,7 @@ async def get_visits_per_language_async( if TYPE_CHECKING: def is_dict_list( - lst: list[dict[str, Any] | list[int]] + lst: list[dict[str, Any] | list[int]], ) -> TypeGuard[list[dict[str, Any]]]: return all(isinstance(d, dict) for d in lst) @@ -301,7 +308,10 @@ def is_dict_list( return result def get_visits_per_language( - self, start_date: date, end_date: date, period: str + self, + start_date: date, + end_date: date, + period: str, ) -> dict[str, Any]: """ Returns the total unique visitors in a timerange as defined in period @@ -336,7 +346,7 @@ def get_visits_per_language( # Execute async request to Matomo API logger.debug("Fetching visits for languages %r asynchronously.", languages) datasets = loop.run_until_complete( - self.get_visits_per_language_async(loop, query_params, languages) + self.get_visits_per_language_async(loop, query_params, languages), ) logger.debug("All asynchronous fetching tasks have finished.") # The last dataset contains the total visits @@ -348,7 +358,9 @@ def get_visits_per_language( language_data, language_legends = self.get_language_data(languages, datasets) access_data, access_legends = self.get_access_data( - total_visits, webapp_downloads, offline_downloads + total_visits, + webapp_downloads, + offline_downloads, ) return { @@ -435,7 +447,8 @@ def simplify_date_labels(date_labels: KeysView[str], period: str) -> list[Promis @staticmethod def get_language_data( - languages: list[Language], datasets: list[dict] + languages: list[Language], + datasets: list[dict], ) -> tuple[list[dict], list[str]]: """ Structure the datasets for languages in a chart.js-compatible format, @@ -448,14 +461,14 @@ def get_language_data( data_entries = [] legend_entries = [] - for language, dataset in zip(languages, datasets): + for language, dataset in zip(languages, datasets, strict=False): data_entries.append( { "label": language.translated_name, "backgroundColor": language.language_color, "borderColor": language.language_color, "data": list(dataset.values()), - } + }, ) legend_entries.append( render_to_string( @@ -465,13 +478,15 @@ def get_language_data( "color": language.language_color, "language": language, }, - ) + ), ) return data_entries, legend_entries @staticmethod def get_access_data( - total_visits: dict, webapp_downloads: dict, offline_downloads: dict + total_visits: dict, + webapp_downloads: dict, + offline_downloads: dict, ) -> tuple[list[dict], list[str]]: """ Structure the datasets for accesses in a chart.js-compatible format, diff --git a/integreat_cms/nominatim_api/nominatim_api_client.py b/integreat_cms/nominatim_api/nominatim_api_client.py index c08b764c5d..fefa438725 100644 --- a/integreat_cms/nominatim_api/nominatim_api_client.py +++ b/integreat_cms/nominatim_api/nominatim_api_client.py @@ -47,9 +47,8 @@ def __init__(self) -> None: user_agent=f"integreat-cms/{__version__} ({settings.HOSTNAME})", timeout=settings.DEFAULT_REQUEST_TIMEOUT, ) - except GeopyError as e: - logger.exception(e) - logger.error("Nominatim API client could not be initialized") + except GeopyError: + logger.exception("Nominatim API client could not be initialized") def search( self, @@ -72,7 +71,7 @@ def search( """ if query_str and query_dict: raise RuntimeError( - "You can either specify query_str or pass additional keyword arguments, not both." + "You can either specify query_str or pass additional keyword arguments, not both.", ) if street := query_dict.get("street"): # This expression matches a number optionally followed by a whitespace and one character @@ -96,30 +95,32 @@ def search( logger.debug("Nominatim API search result: %r", result.raw) else: logger.debug("Nominatim API did not return a match") - return result - except GeopyError as e: - logger.error(e) - logger.error("Nominatim API call failed") + except GeopyError: + logger.exception("Nominatim API call failed") return None + else: + return result def check_availability(self) -> None: """ Check if Nominatim API is available """ - try: - assert self.search(query_str="Deutschland") + if self.search(query_str="Deutschland"): logger.info( "Nominatim API is available at: %r", settings.NOMINATIM_API_URL, ) - except AssertionError: + else: logger.error( "Nominatim API unavailable. You won't be able to " - "automatically import location coordinates." + "automatically import location coordinates.", ) def get_coordinates( - self, street: str, postalcode: str, city: str + self, + street: str, + postalcode: str, + city: str, ) -> tuple[int, int] | tuple[None, None]: """ Get coordinates for given address @@ -134,7 +135,10 @@ def get_coordinates( return None, None def get_bounding_box( - self, administrative_division: str, name: str, aliases: dict | None = None + self, + administrative_division: str, + name: str, + aliases: dict | None = None, ) -> BoundingBox | None: """ Get the bounding box for a given region @@ -181,13 +185,15 @@ def get_city_and_district_bounding_box(self, name: str) -> BoundingBox | None: city_box = BoundingBox.from_result(self.search(city=name)) # Get bounding box of district district_box = BoundingBox.from_result( - self.search(query_str=f"Landkreis {name}") + self.search(query_str=f"Landkreis {name}"), ) # Merge both results return BoundingBox.merge(city_box, district_box) def get_district_bounding_box( - self, administrative_division: str, name: str + self, + administrative_division: str, + name: str, ) -> BoundingBox | None: """ Get the bounding box for a given district @@ -203,7 +209,9 @@ def get_district_bounding_box( return BoundingBox.from_result(self.search(query_str=f"{adm_div} {name}")) def get_region_bounding_box( - self, name: str, aliases: dict[str, str] | None = None + self, + name: str, + aliases: dict[str, str] | None = None, ) -> BoundingBox | None: """ Get the bounding box for a given region and all its aliases @@ -217,7 +225,7 @@ def get_region_bounding_box( # Get bounding box of city bounding_boxes = [BoundingBox.from_result(self.search(city=name))] # Get bounding boxes of all aliases - for alias in aliases.keys(): + for alias in aliases: bounding_boxes.append(BoundingBox.from_result(self.search(city=alias))) return BoundingBox.merge(*bounding_boxes) @@ -238,8 +246,8 @@ def get_address(self, latitude: int, longitude: int) -> Location | None: "Nominatim API did not return an address at coordinates %r", coordinates, ) - return result - except GeopyError as e: - logger.error(e) - logger.error("Nominatim API call failed") + except GeopyError: + logger.exception("Nominatim API call failed") return None + else: + return result diff --git a/integreat_cms/sitemap/sitemaps.py b/integreat_cms/sitemap/sitemaps.py index 3627e7a719..171cbac438 100644 --- a/integreat_cms/sitemap/sitemaps.py +++ b/integreat_cms/sitemap/sitemaps.py @@ -81,7 +81,7 @@ def lastmod(translation: OfferTemplate | AbstractContentTranslation) -> datetime """ return translation.last_updated - def _urls(self, page: int, protocol: str, domain: str) -> list[dict[str, Any]]: + def _urls(self, page: int, _protocol: str, _domain: str) -> list[dict[str, Any]]: """ This is a patched version of :func:`django.contrib.sitemaps.Sitemap._urls` which adds the alternative languages to the list of urls. @@ -92,8 +92,8 @@ def _urls(self, page: int, protocol: str, domain: str) -> list[dict[str, Any]]: support this functionality when used together with :doc:`django:ref/contrib/sites`. :param page: The page for the paginator (will always be ``1`` in our case) - :param protocol: The protocol of the urls - :param domain: The domain of the urls + :param _protocol: The protocol of the urls + :param _domain: The domain of the urls :return: A list of urls """ splitted_url = urlsplit(settings.WEBAPP_URL) @@ -105,7 +105,8 @@ def _urls(self, page: int, protocol: str, domain: str) -> list[dict[str, Any]]: return urls def sitemap_alternates( - self, obj: OfferTemplate | AbstractContentTranslation + self, + obj: OfferTemplate | AbstractContentTranslation, ) -> list[dict[str, str]]: """ This function returns the sitemap alternatives for a given object @@ -129,7 +130,7 @@ class PageSitemap(WebappSitemap): priority: float = 1.0 #: The :class:`~integreat_cms.cms.models.pages.page_translation.PageTranslation` :class:`~django.db.models.query.QuerySet` of this sitemap queryset: QuerySet[PageTranslation] = PageTranslation.objects.filter( - status=status.PUBLIC + status=status.PUBLIC, ) def __init__(self, region: Region, language: Language) -> None: @@ -162,7 +163,8 @@ class EventSitemap(WebappSitemap): #: The :class:`~integreat_cms.cms.models.events.event_translation.EventTranslation` :class:`~django.db.models.query.QuerySet` of this sitemap queryset: QuerySet[EventTranslation] = EventTranslation.objects.filter( - event__archived=False, status=status.PUBLIC + event__archived=False, + status=status.PUBLIC, ) def __init__(self, region: Region, language: Language) -> None: @@ -176,7 +178,8 @@ def __init__(self, region: Region, language: Language) -> None: super().__init__(region, language) # Filter queryset based on region and language self.queryset = self.queryset.filter( - event__in=self.region.events.all(), language=self.language + event__in=self.region.events.all(), + language=self.language, ).distinct("event__pk") @@ -192,7 +195,8 @@ class POISitemap(WebappSitemap): #: The :class:`~integreat_cms.cms.models.pois.poi_translation.POITranslation` :class:`~django.db.models.query.QuerySet` queryset of this sitemap queryset: QuerySet[POITranslation] = POITranslation.objects.filter( - poi__archived=False, status=status.PUBLIC + poi__archived=False, + status=status.PUBLIC, ) def __init__(self, region: Region, language: Language) -> None: @@ -206,7 +210,8 @@ def __init__(self, region: Region, language: Language) -> None: super().__init__(region, language) # Filter queryset based on region and language self.queryset = self.queryset.filter( - poi__in=self.region.pois.all(), language=self.language + poi__in=self.region.pois.all(), + language=self.language, ).distinct("poi__pk") diff --git a/integreat_cms/sitemap/urls.py b/integreat_cms/sitemap/urls.py index 48ebe9cea9..5c95eec273 100644 --- a/integreat_cms/sitemap/urls.py +++ b/integreat_cms/sitemap/urls.py @@ -45,7 +45,7 @@ "wp-json/ig-sitemap/v1/sitemap.xml", SitemapView.as_view(), ), - ] + ], ), ), ] diff --git a/integreat_cms/sitemap/views.py b/integreat_cms/sitemap/views.py index bfd22ead1f..13034c5aba 100644 --- a/integreat_cms/sitemap/views.py +++ b/integreat_cms/sitemap/views.py @@ -6,7 +6,9 @@ from __future__ import annotations +import functools import logging +import operator from typing import TYPE_CHECKING from django.conf import settings @@ -112,7 +114,9 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> TemplateRespon # Only return a sitemap if the region is active region = get_object_or_404( - Region, slug=kwargs.get("region_slug"), status=region_status.ACTIVE + Region, + slug=kwargs.get("region_slug"), + status=region_status.ACTIVE, ) # Only return a sitemap if the language is active language = region.get_language_or_404( @@ -125,7 +129,8 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> TemplateRespon raise Http404 # Join the lists of all urls of all sitemaps - urls: list[dict[str, Any]] = sum( + urls: list[dict[str, Any]] = functools.reduce( + operator.iadd, (sitemap.get_urls(site=get_current_site(request)) for sitemap in sitemaps), [], ) diff --git a/integreat_cms/summ_ai_api/apps.py b/integreat_cms/summ_ai_api/apps.py index 2a367c6773..5db1918b1e 100644 --- a/integreat_cms/summ_ai_api/apps.py +++ b/integreat_cms/summ_ai_api/apps.py @@ -40,11 +40,11 @@ def ready(self) -> None: logger.info("SUMM.AI API is disabled") elif settings.SUMM_AI_TEST_MODE: logger.info( - "SUMM.AI API is enabled, but in test mode. No credits get charged, but only a dummy text is returned." + "SUMM.AI API is enabled, but in test mode. No credits get charged, but only a dummy text is returned.", ) elif settings.DEBUG: logger.info( - "SUMM.AI API is enabled, but in debug mode. Text is really translated and credits get charged, but user is 'testumgebung'" + "SUMM.AI API is enabled, but in debug mode. Text is really translated and credits get charged, but user is 'testumgebung'", ) else: logger.info("SUMM.AI API is enabled in production mode.") diff --git a/integreat_cms/summ_ai_api/summ_ai_api_client.py b/integreat_cms/summ_ai_api/summ_ai_api_client.py index 1ddd8d97b0..ddfaef82fe 100644 --- a/integreat_cms/summ_ai_api/summ_ai_api_client.py +++ b/integreat_cms/summ_ai_api/summ_ai_api_client.py @@ -13,7 +13,6 @@ import aiohttp from django.conf import settings from django.contrib import messages -from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from ..cms.utils.stringify_list import iter_to_string @@ -32,8 +31,8 @@ if TYPE_CHECKING: from asyncio import AbstractEventLoop - from collections.abc import Callable - from typing import Any, Iterator + from collections.abc import Iterator + from typing import Any from aiohttp import ClientSession from django.forms.models import ModelFormMetaclass @@ -59,10 +58,12 @@ def __init__(self, request: HttpRequest, form_class: ModelFormMetaclass) -> None """ super().__init__(request, form_class) if not MachineTranslationProvider.is_permitted( - request.region, request.user, form_class._meta.model + request.region, + request.user, + form_class._meta.model, ): raise RuntimeError( - f'Machine translations are disabled for content type "{form_class._meta.model}" and {request.user!r}.' + f'Machine translations are disabled for content type "{form_class._meta.model}" and {request.user!r}.', ) if not settings.SUMM_AI_ENABLED: raise RuntimeError("SUMM.AI is disabled globally.") @@ -70,7 +71,9 @@ def __init__(self, request: HttpRequest, form_class: ModelFormMetaclass) -> None raise RuntimeError(f"SUMM.AI is disabled in {self.region!r}.") async def translate_text_field( - self, session: ClientSession, text_field: TextField + self, + session: ClientSession, + text_field: TextField, ) -> TextField: """ Uses :meth:`aiohttp.ClientSession.post` to perform an asynchronous POST request to the SUMM.AI API. @@ -124,22 +127,20 @@ async def translate_text_field( try: response_data = await response.json() except aiohttp.ContentTypeError as e: - logger.error( - "SUMM.AI API %s response failed to parse as JSON: %s: %s", + logger.exception( + "SUMM.AI API %s response failed to parse as JSON", response.status, - type(e), - e, ) raise SummAiInvalidJSONError( - f"API delivered invalid JSON: {response.status} - {await response.text()}" + f"API delivered invalid JSON: {response.status} - {await response.text()}", ) from e if self.validate_response(response_data, response.status): # Let the field handle the translated text text_field.translate(response_data["translated_text"]) # If text is not in response, validate_response() # will raise exceptions - so we don't need an else branch. - except (aiohttp.ClientError, asyncio.TimeoutError, SummAiRuntimeError) as e: - logger.error( + except (TimeoutError, aiohttp.ClientError, SummAiRuntimeError) as e: + logger.error( # noqa: TRY400 "SUMM.AI translation of %r failed because of %s: %s", text_field, type(e), @@ -149,7 +150,9 @@ async def translate_text_field( return text_field async def translate_text_fields( - self, loop: AbstractEventLoop, text_fields: Iterator[TextField] + self, + loop: AbstractEventLoop, + text_fields: Iterator[TextField], ) -> chain[list[TextField]]: """ Translate a list of text fields from German into Easy German. @@ -193,11 +196,10 @@ def abort_function(task: partial, reason: Any) -> None: *[ worker(loop, task_generator, str(i)) for i in range(settings.SUMM_AI_MAX_CONCURRENT_REQUESTS) - ] + ], ) # Put all results in one single list - all_results = chain(worker_results) - return all_results + return chain(worker_results) def translate_queryset(self, queryset: list[Page], language_slug: str) -> None: """ @@ -211,7 +213,7 @@ def translate_queryset(self, queryset: list[Page], language_slug: str) -> None: # Make sure both languages exist self.request.region.get_language_or_404(settings.SUMM_AI_GERMAN_LANGUAGE_SLUG) easy_german = self.request.region.get_language_or_404( - settings.SUMM_AI_EASY_GERMAN_LANGUAGE_SLUG + settings.SUMM_AI_EASY_GERMAN_LANGUAGE_SLUG, ) # Initialize translation helpers for each object instance @@ -225,7 +227,7 @@ def translate_queryset(self, queryset: list[Page], language_slug: str) -> None: *[ translation_helper.get_text_fields() for translation_helper in translation_helpers - ] + ], ) # Initialize async event loop @@ -299,10 +301,10 @@ def validate_response(cls, response_data: dict, response_status: int) -> bool: if "translated_text" not in response_data: if "error" in response_data: raise SummAiRuntimeError( - f"API error: {response_status} - {response_data['error']}" + f"API error: {response_status} - {response_data['error']}", ) raise SummAiRuntimeError( - f"Unexpected API result: {response_status} - {response_data!r}" + f"Unexpected API result: {response_status} - {response_data!r}", ) return True diff --git a/integreat_cms/summ_ai_api/utils.py b/integreat_cms/summ_ai_api/utils.py index d951ea5c9a..fd6621db52 100644 --- a/integreat_cms/summ_ai_api/utils.py +++ b/integreat_cms/summ_ai_api/utils.py @@ -35,7 +35,6 @@ from ..cms.models.abstract_content_translation import AbstractContentTranslation from ..cms.constants import status -from ..cms.utils.translation_utils import gettext_many_lazy as __ logger = logging.getLogger(__name__) @@ -105,7 +104,6 @@ def __repr__(self) -> str: class HTMLSegment(TextField): - # pylint: disable=too-few-public-methods """ A class for translatable HTML segments """ @@ -114,7 +112,6 @@ class HTMLSegment(TextField): segment: HtmlElement def __init__(self, segment: HtmlElement) -> None: - # pylint: disable=super-init-not-called """ Convert the lxml tree element to a flat text string. Preserve
tags as new lines characters. @@ -193,7 +190,10 @@ def translated_text(self) -> str | None: """ if self.html is not None: return tostring( - self.html, encoding="unicode", method="html", pretty_print=True + self.html, + encoding="unicode", + method="html", + pretty_print=True, ) return None @@ -205,7 +205,8 @@ def exception(self) -> SummAiException | None: :returns: The first exception of this HTML field """ return next( - (segment.exception for segment in self.segments if segment.exception), None + (segment.exception for segment in self.segments if segment.exception), + None, ) @@ -294,7 +295,7 @@ def get_text_fields(self) -> list[HTMLSegment]: # Get all segments of all HTML fields *[html_field.segments for html_field in self.html_fields], ), - ) + ), ) logger.debug( "Text fields for %r: %r", @@ -319,7 +320,7 @@ def commit(self, easy_german: Language) -> bool: return False # Initialize form to create new translation object existing_target_translation = self.object_instance.get_translation( - settings.SUMM_AI_EASY_GERMAN_LANGUAGE_SLUG + settings.SUMM_AI_EASY_GERMAN_LANGUAGE_SLUG, ) content_translation_form = self.form_class( data={ @@ -356,11 +357,11 @@ def commit(self, easy_german: Language) -> bool: if existing_target_translation: if settings.REDIS_CACHE: existing_target_translation.all_versions.invalidated_update( - currently_in_translation=False + currently_in_translation=False, ) else: existing_target_translation.all_versions.update( - currently_in_translation=False + currently_in_translation=False, ) logger.debug( @@ -461,9 +462,10 @@ async def __anext__(self) -> T: try: task = self.popleft() self._in_progress.append(task) - return task except IndexError as e: raise StopAsyncIteration from e + else: + return task def hit_rate_limit(self, task: T) -> None: """ @@ -471,9 +473,10 @@ def hit_rate_limit(self, task: T) -> None: :param task: The task that failed because of the rate limiting """ - assert ( - task in self._in_progress - ), f"PatientTaskQueue: Failed task not known as in progress: {task}" + if task not in self._in_progress: + raise KeyError( + f"PatientTaskQueue: Failed task not known as in progress: {task}", + ) # Only save current timestamp if this is the first failed request reported if ( @@ -503,7 +506,7 @@ def hit_rate_limit(self, task: T) -> None: if self.retries > self.max_retries and not self._aborted: self.abort( - f"Retried tasks a consecutive {self.max_retries} times. Giving up." + f"Retried tasks a consecutive {self.max_retries} times. Giving up.", ) def completed(self, task: T) -> None: @@ -512,9 +515,10 @@ def completed(self, task: T) -> None: :param task: The task that failed because of the rate limiting """ - assert ( - task in self._in_progress - ), f"PatientTaskQueue: Completed task not known as in progress: {task}" + if task not in self._in_progress: + raise KeyError( + f"PatientTaskQueue: Completed task not known as in progress: {task}", + ) self.retries = 0 self._in_progress.remove(task) diff --git a/integreat_cms/textlab_api/apps.py b/integreat_cms/textlab_api/apps.py index fd0e4c8833..68bab21193 100644 --- a/integreat_cms/textlab_api/apps.py +++ b/integreat_cms/textlab_api/apps.py @@ -39,10 +39,12 @@ def ready(self) -> None: if settings.TEXTLAB_API_ENABLED: try: TextlabClient( - settings.TEXTLAB_API_USERNAME, settings.TEXTLAB_API_KEY + settings.TEXTLAB_API_USERNAME, + settings.TEXTLAB_API_KEY, ) logger.info( - "Textlab API is available at: %r", settings.TEXTLAB_API_URL + "Textlab API is available at: %r", + settings.TEXTLAB_API_URL, ) except (URLError, OSError) as e: logger.info("Textlab API is unavailable: %r", e) diff --git a/integreat_cms/textlab_api/textlab_api_client.py b/integreat_cms/textlab_api/textlab_api_client.py index 97df08938f..3d4adb4a27 100644 --- a/integreat_cms/textlab_api/textlab_api_client.py +++ b/integreat_cms/textlab_api/textlab_api_client.py @@ -55,7 +55,9 @@ def login(self) -> None: self.token = response["token"] def benchmark( - self, text: str, text_type: int = settings.TEXTLAB_API_DEFAULT_BENCHMARK_ID + self, + text: str, + text_type: int = settings.TEXTLAB_API_DEFAULT_BENCHMARK_ID, ) -> TextlabResult: """ Retrieves the hix score of the given text. @@ -84,7 +86,9 @@ def benchmark( @staticmethod def post_request( - path: str, data: dict[str, str], auth_token: str | None = None + path: str, + data: dict[str, str], + auth_token: str | None = None, ) -> dict[str, Any]: """ Sends a request to the api. @@ -96,8 +100,12 @@ def post_request( :raises urllib.error.HTTPError: If the request failed """ data_json: bytes = json.dumps(data).encode("utf-8") - request = Request( - f"{settings.TEXTLAB_API_URL.rstrip('/')}{path}", + + url = f"{settings.TEXTLAB_API_URL.rstrip('/')}{path}" + if not url.startswith(("http:", "https:")): + raise ValueError("URL must start with 'http:' or 'https:'") + request = Request( # noqa: S310 + url, data=data_json, method="POST", ) @@ -105,5 +113,5 @@ def post_request( request.add_header("authorization", f"Bearer {auth_token}") request.add_header("Content-Type", "application/json") request.add_header("User-Agent", "") - with urlopen(request) as response: + with urlopen(request) as response: # noqa: S310 return json.loads(response.read().decode("utf-8")) diff --git a/integreat_cms/textlab_api/utils.py b/integreat_cms/textlab_api/utils.py index bdd3bbcb3b..d3f1f5a612 100644 --- a/integreat_cms/textlab_api/utils.py +++ b/integreat_cms/textlab_api/utils.py @@ -43,7 +43,7 @@ def check_hix_score( messages.error( request, _( - 'HIX score {:.2f} of "{}" is too low for machine translation (minimum required: {})' + 'HIX score {:.2f} of "{}" is too low for machine translation (minimum required: {})', ).format( source_translation.hix_score, source_translation, @@ -56,7 +56,7 @@ def check_hix_score( messages.error( request, _( - 'Machine translations are disabled for "{}", because its HIX value is ignored' + 'Machine translations are disabled for "{}", because its HIX value is ignored', ).format( source_translation.title, ), @@ -103,7 +103,7 @@ def format_hix_feedback(response: dict) -> list[dict[str, Any]]: { "category": "abbreviations", "result": abbreviations_total, - } + }, ) return feedback_details diff --git a/integreat_cms/xliff/base_serializer.py b/integreat_cms/xliff/base_serializer.py index 85cdbf4418..17d98b1fed 100644 --- a/integreat_cms/xliff/base_serializer.py +++ b/integreat_cms/xliff/base_serializer.py @@ -10,9 +10,9 @@ from __future__ import annotations import logging -import xml.dom.minidom from typing import TYPE_CHECKING +import defusedxml.minidom from django.conf import settings from django.core.exceptions import FieldDoesNotExist from django.core.serializers import xml_serializer @@ -23,8 +23,8 @@ if TYPE_CHECKING: from typing import Any - from xml.dom.minidom import Element + from defusedxml.minidom import Element from django.db.models.fields import ( CharField, ForeignKey, @@ -69,7 +69,10 @@ class Serializer(xml_serializer.Serializer): only_public: bool = False def serialize( - self, queryset: list[PageTranslation], *args: Any, **kwargs: Any + self, + queryset: list[PageTranslation], + *args: Any, + **kwargs: Any, ) -> str: r""" Initialize serialization and set the :attr:`~integreat_cms.core.settings.XLIFF_DEFAULT_FIELDS`. @@ -89,7 +92,8 @@ def start_serialization(self) -> None: Start serialization - open the XML document and the root element. """ self.xml = XMLGeneratorWithCDATA( - self.stream, self.options.get("encoding", settings.DEFAULT_CHARSET) + self.stream, + self.options.get("encoding", settings.DEFAULT_CHARSET), ) self.xml.startDocument() @@ -101,7 +105,7 @@ def start_object(self, obj: PageTranslation) -> None: :raises NotImplementedError: If the property is not implemented in the subclass """ raise NotImplementedError( - "subclasses of Serializer must provide a start_object() method" + "subclasses of Serializer must provide a start_object() method", ) def handle_field(self, obj: PageTranslation, field: CharField | TextField) -> None: @@ -113,7 +117,7 @@ def handle_field(self, obj: PageTranslation, field: CharField | TextField) -> No :raises NotImplementedError: If the property is not implemented in the subclass """ raise NotImplementedError( - "subclasses of Serializer must provide a handle_field() method" + "subclasses of Serializer must provide a handle_field() method", ) def handle_fk_field(self, obj: PageTranslation, field: ForeignKey) -> None: @@ -158,7 +162,7 @@ def getvalue(self) -> str | None: """ if callable(getattr(self.stream, "getvalue", None)): # Pretty print output - return xml.dom.minidom.parseString(self.stream.getvalue()).toprettyxml() + return defusedxml.minidom.parseString(self.stream.getvalue()).toprettyxml() return None @@ -196,7 +200,7 @@ def get_object(self, node: Element) -> PageTranslation: :raises NotImplementedError: If the property is not implemented in the subclass """ raise NotImplementedError( - "subclasses of Deserializer must provide a _get_object() method" + "subclasses of Deserializer must provide a _get_object() method", ) def handle_object(self, node: Element) -> DeserializedObject: @@ -225,7 +229,7 @@ def handle_object(self, node: Element) -> DeserializedObject: page_translation.currently_in_translation = False if settings.REDIS_CACHE: page_translation.all_versions.invalidated_update( - currently_in_translation=False + currently_in_translation=False, ) else: page_translation.all_versions.update(currently_in_translation=False) @@ -248,8 +252,7 @@ def handle_object(self, node: Element) -> DeserializedObject: field = page_translation._meta.get_field(field_name) except FieldDoesNotExist: # If the legacy field doesn't exist as well, just raise the initial exception - # pylint: disable=raise-missing-from - raise e + raise e from FieldDoesNotExist # Now get the actual target value of the field if target := field_node.getElementsByTagName("target"): @@ -261,7 +264,7 @@ def handle_object(self, node: Element) -> DeserializedObject: ) else: raise DeserializationError( - f"Field {field_name} does not contain a node." + f"Field {field_name} does not contain a node.", ) logger.debug("Deserialized page translation: %r", page_translation) # Return a DeserializedObject @@ -332,5 +335,5 @@ def require_attribute(node: Element, attribute: str) -> str: if value := node.getAttribute(attribute): return value raise DeserializationError( - f"<{node.nodeName}> node is missing the {attribute} attribute" + f"<{node.nodeName}> node is missing the {attribute} attribute", ) diff --git a/integreat_cms/xliff/generic_serializer.py b/integreat_cms/xliff/generic_serializer.py index 54d9460dec..de47f24066 100644 --- a/integreat_cms/xliff/generic_serializer.py +++ b/integreat_cms/xliff/generic_serializer.py @@ -34,20 +34,22 @@ def __init__(self, *args: str, **kwargs: Any) -> None: if event == "START_ELEMENT" and node.nodeName == "xliff": if not (version := node.getAttribute("version")): raise DeserializationError( - "The -block does not contain a version attribute." + "The -block does not contain a version attribute.", ) if version == "1.2": self.xliff_deserializer = xliff1_serializer.Deserializer( - *args, **kwargs + *args, + **kwargs, ) return if version == "2.0": self.xliff_deserializer = xliff2_serializer.Deserializer( - *args, **kwargs + *args, + **kwargs, ) return raise DeserializationError( - f"This serializer cannot process XLIFF version {version}." + f"This serializer cannot process XLIFF version {version}.", ) raise DeserializationError("The data does not contain an -block.") diff --git a/integreat_cms/xliff/utils.py b/integreat_cms/xliff/utils.py index 1928d9de22..c472bea977 100644 --- a/integreat_cms/xliff/utils.py +++ b/integreat_cms/xliff/utils.py @@ -4,6 +4,7 @@ from __future__ import annotations +import contextlib import datetime import difflib import glob @@ -44,7 +45,8 @@ upload_storage = FileSystemStorage(location=settings.XLIFF_UPLOAD_DIR) download_storage = FileSystemStorage( - location=settings.XLIFF_DOWNLOAD_DIR, base_url=settings.XLIFF_URL + location=settings.XLIFF_DOWNLOAD_DIR, + base_url=settings.XLIFF_URL, ) logger = logging.getLogger(__name__) @@ -80,7 +82,10 @@ def pages_to_xliff_file( for page in pages: try: xliff_paths[f"{target_language}/{page}"] = page_to_xliff( - page, target_language, dir_name, only_public=only_public + page, + target_language, + dir_name, + only_public=only_public, ) except RuntimeError as e: messages.error(request, e) @@ -93,7 +98,7 @@ def pages_to_xliff_file( # If only one xliff file was created, return it directly instead of creating zip file page, xliff_file_path = xliff_paths.popitem() xliff_file_url = download_storage.url( - os.path.relpath(xliff_file_path, settings.XLIFF_DOWNLOAD_DIR) + os.path.relpath(xliff_file_path, settings.XLIFF_DOWNLOAD_DIR), ) logger.info( "XLIFF export: %r converted to XLIFF file %r by %r", @@ -113,7 +118,8 @@ def pages_to_xliff_file( actual_filename = download_storage.save(f"{dir_name}/{zip_name}", ContentFile("")) # Create ZIP archive create_zip_archive( - list(xliff_paths.values()), download_storage.path(actual_filename) + list(xliff_paths.values()), + download_storage.path(actual_filename), ) xliff_file_url = download_storage.url(actual_filename) logger.info( @@ -126,7 +132,10 @@ def pages_to_xliff_file( def page_to_xliff( - page: Page, target_language: Language, dir_name: UUID, only_public: bool = False + page: Page, + target_language: Language, + dir_name: UUID, + only_public: bool = False, ) -> str: """ Export a page to an XLIFF file for a specified target language @@ -168,13 +177,13 @@ def page_to_xliff( raise RuntimeWarning( _( "Page {} does not have a {}source translation in {} and " - "therefore cannot be exported as XLIFF for translation to {}." + "therefore cannot be exported as XLIFF for translation to {}.", ).format( target_page_translation.readable_title, _("published") + " " if only_public else "", source_language, target_language, - ) + ), ) try: @@ -186,17 +195,16 @@ def page_to_xliff( except Exception as e: # All these error should already have been prevented logger.exception( - "An unexpected error has occurred while exporting %r to XLIFF: %s", + "An unexpected error has occurred while exporting %r to XLIFF", target_page_translation, - e, ) raise RuntimeError( __( _("An unexpected error has occurred while exporting page {}.").format( - target_page_translation.readable_title + target_page_translation.readable_title, ), _("Please try again later or contact an administrator."), - ) + ), ) from e filename = ( @@ -205,14 +213,15 @@ def page_to_xliff( ) actual_filename = download_storage.save( - f"{dir_name}/{filename}", ContentFile(xliff_content) + f"{dir_name}/{filename}", + ContentFile(xliff_content), ) logger.debug("Created XLIFF file %r", actual_filename) # Set "currently in translation" status for existing target translations if settings.REDIS_CACHE: target_page_translation.all_versions.invalidated_update( - currently_in_translation=True + currently_in_translation=True, ) else: target_page_translation.all_versions.update(currently_in_translation=True) @@ -221,7 +230,8 @@ def page_to_xliff( def xliffs_to_pages( - request: HttpRequest, xliff_dir: str + request: HttpRequest, + xliff_dir: str, ) -> dict[str, list[DeserializedObject]]: """ Export a page to an XLIFF file for a specified target language @@ -241,7 +251,7 @@ def xliffs_to_pages( xliff_file_paths = sorted(glob.glob(f"{xliff_dir}/**/*.xliff", recursive=True)) page_translations = {} for xliff_file_path in xliff_file_paths: - with open(xliff_file_path, "r", encoding="utf-8") as xliff_file: + with open(xliff_file_path, encoding="utf-8") as xliff_file: logger.debug( "Deserializing XLIFF file %r", xliff_file_path, @@ -250,10 +260,10 @@ def xliffs_to_pages( try: # Try to deserialize the file page_translations[xliff_file_path_rel] = list( - serializers.deserialize("xliff", xliff_file.read()) + serializers.deserialize("xliff", xliff_file.read()), ) except Page.DoesNotExist: - logger.error( + logger.exception( "The page of XLIFF file %r does not exist.", xliff_file_path, ) @@ -261,25 +271,24 @@ def xliffs_to_pages( request, __( _( - 'The page referenced in XLIFF file "{}" could not be found.' + 'The page referenced in XLIFF file "{}" could not be found.', ).format(xliff_file_path_rel), _("Please contact the administrator."), ), ) # In this case, we want to catch all exceptions because the import of the other files should work even if # some xliff files are broken or other unexpected errors occur - except Exception as e: # pylint: disable=broad-except + except Exception: # All these error should already have been prevented, so probably the XLIFF file is broken. logger.exception( - "An unexpected error has occurred while importing XLIFF file %r: %s", + "An unexpected error has occurred while importing XLIFF file %r", xliff_file_path, - e, ) messages.error( request, __( _( - 'An unexpected error has occurred while importing XLIFF file "{}".' + 'An unexpected error has occurred while importing XLIFF file "{}".', ).format(xliff_file_path_rel), _("Please try again later or contact an administrator."), ), @@ -303,10 +312,8 @@ def get_xliff_import_diff(request: HttpRequest, xliff_dir: str) -> list[dict[str page_translation = deserialized.object # The prefetched translations now also contain the new deserialized object with id None, so we have to delete # the cached property and query it from the database again. - try: + with contextlib.suppress(AttributeError): del page_translation.page.prefetched_translations_by_language_slug - except AttributeError: - pass existing_translation = page_translation.latest_version or PageTranslation( page=page_translation.page, language=page_translation.language, @@ -318,13 +325,13 @@ def get_xliff_import_diff(request: HttpRequest, xliff_dir: str) -> list[dict[str format_html( __( _( - "Page {} from file {} was also translated in file {}." + "Page {} from file {} was also translated in file {}.", ), _( - "Please check which of the files contains the most recent version and upload only this file." + "Please check which of the files contains the most recent version and upload only this file.", ), _( - "If you confirm this import, only the first file will be imported and the latter will be ignored." + "If you confirm this import, only the first file will be imported and the latter will be ignored.", ), ), existing_translation.readable_title, @@ -338,11 +345,11 @@ def get_xliff_import_diff(request: HttpRequest, xliff_dir: str) -> list[dict[str "level_tag": "warning", "message": format_html( _( - "This page was also translated in file {}, which will be ignored." + "This page was also translated in file {}, which will be ignored.", ), xliff_file, ), - } + }, ) # Skip this duplicated translation continue @@ -357,8 +364,8 @@ def get_xliff_import_diff(request: HttpRequest, xliff_dir: str) -> list[dict[str [existing_translation.title], [page_translation.title], lineterm="", - ) - )[2:] + ), + )[2:], ), "content": "\n".join( list( @@ -366,14 +373,16 @@ def get_xliff_import_diff(request: HttpRequest, xliff_dir: str) -> list[dict[str existing_translation.content.splitlines(), page_translation.content.splitlines(), lineterm="", - ) - )[2:] + ), + )[2:], ), }, "right_to_left": page_translation.language.text_direction == text_directions.RIGHT_TO_LEFT, "errors": get_xliff_import_errors_and_clean_translation( - request, page_translation, add_message_if_unchanged=True + request, + page_translation, + add_message_if_unchanged=True, )[0], } # Throw away the translation keys, only take the value dictionaries @@ -381,7 +390,9 @@ def get_xliff_import_diff(request: HttpRequest, xliff_dir: str) -> list[dict[str def xliff_import_confirm( - request: HttpRequest, xliff_dir: str, machine_translated: bool + request: HttpRequest, + xliff_dir: str, + machine_translated: bool, ) -> bool: """ Confirm the XLIFF import and write changes to database @@ -400,14 +411,16 @@ def xliff_import_confirm( with update_lock: # Iterate over all xliff files for xliff_file, deserialized_objects in xliffs_to_pages( - request, xliff_dir + request, + xliff_dir, ).items(): # Iterate over all objects of one xliff file # (typically, one xliff file contains exactly one page translation) for deserialized in deserialized_objects: page_translation = deserialized.object errors, has_changed = get_xliff_import_errors_and_clean_translation( - request, page_translation + request, + page_translation, ) if errors: logger.warning( @@ -422,7 +435,7 @@ def xliff_import_confirm( format_html( "{}
    {}
", _( - "Page {} could not be imported successfully because of the errors:" + "Page {} could not be imported successfully because of the errors:", ).format(page_translation.readable_title), format_html_join( "", @@ -445,26 +458,25 @@ def xliff_import_confirm( with transaction.atomic(): page_translation.save() except IntegrityError as e: - logger.error( - "%s when importing new version for %r from %r by %r: %s", + logger.exception( + "%s when importing new version for %r from %r by %r", type(e).__name__, existing_translation, xliff_file, request.user, - e, ) messages.error( request, format_html( __( _( - "Page {} from the file {} could not be imported." + "Page {} from the file {} could not be imported.", ), _( - "Check if you have uploaded any other conflicting files for this page." + "Check if you have uploaded any other conflicting files for this page.", ), _( - "If the problem persists, contact an administrator." + "If the problem persists, contact an administrator.", ), ), page_translation.readable_title, @@ -488,7 +500,7 @@ def xliff_import_confirm( request.user, ) imports_without_changes.append( - page_translation.readable_title + page_translation.readable_title, ) if successful_imports: @@ -538,31 +550,27 @@ def get_xliff_import_errors_and_clean_translation( { "level_tag": "error", "message": _( - "You don't have the permission to import this page." + "You don't have the permission to import this page.", ).format(page_translation.readable_title), - } + }, ) # Check whether the page translation belongs to the current region - if not page_translation.page.region == region: + if page_translation.page.region != region: error_messages.append( { "level_tag": "error", "message": _("This page belongs to another region ({}).").format( page_translation.page.region.full_name, ), - } + }, ) # Retrieve existing page translation in the target language # The prefetched translations now also contain the new deserialized object with id None, so we have to delete # the cached property and query it from the database again. - try: + with contextlib.suppress(AttributeError): del page_translation.latest_version - except AttributeError: - pass - try: + with contextlib.suppress(AttributeError): del page_translation.page.prefetched_translations_by_language_slug - except AttributeError: - pass existing_translation = page_translation.latest_version # Validate page translation page_translation_form = PageTranslationForm( @@ -579,7 +587,7 @@ def get_xliff_import_errors_and_clean_translation( [ {"level_tag": message["type"], "message": message["text"]} for message in page_translation_form.get_error_messages() - ] + ], ) else: # Use the unique slug generated by the form's clean-method for imported page translations @@ -591,7 +599,7 @@ def get_xliff_import_errors_and_clean_translation( { "level_tag": "info", "message": _("No changes detected."), - } + }, ) return error_messages, page_translation_form.has_changed() diff --git a/integreat_cms/xliff/xliff1_serializer.py b/integreat_cms/xliff/xliff1_serializer.py index 1ee7b4dc58..f54324fa4c 100644 --- a/integreat_cms/xliff/xliff1_serializer.py +++ b/integreat_cms/xliff/xliff1_serializer.py @@ -53,7 +53,7 @@ def start_object(self, obj: PageTranslation) -> None: source_language := obj.page.region.get_source_language(obj.language.slug) ): raise base.SerializationError( - "The page translation is in the region's default language." + "The page translation is in the region's default language.", ) self.xml.startElement( "file", @@ -77,7 +77,8 @@ def start_object(self, obj: PageTranslation) -> None: }, ) self.xml.addQuickElement( - "phase", attrs={"phase-name": "post_type", "process-name": "Post type"} + "phase", + attrs={"phase-name": "post_type", "process-name": "Post type"}, ) self.xml.endElement("phase-group") self.xml.endElement("header") @@ -94,7 +95,7 @@ def handle_field(self, obj: PageTranslation, field: CharField | TextField) -> No assert self.xml # Use legacy field name if available REVERSE_XLIFF_LEGACY_FIELDS: dict[str, str] = dict( - map(reversed, settings.XLIFF_LEGACY_FIELDS.items()) # type: ignore[arg-type] + map(reversed, settings.XLIFF_LEGACY_FIELDS.items()), # type: ignore[arg-type] ) field_name = REVERSE_XLIFF_LEGACY_FIELDS.get(field.name, field.name) attrs = { @@ -120,7 +121,7 @@ def handle_field(self, obj: PageTranslation, field: CharField | TextField) -> No self.xml.endElement("trans-unit") - def end_object(self, obj: PageTranslation) -> None: + def end_object(self, _obj: PageTranslation) -> None: """ Called after handling all fields for an object. Ends the ````-block. @@ -158,7 +159,7 @@ def get_object(self, node: Element) -> PageTranslation: # If the id isn't a number or if no page with this id is found, check if the external file reference is given if not (external_file := node.getElementsByTagName("external-file")): # If no such reference is given, just raise the initial error - raise e + raise # Get href of external file and parse url page_link = ( urlparse(self.require_attribute(external_file[0], "href")) @@ -172,7 +173,7 @@ def get_object(self, node: Element) -> PageTranslation: # Expect the link to be in the format ///[]// if len(page_link) < 3: raise base.DeserializationError( - "The page link of the reference needs at least 3 segments" + "The page link of the reference needs at least 3 segments", ) from e page_translation_slug = page_link.pop() region_slug, language_slug = page_link[:2] @@ -183,7 +184,7 @@ def get_object(self, node: Element) -> PageTranslation: ).first() if not page: # If no page matches the link, just raise the initial error - raise e + raise logger.debug( "Referenced original page: %r", @@ -192,7 +193,7 @@ def get_object(self, node: Element) -> PageTranslation: # Get target language of this file target_language = self.get_language( - self.require_attribute(node, "target-language") + self.require_attribute(node, "target-language"), ) # Get existing target translation or create a new one @@ -205,7 +206,7 @@ def get_object(self, node: Element) -> PageTranslation: } # Get source translation to inherit status field source_language = self.get_language( - self.require_attribute(node, "source-language") + self.require_attribute(node, "source-language"), ) if source_translation := page.get_translation(source_language.slug): attrs["status"] = source_translation.status diff --git a/integreat_cms/xliff/xliff2_serializer.py b/integreat_cms/xliff/xliff2_serializer.py index 9f974f6e56..46bed15302 100644 --- a/integreat_cms/xliff/xliff2_serializer.py +++ b/integreat_cms/xliff/xliff2_serializer.py @@ -30,7 +30,10 @@ class Serializer(base_serializer.Serializer): target_language = None def serialize( - self, queryset: list[PageTranslation], *args: Any, **kwargs: Any + self, + queryset: list[PageTranslation], + *args: Any, + **kwargs: Any, ) -> str: r""" Initialize serialization and find out in which source and target language the given elements are. @@ -52,7 +55,7 @@ def serialize( if len(language_set) != 1: raise base.SerializationError( "The page translations have different languages, but in XLIFF 2.0 " - "all objects of one file need to have the same language." + "all objects of one file need to have the same language.", ) # Get all region objects of the given page translations @@ -61,13 +64,13 @@ def serialize( # Check if all given translations are of the same region if len(region_set) != 1: raise base.SerializationError( - "The page translations are from different regions." + "The page translations are from different regions.", ) region = next(iter(region_set)) if (target_language := next(iter(language_set))) == region.default_language: raise base.SerializationError( - "The page translation is in the region's default language." + "The page translation is in the region's default language.", ) self.target_language = target_language self.source_language = region.get_source_language(target_language.slug) @@ -130,7 +133,9 @@ def handle_field(self, obj: PageTranslation, field: CharField | TextField) -> No if TYPE_CHECKING: assert self.xml logger.debug( - "XLIFF 2.0 serialization handling field %r of object %r", field, obj + "XLIFF 2.0 serialization handling field %r of object %r", + field, + obj, ) attrs = { "id": field.name, @@ -150,7 +155,7 @@ def handle_field(self, obj: PageTranslation, field: CharField | TextField) -> No if not source_translation: raise base.SerializationError( f"Page translation {obj!r} does not have a source translation in " - f"{self.source_language!r} and therefore cannot be serialized to XLIFF." + f"{self.source_language!r} and therefore cannot be serialized to XLIFF.", ) logger.debug("XLIFF 2.0 source translation %r", source_translation) self.xml.cdata(field.value_to_string(source_translation)) @@ -201,11 +206,11 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: if event == "START_ELEMENT" and node.nodeName == "xliff": # Get source language stored in the xliff node self.source_language = self.get_language( - self.require_attribute(node, "srcLang") + self.require_attribute(node, "srcLang"), ) # Get target language stored in the xliff node self.target_language = self.get_language( - self.require_attribute(node, "trgLang") + self.require_attribute(node, "trgLang"), ) logger.debug( "Starting XLIFF 2.0 deserialization for translation from %r to %r", @@ -214,7 +219,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: ) return raise base.DeserializationError( - "The XLIFF file does not contain an -block," + "The XLIFF file does not contain an -block,", ) def get_object(self, node: Element) -> PageTranslation: diff --git a/pyproject.toml b/pyproject.toml index 55e5061675..4555c3dc03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,19 +83,14 @@ dependencies = [ [project.optional-dependencies] dev = [ - "black", "build", "bumpver", "debugpy", "djlint", "freezegun", - "isort", "mypy", "pre-commit", "pyjwt", - "pylint", - "pylint-django", - "pylint-per-file-ignores", "pytest-circleci-parallelized", "pytest-cov", "pytest-django", @@ -245,7 +240,6 @@ dev-pinned = [ "astroid==3.3.4", "babel==2.16.0", "backports.tarfile==1.2.0", - "black==24.8.0", "build==1.2.2", "bumpver==2023.1129", "cfgv==3.4.0", @@ -268,7 +262,6 @@ dev-pinned = [ "imagesize==1.4.1", "importlib_metadata==8.5.0", "iniconfig==2.0.0", - "isort==5.13.2", "jaraco.classes==3.4.0", "jaraco.context==6.0.1", "jaraco.functools==4.1.0", @@ -296,10 +289,6 @@ dev-pinned = [ "pprintpp==0.4.0", "pre-commit==3.8.0", "PyJWT==2.9.0", - "pylint==3.3.1", - "pylint-django==2.5.5", - "pylint-per-file-ignores==1.3.2", - "pylint-plugin-utils==0.8.2", "pyproject_hooks==1.2.0", "pytest==7.4.4", "pytest-circleci-parallelized==0.1.0", @@ -316,7 +305,7 @@ dev-pinned = [ "requests-toolbelt==1.0.0", "rfc3986==2.0.0", "rich==13.8.1", - "ruff==0.6.8", + "ruff==0.9.4", "SecretStorage==3.3.3", "shellcheck-py==0.10.0.1", "snowballstemmer==2.2.0", @@ -394,21 +383,6 @@ line_break_after_multiline_tag=true # exclude invalid html files until this is resolved https://github.com/djlint/djLint/issues/703 exclude = "integreat_cms/api/v3/templates/" -[tool.isort] -profile = "black" -order_by_type = false - -[tool.pylint.main] -jobs = 0 -load-plugins = [ - "pylint_django", - "pylint_per_file_ignores", - "pylint.extensions.code_style", - "pylint.extensions.comparison_placement", - "pylint.extensions.consider_ternary_expression", - "pylint.extensions.docparams", - "pylint.extensions.for_any_all", -] recursive = true ignore-paths = [ ".venv", @@ -419,41 +393,6 @@ fail-on = "I" extension-pkg-whitelist = ["lxml"] django-settings-module="integreat_cms.core.settings" -[tool.pylint.design] -max-args = 7 -max-attributes = 7 -max-branches = 12 -max-locals = 15 -max-parents = 8 -max-position = 6 - -[tool.pylint.messages_control] -disable = [ - "consider-using-namedtuple-or-dataclass", - "consider-using-tuple", - "duplicate-code", - "invalid-name", - "line-too-long", - "missing-module-docstring", - "no-member", - "too-many-lines", - "ungrouped-imports", - "unsupported-binary-operation", - "wrong-import-position", -] -enable = [ - "consider-using-augmented-assign", - "use-implicit-booleaness-not-comparison-to-string", - "use-implicit-booleaness-not-comparison-to-zero", - "useless-suppression", -] - -[tool.pylint.reports] -output-format = "colorized" - -[tool.pylint-per-file-ignores] -"/tests/"="unused-argument,missing-function-docstring" - [tool.mypy] disallow_untyped_defs = true ignore_missing_imports = true @@ -461,75 +400,102 @@ warn_redundant_casts = true strict_equality = true [tool.ruff] -line-length = 279 +line-length = 88 target-version = "py311" +# [tool.ruff.lint.pydocstyle] +# waiting for: https://github.com/astral-sh/ruff/issues/6606 +# convention = "sphinx" + +[tool.ruff.lint.pycodestyle] +max-doc-length = 279 + [tool.ruff.lint] select = [ - "I", # isort "A", # flake8-builtins "AIR", # Airflow + "ARG", # flake8-unused-arguments - TODO "ASYNC", # flake8-async + "ASYNC1", # flake8-trio + "B", # flake8-bugbear "BLE", # flake8-blind-except - "C4", # flake8-comprehensions + "C4", # flake8-comprehensions "C90", # McCabe cyclomatic complexity + "COM", # flake8-commas + # "D", # pydocstyle - makes no sense until sphinx docstyle available "DJ", # flake8-django "E", # pycodestyle + "ERA", # eradicate "EXE", # flake8-executable "F", # Pyflakes "FA", # flake8-future-annotations "FIX", # flake8-fixme "FLY", # flynt + "FURB", # refurb "G", # flake8-logging-format + "I", # isort "ICN", # flake8-import-conventions "INP", # flake8-no-pep420 "INT", # flake8-gettext "ISC", # flake8-implicit-str-concat "LOG", # flake8-logging + "N", # pep8-naming "NPY", # NumPy-specific rules "PD", # pandas-vet "PERF", # Perflint "PGH", # pygrep-hooks "PIE", # flake8-pie "PL", # Pylint + "PLC", # Pylint conventions + "PLE", # Pylint errors + "PLR", # Pylint refactor + "PLW", # Pylint warnings "PYI", # flake8-pyi + "Q", # flake8-quotes + "RET", # flake8-return "RSE", # flake8-raise + "RUF", # Ruff-specific rules + "S", # flake8-bandit + "SIM", # flake8-simplify "SLOT", # flake8-slots "T10", # flake8-debugger - "TRIO", # flake8-trio + "T20", # flake8-print + "TD", # flake8-todos + "TCH", # flake8-type-checking + "TRY", # tryceratops + "UP", # pyupgrade "W", # pycodestyle "YTT", # flake8-2020 # "ANN", # flake8-annotations - # "ARG", # flake8-unused-arguments - # "B", # flake8-bugbear - # "COM", # flake8-commas # "CPY", # flake8-copyright - # "D", # pydocstyle # "DTZ", # flake8-datetimez # "EM", # flake8-errmsg - # "ERA", # eradicate # "FBT", # flake8-boolean-trap - # "FURB", # refurb - # "N", # pep8-naming # "PT", # flake8-pytest-style # "PTH", # flake8-use-pathlib - # "Q", # flake8-quotes - # "RET", # flake8-return - # "RUF", # Ruff-specific rules - # "S", # flake8-bandit - # "SIM", # flake8-simplify # "SLF", # flake8-self - # "T20", # flake8-print - # "TCH", # flake8-type-checking - # "TD", # flake8-todos # "TID", # flake8-tidy-imports - # "TRY", # tryceratops - # "UP", # pyupgrade ] ignore = [ - "F401", "INP001", + "N806", + "N818", + "N999", + "RUF001", + "RUF002", + "RUF003", + "RUF012", + "E501", # line too long - only applicable to strings and comments, everything else is auto-formatted by ruff + "TRY003", + "S308", # mark_safe may expose cross-site scripting vulnerabilities + "COM812", # missing trailing comma - recommended to disable when using ruff formatter + "ISC001", # single line implicit line concatenation - recommended to disable when using ruff formatter ] +extend-select = [ + "D419" +] +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))|request|region_slug|language_slug|queryset|commit$" + [tool.ruff.lint.mccabe] max-complexity = 23 # default is 10 @@ -537,6 +503,9 @@ max-complexity = 23 # default is 10 [tool.ruff.lint.isort] order-by-type = false +[tool.ruff.lint.flake8-unused-arguments] +ignore-variadic-names = true + [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401", "F403"] "docs/src/conf.py" = ["A001"] @@ -559,7 +528,9 @@ order-by-type = false "integreat_cms/google_translate_api/apps.py" = ["BLE001"] "integreat_cms/google_translate_api/google_translate_api_client.py" = ["BLE001"] "integreat_cms/nominatim_api/nominatim_api_client.py" = ["PERF401"] -"tests/*" = ["S101"] +"tests/*" = ["ARG001", "ARG002", "ARG005", "S101", "S105", "T201"] +"integreat_cms/cms/migrations/*" = ["T201"] +".circleci/*" = ["T201"] "tests/core/management/commands/test_summ_ai_bulk.py" = ["FIX002"] "tests/deepl_api/deepl_api_test.py" = ["PIE804"] "tests/pdf/test_pdf_export.py" = ["PLW2901"] diff --git a/setup.py b/setup.py index 2f0a9b1e63..73fd0a742a 100755 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ Minimal setup.py for legacy builds. See pyproject.toml for all setup configuration options. """ + from __future__ import annotations from setuptools import setup diff --git a/tests/api/test_api_chat.py b/tests/api/test_api_chat.py index 8d87202173..f5bfd85114 100644 --- a/tests/api/test_api_chat.py +++ b/tests/api/test_api_chat.py @@ -65,7 +65,8 @@ def test_api_chat_incorrect_auth_error(load_test_data: None) -> None: """ mock_api = MagicMock() with patch( - "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", return_value=mock_api + "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", + return_value=mock_api, ): mock_api.user.me.side_effect = HTTPError() @@ -78,7 +79,7 @@ def test_api_chat_incorrect_auth_error(load_test_data: None) -> None: assert response.status_code == 500 assert response.json() == { - "error": "An error occurred while attempting to connect to the chat server." + "error": "An error occurred while attempting to connect to the chat server.", } @@ -91,7 +92,8 @@ def test_api_chat_first_chat(load_test_data: None) -> None: """ mock_api = MagicMock() with patch( - "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", return_value=mock_api + "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", + return_value=mock_api, ): mock_api.user.all.return_value = [] mock_api.user.me.return_value = {"login": "bot-user"} @@ -117,7 +119,8 @@ def test_api_chat_force_new_chat(load_test_data: None) -> None: """ mock_api = MagicMock() with patch( - "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", return_value=mock_api + "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", + return_value=mock_api, ): mock_api.user.all.return_value = [] mock_api.user.me.return_value = {"login": "bot-user"} @@ -146,7 +149,8 @@ def test_api_chat_send_message(load_test_data: None) -> None: mock_api = MagicMock() previous_chat = UserChat.objects.current_chat(default_kwargs["device_id"]).zammad_id with patch( - "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", return_value=mock_api + "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", + return_value=mock_api, ): mock_api.user.all.return_value = [] mock_api.user.me.return_value = {"login": "bot-user"} @@ -175,7 +179,8 @@ def test_api_chat_get_messages_success(load_test_data: None) -> None: """ mock_api = MagicMock() with patch( - "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", return_value=mock_api + "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", + return_value=mock_api, ): mock_api.user.all.return_value = [] mock_api.user.me.return_value = {"login": "bot-user"} @@ -200,7 +205,8 @@ def test_api_chat_get_messages_failure(load_test_data: None) -> None: """ mock_api = MagicMock() with patch( - "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", return_value=mock_api + "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", + return_value=mock_api, ): mock_api.user.all.return_value = [] mock_api.user.me.return_value = {"login": "bot-user"} @@ -214,7 +220,7 @@ def test_api_chat_get_messages_failure(load_test_data: None) -> None: assert response.status_code == 404 assert response.json() == { - "error": "The requested chat does not exist. Did you delete it?" + "error": "The requested chat does not exist. Did you delete it?", } @@ -227,7 +233,8 @@ def test_api_chat_create_attachment_success(load_test_data: None) -> None: """ mock_api = MagicMock() with patch( - "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", return_value=mock_api + "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", + return_value=mock_api, ): mock_api.user.all.return_value = [] mock_api.user.me.return_value = {"login": "bot-user"} @@ -242,7 +249,7 @@ def test_api_chat_create_attachment_success(load_test_data: None) -> None: "filename": "sample.pdf", "size": "41412", "preferences": {"Content-Type": "application/pdf"}, - } + }, ], }, ] @@ -268,7 +275,8 @@ def test_api_chat_get_attachment_success(load_test_data: None) -> None: """ mock_api = MagicMock() with patch( - "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", return_value=mock_api + "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", + return_value=mock_api, ): mock_api.user.all.return_value = [] mock_api.user.me.return_value = {"login": "bot-user"} @@ -280,7 +288,8 @@ def test_api_chat_get_attachment_success(load_test_data: None) -> None: response = client.get(url) url = reverse( - "api:chat", kwargs=default_kwargs | {"attachment_id": "exampleRandomHash"} + "api:chat", + kwargs=default_kwargs | {"attachment_id": "exampleRandomHash"}, ) response = client.get(url) @@ -296,7 +305,8 @@ def test_api_chat_get_attachment_incorrect_chat_failure(load_test_data: None) -> """ mock_api = MagicMock() with patch( - "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", return_value=mock_api + "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", + return_value=mock_api, ): mock_api.user.all.return_value = [] mock_api.user.me.return_value = {"login": "bot-user"} @@ -327,7 +337,8 @@ def test_api_chat_get_attachment_missing_attachment_failure( """ mock_api = MagicMock() with patch( - "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", return_value=mock_api + "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", + return_value=mock_api, ): mock_api.user.all.return_value = [] mock_api.user.me.return_value = {"login": "bot-user"} @@ -352,7 +363,8 @@ def test_api_chat_ratelimiting(load_test_data: None) -> None: """ mock_api = MagicMock() with patch( - "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", return_value=mock_api + "integreat_cms.api.v3.chat.utils.zammad_api.ZammadAPI", + return_value=mock_api, ): mock_api.user.all.return_value = [] mock_api.user.me.return_value = {"login": "bot-user"} @@ -381,6 +393,7 @@ def test_api_chat_ratelimiting(load_test_data: None) -> None: # make sure ratelimiting cannot be circumvented by force-creating new chats response_force = client.post( - url, data={"message": "no, it's spam.", "force_new": True} + url, + data={"message": "no, it's spam.", "force_new": True}, ) assert response_force.status_code == 429 diff --git a/tests/api/test_api_feedback.py b/tests/api/test_api_feedback.py index 6e784ef6d9..a2b7f71414 100644 --- a/tests/api/test_api_feedback.py +++ b/tests/api/test_api_feedback.py @@ -46,7 +46,9 @@ @pytest.mark.django_db @pytest.mark.parametrize("view_name,post_data", API_FEEDBACK_VIEWS) def test_api_feedback_success( - load_test_data: None, view_name: str, post_data: dict[str, str] + load_test_data: None, + view_name: str, + post_data: dict[str, str], ) -> None: """ Check successful feedback submission for different feedback types @@ -89,7 +91,8 @@ def test_api_feedback_success( @pytest.mark.django_db @pytest.mark.parametrize( - "view_name,kwargs,post_data,response_data", API_FEEDBACK_ERRORS + "view_name,kwargs,post_data,response_data", + API_FEEDBACK_ERRORS, ) def test_api_feedback_errors( load_test_data: None, @@ -126,7 +129,8 @@ def test_api_feedback_invalid_method(load_test_data: None) -> None: """ client = Client() url = reverse( - "api:region_feedback", kwargs={"region_slug": "augsburg", "language_slug": "de"} + "api:region_feedback", + kwargs={"region_slug": "augsburg", "language_slug": "de"}, ) response = client.get(url) assert response.status_code == 405 diff --git a/tests/api/test_api_push_page.py b/tests/api/test_api_push_page.py index 7f36ee763c..dfb81e9607 100644 --- a/tests/api/test_api_push_page.py +++ b/tests/api/test_api_push_page.py @@ -22,7 +22,8 @@ def test_api_push_page_content(load_test_data: None) -> None: new_content = "

new content

" view_name = "api:push_page_translation_content" endpoint = reverse( - view_name, kwargs={"region_slug": "augsburg", "language_slug": "de"} + view_name, + kwargs={"region_slug": "augsburg", "language_slug": "de"}, ) # Check whether the endpoints resolve correctly match = resolve("/api/v3/augsburg/de/pushpage/") diff --git a/tests/api/test_api_result.py b/tests/api/test_api_result.py index 94308da026..e5c07b7f88 100644 --- a/tests/api/test_api_result.py +++ b/tests/api/test_api_result.py @@ -14,7 +14,8 @@ @pytest.mark.django_db @pytest.mark.parametrize( - "endpoint,wp_endpoint,expected_result,expected_code,expected_queries", API_ENDPOINTS + "endpoint,wp_endpoint,expected_result,expected_code,expected_queries", + API_ENDPOINTS, ) def test_api_result( load_test_data: None, @@ -25,7 +26,6 @@ def test_api_result( expected_code: int, expected_queries: int, ) -> None: - # pylint: disable=too-many-positional-arguments """ This test class checks all endpoints defined in :attr:`~tests.api.api_config.API_ENDPOINTS`. It verifies that the content delivered by the endpoint is equivalent with the data diff --git a/tests/api/test_api_social.py b/tests/api/test_api_social.py index 7ca0144cac..2125a0ebda 100644 --- a/tests/api/test_api_social.py +++ b/tests/api/test_api_social.py @@ -13,7 +13,8 @@ @pytest.mark.django_db @pytest.mark.parametrize( - "endpoint,expected_result,expected_code,expected_queries", API_SOCIAL_ENDPOINTS + "endpoint,expected_result,expected_code,expected_queries", + API_SOCIAL_ENDPOINTS, ) def test_api_result( load_test_data: None, diff --git a/tests/cms/models/contacts/test_contacts.py b/tests/cms/models/contacts/test_contacts.py index d4e26cf784..cfaa5db670 100644 --- a/tests/cms/models/contacts/test_contacts.py +++ b/tests/cms/models/contacts/test_contacts.py @@ -7,7 +7,6 @@ import pytest -from django.utils import translation from integreat_cms.cms.models import Contact diff --git a/tests/cms/models/events/test_recurrence_rule.py b/tests/cms/models/events/test_recurrence_rule.py index 18d682f0a4..f17390da25 100644 --- a/tests/cms/models/events/test_recurrence_rule.py +++ b/tests/cms/models/events/test_recurrence_rule.py @@ -57,7 +57,8 @@ def test_api_rrule_every_three_days(self) -> None: recurrence_end_date=None, ) self.check_rrule( - recurrence_rule, "DTSTART:20300101T113000\nRRULE:FREQ=DAILY;INTERVAL=3" + recurrence_rule, + "DTSTART:20300101T113000\nRRULE:FREQ=DAILY;INTERVAL=3", ) def test_api_rrule_weekly(self) -> None: @@ -70,7 +71,8 @@ def test_api_rrule_weekly(self) -> None: recurrence_end_date=None, ) self.check_rrule( - recurrence_rule, "DTSTART:20300101T113000\nRRULE:FREQ=WEEKLY;BYDAY=MO,TU" + recurrence_rule, + "DTSTART:20300101T113000\nRRULE:FREQ=WEEKLY;BYDAY=MO,TU", ) def test_api_rrule_monthly(self) -> None: @@ -83,7 +85,8 @@ def test_api_rrule_monthly(self) -> None: recurrence_end_date=None, ) self.check_rrule( - recurrence_rule, "DTSTART:20300101T113000\nRRULE:FREQ=MONTHLY;BYDAY=+1FR" + recurrence_rule, + "DTSTART:20300101T113000\nRRULE:FREQ=MONTHLY;BYDAY=+1FR", ) def test_api_rrule_last_week_in_month(self) -> None: @@ -96,7 +99,8 @@ def test_api_rrule_last_week_in_month(self) -> None: recurrence_end_date=None, ) self.check_rrule( - recurrence_rule, "DTSTART:20300101T113000\nRRULE:FREQ=MONTHLY;BYDAY=-1WE" + recurrence_rule, + "DTSTART:20300101T113000\nRRULE:FREQ=MONTHLY;BYDAY=-1WE", ) def test_api_rrule_bimonthly_until(self) -> None: diff --git a/tests/cms/models/media/test_media_file.py b/tests/cms/models/media/test_media_file.py index 063261556f..6fdfa3d067 100644 --- a/tests/cms/models/media/test_media_file.py +++ b/tests/cms/models/media/test_media_file.py @@ -43,7 +43,7 @@ def test_content_usage_count(self) -> None: start=timezone.now() - timedelta(days=2), end=timezone.now() - timedelta(days=1), region=region, - ) + ), ) assert file.past_event_usages.count() == 1 @@ -69,7 +69,7 @@ def test_only_used_in_past_events(self) -> None: start=timezone.now() - timedelta(days=2), end=timezone.now() - timedelta(days=1), region=region, - ) + ), ) assert file.is_only_used_in_past_events @@ -95,7 +95,7 @@ def test_file_is_not_deletable_if_in_use(self) -> None: start=timezone.now() - timedelta(days=2), end=timezone.now() - timedelta(days=1), region=region, - ) + ), ) # Upcoming event @@ -104,7 +104,7 @@ def test_file_is_not_deletable_if_in_use(self) -> None: start=timezone.now() + timedelta(days=2), end=timezone.now() + timedelta(days=1), region=region, - ) + ), ) assert not file.is_deletable @@ -130,7 +130,7 @@ def test_file_is_deletable_if_only_used_in_past_event(self) -> None: start=timezone.now() - timedelta(days=2), end=timezone.now() - timedelta(days=1), region=region, - ) + ), ) assert file.is_deletable @@ -156,7 +156,7 @@ def test_file_is_not_deletable_if_used_as_icon_and_by_past_event(self) -> None: start=timezone.now() - timedelta(days=2), end=timezone.now() - timedelta(days=1), region=region, - ) + ), ) # Organization Icon @@ -278,7 +278,7 @@ def test_is_deletable_if_used_in_translation_of_past_event(self) -> None: find_all_links() - file.is_deletable + assert file.is_deletable @pytest.mark.django_db def test_is_deletable_when_used_in_translation_of_event_which_rrule_ended( diff --git a/tests/cms/models/push_notifications/test_push_notification.py b/tests/cms/models/push_notifications/test_push_notification.py index f5b41cf895..0a868ff6a4 100644 --- a/tests/cms/models/push_notifications/test_push_notification.py +++ b/tests/cms/models/push_notifications/test_push_notification.py @@ -2,7 +2,6 @@ from django.utils import translation from integreat_cms.cms.models import ( - Language, PushNotification, PushNotificationTranslation, ) @@ -25,7 +24,8 @@ def test_best_translation(load_test_data: None) -> None: assert pn.best_translation.title == "Test" PushNotificationTranslation.objects.filter( - push_notification=pn, language__slug="en" + push_notification=pn, + language__slug="en", ).update(title="English test") with translation.override("en"): assert pn.best_translation.title == "English test" diff --git a/tests/cms/test_duplicate_regions.py b/tests/cms/test_duplicate_regions.py index 77133266ee..2f83bba8bd 100644 --- a/tests/cms/test_duplicate_regions.py +++ b/tests/cms/test_duplicate_regions.py @@ -1,8 +1,9 @@ from __future__ import annotations +from typing import TYPE_CHECKING + import pytest from django.forms.models import model_to_dict -from django.test.client import Client from django.urls import reverse from django.utils import timezone from linkcheck.models import Link @@ -11,13 +12,16 @@ from integreat_cms.cms.models import LanguageTreeNode, Page, Region from integreat_cms.cms.utils.linkcheck_utils import get_url_count +if TYPE_CHECKING: + from django.test.client import Client + @pytest.mark.order("last") @pytest.mark.django_db(transaction=True, serialized_rollback=True) def test_duplicate_regions( - load_test_data_transactional: None, admin_client: Client + load_test_data_transactional: None, + admin_client: Client, ) -> None: - # pylint: disable=too-many-locals """ Test whether duplicating regions works as expected @@ -27,17 +31,19 @@ def test_duplicate_regions( source_region = Region.objects.get(id=1) target_region_slug = "cloned" assert not Region.objects.filter( - slug=target_region_slug + slug=target_region_slug, ).exists(), "The target region should not exist before cloning" # Assert correct preconditions for link replacement test test_url = "https://integreat.app/augsburg/de/willkommen/" assert Link.objects.filter( - url__url=test_url, page_translation__page__region=source_region + url__url=test_url, + page_translation__page__region=source_region, ).exists(), "The internal test URL should exist in the source region" replaced_url = test_url.replace(source_region.slug, target_region_slug) assert not Link.objects.filter( - url__url=replaced_url, page_translation__page__region=source_region + url__url=replaced_url, + page_translation__page__region=source_region, ).exists(), "The replaced internal URL not should exist in the source region" before_cloning = timezone.now() @@ -68,7 +74,7 @@ def test_duplicate_regions( source_pages = source_region.non_archived_pages target_pages = target_region.pages.all() assert len(source_pages) == len(target_pages) - for source_page, target_page in zip(source_pages, target_pages): + for source_page, target_page in zip(source_pages, target_pages, strict=False): source_page_dict = model_to_dict( source_page, exclude=[ @@ -99,11 +105,13 @@ def test_duplicate_regions( source_page_translations = source_page.translations.all() # Limit target page translations to all that existed before the links might have been replaced target_page_translations = target_page.translations.filter( - last_updated__lt=before_cloning + last_updated__lt=before_cloning, ) assert len(source_page_translations) == len(target_page_translations) for source_page_translation, target_page_translation in zip( - source_page_translations, target_page_translations + source_page_translations, + target_page_translations, + strict=False, ): source_page_translation_dict = model_to_dict( source_page_translation, @@ -141,29 +149,39 @@ def test_duplicate_regions( # Since link replacement in the region cloning process is now deactivated, they should appear in the new region too test_url = "https://integreat.app/augsburg/de/willkommen/" assert Link.objects.filter( - url__url=test_url, page_translation__page__region=source_region + url__url=test_url, + page_translation__page__region=source_region, ).exists(), "The internal test URL should exist in the source region" assert not Link.objects.filter( - url__url=test_url, page_translation__page__region=target_region + url__url=test_url, + page_translation__page__region=target_region, ).exists(), "The internal test URL should not exist in the target region" replaced_url = test_url.replace(source_region.slug, target_region.slug) assert not Link.objects.filter( - url__url=replaced_url, page_translation__page__region=source_region + url__url=replaced_url, + page_translation__page__region=source_region, ).exists(), "The replaced internal URL not should exist in the source region" assert Link.objects.filter( - url__url=replaced_url, page_translation__page__region=target_region + url__url=replaced_url, + page_translation__page__region=target_region, ).exists(), "The replaced internal URL should exist in the target region" # Check if all cloned language tree nodes exist and are identical source_language_tree = source_region.language_tree_nodes.all() target_language_tree = target_region.language_tree_nodes.all() assert len(source_language_tree) == len(target_language_tree) - for source_node, target_node in zip(source_language_tree, target_language_tree): + for source_node, target_node in zip( + source_language_tree, + target_language_tree, + strict=False, + ): source_node_dict = model_to_dict( - source_node, exclude=["id", "tree_id", "region", "parent"] + source_node, + exclude=["id", "tree_id", "region", "parent"], ) target_node_dict = model_to_dict( - target_node, exclude=["id", "tree_id", "region", "parent"] + target_node, + exclude=["id", "tree_id", "region", "parent"], ) assert source_node_dict == target_node_dict @@ -180,11 +198,11 @@ def test_duplicate_regions( assert len(tree_ids) == len(set(tree_ids)) -# pylint: disable=too-many-locals @pytest.mark.order("last") @pytest.mark.django_db(transaction=True, serialized_rollback=True) def test_duplicate_regions_no_translations( - load_test_data_transactional: None, admin_client: Client + load_test_data_transactional: None, + admin_client: Client, ) -> None: """ Test whether duplicating regions works as expected when disabling the duplication of translations @@ -195,7 +213,7 @@ def test_duplicate_regions_no_translations( source_region = Region.objects.get(id=1) target_region_slug = "cloned" assert not Region.objects.filter( - slug=target_region_slug + slug=target_region_slug, ).exists(), "The target region should not exist before cloning" before_cloning = timezone.now() @@ -231,7 +249,7 @@ def test_duplicate_regions_no_translations( source_pages = source_region.non_archived_pages target_pages = target_region.pages.all() assert len(source_pages) == len(target_pages) - for source_page, target_page in zip(source_pages, target_pages): + for source_page, target_page in zip(source_pages, target_pages, strict=False): source_page_dict = model_to_dict( source_page, exclude=[ @@ -259,10 +277,10 @@ def test_duplicate_regions_no_translations( assert source_page_dict == target_page_dict source_page_translations_filtered = source_page.translations.filter( - language=source_language_root + language=source_language_root, ) target_page_translations = target_page.translations.filter( - last_updated__lt=before_cloning + last_updated__lt=before_cloning, ) assert len(source_page_translations_filtered) == len(target_page_translations) assert target_page_translations[0].language == source_language_root diff --git a/tests/cms/test_language.py b/tests/cms/test_language.py index dc1a227589..26b20e24d8 100644 --- a/tests/cms/test_language.py +++ b/tests/cms/test_language.py @@ -1,8 +1,9 @@ from __future__ import annotations +from typing import TYPE_CHECKING + import pytest from django.conf import settings -from django.test.client import Client from django.urls import reverse from integreat_cms.cms.models import Language, LanguageTreeNode, Region @@ -12,6 +13,9 @@ setup_google_translate_supported_languages, ) +if TYPE_CHECKING: + from django.test.client import Client + @pytest.mark.django_db def test_create_new_language_node( @@ -30,7 +34,8 @@ def test_create_new_language_node( assert new_language not in region.languages next_url = url = reverse( - "new_languagetreenode", kwargs={"region_slug": region.slug} + "new_languagetreenode", + kwargs={"region_slug": region.slug}, ) response = client.post( url, @@ -48,7 +53,8 @@ def test_create_new_language_node( # Verify the new language node was created assert ( LanguageTreeNode.objects.filter( - region=region, language=new_language + region=region, + language=new_language, ).count() == 1 ) @@ -87,12 +93,13 @@ def test_update_language_node( region = Region.objects.filter(slug="nurnberg").first() language = Language.objects.filter(slug="fa").first() language_node = LanguageTreeNode.objects.filter( - region=region, language=language + region=region, + language=language, ).first() root_language_node = region.language_tree_root # Make sure the parent node is not German - assert not language_node.parent.id == root_language_node.id + assert language_node.parent.id != root_language_node.id next_url = url = reverse( "edit_languagetreenode", @@ -140,7 +147,8 @@ def test_move_language_node( region = Region.objects.filter(slug="augsburg").first() language = Language.objects.filter(slug="ar").first() language_node = LanguageTreeNode.objects.filter( - language=language, region=region + language=language, + region=region, ).first() next_url = url = reverse( @@ -184,12 +192,14 @@ def test_delete_language_node( # Choose a node without children to verify the delete function successes deletable_language = Language.objects.filter(slug="ar").first() deletable_language_node = LanguageTreeNode.objects.filter( - language=deletable_language, region=region + language=deletable_language, + region=region, ).first() # Choose a node with children to verify the delete function is cancelled not_deletable_language = Language.objects.filter(slug="en").first() not_deletable_language_node = LanguageTreeNode.objects.filter( - language=not_deletable_language, region=region + language=not_deletable_language, + region=region, ).first() deletable_next_url = deletable_url = reverse( @@ -209,11 +219,13 @@ def test_delete_language_node( assert response_fail.status_code == 302 # Verify that the language node without children was deleted assert not LanguageTreeNode.objects.filter( - language=deletable_language, region=region + language=deletable_language, + region=region, ).first() # Verify that the language node with child nodes was not deleted assert LanguageTreeNode.objects.filter( - language=not_deletable_language, region=region + language=not_deletable_language, + region=region, ).first() elif role == ANONYMOUS: # For anonymous users, we want to redirect to the login form instead of showing an error diff --git a/tests/cms/test_login.py b/tests/cms/test_login.py index b4db372bcd..2f96483a32 100644 --- a/tests/cms/test_login.py +++ b/tests/cms/test_login.py @@ -1,15 +1,20 @@ from __future__ import annotations +from typing import TYPE_CHECKING + import pytest from django.contrib import auth -from django.test.client import Client from django.urls import reverse -from pytest_django.fixtures import SettingsWrapper + +if TYPE_CHECKING: + from django.test.client import Client + from pytest_django.fixtures import SettingsWrapper @pytest.mark.django_db @pytest.mark.parametrize( - "username", ["root", "root@root.root", "management", "management@example.com"] + "username", + ["root", "root@root.root", "management", "management@example.com"], ) def test_login_success( load_test_data: None, @@ -27,7 +32,8 @@ def test_login_success( """ # Test login via username/password response = client.post( - settings.LOGIN_URL, data={"username": username, "password": "root1234"} + settings.LOGIN_URL, + data={"username": username, "password": "root1234"}, ) print(response.headers) assert response.status_code == 302 @@ -43,7 +49,8 @@ def test_login_success( else: # Region user should get redirected to the region dashboard assert response.headers.get("location") == reverse( - "dashboard", kwargs={"region_slug": "augsburg"} + "dashboard", + kwargs={"region_slug": "augsburg"}, ) @@ -61,7 +68,10 @@ def test_login_success( ], ) def test_login_failure( - load_test_data: None, client: Client, settings: SettingsWrapper, username: str + load_test_data: None, + client: Client, + settings: SettingsWrapper, + username: str, ) -> None: """ Test whether login with incorrect credentials does not work @@ -75,7 +85,8 @@ def test_login_failure( settings.LANGUAGE_CODE = "en" # Test login via username/password response = client.post( - settings.LOGIN_URL, data={"username": username, "password": "incorrect"} + settings.LOGIN_URL, + data={"username": username, "password": "incorrect"}, ) print(response.headers) assert response.status_code == 200 diff --git a/tests/cms/test_media_library.py b/tests/cms/test_media_library.py index 6f651f3aa9..40c4acd08a 100644 --- a/tests/cms/test_media_library.py +++ b/tests/cms/test_media_library.py @@ -1,12 +1,12 @@ from __future__ import annotations import os +from typing import TYPE_CHECKING from urllib import parse import pytest from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile -from django.test.client import Client from django.urls import reverse from integreat_cms.cms.models import Directory, MediaFile @@ -17,6 +17,9 @@ STAFF_ROLES, ) +if TYPE_CHECKING: + from django.test.client import Client + @pytest.mark.django_db def test_directory_path( @@ -29,7 +32,7 @@ def test_directory_path( query = parse.urlencode( { "directory": 1, - } + }, ) next_url = reverse("mediacenter_directory_path") url = f"{next_url}?{query}" @@ -62,7 +65,7 @@ def test_get_directory_content( query = parse.urlencode( { "directory": 1, - } + }, ) next_url = reverse("mediacenter_get_directory_content") url = f"{next_url}?{query}" @@ -97,7 +100,8 @@ def test_create_directory( response_directory = client.post(url, data={"name": "new directory"}) # A new sub directory response_sub_directory = client.post( - url, data={"name": "sub directory", "parent": 1} + url, + data={"name": "sub directory", "parent": 1}, ) if role in PRIV_STAFF_ROLES: @@ -220,7 +224,9 @@ def test_upload_file( "rb", ) as file: uploaded_file = SimpleUploadedFile( - "file", file.read(), content_type="image/png" + "file", + file.read(), + content_type="image/png", ) response = client.post( url, @@ -261,7 +267,9 @@ def test_replace_file( "rb", ) as file: uploaded_file = SimpleUploadedFile( - "file_replaced", file.read(), content_type="image/png" + "file_replaced", + file.read(), + content_type="image/png", ) response = client.post( url, @@ -422,7 +430,7 @@ def test_get_file_usages( query = parse.urlencode( { "file": 1, - } + }, ) next_url = reverse("mediacenter_get_file_usages") url = f"{next_url}?{query}" @@ -455,7 +463,7 @@ def test_get_search_result( query = parse.urlencode( { "query": "test", - } + }, ) next_url = reverse("mediacenter_get_search_result") url = f"{next_url}?{query}" diff --git a/tests/cms/test_page_filters.py b/tests/cms/test_page_filters.py index b50c5ce93e..540f139cb1 100644 --- a/tests/cms/test_page_filters.py +++ b/tests/cms/test_page_filters.py @@ -1,11 +1,14 @@ from __future__ import annotations +from typing import TYPE_CHECKING from urllib.parse import urlencode import pytest -from django.test.client import Client from django.urls import reverse +if TYPE_CHECKING: + from django.test.client import Client + @pytest.mark.django_db def test_page_filters(load_test_data: None, admin_client: Client) -> None: @@ -16,7 +19,8 @@ def test_page_filters(load_test_data: None, admin_client: Client) -> None: :param admin_client: The fixture providing the logged in admin """ page_tree = reverse( - "pages", kwargs={"region_slug": "augsburg", "language_slug": "en"} + "pages", + kwargs={"region_slug": "augsburg", "language_slug": "en"}, ) # Get with no data should result in the normal, unfiltered view with only root pages response = admin_client.get(page_tree) diff --git a/tests/cms/utils/test_cancel_translation.py b/tests/cms/utils/test_cancel_translation.py index b91927236e..69993724e8 100644 --- a/tests/cms/utils/test_cancel_translation.py +++ b/tests/cms/utils/test_cancel_translation.py @@ -8,10 +8,7 @@ from pytest_django.fixtures import SettingsWrapper import pytest -from _pytest.logging import LogCaptureFixture -from django.test.client import Client from django.urls import reverse -from pytest_django.fixtures import SettingsWrapper from integreat_cms.cms.constants import translation_status from integreat_cms.cms.models import Page @@ -43,10 +40,8 @@ def test_bulk_cancel_translation_process( ) for page_id in IDS_PAGES_NOT_IN_TRANSLATION_PROCESS: assert ( - not Page.objects.filter(id=page_id) - .first() - .get_translation_state(LANGUAGE_SLUG) - == translation_status.IN_TRANSLATION + Page.objects.filter(id=page_id).first().get_translation_state(LANGUAGE_SLUG) + != translation_status.IN_TRANSLATION ) kwargs = {"region_slug": REGION_SLUG, "language_slug": LANGUAGE_SLUG} @@ -58,7 +53,7 @@ def test_bulk_cancel_translation_process( cancel_translation_process, data={"selected_ids[]": combined_page_ids} ) - if role in PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR]: + if role in [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR]: assert response.status_code == 302 redirect_url = response.headers.get("location") response = client.get(redirect_url) @@ -80,10 +75,10 @@ def test_bulk_cancel_translation_process( ) for page_id in combined_page_ids: assert ( - not Page.objects.filter(id=page_id) + Page.objects.filter(id=page_id) .first() .get_translation_state(LANGUAGE_SLUG) - == translation_status.IN_TRANSLATION + != translation_status.IN_TRANSLATION ) elif role == ANONYMOUS: assert response.status_code == 302 diff --git a/tests/cms/utils/test_content_utils.py b/tests/cms/utils/test_content_utils.py index 5ab1d9c904..d8ea6560cf 100644 --- a/tests/cms/utils/test_content_utils.py +++ b/tests/cms/utils/test_content_utils.py @@ -6,7 +6,9 @@ @pytest.mark.parametrize( - "login_role_user", PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], indirect=True + "login_role_user", + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], + indirect=True, ) @pytest.mark.django_db def test_clean_content( diff --git a/tests/cms/utils/test_disable_hix_post_save_signal.py b/tests/cms/utils/test_disable_hix_post_save_signal.py index 53e1308b00..b6459a8fc3 100644 --- a/tests/cms/utils/test_disable_hix_post_save_signal.py +++ b/tests/cms/utils/test_disable_hix_post_save_signal.py @@ -54,7 +54,6 @@ def dummy_region(django_db_setup: None, django_db_blocker: _DatabaseBlocker) -> @pytest.mark.skip(reason="disabled to avoid failing test to merge a hot fix in #2974") @pytest.mark.django_db def test_disable_hix_post_save_signal(dummy_region: Region) -> None: - # pylint: disable=redefined-outer-name with disable_hix_post_save_signal(): demo_page_translation = create_dummy_page_translation(dummy_region) @@ -64,14 +63,12 @@ def test_disable_hix_post_save_signal(dummy_region: Region) -> None: @pytest.mark.skip(reason="disabled to avoid failing test to merge a hot fix in #2974") @pytest.mark.django_db def test_rule_out_false_positive(dummy_region: Region) -> None: - # pylint: disable=redefined-outer-name demo_page_translation = create_dummy_page_translation(dummy_region) assert demo_page_translation.hix_score is None def create_dummy_page_translation(dummy_region: Region) -> PageTranslation: - # pylint: disable=redefined-outer-name dummy_page = Page.add_root(region=dummy_region) dummy_page.save() diff --git a/tests/cms/utils/test_external_calendar_utils.py b/tests/cms/utils/test_external_calendar_utils.py index 78c2f740eb..0c54f6dc2d 100644 --- a/tests/cms/utils/test_external_calendar_utils.py +++ b/tests/cms/utils/test_external_calendar_utils.py @@ -40,7 +40,7 @@ def test_import_fails(calendar_data: tuple[str, str]) -> None: :param calendar_data: A tuple of calendar path and expected error message """ calendar_name, error_msg = calendar_data - with open(calendar_name, "r", encoding="utf-8") as calendar_file: + with open(calendar_name, encoding="utf-8") as calendar_file: file_contents = calendar_file.read() ical = icalendar.Calendar.from_ical(file_contents) diff --git a/tests/cms/utils/test_internal_link_checker.py b/tests/cms/utils/test_internal_link_checker.py index a830db86df..34197fc23c 100644 --- a/tests/cms/utils/test_internal_link_checker.py +++ b/tests/cms/utils/test_internal_link_checker.py @@ -88,7 +88,9 @@ def prepage_url(link: str, trailing_slash: bool) -> Url: @pytest.mark.parametrize("link", VALID_INTERNAL_LINKS) @pytest.mark.parametrize("trailing_slash", [True, False]) def test_check_internal_valid( - load_test_data: None, link: str, trailing_slash: bool + load_test_data: None, + link: str, + trailing_slash: bool, ) -> None: """ Check whether the given internal URL is correctly identified as valid link @@ -105,7 +107,9 @@ def test_check_internal_valid( @pytest.mark.parametrize("link", INVALID_INTERNAL_LINKS) @pytest.mark.parametrize("trailing_slash", [True, False]) def test_check_internal_invalid( - load_test_data: None, link: str, trailing_slash: bool + load_test_data: None, + link: str, + trailing_slash: bool, ) -> None: """ Check whether the given internal URL is correctly identified as invalid link @@ -116,7 +120,7 @@ def test_check_internal_invalid( """ url = prepage_url(link, trailing_slash) assert not check_internal( - url + url, ), f"URL '{link}' is not correctly identified as invalid" @@ -124,7 +128,9 @@ def test_check_internal_invalid( @pytest.mark.parametrize("link", SKIPPED_INTERNAL_LINKS) @pytest.mark.parametrize("trailing_slash", [True, False]) def test_check_internal_skipped( - load_test_data: None, link: str, trailing_slash: bool + load_test_data: None, + link: str, + trailing_slash: bool, ) -> None: """ Check whether the given internal URL is correctly skipped diff --git a/tests/cms/utils/test_repair_tree.py b/tests/cms/utils/test_repair_tree.py index 700c993229..5306adcfc0 100644 --- a/tests/cms/utils/test_repair_tree.py +++ b/tests/cms/utils/test_repair_tree.py @@ -34,10 +34,20 @@ def test_repair_tree(load_test_data_transactional: None) -> None: root_page = Page(region=region, lft=2, rgt=6, tree_id=999, depth=0) child1_page = Page( - region=region, lft=1, rgt=4, tree_id=999, depth=1, parent=root_page + region=region, + lft=1, + rgt=4, + tree_id=999, + depth=1, + parent=root_page, ) child2_page = Page( - region=region, lft=4, rgt=5, tree_id=999, depth=1, parent=root_page + region=region, + lft=4, + rgt=5, + tree_id=999, + depth=1, + parent=root_page, ) root_page.save() @@ -82,11 +92,11 @@ def test_repair_tree(load_test_data_transactional: None) -> None: @pytest.mark.xfail( - reason="Constraints on the model prohibit the broken state used as basis for this test" + reason="Constraints on the model prohibit the broken state used as basis for this test", ) @pytest.mark.order("last", after=after_tests) @pytest.mark.django_db(transaction=True, serialized_rollback=True) -def test_repair_tree_complex( # pylint: disable=too-many-locals, too-many-statements # noqa: PLR0915 +def test_repair_tree_complex( # noqa: PLR0915 load_test_data_transactional: None, ) -> None: """ diff --git a/tests/cms/utils/test_rounded_hix_value.py b/tests/cms/utils/test_rounded_hix_value.py index 036c568f90..1a7977f8be 100644 --- a/tests/cms/utils/test_rounded_hix_value.py +++ b/tests/cms/utils/test_rounded_hix_value.py @@ -10,7 +10,6 @@ ] -# pylint:disable=redefined-outer-name @pytest.mark.django_db @pytest.mark.parametrize("raw_hix_expected_rounding", raw_hix_expected_rounding) def test_rounded_hix_value(raw_hix_expected_rounding: tuple[float, float]) -> None: diff --git a/tests/cms/utils/test_slug_utils.py b/tests/cms/utils/test_slug_utils.py index ec90e0d4dd..ea02e7fe9d 100644 --- a/tests/cms/utils/test_slug_utils.py +++ b/tests/cms/utils/test_slug_utils.py @@ -16,7 +16,8 @@ @pytest.mark.django_db def test_generate_unique_slug_fallback( - settings: SettingsWrapper, load_test_data: None + settings: SettingsWrapper, + load_test_data: None, ) -> None: """ Test whether the :meth:`~integreat_cms.cms.utils.slug_utils.generate_unique_slug_helper` function correctly uses the fallback property @@ -30,14 +31,15 @@ def test_generate_unique_slug_fallback( "foreign_model": "region", "fallback": "name", } - assert ( - generate_unique_slug(**kwargs) == "unique_slug_fallback" - ), "Name is not used as fallback when slug is missing" + assert generate_unique_slug(**kwargs) == "unique_slug_fallback", ( + "Name is not used as fallback when slug is missing" + ) @pytest.mark.django_db def test_generate_unique_slug_reserved_region_slug( - settings: SettingsWrapper, load_test_data: None + settings: SettingsWrapper, + load_test_data: None, ) -> None: """ Test whether the :meth:`~integreat_cms.cms.utils.slug_utils.generate_unique_slug_helper` function returns the correct unique slug @@ -52,14 +54,15 @@ def test_generate_unique_slug_reserved_region_slug( "foreign_model": "region", "fallback": "name", } - assert ( - generate_unique_slug(**kwargs) == "landing-2" - ), "Reserved region slug is not prevented" + assert generate_unique_slug(**kwargs) == "landing-2", ( + "Reserved region slug is not prevented" + ) @pytest.mark.django_db def test_generate_unique_slug_reserved_page_slug( - settings: SettingsWrapper, load_test_data: None + settings: SettingsWrapper, + load_test_data: None, ) -> None: """ Test whether the :meth:`~integreat_cms.cms.utils.slug_utils.generate_unique_slug_helper` function function returns the correct unique slug @@ -74,9 +77,9 @@ def test_generate_unique_slug_reserved_page_slug( "object_instance": page, "foreign_model": "page", } - assert ( - generate_unique_slug(**kwargs) == "disclaimer-2" - ), "Reserved imprint slug is not prevented for pages" + assert generate_unique_slug(**kwargs) == "disclaimer-2", ( + "Reserved imprint slug is not prevented for pages" + ) def test_generate_unique_slug_no_fallback() -> None: diff --git a/tests/cms/utils/test_tree_mutex.py b/tests/cms/utils/test_tree_mutex.py index de2af40ddf..2a40ce24af 100644 --- a/tests/cms/utils/test_tree_mutex.py +++ b/tests/cms/utils/test_tree_mutex.py @@ -16,7 +16,7 @@ from __future__ import annotations from threading import Thread -from typing import Callable +from typing import TYPE_CHECKING import pytest from django.db.utils import IntegrityError @@ -25,6 +25,9 @@ from integreat_cms.cms.models import Page from integreat_cms.cms.utils.tree_mutex import tree_mutex +if TYPE_CHECKING: + from collections.abc import Callable + after_tests = ( "tests/core/management/commands/test_replace_links.py::test_replace_links_commit", "tests/core/management/commands/test_fix_internal_links.py::test_fix_internal_links_commit", @@ -42,7 +45,7 @@ def test_tree_mutex(load_test_data_transactional: None) -> None: run_mutex_test(use_mutex=True) -@pytest.mark.order("last", after=after_tests + ("test_tree_mutex",)) +@pytest.mark.order("last", after=(*after_tests, "test_tree_mutex")) @pytest.mark.django_db(transaction=True, serialized_rollback=True) def test_rule_out_false_positive(load_test_data_transactional: None) -> None: """ @@ -112,7 +115,7 @@ def handle_exception(e: Exception) -> None: if exception: # Raise the exception from the child thread here in the main thread - raise exception # pylint: disable-msg=E0702 + raise exception def five_ten_five( @@ -139,11 +142,11 @@ def five_ten_five( else: forth(contestant_id) back(contestant_id) - except Exception as e: # pylint: disable=broad-exception-caught # noqa: BLE001 + except Exception as e: # noqa: BLE001 if handle_exception: handle_exception(e) print( - f"failed 5-10-5 of contestant #{contestant_id} {'with' if use_mutex else 'without'} tree_mutex:\n {repr(e)}" + f"failed 5-10-5 of contestant #{contestant_id} {'with' if use_mutex else 'without'} tree_mutex:\n {e!r}" ) else: print( diff --git a/tests/cms/views/contacts/test_contact_actions.py b/tests/cms/views/contacts/test_contact_actions.py index c77bddc8bf..9c0f392876 100644 --- a/tests/cms/views/contacts/test_contact_actions.py +++ b/tests/cms/views/contacts/test_contact_actions.py @@ -8,7 +8,6 @@ from pytest_django.fixtures import SettingsWrapper import pytest -from django.test.client import Client from django.urls import reverse from integreat_cms.cms.models import Contact @@ -65,7 +64,7 @@ def test_archive_contact( caplog, ) assert f"Contact {contact_string} was successfully archived" in client.get( - redirect_url + redirect_url, ).content.decode("utf-8") assert Contact.objects.filter(id=contact_id).first().archived else: @@ -73,10 +72,9 @@ def test_archive_contact( f'ERROR Cannot archive contact "{contact_string}" while content objects refer to it.', caplog, ) - assert f"Cannot archive contact "{contact_string}" while content objects refer to it." in client.get( - redirect_url - ).content.decode( - "utf-8" + assert ( + f"Cannot archive contact "{contact_string}" while content objects refer to it." + in client.get(redirect_url).content.decode("utf-8") ) assert not Contact.objects.filter(id=contact_id).first().archived elif role == ANONYMOUS: @@ -131,7 +129,7 @@ def test_delete_contact( caplog, ) assert f"Contact {contact_string} was successfully deleted" in client.get( - redirect_url + redirect_url, ).content.decode("utf-8") assert not Contact.objects.filter(id=contact_id).first() else: @@ -139,10 +137,9 @@ def test_delete_contact( f'ERROR Cannot delete contact "{contact_string}" while content objects refer to it.', caplog, ) - assert f"Cannot delete contact "{contact_string}" while content objects refer to it." in client.get( - redirect_url - ).content.decode( - "utf-8" + assert ( + f"Cannot delete contact "{contact_string}" while content objects refer to it." + in client.get(redirect_url).content.decode("utf-8") ) assert Contact.objects.filter(id=contact_id).first() elif role == ANONYMOUS: @@ -192,7 +189,7 @@ def test_restore_contact( caplog, ) assert f"Contact {contact_string} was successfully restored" in client.get( - redirect_url + redirect_url, ).content.decode("utf-8") assert not Contact.objects.filter(id=ARCHIVED_CONTACT_ID).first().archived @@ -225,7 +222,7 @@ def test_bulk_archive_contacts( settings.LANGUAGE_CODE = "en" not_used_contact_string = str( - Contact.objects.filter(id=NOT_USED_CONTACT_ID).first() + Contact.objects.filter(id=NOT_USED_CONTACT_ID).first(), ) used_contact_string = str(Contact.objects.filter(id=USED_CONTACT_ID).first()) @@ -291,7 +288,7 @@ def test_bulk_delete_contacts( settings.LANGUAGE_CODE = "en" not_used_contact_string = str( - Contact.objects.filter(id=NOT_USED_CONTACT_ID).first() + Contact.objects.filter(id=NOT_USED_CONTACT_ID).first(), ) used_contact_string = str(Contact.objects.filter(id=USED_CONTACT_ID).first()) diff --git a/tests/cms/views/contacts/test_contact_form.py b/tests/cms/views/contacts/test_contact_form.py index 2150228ac3..11f7d9adc0 100644 --- a/tests/cms/views/contacts/test_contact_form.py +++ b/tests/cms/views/contacts/test_contact_form.py @@ -8,7 +8,6 @@ from pytest_django.fixtures import SettingsWrapper import pytest -from django.test.client import Client from django.urls import reverse from integreat_cms.cms.models import Contact, Region diff --git a/tests/cms/views/dashboard/test_dashboard.py b/tests/cms/views/dashboard/test_dashboard.py index 21f0910aeb..f5762a3516 100644 --- a/tests/cms/views/dashboard/test_dashboard.py +++ b/tests/cms/views/dashboard/test_dashboard.py @@ -25,7 +25,6 @@ def test_permission_to_view_chat( load_test_data: None, login_role_user: tuple[Client, str], ) -> None: - client, role = login_role_user get_dashboard = reverse( @@ -51,7 +50,7 @@ def test_permission_to_view_chat( assert response.status_code == 200 assert chat_head not in response.content.decode("utf-8") - elif role in STAFF_ROLES + [MANAGEMENT]: + elif role in [*STAFF_ROLES, MANAGEMENT]: assert response.status_code == 200 assert chat_head in response.content.decode("utf-8") @@ -109,7 +108,7 @@ def test_permission_to_view_to_do_board( not in response.content.decode("utf-8") ) - elif role in [PRIV_STAFF_ROLES] + [AUTHOR, EDITOR, MANAGEMENT]: + elif role in [PRIV_STAFF_ROLES, AUTHOR, EDITOR, MANAGEMENT]: assert response.status_code == 200 assert "To-Dos" in response.content.decode("utf-8") assert ( @@ -144,7 +143,9 @@ def test_permission_to_view_admin_dashboard( @pytest.mark.parametrize( - "login_role_user", PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], indirect=True + "login_role_user", + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], + indirect=True, ) @pytest.mark.django_db @freeze_time("2024-01-01") @@ -173,13 +174,15 @@ def test_number_of_outdated_pages_is_correct( response.content.decode("utf-8"), ) assert match, "Number of outdated pages not displayed" - assert ( - int(match.group(3)) == expected_number_of_pages - ), "Wrong number of pages displayed as outdated" + assert int(match.group(3)) == expected_number_of_pages, ( + "Wrong number of pages displayed as outdated" + ) @pytest.mark.parametrize( - "login_role_user", PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], indirect=True + "login_role_user", + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], + indirect=True, ) @pytest.mark.django_db @freeze_time("2024-01-01") @@ -203,13 +206,15 @@ def test_most_outdated_page_is_correct( response.content.decode("utf-8"), ) assert match, "Not updated message missing" - assert ( - int(match.group(1)) == days_since_page_is_outdated - ), "Not updated message displaying wrong days count" + assert int(match.group(1)) == days_since_page_is_outdated, ( + "Not updated message displaying wrong days count" + ) @pytest.mark.parametrize( - "login_role_user", PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], indirect=True + "login_role_user", + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], + indirect=True, ) @pytest.mark.django_db def test_link_to_most_outdated_page_is_valid( @@ -243,7 +248,9 @@ def assert_button_leads_to_valid_page(client: Client) -> None: @pytest.mark.parametrize( - "login_role_user", PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], indirect=True + "login_role_user", + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], + indirect=True, ) @pytest.mark.django_db def test_number_of_drafted_pages_is_correct( @@ -271,13 +278,15 @@ def test_number_of_drafted_pages_is_correct( response.content.decode("utf-8"), ) assert match, "Number of drafted pages not displayed" - assert ( - int(match.group(3)) == expected_number_of_pages - ), "Wrong number of pages displayed as outdated" + assert int(match.group(3)) == expected_number_of_pages, ( + "Wrong number of pages displayed as outdated" + ) @pytest.mark.parametrize( - "login_role_user", PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], indirect=True + "login_role_user", + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], + indirect=True, ) @pytest.mark.django_db def test_single_drafted_page_is_correct( diff --git a/tests/cms/views/events/external_calendar.py b/tests/cms/views/events/external_calendar.py index ef2a467602..e07f0f8cd6 100644 --- a/tests/cms/views/events/external_calendar.py +++ b/tests/cms/views/events/external_calendar.py @@ -83,24 +83,24 @@ def test_creating_new_external_calendar( edit_url = response.headers.get("location") url_params = resolve(edit_url) - assert ( - url_params.url_name == "edit_external_calendar" - ), "We should be redirected to the edit view" - assert ( - url_params.kwargs["region_slug"] == "augsburg" - ), "The region shouldn't be different from the request" + assert url_params.url_name == "edit_external_calendar", ( + "We should be redirected to the edit view" + ) + assert url_params.kwargs["region_slug"] == "augsburg", ( + "The region shouldn't be different from the request" + ) id_of_external_calendar = url_params.kwargs["calendar_id"] external_calendar = ExternalCalendar.objects.get(id=id_of_external_calendar) - assert ( - external_calendar.name == "Test external calendar" - ), "Name should be successfully set on the model" - assert ( - external_calendar.url == "https://integreat.app/" - ), "URL should be successfully set on the model" - assert ( - external_calendar.import_filter_category == "integreat" - ), "Filter category should be successfully set on the model" + assert external_calendar.name == "Test external calendar", ( + "Name should be successfully set on the model" + ) + assert external_calendar.url == "https://integreat.app/", ( + "URL should be successfully set on the model" + ) + assert external_calendar.import_filter_category == "integreat", ( + "Filter category should be successfully set on the model" + ) elif role == ANONYMOUS: assert response.status_code == 302, "We should be redirected to the login view" assert ( diff --git a/tests/cms/views/events/test_event_form.py b/tests/cms/views/events/test_event_form.py index a51a2ecfd2..bea060b4b9 100644 --- a/tests/cms/views/events/test_event_form.py +++ b/tests/cms/views/events/test_event_form.py @@ -9,7 +9,6 @@ from pytest_django.fixtures import SettingsWrapper import pytest -from django.test.client import Client from django.urls import reverse from integreat_cms.cms.constants import status @@ -75,8 +74,7 @@ def test_create_event( data=data, ) if role in PRIV_STAFF_ROLES + WRITE_ROLES: - response.status_code == 302 - + assert response.status_code in [200, 302] if successfully_created: redirect_url = response.headers.get("location") assert_message_in_log( @@ -89,7 +87,7 @@ def test_create_event( ) event_translation = EventTranslation.objects.filter( - title=EVENT_TITLE + title=EVENT_TITLE, ).first() assert event_translation assert Event.objects.filter(id=event_translation.event.id).first() @@ -105,7 +103,7 @@ def test_create_event( ) event_translation = EventTranslation.objects.filter( - title=EVENT_TITLE + title=EVENT_TITLE, ).first() assert not event_translation diff --git a/tests/cms/views/feedback/test_admin_feedback_actions.py b/tests/cms/views/feedback/test_admin_feedback_actions.py index 9884d56858..083b831da0 100644 --- a/tests/cms/views/feedback/test_admin_feedback_actions.py +++ b/tests/cms/views/feedback/test_admin_feedback_actions.py @@ -9,7 +9,6 @@ import pytest from django.conf import settings from django.contrib import auth -from django.test.client import Client from django.urls import reverse from integreat_cms.cms.models.feedback.feedback import Feedback @@ -30,7 +29,8 @@ def test_mark_admin_feedback_as_read( mark_admin_feedback_as_read = reverse("mark_admin_feedback_as_read") test_feedback_id = 1 response = client.post( - mark_admin_feedback_as_read, data={"selected_ids[]": [test_feedback_id]} + mark_admin_feedback_as_read, + data={"selected_ids[]": [test_feedback_id]}, ) if role in PRIV_STAFF_ROLES: @@ -42,7 +42,8 @@ def test_mark_admin_feedback_as_read( # Check for a success message response = client.get(feedback_list) assert_message_in_log( - "SUCCESS Feedback was successfully marked as read", caplog + "SUCCESS Feedback was successfully marked as read", + caplog, ) # Check that feedback has been marked as read by the user in the database @@ -75,7 +76,8 @@ def test_mark_admin_feedback_as_unread( mark_admin_feedback_as_unread = reverse("mark_admin_feedback_as_unread") test_feedback_id = 3 response = client.post( - mark_admin_feedback_as_unread, data={"selected_ids[]": [test_feedback_id]} + mark_admin_feedback_as_unread, + data={"selected_ids[]": [test_feedback_id]}, ) if role in PRIV_STAFF_ROLES: @@ -87,7 +89,8 @@ def test_mark_admin_feedback_as_unread( # Check for a success message response = client.get(feedback_list) assert_message_in_log( - "SUCCESS Feedback was successfully marked as unread", caplog + "SUCCESS Feedback was successfully marked as unread", + caplog, ) # Check that feedback has been marked as unread by the user in the database @@ -120,7 +123,8 @@ def test_archive_admin_feedback( archive_admin_feedback = reverse("archive_admin_feedback") test_feedback_id = 1 response = client.post( - archive_admin_feedback, data={"selected_ids[]": [test_feedback_id]} + archive_admin_feedback, + data={"selected_ids[]": [test_feedback_id]}, ) if role in PRIV_STAFF_ROLES: @@ -163,7 +167,8 @@ def test_restore_admin_feedback( restore_admin_feedback = reverse("restore_admin_feedback") test_feedback_id = 2 response = client.post( - restore_admin_feedback, data={"selected_ids[]": [test_feedback_id]} + restore_admin_feedback, + data={"selected_ids[]": [test_feedback_id]}, ) if role in PRIV_STAFF_ROLES: @@ -206,7 +211,8 @@ def test_delete_admin_feedback( delete_admin_feedback = reverse("delete_admin_feedback") feedback_to_delete_id = 1 response = client.post( - delete_admin_feedback, data={"selected_ids[]": [feedback_to_delete_id]} + delete_admin_feedback, + data={"selected_ids[]": [feedback_to_delete_id]}, ) if role in HIGH_PRIV_STAFF_ROLES: diff --git a/tests/cms/views/feedback/test_region_feedback_actions.py b/tests/cms/views/feedback/test_region_feedback_actions.py index 09dff1e609..cab32f74e8 100644 --- a/tests/cms/views/feedback/test_region_feedback_actions.py +++ b/tests/cms/views/feedback/test_region_feedback_actions.py @@ -12,7 +12,6 @@ import pytest from django.conf import settings from django.contrib import auth -from django.test.client import Client from django.urls import reverse from integreat_cms.cms.models.feedback.feedback import Feedback @@ -39,14 +38,16 @@ def test_mark_region_feedback_as_read( # Mark feedback as read mark_region_feedback_as_read = reverse( - "mark_region_feedback_as_read", kwargs=region_slug_param + "mark_region_feedback_as_read", + kwargs=region_slug_param, ) test_feedback_id = 4 response = client.post( - mark_region_feedback_as_read, data={"selected_ids[]": [test_feedback_id]} + mark_region_feedback_as_read, + data={"selected_ids[]": [test_feedback_id]}, ) - if role in PRIV_STAFF_ROLES + [MANAGEMENT]: + if role in [*PRIV_STAFF_ROLES, MANAGEMENT]: # Check for a redirect to the feedback list feedback_list = reverse("region_feedback", kwargs=region_slug_param) assert response.status_code == 302 @@ -55,7 +56,8 @@ def test_mark_region_feedback_as_read( # Check for a success message response = client.get(feedback_list) assert_message_in_log( - "SUCCESS Feedback was successfully marked as read", caplog + "SUCCESS Feedback was successfully marked as read", + caplog, ) # Check that feedback has been marked as read by the user in the database @@ -86,14 +88,16 @@ def test_mark_region_feedback_as_unread( # Mark feedback as unread mark_region_feedback_as_unread = reverse( - "mark_region_feedback_as_unread", kwargs=region_slug_param + "mark_region_feedback_as_unread", + kwargs=region_slug_param, ) test_feedback_id = 6 response = client.post( - mark_region_feedback_as_unread, data={"selected_ids[]": [test_feedback_id]} + mark_region_feedback_as_unread, + data={"selected_ids[]": [test_feedback_id]}, ) - if role in PRIV_STAFF_ROLES + [MANAGEMENT]: + if role in [*PRIV_STAFF_ROLES, MANAGEMENT]: # Check for a redirect to the feedback list feedback_list = reverse("region_feedback", kwargs=region_slug_param) assert response.status_code == 302 @@ -102,7 +106,8 @@ def test_mark_region_feedback_as_unread( # Check for a success message response = client.get(feedback_list) assert_message_in_log( - "SUCCESS Feedback was successfully marked as unread", caplog + "SUCCESS Feedback was successfully marked as unread", + caplog, ) # Check that feedback has been marked as unread by the user in the database @@ -133,14 +138,16 @@ def test_archive_region_feedback( # Archive feedback archive_region_feedback = reverse( - "archive_region_feedback", kwargs=region_slug_param + "archive_region_feedback", + kwargs=region_slug_param, ) test_feedback_id = 4 response = client.post( - archive_region_feedback, data={"selected_ids[]": [test_feedback_id]} + archive_region_feedback, + data={"selected_ids[]": [test_feedback_id]}, ) - if role in PRIV_STAFF_ROLES + [MANAGEMENT]: + if role in [*PRIV_STAFF_ROLES, MANAGEMENT]: # Check for a redirect to the feedback list feedback_list = reverse("region_feedback", kwargs=region_slug_param) assert response.status_code == 302 @@ -178,14 +185,16 @@ def test_restore_region_feedback( # Restore feedback restore_region_feedback = reverse( - "restore_region_feedback", kwargs=region_slug_param + "restore_region_feedback", + kwargs=region_slug_param, ) test_feedback_id = 5 response = client.post( - restore_region_feedback, data={"selected_ids[]": [test_feedback_id]} + restore_region_feedback, + data={"selected_ids[]": [test_feedback_id]}, ) - if role in PRIV_STAFF_ROLES + [MANAGEMENT]: + if role in [*PRIV_STAFF_ROLES, MANAGEMENT]: # Check for a redirect to the feedback list feedback_list = reverse("region_feedback_archived", kwargs=region_slug_param) assert response.status_code == 302 @@ -225,10 +234,11 @@ def test_delete_region_feedback( delete_region_feedback = reverse("delete_region_feedback", kwargs=region_slug_param) feedback_to_delete_id = 4 response = client.post( - delete_region_feedback, data={"selected_ids[]": [feedback_to_delete_id]} + delete_region_feedback, + data={"selected_ids[]": [feedback_to_delete_id]}, ) - if role in HIGH_PRIV_STAFF_ROLES + [MANAGEMENT]: + if role in [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT]: # Check for a redirect to the feedback list feedback_list = reverse("region_feedback", kwargs=region_slug_param) assert response.status_code == 302 @@ -263,12 +273,13 @@ def test_csv_export_feedback( client, role = login_role_user csv_export = reverse( - "export_region_feedback", kwargs={**region_slug_param, "file_format": "csv"} + "export_region_feedback", + kwargs={**region_slug_param, "file_format": "csv"}, ) csv_to_export_ids = [7, 8] response = client.post(csv_export, data={"selected_ids[]": csv_to_export_ids}) - if role in STAFF_ROLES + [MANAGEMENT]: + if role in [*STAFF_ROLES, MANAGEMENT]: assert response.status_code == 200 assert response.headers.get("Content-Type") == "text/csv" diff --git a/tests/cms/views/link_replace/test_link_actions.py b/tests/cms/views/link_replace/test_link_actions.py index 4cf5bb24b0..3c7330c67f 100644 --- a/tests/cms/views/link_replace/test_link_actions.py +++ b/tests/cms/views/link_replace/test_link_actions.py @@ -4,16 +4,14 @@ if TYPE_CHECKING: from _pytest.logging import LogCaptureFixture + from django.test.client import Client from pytest_django.fixtures import SettingsWrapper import pytest -from django.test.client import Client from django.urls import reverse -from linkcheck import listeners from linkcheck.listeners import enable_listeners from linkcheck.models import Link, Url -from integreat_cms.cms.models import Region from integreat_cms.cms.utils.linkcheck_utils import filter_urls from tests.conftest import ANONYMOUS, EDITOR, MANAGEMENT, STAFF_ROLES from tests.utils import assert_message_in_log @@ -26,12 +24,10 @@ OLD_URL = "https://integreat.app/augsburg/de/willkommen/" # Url to replace with NEW_URL = "https://integreat.app/augsburg/de/this-is-new-url/" -# Parameters for test -# ( -# , +# Parameters for test: +# ( , # , -# -# ) +# ) url_replace_parameters = [("network_management", 4, 0), ("augsburg", 4, 1)] @@ -60,7 +56,7 @@ def test_url_replace( "url_filter": "valid", "url_id": url_object.id, } - if not region == "network_management": + if region != "network_management": kwargs.update( { "region_slug": region, @@ -82,7 +78,7 @@ def test_url_replace( entitled_roles = ( STAFF_ROLES if region == "network_management" - else STAFF_ROLES + [MANAGEMENT, EDITOR] + else [*STAFF_ROLES, MANAGEMENT, EDITOR] ) if role in entitled_roles: @@ -94,7 +90,9 @@ def test_url_replace( ) assert Link.objects.filter(url__url=OLD_URL).count() == after - # assert Link.objects.filter(url__url=NEW_URL).count() == before - after + """ + assert Link.objects.filter(url__url=NEW_URL).count() == before - after + """ elif role == ANONYMOUS: assert response.status_code == 302 @@ -121,11 +119,9 @@ def test_url_replace( SEARCH_REPLACE_TARGET_URL = "https://integreat.app/augsburg/de/willkommen/" TARGET_URL_AFTER_REPLACE = "https://integreat.app/i/am/replaced/" # Parameters for test -# ( -# , +# ( , # , -# -# ) +# ) search_replace_parameters = [("network_management", 4, 0), ("augsburg", 4, 1)] @@ -151,7 +147,7 @@ def test_search_and_replace_links( assert Link.objects.filter(url=target_url_object).count() == before kwargs = {} - if not region == "network_management": + if region != "network_management": kwargs.update( { "region_slug": region, @@ -176,7 +172,7 @@ def test_search_and_replace_links( entitled_roles = ( STAFF_ROLES if region == "network_management" - else STAFF_ROLES + [MANAGEMENT, EDITOR] + else [*STAFF_ROLES, MANAGEMENT, EDITOR] ) if role in entitled_roles: @@ -188,10 +184,12 @@ def test_search_and_replace_links( ) assert Link.objects.filter(url__url=SEARCH_REPLACE_TARGET_URL).count() == after - # assert ( - # Link.objects.filter(url__url=TARGET_URL_AFTER_REPLACE).count() - # == before - after - # ) + """ + assert ( + Link.objects.filter(url__url=TARGET_URL_AFTER_REPLACE).count() + == before - after + ) + """ elif role == ANONYMOUS: assert response.status_code == 302 @@ -216,12 +214,10 @@ def test_search_and_replace_links( IGNORE_UNIGNORE_TARGET_URL = "https://integreat.app/augsburg/de/willkommen/" # Parameters for test -# ( -# , +# ( , # , # (, ), -# (, after successful action) -# ) +# (, after successful action) ) ignore_unignore_parameters = [ ("ignore", "network_management", (0, 4), (4, 0)), ("ignore", "augsburg", (0, 4), (3, 1)), @@ -261,7 +257,7 @@ def test_bulk_ignore_unignore_links( assert target_links.filter(ignore=False).count() == before[1] kwargs = {"url_filter": "valid"} - if not region == "network_management": + if region != "network_management": kwargs.update( { "region_slug": region, @@ -283,7 +279,7 @@ def test_bulk_ignore_unignore_links( entitled_roles = ( STAFF_ROLES if region == "network_management" - else STAFF_ROLES + [MANAGEMENT, EDITOR] + else [*STAFF_ROLES, MANAGEMENT, EDITOR] ) if role in entitled_roles: @@ -347,12 +343,12 @@ def test_bulk_recheck_links( target_url_object = Url.objects.filter(url=RECHECK_TARGET_URL).first() assert target_url_object - region_slug = region if not region == "network_management" else None + region_slug = region if region != "network_management" else None unchecked_url, _ = filter_urls(region_slug, "unchecked") assert len(unchecked_url) == 1 kwargs = {"url_filter": "unchecked"} - if not region == "network_management": + if region != "network_management": kwargs.update( { "region_slug": region, @@ -374,7 +370,7 @@ def test_bulk_recheck_links( entitled_roles = ( STAFF_ROLES if region == "network_management" - else STAFF_ROLES + [MANAGEMENT, EDITOR] + else [*STAFF_ROLES, MANAGEMENT, EDITOR] ) unchecked_url, _ = filter_urls(region_slug, "unchecked") diff --git a/tests/cms/views/link_replace/test_link_replace.py b/tests/cms/views/link_replace/test_link_replace.py index c67b64d2e6..3433e722d1 100644 --- a/tests/cms/views/link_replace/test_link_replace.py +++ b/tests/cms/views/link_replace/test_link_replace.py @@ -3,11 +3,13 @@ from typing import TYPE_CHECKING import pytest -from django.contrib.contenttypes.models import ContentType from integreat_cms.cms.models import PageTranslation, Region from integreat_cms.cms.utils.linkcheck_utils import find_target_url_per_content +if TYPE_CHECKING: + from django.contrib.contenttypes.models import ContentType + REGION: str = "nurnberg" # list of tuple (, , ) @@ -46,7 +48,10 @@ def test_find_target_url_per_content_invalid_links( # "d" is something many of our links can contain thanks to ".de" or language slug "de" result = find_target_url_per_content( - "d", "d", Region.objects.filter(slug=REGION).first(), link_types + "d", + "d", + Region.objects.filter(slug=REGION).first(), + link_types, ) content_type, content_id = target_content_detail diff --git a/tests/cms/views/organizations/test_organization_actions.py b/tests/cms/views/organizations/test_organization_actions.py index a86c568b88..3650678e90 100644 --- a/tests/cms/views/organizations/test_organization_actions.py +++ b/tests/cms/views/organizations/test_organization_actions.py @@ -8,7 +8,6 @@ from pytest_django.fixtures import SettingsWrapper import pytest -from django.test.client import Client from django.urls import reverse from integreat_cms.cms.models import Organization @@ -54,7 +53,7 @@ def test_archive_organization( ) response = client.post(archive_organization) - if role in HIGH_PRIV_STAFF_ROLES + [MANAGEMENT]: + if role in [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT]: assert response.status_code == 302 redirect_url = response.headers.get("location") @@ -65,7 +64,7 @@ def test_archive_organization( caplog, ) assert "Organization was successfully archived" in client.get( - redirect_url + redirect_url, ).content.decode("utf-8") assert Organization.objects.filter(id=organization_id).first().archived else: @@ -73,10 +72,9 @@ def test_archive_organization( "ERROR Organization couldn't be archived as it's used by a page, poi or user", caplog, ) - assert "Organization couldn't be archived as it's used by a page, poi or user" in client.get( - redirect_url - ).content.decode( - "utf-8" + assert ( + "Organization couldn't be archived as it's used by a page, poi or user" + in client.get(redirect_url).content.decode("utf-8") ) assert not Organization.objects.filter(id=organization_id).first().archived @@ -124,7 +122,7 @@ def test_delete_organization( ) response = client.post(delete_organization) - if role in HIGH_PRIV_STAFF_ROLES + [MANAGEMENT]: + if role in [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT]: assert response.status_code == 302 redirect_url = response.headers.get("location") @@ -135,7 +133,7 @@ def test_delete_organization( caplog, ) assert "Organization was successfully deleted" in client.get( - redirect_url + redirect_url, ).content.decode("utf-8") assert not Organization.objects.filter(id=organization_id).first() else: @@ -143,10 +141,9 @@ def test_delete_organization( "ERROR Organization couldn't be deleted as it's used by a page, poi or user", caplog, ) - assert "Organization couldn't be deleted as it's used by a page, poi or user" in client.get( - redirect_url - ).content.decode( - "utf-8" + assert ( + "Organization couldn't be deleted as it's used by a page, poi or user" + in client.get(redirect_url).content.decode("utf-8") ) assert Organization.objects.filter(id=organization_id).first() @@ -176,7 +173,8 @@ def test_restore_organization( settings.LANGUAGE_CODE = "en" archived_organization = Organization.objects.filter( - region__slug=REGION_SLUG, archived=True + region__slug=REGION_SLUG, + archived=True, ).first() assert archived_organization @@ -191,15 +189,15 @@ def test_restore_organization( ) response = client.post(restore_organization) - if role in HIGH_PRIV_STAFF_ROLES + [MANAGEMENT]: - response.status_code == 302 + if role in [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT]: + assert response.status_code == 302 redirect_url = response.headers.get("location") assert_message_in_log( "SUCCESS Organization was successfully restored", caplog, ) assert "Organization was successfully restored" in client.get( - redirect_url + redirect_url, ).content.decode("utf-8") assert ( not Organization.objects.filter(id=archived_organization_id) @@ -246,8 +244,8 @@ def test_bulk_archive_organizations( data={"selected_ids[]": BULK_ARCHIVE_SELECTED_IDS}, ) - if role in HIGH_PRIV_STAFF_ROLES + [MANAGEMENT]: - response.status_code == 302 + if role in [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT]: + assert response.status_code == 302 redirect_url = response.headers.get("location") redirect_page = client.get(redirect_url).content.decode("utf-8") @@ -316,8 +314,8 @@ def test_bulk_delete_organizations( data={"selected_ids[]": BULK_DELETE_SELECTED_IDS}, ) - if role in HIGH_PRIV_STAFF_ROLES + [MANAGEMENT]: - response.status_code == 302 + if role in [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT]: + assert response.status_code == 302 redirect_url = response.headers.get("location") redirect_page = client.get(redirect_url).content.decode("utf-8") @@ -339,7 +337,7 @@ def test_bulk_delete_organizations( in redirect_page ) assert not Organization.objects.filter( - id=NOT_REFERENCED_ORGANIZATION_ID + id=NOT_REFERENCED_ORGANIZATION_ID, ).exists() elif role == ANONYMOUS: assert response.status_code == 302 @@ -380,8 +378,8 @@ def test_bulk_restore_organizations( data={"selected_ids[]": BULK_RESTORE_SELECTED_IDS}, ) - if role in HIGH_PRIV_STAFF_ROLES + [MANAGEMENT]: - response.status_code == 302 + if role in [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT]: + assert response.status_code == 302 redirect_url = response.headers.get("location") redirect_page = client.get(redirect_url).content.decode("utf-8") diff --git a/tests/cms/views/organizations/test_organization_form.py b/tests/cms/views/organizations/test_organization_form.py index a506841138..bc3b07c892 100644 --- a/tests/cms/views/organizations/test_organization_form.py +++ b/tests/cms/views/organizations/test_organization_form.py @@ -8,10 +8,9 @@ from pytest_django.fixtures import SettingsWrapper import pytest -from django.test.client import Client from django.urls import reverse -from integreat_cms.cms.models import MediaFile, Organization, Page, POI, Region +from integreat_cms.cms.models import Organization, Page, POI from tests.conftest import ANONYMOUS, HIGH_PRIV_STAFF_ROLES, MANAGEMENT, STAFF_ROLES from tests.utils import assert_message_in_log @@ -48,7 +47,7 @@ def test_organization_form_shows_no_contents( ) response = client.get(edit_organization) - if role in STAFF_ROLES + [MANAGEMENT]: + if role in [*STAFF_ROLES, MANAGEMENT]: assert ( "This organization currently has no maintained pages." in response.content.decode("utf-8") @@ -116,21 +115,23 @@ def test_organization_form_shows_associated_contents( non_organization_page = Page.objects.filter(id=NON_ORGANIZATION_PAGE_ID).first() non_organization_poi = POI.objects.filter(id=NON_ORGANIZATION_POI_ID).first() - if role in STAFF_ROLES + [MANAGEMENT]: + if role in [*STAFF_ROLES, MANAGEMENT]: for content in organization_pages + organization_pois: assert content.get_translation( - region.default_language.slug + region.default_language.slug, ).title in response.content.decode("utf-8") - if not region.default_language.slug == "en": + if region.default_language.slug != "en": if english_translation := content.get_translation("en"): - english_translation.title in response.content.decode("utf-8") + assert english_translation.title in response.content.decode("utf-8") else: - "Translation not available" in response.content.decode("utf-8") + assert "Translation not available" in response.content.decode( + "utf-8", + ) assert non_organization_page.get_translation( - region.default_language.slug + region.default_language.slug, ).title not in response.content.decode("utf-8") assert non_organization_poi.get_translation( - region.default_language.slug + region.default_language.slug, ).title not in response.content.decode("utf-8") elif role == ANONYMOUS: @@ -176,7 +177,7 @@ def test_create_new_organization( }, ) - if role in HIGH_PRIV_STAFF_ROLES + [MANAGEMENT]: + if role in [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT]: assert response.status_code == 302 assert_message_in_log( f'SUCCESS Organization "{NEW_ORGANIZATION_NAME}" was successfully created', @@ -215,7 +216,7 @@ def test_cannot_create_organization_with_duplicate_name( settings.LANGUAGE_CODE = "en" existing_organization = Organization.objects.filter( - region__slug=REGION_SLUG + region__slug=REGION_SLUG, ).first() assert existing_organization @@ -235,7 +236,7 @@ def test_cannot_create_organization_with_duplicate_name( }, ) - if role in HIGH_PRIV_STAFF_ROLES + [MANAGEMENT]: + if role in [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT]: assert response.status_code == 200 assert_message_in_log( "ERROR Name: An organization with the same name already exists in this region. Please choose another name.", @@ -287,7 +288,7 @@ def test_edit_organization( }, ) - if role in HIGH_PRIV_STAFF_ROLES + [MANAGEMENT]: + if role in [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT]: assert response.status_code == 200 assert_message_in_log( 'SUCCESS Organization "I got a new name" was successfully saved', diff --git a/tests/cms/views/pages/test_page_translations.py b/tests/cms/views/pages/test_page_translations.py index 97ec2a7b91..0ec062f92b 100644 --- a/tests/cms/views/pages/test_page_translations.py +++ b/tests/cms/views/pages/test_page_translations.py @@ -8,7 +8,9 @@ @pytest.mark.parametrize( - "login_role_user", PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], indirect=True + "login_role_user", + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], + indirect=True, ) @pytest.mark.django_db def test_cleanup_autosaves( diff --git a/tests/cms/views/poi/test_poi_form.py b/tests/cms/views/poi/test_poi_form.py index 8b28995b95..96a0d5cfe0 100644 --- a/tests/cms/views/poi/test_poi_form.py +++ b/tests/cms/views/poi/test_poi_form.py @@ -10,7 +10,6 @@ from pytest_django.fixtures import SettingsWrapper from django.conf import settings -from django.test.client import Client from django.urls import reverse from integreat_cms.cms.constants import status @@ -198,7 +197,8 @@ def test_poi_in_use_not_bulk_archived( # Try to archive the POI by bulk action bulk_archive_pois = reverse( - "bulk_archive_pois", kwargs={"region_slug": "augsburg", "language_slug": "de"} + "bulk_archive_pois", + kwargs={"region_slug": "augsburg", "language_slug": "de"}, ) response = client.post(bulk_archive_pois, data={"selected_ids[]": [poi_id]}) diff --git a/tests/cms/views/poi_categories/test_poi_category_form_view.py b/tests/cms/views/poi_categories/test_poi_category_form_view.py index 146f3e8b0a..e990e7adf7 100644 --- a/tests/cms/views/poi_categories/test_poi_category_form_view.py +++ b/tests/cms/views/poi_categories/test_poi_category_form_view.py @@ -1,5 +1,3 @@ -import json - import pytest from django.conf import settings from django.test.client import Client @@ -100,7 +98,9 @@ def test_permission_to_create_new_poicategory( @pytest.mark.parametrize( - "login_role_user", [CMS_TEAM, ROOT, SERVICE_TEAM], indirect=True + "login_role_user", + [CMS_TEAM, ROOT, SERVICE_TEAM], + indirect=True, ) @pytest.mark.django_db def test_create_poi_category_with_missing_translation_was_not_successful( @@ -121,12 +121,14 @@ def test_create_poi_category_with_missing_translation_was_not_successful( assert response.status_code == 200 assert "Mindestens eine Übersetzung ist erforderlich." in response.content.decode( - "utf-8" + "utf-8", ) @pytest.mark.parametrize( - "login_role_user", [CMS_TEAM, ROOT, SERVICE_TEAM], indirect=True + "login_role_user", + [CMS_TEAM, ROOT, SERVICE_TEAM], + indirect=True, ) @pytest.mark.django_db def test_create_poi_category_was_successful( @@ -157,7 +159,9 @@ def test_create_poi_category_was_successful( @pytest.mark.parametrize( - "login_role_user", [CMS_TEAM, ROOT, SERVICE_TEAM], indirect=True + "login_role_user", + [CMS_TEAM, ROOT, SERVICE_TEAM], + indirect=True, ) @pytest.mark.django_db def test_edit_poi_category_was_successful( @@ -219,7 +223,9 @@ def test_edit_poi_category_was_successful( @pytest.mark.parametrize( - "login_role_user", [CMS_TEAM, ROOT, SERVICE_TEAM], indirect=True + "login_role_user", + [CMS_TEAM, ROOT, SERVICE_TEAM], + indirect=True, ) @pytest.mark.django_db def test_no_changes_were_made_message( @@ -277,7 +283,9 @@ def test_no_changes_were_made_message( @pytest.mark.parametrize( - "login_role_user", [CMS_TEAM, ROOT, SERVICE_TEAM], indirect=True + "login_role_user", + [CMS_TEAM, ROOT, SERVICE_TEAM], + indirect=True, ) @pytest.mark.django_db def test_delete_unused_poi_category_was_successful( @@ -303,7 +311,8 @@ def test_delete_unused_poi_category_was_successful( assert amount_of_poicategories == expected_amount_of_poicategories + 1 delete_poicategory_url = reverse( - "delete_poicategory", kwargs={"pk": id_of_unused_poicategory} + "delete_poicategory", + kwargs={"pk": id_of_unused_poicategory}, ) client.post(delete_poicategory_url, data={}) @@ -312,7 +321,9 @@ def test_delete_unused_poi_category_was_successful( @pytest.mark.parametrize( - "login_role_user", [CMS_TEAM, ROOT, SERVICE_TEAM], indirect=True + "login_role_user", + [CMS_TEAM, ROOT, SERVICE_TEAM], + indirect=True, ) @pytest.mark.django_db def test_delete_used_poi_category_was_not_successful( @@ -350,7 +361,8 @@ def test_delete_used_poi_category_was_not_successful( ) delete_poicategory_url = reverse( - "delete_poicategory", kwargs={"pk": id_of_unused_poicategory} + "delete_poicategory", + kwargs={"pk": id_of_unused_poicategory}, ) response = client.post(delete_poicategory_url, data={}) redirect = response.headers.get("location") diff --git a/tests/cms/views/push_notifications/test_push_notification_form_view.py b/tests/cms/views/push_notifications/test_push_notification_form_view.py index ecb46ad459..715dbba3e2 100644 --- a/tests/cms/views/push_notifications/test_push_notification_form_view.py +++ b/tests/cms/views/push_notifications/test_push_notification_form_view.py @@ -7,10 +7,9 @@ from pytest_django.fixtures import SettingsWrapper import pytest -from django.test.client import Client from django.urls import reverse -from tests.conftest import ANONYMOUS, PRIV_STAFF_ROLES, STAFF_ROLES, WRITE_ROLES +from tests.conftest import ANONYMOUS, PRIV_STAFF_ROLES, STAFF_ROLES # We use Augsburg (region with German as default language) and Berlin (region with English as default language) # to test every language is required which is the default language of at least one region of the push notification @@ -67,7 +66,7 @@ def test_validate_forms_with_no_german_title( if role in PRIV_STAFF_ROLES: assert "Title (German): This field is required" in response.content.decode( - "utf-8" + "utf-8", ) @@ -126,5 +125,5 @@ def test_validate_forms_with_only_german_title( if role in PRIV_STAFF_ROLES: assert "Title (English): This field is required" in response.content.decode( - "utf-8" + "utf-8", ) diff --git a/tests/cms/views/regions/delete_region.py b/tests/cms/views/regions/delete_region.py index 4854559ad0..fb014b4bfe 100644 --- a/tests/cms/views/regions/delete_region.py +++ b/tests/cms/views/regions/delete_region.py @@ -71,7 +71,7 @@ def test_delete_all_regions_is_successful( redirect = response.headers.get("location") response = client.get(redirect) assert "Region wurde erfolgreich gelöscht" in response.content.decode( - "utf-8" + "utf-8", ) if role in [CMS_TEAM, SERVICE_TEAM, ROOT]: diff --git a/tests/cms/views/regions/test_region_mt_management.py b/tests/cms/views/regions/test_region_mt_management.py index 0208ce91c6..9b6981c60c 100644 --- a/tests/cms/views/regions/test_region_mt_management.py +++ b/tests/cms/views/regions/test_region_mt_management.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING - import pytest from integreat_cms.cms.constants import months diff --git a/tests/cms/views/status_code/test_view_status_code_1.py b/tests/cms/views/status_code/test_view_status_code_1.py index 03ab81a3eb..ff36422ee2 100644 --- a/tests/cms/views/status_code/test_view_status_code_1.py +++ b/tests/cms/views/status_code/test_view_status_code_1.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db diff --git a/tests/cms/views/status_code/test_view_status_code_10.py b/tests/cms/views/status_code/test_view_status_code_10.py index 3dbaeaac54..143c9c6fe5 100644 --- a/tests/cms/views/status_code/test_view_status_code_10.py +++ b/tests/cms/views/status_code/test_view_status_code_10.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db diff --git a/tests/cms/views/status_code/test_view_status_code_11.py b/tests/cms/views/status_code/test_view_status_code_11.py index 2683c1e42a..62ba15b8c0 100644 --- a/tests/cms/views/status_code/test_view_status_code_11.py +++ b/tests/cms/views/status_code/test_view_status_code_11.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db @@ -25,5 +25,4 @@ def test_view_status_code_11( roles: list[str], mock_firebase_credentials: None, ) -> None: - # pylint: disable=too-many-positional-arguments check_view_status_code(login_role_user, caplog, view_name, kwargs, post_data, roles) diff --git a/tests/cms/views/status_code/test_view_status_code_12.py b/tests/cms/views/status_code/test_view_status_code_12.py index 96a550ab12..d60b430f87 100644 --- a/tests/cms/views/status_code/test_view_status_code_12.py +++ b/tests/cms/views/status_code/test_view_status_code_12.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db diff --git a/tests/cms/views/status_code/test_view_status_code_13.py b/tests/cms/views/status_code/test_view_status_code_13.py index abf5532469..4584eb3449 100644 --- a/tests/cms/views/status_code/test_view_status_code_13.py +++ b/tests/cms/views/status_code/test_view_status_code_13.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db diff --git a/tests/cms/views/status_code/test_view_status_code_14.py b/tests/cms/views/status_code/test_view_status_code_14.py index 24a59fd1f0..a89224f83a 100644 --- a/tests/cms/views/status_code/test_view_status_code_14.py +++ b/tests/cms/views/status_code/test_view_status_code_14.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db diff --git a/tests/cms/views/status_code/test_view_status_code_15.py b/tests/cms/views/status_code/test_view_status_code_15.py index 861f4872d3..b0241a29eb 100644 --- a/tests/cms/views/status_code/test_view_status_code_15.py +++ b/tests/cms/views/status_code/test_view_status_code_15.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db diff --git a/tests/cms/views/status_code/test_view_status_code_16.py b/tests/cms/views/status_code/test_view_status_code_16.py index 085c00a9cb..66b1d8105d 100644 --- a/tests/cms/views/status_code/test_view_status_code_16.py +++ b/tests/cms/views/status_code/test_view_status_code_16.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db diff --git a/tests/cms/views/status_code/test_view_status_code_2.py b/tests/cms/views/status_code/test_view_status_code_2.py index 93c7d3cc6e..db3a14002d 100644 --- a/tests/cms/views/status_code/test_view_status_code_2.py +++ b/tests/cms/views/status_code/test_view_status_code_2.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db diff --git a/tests/cms/views/status_code/test_view_status_code_3.py b/tests/cms/views/status_code/test_view_status_code_3.py index 88e466f3c4..626c894bf7 100644 --- a/tests/cms/views/status_code/test_view_status_code_3.py +++ b/tests/cms/views/status_code/test_view_status_code_3.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db diff --git a/tests/cms/views/status_code/test_view_status_code_4.py b/tests/cms/views/status_code/test_view_status_code_4.py index 9af362a8ff..0d1c948650 100644 --- a/tests/cms/views/status_code/test_view_status_code_4.py +++ b/tests/cms/views/status_code/test_view_status_code_4.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db diff --git a/tests/cms/views/status_code/test_view_status_code_5.py b/tests/cms/views/status_code/test_view_status_code_5.py index bfc2c8f93c..ea8720dc9a 100644 --- a/tests/cms/views/status_code/test_view_status_code_5.py +++ b/tests/cms/views/status_code/test_view_status_code_5.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db @@ -25,5 +25,4 @@ def test_view_status_code_5( roles: list[str], mock_firebase_credentials: None, ) -> None: - # pylint: disable=too-many-positional-arguments check_view_status_code(login_role_user, caplog, view_name, kwargs, post_data, roles) diff --git a/tests/cms/views/status_code/test_view_status_code_6.py b/tests/cms/views/status_code/test_view_status_code_6.py index 55ccf93c1d..cba97b0406 100644 --- a/tests/cms/views/status_code/test_view_status_code_6.py +++ b/tests/cms/views/status_code/test_view_status_code_6.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db diff --git a/tests/cms/views/status_code/test_view_status_code_7.py b/tests/cms/views/status_code/test_view_status_code_7.py index 662fea1f90..608fda388a 100644 --- a/tests/cms/views/status_code/test_view_status_code_7.py +++ b/tests/cms/views/status_code/test_view_status_code_7.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db diff --git a/tests/cms/views/status_code/test_view_status_code_8.py b/tests/cms/views/status_code/test_view_status_code_8.py index f510720910..cff42e9112 100644 --- a/tests/cms/views/status_code/test_view_status_code_8.py +++ b/tests/cms/views/status_code/test_view_status_code_8.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db diff --git a/tests/cms/views/status_code/test_view_status_code_9.py b/tests/cms/views/status_code/test_view_status_code_9.py index 44f82bdeb7..7fcc1a28c2 100644 --- a/tests/cms/views/status_code/test_view_status_code_9.py +++ b/tests/cms/views/status_code/test_view_status_code_9.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from ..utils import check_view_status_code from ..view_config import PARAMETRIZED_VIEWS @@ -12,6 +11,7 @@ from typing import Any from _pytest.logging import LogCaptureFixture + from django.test.client import Client @pytest.mark.django_db diff --git a/tests/cms/views/test_view_redirect.py b/tests/cms/views/test_view_redirect.py index a7bcc48a53..c3913907b1 100644 --- a/tests/cms/views/test_view_redirect.py +++ b/tests/cms/views/test_view_redirect.py @@ -3,15 +3,13 @@ from typing import TYPE_CHECKING import pytest -from django.test.client import Client from django.urls import reverse from .view_config import PARAMETRIZED_REDIRECT_VIEWS if TYPE_CHECKING: - from typing import Any - from _pytest.logging import LogCaptureFixture + from django.test.client import Client from .view_config import RedirectTarget, Roles, ViewKwargs, ViewNameStr diff --git a/tests/cms/views/utils.py b/tests/cms/views/utils.py index 814d78235f..61835d0a34 100644 --- a/tests/cms/views/utils.py +++ b/tests/cms/views/utils.py @@ -3,16 +3,14 @@ from typing import TYPE_CHECKING from django.conf import settings -from django.test.client import Client from django.urls import reverse from ...conftest import ANONYMOUS from ...utils import assert_no_error_messages if TYPE_CHECKING: - from typing import Any - from _pytest.logging import LogCaptureFixture + from django.test.client import Client from .view_config import PostData, Roles, ViewKwargs, ViewName @@ -62,28 +60,32 @@ def check_view_status_code( assert response.status_code in [ 200, 201, - ], f"JSON view {view_name} returned status code {response.status_code} instead of 200 or 201 for role {role}" + ], ( + f"JSON view {view_name} returned status code {response.status_code} instead of 200 or 201 for role {role}" + ) else: # Normal post-views should redirect after a successful operation (200 usually mean form errors) - assert ( - response.status_code == 302 - ), f"POST view {view_name} returned status code {response.status_code} instead of 302 for role {role}" + assert response.status_code == 302, ( + f"POST view {view_name} returned status code {response.status_code} instead of 302 for role {role}" + ) else: # Get-views should return 200 - assert ( - response.status_code == 200 - ), f"GET view {view_name} returned status code {response.status_code} instead of 200 for role {role}" + assert response.status_code == 200, ( + f"GET view {view_name} returned status code {response.status_code} instead of 200 for role {role}" + ) elif role == ANONYMOUS: # For anonymous users, we want to redirect to the login form instead of showing an error - assert ( - response.status_code == 302 - ), f"View {view_name} did not enforce access control for anonymous users (status code {response.status_code} instead of 302)" + assert response.status_code == 302, ( + f"View {view_name} did not enforce access control for anonymous users (status code {response.status_code} instead of 302)" + ) assert ( response.headers.get("location") == f"{settings.LOGIN_URL}?next={next_url}" - ), f"View {view_name} did not redirect to login for anonymous users (location header {response.headers.get('location')})" + ), ( + f"View {view_name} did not redirect to login for anonymous users (location header {response.headers.get('location')})" + ) else: # For logged in users, we want to show an error if they get a permission denied - assert ( - response.status_code == 403 - ), f"View {view_name} did not enforce access control for role {role} (status code {response.status_code} instead of 403)" + assert response.status_code == 403, ( + f"View {view_name} did not enforce access control for role {role} (status code {response.status_code} instead of 403)" + ) diff --git a/tests/cms/views/utils/test_hix.py b/tests/cms/views/utils/test_hix.py index acd982e749..a4dfcdbbc5 100644 --- a/tests/cms/views/utils/test_hix.py +++ b/tests/cms/views/utils/test_hix.py @@ -5,7 +5,6 @@ from typing import TYPE_CHECKING import pytest -from pytest_django.fixtures import SettingsWrapper from integreat_cms.cms.constants.administrative_division import MUNICIPALITY from integreat_cms.cms.constants.region_status import ACTIVE @@ -26,6 +25,7 @@ from tests.utils import disable_hix_post_save_signal if TYPE_CHECKING: + from pytest_django.fixtures import SettingsWrapper from pytest_django.plugin import _DatabaseBlocker # type: ignore[attr-defined] @@ -68,10 +68,12 @@ def test_hix_rounding(settings: SettingsWrapper) -> None: # Since this will likely differ from the mathematical threshold due to floating point errors, # get the nearest value representable in memory as examples for the raw hix score hix_score_just_on_threshold = math.nextafter( - actual_raw_hix_score_threshold, math.inf + actual_raw_hix_score_threshold, + math.inf, ) hix_score_just_under_threshold = math.nextafter( - actual_raw_hix_score_threshold, -math.inf + actual_raw_hix_score_threshold, + -math.inf, ) assert ( @@ -84,7 +86,8 @@ def test_hix_rounding(settings: SettingsWrapper) -> None: @pytest.mark.django_db def test_disregard_archived_pages( - settings: SettingsWrapper, dummy_region: Region + settings: SettingsWrapper, + dummy_region: Region, ) -> None: dummy_language = dummy_region.default_language settings.TEXTLAB_API_LANGUAGES = [dummy_language.slug] @@ -146,7 +149,8 @@ def test_disregard_archived_pages( @pytest.mark.django_db def test_disregard_pages_with_hix_ignore( - settings: SettingsWrapper, dummy_region: Region + settings: SettingsWrapper, + dummy_region: Region, ) -> None: dummy_language = dummy_region.default_language settings.TEXTLAB_API_LANGUAGES = [dummy_language.slug] @@ -223,10 +227,12 @@ def test_hix_values(settings: SettingsWrapper, dummy_region: Region) -> None: # Since this will likely differ from the mathematical threshold due to floating point errors, # get the nearest value representable in memory as examples for the raw hix score hix_score_just_on_threshold = math.nextafter( - actual_raw_hix_score_threshold, math.inf + actual_raw_hix_score_threshold, + math.inf, ) hix_score_just_under_threshold = math.nextafter( - actual_raw_hix_score_threshold, -math.inf + actual_raw_hix_score_threshold, + -math.inf, ) with disable_hix_post_save_signal(): diff --git a/tests/cms/views/view_config.py b/tests/cms/views/view_config.py index c9c351fa68..3d96aaf186 100644 --- a/tests/cms/views/view_config.py +++ b/tests/cms/views/view_config.py @@ -31,18 +31,17 @@ ) if TYPE_CHECKING: - import sys - from typing import Any, Final, TypeAlias, Union + from typing import Any, Final, TypeAlias ViewNameStr: TypeAlias = str ViewNameGetparams: TypeAlias = str - ViewName: TypeAlias = Union[ViewNameStr, tuple[ViewNameStr, ViewNameGetparams]] + ViewName: TypeAlias = ViewNameStr | tuple[ViewNameStr, ViewNameGetparams] Roles: TypeAlias = list[str] PostDataDict: TypeAlias = dict[str, Any] PostDataJSON: TypeAlias = str - PostData: TypeAlias = Union[PostDataDict, PostDataJSON] - View: TypeAlias = Union[tuple[ViewName, Roles], tuple[ViewName, Roles, PostData]] - ViewKwargs: TypeAlias = dict[str, Union[str, int]] + PostData: TypeAlias = PostDataDict | PostDataJSON + View: TypeAlias = tuple[ViewName, Roles] | tuple[ViewName, Roles, PostData] + ViewKwargs: TypeAlias = dict[str, str | int] ViewGroup: TypeAlias = tuple[list[View], ViewKwargs] ViewConfig: TypeAlias = list[ViewGroup] @@ -55,7 +54,10 @@ RedirectViewConfig: TypeAlias = list[RedirectViewGroup] ParametrizedRedirectView: TypeAlias = tuple[ - ViewName, ViewKwargs, Roles, RedirectTarget + ViewName, + ViewKwargs, + Roles, + RedirectTarget, ] ParametrizedRedirectViewConfig: TypeAlias = list[ParametrizedRedirectView] @@ -153,18 +155,18 @@ HIGH_PRIV_STAFF_ROLES, {"selected_ids[]": [1, 2, 3]}, ), - ("new_region_user", STAFF_ROLES + [MANAGEMENT]), + ("new_region_user", [*STAFF_ROLES, MANAGEMENT]), ( "new_region_user", - HIGH_PRIV_STAFF_ROLES + [MANAGEMENT], + [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT], {"username": "new_username", "email": "new@email.address", "role": 1}, ), ("release_notes", ROLES), - ("region_feedback", STAFF_ROLES + [MANAGEMENT]), - ("region_users", STAFF_ROLES + [MANAGEMENT]), - ("translation_coverage", STAFF_ROLES + [MANAGEMENT, EDITOR]), - ("organizations", STAFF_ROLES + [MANAGEMENT]), - ("new_organization", STAFF_ROLES + [MANAGEMENT]), + ("region_feedback", [*STAFF_ROLES, MANAGEMENT]), + ("region_users", [*STAFF_ROLES, MANAGEMENT]), + ("translation_coverage", [*STAFF_ROLES, MANAGEMENT, EDITOR]), + ("organizations", [*STAFF_ROLES, MANAGEMENT]), + ("new_organization", [*STAFF_ROLES, MANAGEMENT]), ("user_settings", ROLES), ( "user_settings", @@ -183,10 +185,10 @@ }, ), ("authenticate_modify_mfa", ROLES), - ("translations_management", HIGH_PRIV_STAFF_ROLES + [MANAGEMENT]), + ("translations_management", [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT]), ( "translations_management", - HIGH_PRIV_STAFF_ROLES + [MANAGEMENT], + [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT], { "machine_translate_pages": 0, "machine_translate_events": 1, @@ -195,46 +197,46 @@ ), ( "grant_page_permission_ajax", - HIGH_PRIV_STAFF_ROLES + [MANAGEMENT], + [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT], json.dumps( { "page_id": 21, "permission": "edit", "user_id": 10, - } + }, ), ), ( "grant_page_permission_ajax", - HIGH_PRIV_STAFF_ROLES + [MANAGEMENT], + [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT], json.dumps( { "page_id": 21, "permission": "publish", "user_id": 9, - } + }, ), ), ( "revoke_page_permission_ajax", - HIGH_PRIV_STAFF_ROLES + [MANAGEMENT], + [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT], json.dumps( { "page_id": 5, "permission": "edit", "user_id": 10, - } + }, ), ), ( "revoke_page_permission_ajax", - HIGH_PRIV_STAFF_ROLES + [MANAGEMENT], + [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT], json.dumps( { "page_id": 6, "permission": "publish", "user_id": 10, - } + }, ), ), ], @@ -300,12 +302,12 @@ ( [ ("sitemap:region_language", ALL_ROLES), - ("archived_pages", STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), + ("archived_pages", [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), ("archived_pois", ROLES), - ("edit_imprint", STAFF_ROLES + [MANAGEMENT]), + ("edit_imprint", [*STAFF_ROLES, MANAGEMENT]), ( "edit_imprint", - PRIV_STAFF_ROLES + [MANAGEMENT], + [*PRIV_STAFF_ROLES, MANAGEMENT], {"title": "imprint", "status": status.DRAFT}, ), ("events", ROLES), @@ -317,7 +319,7 @@ "events_time_range": "CUSTOM", "date_from": "2023-01-01", "date_to": "2030-12-31", - } + }, ), ), ROLES, @@ -329,7 +331,7 @@ { "events_time_range": "CUSTOM", "date_from": "2023-01-01", - } + }, ), ), ROLES, @@ -341,7 +343,7 @@ { "events_time_range": "CUSTOM", "date_to": "2030-12-31", - } + }, ), ), ROLES, @@ -352,7 +354,7 @@ parse.urlencode( { "events_time_range": ["PAST", "UPCOMING"], - } + }, ), ), ROLES, @@ -366,7 +368,7 @@ "poi_id": 4, "all_day": 1, "recurring": 1, - } + }, ), ), ROLES, @@ -380,7 +382,7 @@ "all_day": 2, "recurring": 2, "query": "test", - } + }, ), ), ROLES, @@ -399,10 +401,10 @@ "has_not_location": True, }, ), - ("new_page", STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), + ("new_page", [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), ( "new_page", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], { "title": "new page", "mirrored_page_region": "", @@ -413,7 +415,7 @@ ), ( "new_page", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], { "title": "new page", "mirrored_page_region": "", @@ -440,26 +442,26 @@ "category": 1, }, ), - ("pages", STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), + ("pages", [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), ("pois", ROLES), ( "bulk_archive_pages", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"selected_ids[]": [1, 2, 3]}, ), ( "bulk_restore_pages", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"selected_ids[]": [1, 2, 3]}, ), ( "publish_multiple_pages", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"selected_ids[]": [1]}, ), ( "draft_multiple_pages", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"selected_ids[]": [1]}, ), ( @@ -510,7 +512,7 @@ "query_string": "Test-Veranstaltung", "object_types": ["event"], "archived": False, - } + }, ), ), ( @@ -521,40 +523,40 @@ "query_string": "Test-Ort", "object_types": ["poi"], "archived": False, - } + }, ), ), ( "search_content_ajax", - STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER], + [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER], json.dumps( { "query_string": "Willkommen", "object_types": ["page"], "archived": False, - } + }, ), ), ( "search_content_ajax", - STAFF_ROLES + [MANAGEMENT], + [*STAFF_ROLES, MANAGEMENT], json.dumps( { "query_string": "Test", "object_types": ["feedback"], "archived": False, - } + }, ), ), ( "search_content_ajax", - STAFF_ROLES + [MANAGEMENT], + [*STAFF_ROLES, MANAGEMENT], json.dumps( { "query_string": "Test", "object_types": ["push_notification"], "archived": False, - } + }, ), ), ( @@ -565,18 +567,18 @@ "query_string": "Augsburg", "object_types": ["region"], "archived": False, - } + }, ), ), ( "search_content_ajax", - STAFF_ROLES + [MANAGEMENT], + [*STAFF_ROLES, MANAGEMENT], json.dumps( { "query_string": "root", "object_types": ["user"], "archived": False, - } + }, ), ), ( @@ -587,7 +589,7 @@ "query_string": "Test", "object_types": ["media"], "archived": False, - } + }, ), ), ], @@ -603,7 +605,7 @@ { "title": "Slugify event", "model_id": 1, - } + }, ), ), ], @@ -618,7 +620,7 @@ { "title": "Slugify poi", "model_id": 4, - } + }, ), ), ], @@ -628,12 +630,12 @@ [ ( "slugify_ajax", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], json.dumps( { "title": "Slugify page", "model_id": 1, - } + }, ), ), ], @@ -643,7 +645,7 @@ [ ( "publish_multiple_pages", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"selected_ids[]": [5]}, ), ], @@ -651,10 +653,10 @@ ), ( [ - ("sbs_edit_imprint", STAFF_ROLES + [MANAGEMENT]), + ("sbs_edit_imprint", [*STAFF_ROLES, MANAGEMENT]), ( "sbs_edit_imprint", - PRIV_STAFF_ROLES + [MANAGEMENT], + [*PRIV_STAFF_ROLES, MANAGEMENT], {"title": "imprint", "status": status.DRAFT}, ), ], @@ -665,7 +667,7 @@ [ ( "edit_page", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], { "title": "new page", "mirrored_page_region": "", @@ -681,7 +683,7 @@ [ ( "edit_page", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER], { "title": "new page", "mirrored_page_region": "", @@ -697,7 +699,7 @@ [ ( "edit_page", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, OBSERVER], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, OBSERVER], { "title": "new page", "mirrored_page_region": "", @@ -888,13 +890,13 @@ "translations-1-language": 2, "translations-1-name": "Test category", }, - ) + ), ], # The kwargs for these views {"pk": 1}, ), ( - [("linkcheck", STAFF_ROLES + [MANAGEMENT, EDITOR])], + [("linkcheck", [*STAFF_ROLES, MANAGEMENT, EDITOR])], # The kwargs for these views {"region_slug": "augsburg", "url_filter": "valid"}, ), @@ -904,7 +906,7 @@ {"region_slug": "nurnberg", "url_filter": "valid"}, ), ( - [("linkcheck", STAFF_ROLES + [MANAGEMENT, EDITOR])], + [("linkcheck", [*STAFF_ROLES, MANAGEMENT, EDITOR])], # The kwargs for these views {"region_slug": "augsburg", "url_filter": "unchecked"}, ), @@ -914,7 +916,7 @@ {"region_slug": "nurnberg", "url_filter": "unchecked"}, ), ( - [("linkcheck", STAFF_ROLES + [MANAGEMENT, EDITOR])], + [("linkcheck", [*STAFF_ROLES, MANAGEMENT, EDITOR])], # The kwargs for these views {"region_slug": "augsburg", "url_filter": "ignored"}, ), @@ -924,7 +926,7 @@ {"region_slug": "nurnberg", "url_filter": "ignored"}, ), ( - [("linkcheck", STAFF_ROLES + [MANAGEMENT, EDITOR])], + [("linkcheck", [*STAFF_ROLES, MANAGEMENT, EDITOR])], # The kwargs for these views {"region_slug": "augsburg", "url_filter": "invalid"}, ), @@ -937,8 +939,8 @@ [ ( "get_page_order_table_ajax", - STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER], - ) + [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER], + ), ], # The kwargs for these views {"region_slug": "augsburg", "parent_id": 1}, @@ -952,22 +954,22 @@ [ ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"revision": 1, "status": status.REVIEW}, ), ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], {"revision": 1, "status": status.PUBLIC}, ), ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"revision": 1, "status": status.DRAFT}, ), ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], {"revision": 2, "status": status.PUBLIC}, ), ], @@ -978,22 +980,22 @@ [ ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"revision": 1, "status": status.REVIEW}, ), ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], {"revision": 1, "status": status.PUBLIC}, ), ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"revision": 1, "status": status.DRAFT}, ), ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], {"revision": 2, "status": status.PUBLIC}, ), ], @@ -1004,27 +1006,27 @@ [ ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"revision": 1, "status": status.REVIEW}, ), ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], {"revision": 1, "status": status.PUBLIC}, ), ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"revision": 1, "status": status.DRAFT}, ), ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], {"revision": 2, "status": status.PUBLIC}, ), ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"revision": 2}, ), ], @@ -1035,27 +1037,27 @@ [ ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"revision": 1, "status": status.REVIEW}, ), ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], {"revision": 1, "status": status.PUBLIC}, ), ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"revision": 1, "status": status.DRAFT}, ), ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], {"revision": 2, "status": status.PUBLIC}, ), ( "page_versions", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"revision": 2}, ), ], @@ -1069,7 +1071,7 @@ # Archived pages should raise PermissionDenied for all roles [], {"revision": 1, "status": status.PUBLIC}, - ) + ), ], # The kwargs for these views {"region_slug": "augsburg", "language_slug": "de", "page_id": 25}, @@ -1140,27 +1142,27 @@ [ ( "imprint_versions", - PRIV_STAFF_ROLES + [MANAGEMENT], + [*PRIV_STAFF_ROLES, MANAGEMENT], {"revision": 1, "status": status.REVIEW}, ), ( "imprint_versions", - PRIV_STAFF_ROLES + [MANAGEMENT], + [*PRIV_STAFF_ROLES, MANAGEMENT], {"revision": 1, "status": status.PUBLIC}, ), ( "imprint_versions", - PRIV_STAFF_ROLES + [MANAGEMENT], + [*PRIV_STAFF_ROLES, MANAGEMENT], {"revision": 1, "status": status.DRAFT}, ), ( "imprint_versions", - PRIV_STAFF_ROLES + [MANAGEMENT], + [*PRIV_STAFF_ROLES, MANAGEMENT], {"revision": 1}, ), ( "imprint_versions", - PRIV_STAFF_ROLES + [MANAGEMENT], + [*PRIV_STAFF_ROLES, MANAGEMENT], {"revision": 2, "status": status.PUBLIC}, ), ], @@ -1171,8 +1173,8 @@ [ ( "get_page_order_table_ajax", - STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER], - ) + [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER], + ), ], # The kwargs for these views {"region_slug": "augsburg", "parent_id": 1, "page_id": 2}, @@ -1186,8 +1188,8 @@ [ ( "get_page_order_table_ajax", - STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER], - ) + [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER], + ), ], # The kwargs for these views {"region_slug": "augsburg", "page_id": 2}, @@ -1201,9 +1203,9 @@ [ ( "get_page_tree_ajax", - STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER], + [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER], json.dumps([2]), - ) + ), ], # The kwargs for these views {"region_slug": "augsburg", "language_slug": "de"}, @@ -1215,11 +1217,11 @@ ), ( [ - ("edit_page", STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), - ("edit_page", STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), + ("edit_page", [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), + ("edit_page", [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), ( "edit_page", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], { "title": "new title", "mirrored_page_region": "", @@ -1230,7 +1232,7 @@ ), ( "edit_page", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], { "title": "new title", "mirrored_page_region": "", @@ -1241,7 +1243,7 @@ ), ( "edit_page", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], { "title": "new title", "mirrored_page_region": "", @@ -1252,7 +1254,7 @@ ), ( "edit_page", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], { "title": "new title", "mirrored_page_region": "", @@ -1261,16 +1263,16 @@ "status": status.PUBLIC, }, ), - ("sbs_edit_page", STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), - ("page_versions", STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), + ("sbs_edit_page", [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), + ("page_versions", [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), ( "archive_page", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"post_data": True}, ), ( "restore_page", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"post_data": True}, ), ], @@ -1279,11 +1281,11 @@ ), ( [ - ("edit_page", STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), - ("edit_page", STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), + ("edit_page", [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), + ("edit_page", [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), ( "edit_page", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], { "title": "new title", "mirrored_page_region": "", @@ -1294,7 +1296,7 @@ ), ( "edit_page", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], { "title": "new title", "mirrored_page_region": "", @@ -1305,7 +1307,7 @@ ), ( "edit_page", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR], { "title": "new title", "mirrored_page_region": "", @@ -1314,7 +1316,7 @@ "status": status.PUBLIC, }, ), - ("sbs_edit_page", STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), + ("sbs_edit_page", [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER]), ("delete_page", HIGH_PRIV_STAFF_ROLES, {"post_data": True}), ], # The kwargs for these views @@ -1342,9 +1344,9 @@ [ ( "move_page", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"post_data": True}, - ) + ), ], # The kwargs for these views { @@ -1356,7 +1358,7 @@ }, ), ( - [("page_versions", STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER])], + [("page_versions", [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER])], # The kwargs for these views { "region_slug": "augsburg", @@ -1449,10 +1451,10 @@ ), ( [ - ("edit_region_user", STAFF_ROLES + [MANAGEMENT]), + ("edit_region_user", [*STAFF_ROLES, MANAGEMENT]), ( "edit_region_user", - HIGH_PRIV_STAFF_ROLES + [MANAGEMENT], + [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT], {"username": "new_username", "email": "new@email.address", "role": 1}, ), ], @@ -1473,9 +1475,9 @@ [ ( "cancel_translation_process_ajax", - PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], {"post_data": True}, - ) + ), ], # The kwargs for these views {"region_slug": "augsburg", "language_slug": "en", "page_id": 1}, @@ -1486,10 +1488,10 @@ VIEWS += [ ( [ - ("new_push_notification", STAFF_ROLES + [MANAGEMENT]), + ("new_push_notification", [*STAFF_ROLES, MANAGEMENT]), ( "new_push_notification", - PRIV_STAFF_ROLES + [MANAGEMENT], + [*PRIV_STAFF_ROLES, MANAGEMENT], { "translations-TOTAL_FORMS": 3, "translations-INITIAL_FORMS": 0, @@ -1512,7 +1514,7 @@ ), ( "new_push_notification", - PRIV_STAFF_ROLES + [MANAGEMENT], + [*PRIV_STAFF_ROLES, MANAGEMENT], { "translations-TOTAL_FORMS": 3, "translations-INITIAL_FORMS": 0, @@ -1538,7 +1540,7 @@ ), ( "new_push_notification", - PRIV_STAFF_ROLES + [MANAGEMENT], + [*PRIV_STAFF_ROLES, MANAGEMENT], { "translations-TOTAL_FORMS": 2, "translations-INITIAL_FORMS": 0, @@ -1558,8 +1560,8 @@ "submit_draft": True, }, ), - ("push_notifications", STAFF_ROLES + [MANAGEMENT]), - ("push_notifications_templates", STAFF_ROLES + [MANAGEMENT]), + ("push_notifications", [*STAFF_ROLES, MANAGEMENT]), + ("push_notifications_templates", [*STAFF_ROLES, MANAGEMENT]), ], # The kwargs for these views {"region_slug": "augsburg", "language_slug": "de"}, @@ -1568,7 +1570,7 @@ [ ( "edit_push_notification", - PRIV_STAFF_ROLES + [MANAGEMENT], + [*PRIV_STAFF_ROLES, MANAGEMENT], { "translations-TOTAL_FORMS": 2, "translations-INITIAL_FORMS": 2, @@ -1590,7 +1592,7 @@ ), ( "edit_push_notification", - PRIV_STAFF_ROLES + [MANAGEMENT], + [*PRIV_STAFF_ROLES, MANAGEMENT], { "translations-TOTAL_FORMS": 2, "translations-INITIAL_FORMS": 2, @@ -1622,7 +1624,7 @@ [ ( "edit_push_notification", - PRIV_STAFF_ROLES + [MANAGEMENT], + [*PRIV_STAFF_ROLES, MANAGEMENT], { "translations-TOTAL_FORMS": 1, "translations-INITIAL_FORMS": 1, @@ -1642,7 +1644,7 @@ ), ( "edit_push_notification", - PRIV_STAFF_ROLES + [MANAGEMENT], + [*PRIV_STAFF_ROLES, MANAGEMENT], { "translations-TOTAL_FORMS": 1, "translations-INITIAL_FORMS": 1, @@ -1731,7 +1733,7 @@ [ ( "statistics", - STAFF_ROLES + [MANAGEMENT], + [*STAFF_ROLES, MANAGEMENT], reverse("dashboard", kwargs={"region_slug": "augsburg"}), ), ( @@ -1746,7 +1748,7 @@ ), ( "linkcheck_landing", - STAFF_ROLES + [MANAGEMENT, EDITOR], + [*STAFF_ROLES, MANAGEMENT, EDITOR], reverse( "linkcheck", kwargs={"region_slug": "augsburg", "url_filter": "invalid"}, @@ -1791,7 +1793,7 @@ [ ( "sbs_edit_page", - STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR], + [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR], reverse( "edit_page", kwargs={ @@ -1800,7 +1802,7 @@ "page_id": 1, }, ), - ) + ), ], # The kwargs for these views {"region_slug": "augsburg", "language_slug": "de", "page_id": 1}, @@ -1811,7 +1813,7 @@ "public:expand_page_translation_id", ALL_ROLES, "https://integreat.app/augsburg/de/willkommen/", - ) + ), ], # The kwargs for these views {"short_url_id": 1}, diff --git a/tests/conftest.py b/tests/conftest.py index cff5ac61ba..6a29ce78bb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,14 +4,13 @@ from __future__ import annotations -from typing import Generator, TYPE_CHECKING +from typing import TYPE_CHECKING from unittest.mock import patch import pytest from django.contrib.auth import get_user_model from django.core.management import call_command from django.test.client import AsyncClient, Client -from pytest_httpserver.httpserver import HTTPServer from integreat_cms.cms.constants.roles import ( APP_TEAM, @@ -28,10 +27,12 @@ from tests.mock import MockServer if TYPE_CHECKING: + from collections.abc import Generator from typing import Final from _pytest.fixtures import SubRequest from pytest_django.plugin import _DatabaseBlocker # type: ignore[attr-defined] + from pytest_httpserver.httpserver import HTTPServer #: A role identifier for superusers @@ -42,7 +43,7 @@ #: All roles with editing permissions WRITE_ROLES: Final = [MANAGEMENT, EDITOR, AUTHOR, EVENT_MANAGER] #: All roles of region users -REGION_ROLES: Final = WRITE_ROLES + [OBSERVER] +REGION_ROLES: Final = [*WRITE_ROLES, OBSERVER] #: All roles of staff users STAFF_ROLES: Final = [ROOT, SERVICE_TEAM, CMS_TEAM, APP_TEAM, MARKETING_TEAM] #: All roles of staff users that don't just have read-only permissions @@ -52,7 +53,7 @@ #: All region and staff roles ROLES: Final = REGION_ROLES + STAFF_ROLES #: All region and staff roles and anonymous users -ALL_ROLES: Final = ROLES + [ANONYMOUS] +ALL_ROLES: Final = [*ROLES, ANONYMOUS] #: Enable the aiohttp pytest plugin to make use of the test server pytest_plugins: Final = "aiohttp.pytest_plugin" @@ -72,7 +73,8 @@ def load_test_data(django_db_setup: None, django_db_blocker: _DatabaseBlocker) - @pytest.fixture(scope="function") def load_test_data_transactional( - transactional_db: None, django_db_blocker: _DatabaseBlocker + transactional_db: None, + django_db_blocker: _DatabaseBlocker, ) -> None: """ Load the test data initially for all transactional test cases @@ -87,9 +89,10 @@ def load_test_data_transactional( @pytest.fixture(scope="session", params=ALL_ROLES) def login_role_user( - request: SubRequest, load_test_data: None, django_db_blocker: _DatabaseBlocker + request: SubRequest, + load_test_data: None, + django_db_blocker: _DatabaseBlocker, ) -> tuple[Client, str]: - # pylint: disable=redefined-outer-name """ Get the test user of the current role and force a login. Gets executed only once per user. @@ -109,9 +112,10 @@ def login_role_user( @pytest.fixture(scope="session", params=ALL_ROLES) def login_role_user_async( - request: SubRequest, load_test_data: None, django_db_blocker: _DatabaseBlocker + request: SubRequest, + load_test_data: None, + django_db_blocker: _DatabaseBlocker, ) -> tuple[AsyncClient, str]: - # pylint: disable=redefined-outer-name """ Get the test user of the current role and force a login. Gets executed only once per user. Identical to :meth:`~tests.conftest.login_role_user` with the difference that it returns diff --git a/tests/core/management/commands/test_duplicate_pages.py b/tests/core/management/commands/test_duplicate_pages.py index 5887cee14c..c190f814cb 100644 --- a/tests/core/management/commands/test_duplicate_pages.py +++ b/tests/core/management/commands/test_duplicate_pages.py @@ -1,13 +1,17 @@ from __future__ import annotations +from typing import TYPE_CHECKING + import pytest from django.core.management.base import CommandError -from pytest_django.fixtures import SettingsWrapper from integreat_cms.cms.models import Page, PageTranslation from ..utils import get_command_output +if TYPE_CHECKING: + from pytest_django.fixtures import SettingsWrapper + def test_duplicate_pages_prod() -> None: """ @@ -51,7 +55,7 @@ def test_duplicate_pages(settings: SettingsWrapper, load_test_data: None) -> Non region_slug = "augsburg" page_count_before = Page.objects.filter(region__slug=region_slug).count() translations_count_before = PageTranslation.objects.filter( - page__region__slug=region_slug + page__region__slug=region_slug, ).count() out, err = get_command_output("duplicate_pages", "augsburg") assert ( @@ -60,11 +64,11 @@ def test_duplicate_pages(settings: SettingsWrapper, load_test_data: None) -> Non assert not err page_count_after = Page.objects.filter(region__slug=region_slug).count() translations_count_after = PageTranslation.objects.filter( - page__region__slug=region_slug + page__region__slug=region_slug, ).count() - assert ( - page_count_after == 2 * page_count_before - ), "The count of pages should have doubled after the command" - assert ( - translations_count_after == 2 * translations_count_before - ), "The count of page translations should have doubled after the command" + assert page_count_after == 2 * page_count_before, ( + "The count of pages should have doubled after the command" + ) + assert translations_count_after == 2 * translations_count_before, ( + "The count of page translations should have doubled after the command" + ) diff --git a/tests/core/management/commands/test_fix_internal_links.py b/tests/core/management/commands/test_fix_internal_links.py index 8f6be26d1a..95001dc10c 100644 --- a/tests/core/management/commands/test_fix_internal_links.py +++ b/tests/core/management/commands/test_fix_internal_links.py @@ -23,7 +23,7 @@ def test_fix_internal_links_non_existing_region(load_test_data: None) -> None: """ with pytest.raises(CommandError) as exc_info: assert not any( - get_command_output("fix_internal_links", "--region-slug=non-existing") + get_command_output("fix_internal_links", "--region-slug=non-existing"), ) assert str(exc_info.value) == 'Region with slug "non-existing" does not exist.' @@ -39,7 +39,7 @@ def test_fix_internal_links_non_existing_username( """ with pytest.raises(CommandError) as exc_info: assert not any( - get_command_output("fix_internal_links", "--username=non-existing") + get_command_output("fix_internal_links", "--username=non-existing"), ) assert str(exc_info.value) == 'User with username "non-existing" does not exist.' @@ -87,20 +87,24 @@ def test_fix_internal_links_dry_run( assert "✔ Finished dry-run of fixing broken internal links." in out assert not err - for link_occurences, old_url in zip(old_link_occurrence_counts, old_urls): + for link_occurences, old_url in zip( + old_link_occurrence_counts, + old_urls, + strict=False, + ): assert Url.objects.filter( - url=old_url + url=old_url, ).exists(), "Old URL should not be removed during dry run" - assert ( - Link.objects.filter(url__url=old_url).count() == link_occurences - ), "Old link should not be modified during dry run" + assert Link.objects.filter(url__url=old_url).count() == link_occurences, ( + "Old link should not be modified during dry run" + ) for new_url in new_urls: assert not Url.objects.filter( - url=new_url + url=new_url, ).exists(), "New URL should not be created during dry run" assert not Link.objects.filter( - url__url=new_url + url__url=new_url, ).exists(), "New link should not be created during dry run" @@ -129,15 +133,19 @@ def test_fix_internal_links_commit(load_test_data_transactional: Any | None) -> assert "✔ Successfully finished fixing broken internal links." in out assert not err - for link_occurrences, old_url in zip(old_link_occurrence_counts, old_urls): - assert ( - Link.objects.filter(url__url=old_url).count() < link_occurrences - ), "Some old links should not exist after replacement" + for link_occurrences, old_url in zip( + old_link_occurrence_counts, + old_urls, + strict=False, + ): + assert Link.objects.filter(url__url=old_url).count() < link_occurrences, ( + "Some old links should not exist after replacement" + ) for new_url in new_urls: assert Url.objects.filter( - url=new_url + url=new_url, ).exists(), "New URL should exist after replacement" - assert ( - Link.objects.filter(url__url=new_url).count() == 1 - ), "New link should exist after replacement" + assert Link.objects.filter(url__url=new_url).count() == 1, ( + "New link should exist after replacement" + ) diff --git a/tests/core/management/commands/test_hix_bulk.py b/tests/core/management/commands/test_hix_bulk.py index c5452e9fe7..32e8862193 100644 --- a/tests/core/management/commands/test_hix_bulk.py +++ b/tests/core/management/commands/test_hix_bulk.py @@ -1,11 +1,15 @@ from __future__ import annotations +from typing import TYPE_CHECKING + import pytest from django.core.management.base import CommandError -from pytest_django.fixtures import SettingsWrapper from ..utils import get_command_output +if TYPE_CHECKING: + from pytest_django.fixtures import SettingsWrapper + @pytest.mark.django_db def test_hix_bulk_textlab_api_disabled(settings: SettingsWrapper) -> None: diff --git a/tests/core/management/commands/test_import_events.py b/tests/core/management/commands/test_import_events.py index 39252a9d99..4efd8b8066 100644 --- a/tests/core/management/commands/test_import_events.py +++ b/tests/core/management/commands/test_import_events.py @@ -1,7 +1,8 @@ from __future__ import annotations +from typing import TYPE_CHECKING + import pytest -from pytest_httpserver import HTTPServer from integreat_cms.cms.models import ( EventTranslation, @@ -12,6 +13,9 @@ from ..utils import get_command_output +if TYPE_CHECKING: + from pytest_httpserver import HTTPServer + CALENDAR_V1_EVENT_NAME = "Testevent" CALENDAR_V1 = "tests/core/management/commands/assets/calendars/single_event.ics" CALENDAR_V2_EVENT_NAME = "Testeventv2" @@ -89,7 +93,7 @@ def serve(server: HTTPServer, file: str) -> str: :param file: The file to serve :return: The url of the served file """ - with open(file, "r", encoding="utf-8") as f: + with open(file, encoding="utf-8") as f: server.expect_oneshot_request("/get_calendar").respond_with_data(f.read()) return server.url_for("/get_calendar") @@ -102,7 +106,10 @@ def setup_calendar(url: str) -> ExternalCalendar: """ region = Region.objects.get(slug="testumgebung") calendar = ExternalCalendar.objects.create( - region=region, url=url, name="Test Calendar", import_filter_category="" + region=region, + url=url, + name="Test Calendar", + import_filter_category="", ) calendar.save() return calendar @@ -135,20 +142,22 @@ def test_import_successful( calendar = setup_calendar(calendar_url) assert not EventTranslation.objects.filter( - event__region=calendar.region, title__in=event_names + event__region=calendar.region, + title__in=event_names, ).exists(), "Event should not exist before import" for rule in RecurrenceRule.objects.all(): - assert ( - rule.to_ical_rrule_string() not in recurrence_rules - ), "Recurrence rule should not exist before import" + assert rule.to_ical_rrule_string() not in recurrence_rules, ( + "Recurrence rule should not exist before import" + ) _, err = get_command_output("import_events") assert not err assert all( EventTranslation.objects.filter( - event__region=calendar.region, title=title + event__region=calendar.region, + title=title, ).exists() for title in event_names ), "Events should exist after import" @@ -174,7 +183,8 @@ def test_update_event(httpserver: HTTPServer, load_test_data: None) -> None: assert "Imported event" in out event_translation = EventTranslation.objects.filter( - event__region=calendar.region, title=CALENDAR_V1_EVENT_NAME + event__region=calendar.region, + title=CALENDAR_V1_EVENT_NAME, ).first() assert event_translation is not None, "Event should exist after import" @@ -183,9 +193,9 @@ def test_update_event(httpserver: HTTPServer, load_test_data: None) -> None: assert not err assert "Imported event" in out - assert ( - event_translation.latest_version.title == CALENDAR_V2_EVENT_NAME - ), "event should be renamed" + assert event_translation.latest_version.title == CALENDAR_V2_EVENT_NAME, ( + "event should be renamed" + ) @pytest.mark.django_db @@ -202,7 +212,8 @@ def test_delete_event(httpserver: HTTPServer, load_test_data: None) -> None: assert not err event_translation = EventTranslation.objects.filter( - event__region=calendar.region, title=CALENDAR_V1_EVENT_NAME + event__region=calendar.region, + title=CALENDAR_V1_EVENT_NAME, ).first() assert event_translation is not None, "Event should exist after import" @@ -212,7 +223,7 @@ def test_delete_event(httpserver: HTTPServer, load_test_data: None) -> None: assert "Deleting 1 unused events: " in out assert not EventTranslation.objects.filter( - slug=event_translation.slug + slug=event_translation.slug, ).exists(), "Event should be deleted" @@ -232,7 +243,8 @@ def test_import_corrupted_event(httpserver: HTTPServer, load_test_data: None) -> @pytest.mark.django_db def test_import_event_without_tags( - httpserver: HTTPServer, load_test_data: None + httpserver: HTTPServer, + load_test_data: None, ) -> None: """ Tests that an event does not get imported if it does not have tags, but tags are required @@ -252,7 +264,8 @@ def test_import_event_without_tags( @pytest.mark.django_db def test_import_event_with_wrong_tag( - httpserver: HTTPServer, load_test_data: None + httpserver: HTTPServer, + load_test_data: None, ) -> None: """ Tests that an event does not get imported if it does not have the right tag @@ -275,7 +288,8 @@ def test_import_event_with_wrong_tag( @pytest.mark.django_db def test_import_event_with_correct_tag( - httpserver: HTTPServer, load_test_data: None + httpserver: HTTPServer, + load_test_data: None, ) -> None: """ Tests that an event gets imported if it has the right tag @@ -289,7 +303,8 @@ def test_import_event_with_correct_tag( calendar.save() assert not EventTranslation.objects.filter( - event__region=calendar.region, title=CALENDAR_WRONG_CATEGORY_EVENT_NAME + event__region=calendar.region, + title=CALENDAR_WRONG_CATEGORY_EVENT_NAME, ).exists(), "Event should not exist before import" out, err = get_command_output("import_events") @@ -297,13 +312,15 @@ def test_import_event_with_correct_tag( assert "Imported event" in out assert EventTranslation.objects.filter( - event__region=calendar.region, title=CALENDAR_WRONG_CATEGORY_EVENT_NAME + event__region=calendar.region, + title=CALENDAR_WRONG_CATEGORY_EVENT_NAME, ).exists(), "Event should exist after import" @pytest.mark.django_db def test_import_event_with_multiple_categories( - httpserver: HTTPServer, load_test_data: None + httpserver: HTTPServer, + load_test_data: None, ) -> None: """ Tests that an event does not get imported if it has multiple category definitions @@ -321,7 +338,8 @@ def test_import_event_with_multiple_categories( @pytest.mark.django_db def test_import_and_remove_recurrence_rule( - httpserver: HTTPServer, load_test_data: None + httpserver: HTTPServer, + load_test_data: None, ) -> None: """ Imports an event with a recurrence rule and later the same event without recurrence rule. @@ -336,7 +354,7 @@ def test_import_and_remove_recurrence_rule( assert not err assert RecurrenceRule.objects.filter( - event__external_calendar=calendar + event__external_calendar=calendar, ).exists(), "The recurrence rule should exist after import" event = calendar.events.first() assert event, "Event should have been created" @@ -347,7 +365,7 @@ def test_import_and_remove_recurrence_rule( assert not err assert not RecurrenceRule.objects.filter( - event__external_calendar=calendar + event__external_calendar=calendar, ).exists(), "The recurrence rule should not exist anymore after update" new_event = calendar.events.first() assert event.id == new_event.id, "The event should still exist" diff --git a/tests/core/management/commands/test_repair_tree.py b/tests/core/management/commands/test_repair_tree.py index 1d89a845d6..012e6ae494 100644 --- a/tests/core/management/commands/test_repair_tree.py +++ b/tests/core/management/commands/test_repair_tree.py @@ -41,7 +41,7 @@ def test_check_clean_tree_fields(load_test_data_transactional: None) -> None: for root in Page.get_root_pages(region.slug): out, err = get_command_output("repair_tree", page_id=[root.id]) assert ( - f"Detecting problems in tree with id {root.tree_id}... ({repr(root)})" + f"Detecting problems in tree with id {root.tree_id}... ({root!r})" in out ) assert not err @@ -59,7 +59,7 @@ def test_fix_clean_tree_fields(load_test_data_transactional: None) -> None: for region in Region.objects.all(): for root in Page.get_root_pages(region.slug): out, err = get_command_output("repair_tree", page_id=[root.id], commit=True) - assert f"Fixing tree with id {root.tree_id}... ({repr(root)})" in out + assert f"Fixing tree with id {root.tree_id}... ({root!r})" in out assert not err @@ -78,10 +78,7 @@ def test_check_broken_tree_fields(load_test_data_transactional: None) -> None: original_tree_id = page.tree_id out, err = get_command_output("repair_tree", page_id=[page.id]) - assert ( - f"Detecting problems in tree with id {original_tree_id}... ({repr(page)})" - in out - ) + assert f"Detecting problems in tree with id {original_tree_id}... ({page!r})" in out assert "lft: 11 → 1" in err assert "rgt: 12 → 10" in err @@ -99,7 +96,7 @@ def test_fix_broken_tree_fields(load_test_data_transactional: None) -> None: page.save() out, err = get_command_output("repair_tree", page_id=[page.id], commit=True) - assert f"Fixing tree with id {page.tree_id}... ({repr(page)})" in out + assert f"Fixing tree with id {page.tree_id}... ({page!r})" in out assert "lft: 11 → 1" in err assert "rgt: 12 → 10" in err diff --git a/tests/core/management/commands/test_replace_links.py b/tests/core/management/commands/test_replace_links.py index 4ec979c782..45ceeacfde 100644 --- a/tests/core/management/commands/test_replace_links.py +++ b/tests/core/management/commands/test_replace_links.py @@ -45,8 +45,11 @@ def test_replace_links_non_existing_region(load_test_data: None) -> None: with pytest.raises(CommandError) as exc_info: assert not any( get_command_output( - "replace_links", "replace_links", "search", "--region-slug=non-existing" - ) + "replace_links", + "replace_links", + "search", + "--region-slug=non-existing", + ), ) assert str(exc_info.value) == 'Region with slug "non-existing" does not exist.' @@ -61,8 +64,11 @@ def test_replace_links_non_existing_username(load_test_data: None) -> None: with pytest.raises(CommandError) as exc_info: assert not any( get_command_output( - "replace_links", "replace_links", "search", "--username=non-existing" - ) + "replace_links", + "replace_links", + "search", + "--username=non-existing", + ), ) assert str(exc_info.value) == 'User with username "non-existing" does not exist.' @@ -80,16 +86,16 @@ def test_replace_links_dry_run(load_test_data_transactional: Any | None) -> None replace = "/new-slug/" replaced_url = test_url.replace(search, replace) assert Url.objects.filter( - url=test_url + url=test_url, ).exists(), "Test URL should exist in test data" - assert ( - Link.objects.filter(url__url=test_url).count() == 4 - ), "Test link should exist in test data" + assert Link.objects.filter(url__url=test_url).count() == 4, ( + "Test link should exist in test data" + ) assert not Url.objects.filter( - url=replaced_url + url=replaced_url, ).exists(), "Replaced URL should not exist in test data" assert not Link.objects.filter( - url__url=replaced_url + url__url=replaced_url, ).exists(), "Replaced link should not exist in test data" # Test dry run without --commit with enable_listeners(): @@ -100,16 +106,16 @@ def test_replace_links_dry_run(load_test_data_transactional: Any | None) -> None ) assert not err assert Url.objects.filter( - url=test_url + url=test_url, ).exists(), "Test URL should not be removed during dry run" - assert ( - Link.objects.filter(url__url=test_url).count() == 4 - ), "Test link should not be removed during dry run" + assert Link.objects.filter(url__url=test_url).count() == 4, ( + "Test link should not be removed during dry run" + ) assert not Url.objects.filter( - url=replaced_url + url=replaced_url, ).exists(), "Replaced URL should not be created during dry run" assert not Link.objects.filter( - url__url=replaced_url + url__url=replaced_url, ).exists(), "Replaced link should not be created during dry run" @@ -126,16 +132,16 @@ def test_replace_links_commit(load_test_data_transactional: Any | None) -> None: replace = "/new-slug/" replaced_url = test_url.replace(search, replace) assert Url.objects.filter( - url=test_url + url=test_url, ).exists(), "Test URL should not be removed during dry run" - assert ( - Link.objects.filter(url__url=test_url).count() == 4 - ), "Test link should not be removed during dry run" + assert Link.objects.filter(url__url=test_url).count() == 4, ( + "Test link should not be removed during dry run" + ) assert not Url.objects.filter( - url=replaced_url + url=replaced_url, ).exists(), "Replaced URL should not be created during dry run" assert not Link.objects.filter( - url__url=replaced_url + url__url=replaced_url, ).exists(), "Replaced link should not be created during dry run" # Now pass --commit to write changes to database with enable_listeners(): @@ -145,14 +151,14 @@ def test_replace_links_commit(load_test_data_transactional: Any | None) -> None: ) assert not err assert not Url.objects.filter( - url=test_url + url=test_url, ).exists(), "Test URL should not exist after replacement" assert not Link.objects.filter( - url__url=test_url + url__url=test_url, ).exists(), "Test link should not exist after replacement" assert Url.objects.filter( - url=replaced_url + url=replaced_url, ).exists(), "Replaced URL should exist after replacement" - assert ( - Link.objects.filter(url__url=replaced_url).count() == 4 - ), "Replaced link should exist after replacement" + assert Link.objects.filter(url__url=replaced_url).count() == 4, ( + "Replaced link should exist after replacement" + ) diff --git a/tests/core/management/commands/test_reset_deepl_budget.py b/tests/core/management/commands/test_reset_deepl_budget.py index cdbe1546de..f451071760 100644 --- a/tests/core/management/commands/test_reset_deepl_budget.py +++ b/tests/core/management/commands/test_reset_deepl_budget.py @@ -16,8 +16,7 @@ from ..utils import get_command_output -class datetime_not_first_day: - # pylint: disable=too-few-public-methods +class DatetimeNotFirstDay: """ Fake datetime object where now() is never the first day of the month """ @@ -33,7 +32,7 @@ def now(cls) -> datetime: return real_now.replace(day=2) -@patch("datetime.datetime", datetime_not_first_day) +@patch("datetime.datetime", DatetimeNotFirstDay) def test_not_first_day() -> None: """ Ensure that the command will not run when it's not the 1st day of the month without --force @@ -72,15 +71,15 @@ def test_reset_mt_budget(load_test_data_transactional: Any | None) -> None: region1.refresh_from_db() region2.refresh_from_db() assert "✔ MT budget has been reset." in out - assert ( - not region1.mt_budget_used - ), "The MT budget of region 1 should have been reset to 0." - assert ( - region2.mt_budget_used == 42 - ), "The MT budget of region 2 should remain unchanged." - assert ( - region1.mt_midyear_start_month is None - ), "The midyear start month of region 1 should have been reset to None." - assert ( - region2.mt_midyear_start_month == current_month + 1 - ), "The midyear start month of region 2 should not have been reset to None." + assert not region1.mt_budget_used, ( + "The MT budget of region 1 should have been reset to 0." + ) + assert region2.mt_budget_used == 42, ( + "The MT budget of region 2 should remain unchanged." + ) + assert region1.mt_midyear_start_month is None, ( + "The midyear start month of region 1 should have been reset to None." + ) + assert region2.mt_midyear_start_month == current_month + 1, ( + "The midyear start month of region 2 should not have been reset to None." + ) diff --git a/tests/core/management/commands/test_send_push_notifications.py b/tests/core/management/commands/test_send_push_notifications.py index f004e42efc..7c9de10370 100644 --- a/tests/core/management/commands/test_send_push_notifications.py +++ b/tests/core/management/commands/test_send_push_notifications.py @@ -5,17 +5,18 @@ from unittest.mock import patch import pytest -import requests from django.core.management import call_command from django.core.management.base import CommandError -from pytest_django.fixtures import SettingsWrapper -from requests_mock.mocker import Mocker from integreat_cms.firebase_api.firebase_security_service import FirebaseSecurityService if TYPE_CHECKING: from typing import Any + import requests + from pytest_django.fixtures import SettingsWrapper + from requests_mock.mocker import Mocker + from integreat_cms.cms.models import ( Language, LanguageTreeNode, @@ -55,7 +56,9 @@ def reset_state(self) -> None: self.called_success = 0 def success_json_function( - self, _request: requests.PreparedRequest, _context: Any + self, + request: requests.PreparedRequest, + context: Any, ) -> dict[str, str | int]: self.called_success += 1 @@ -65,7 +68,9 @@ def success_json_function( } def error_json_function( - self, _request: requests.PreparedRequest, _context: Any + self, + request: requests.PreparedRequest, + context: Any, ) -> dict[str, str | int]: self.called_error += 1 @@ -103,7 +108,9 @@ def test_push_notifications_nonexisting_testregion( @pytest.mark.django_db def test_ignore_overdue_notification( - self, settings: SettingsWrapper, requests_mock: Mocker + self, + settings: SettingsWrapper, + requests_mock: Mocker, ) -> None: requests_mock.post( "https://fcm.googleapis.com/v1/projects/integreat-2020/messages:send", @@ -171,7 +178,9 @@ def test_ignore_overdue_notification( @pytest.mark.django_db def test_retry_failed_notification( - self, settings: SettingsWrapper, requests_mock: Mocker + self, + settings: SettingsWrapper, + requests_mock: Mocker, ) -> None: requests_mock.post( "https://fcm.googleapis.com/v1/projects/integreat-2020/messages:send", diff --git a/tests/core/management/commands/test_summ_ai_bulk.py b/tests/core/management/commands/test_summ_ai_bulk.py index 28d209bc9a..c7e72de7a0 100644 --- a/tests/core/management/commands/test_summ_ai_bulk.py +++ b/tests/core/management/commands/test_summ_ai_bulk.py @@ -1,13 +1,17 @@ from __future__ import annotations +from typing import TYPE_CHECKING + import pytest from django.core.management.base import CommandError -from pytest_django.fixtures import SettingsWrapper from integreat_cms.cms.models import Region from ..utils import get_command_output +if TYPE_CHECKING: + from pytest_django.fixtures import SettingsWrapper + def test_summ_ai_bulk_missing_args() -> None: """ @@ -58,12 +62,12 @@ def test_summ_ai_bulk_disabled_region(load_test_data: None) -> None: """ Ensure that calling when disabled in a region throws an error """ - # pylint: disable=fixme - # TODO: Ensure there are no race conditions with tests.summ_ai_api.summ.ai_test module + # TODO(timobrembeck): Ensure there are no race conditions with tests.summ_ai_api.summ.ai_test module + # https://github.com/digitalfabrik/integreat-cms/issues/2119 # # with pytest.raises(CommandError) as exc_info: - # assert not any(get_command_output("summ_ai_bulk", "augsburg", "non-existing")) - # assert str(exc_info.value) == 'SUMM.AI API is disabled in "Stadt Augsburg".' + # assert not any(get_command_output("summ_ai_bulk", "augsburg", "non-existing")) # noqa: ERA001 + # assert str(exc_info.value) == 'SUMM.AI API is disabled in "Stadt Augsburg".' # noqa: ERA001 @pytest.mark.django_db diff --git a/tests/firebase_api/test_firebase_api_client.py b/tests/firebase_api/test_firebase_api_client.py index 8bb4890200..787c6e91fa 100644 --- a/tests/firebase_api/test_firebase_api_client.py +++ b/tests/firebase_api/test_firebase_api_client.py @@ -65,7 +65,9 @@ def teardown_method(self) -> None: @pytest.mark.django_db def test_client_throws_exception_when_fcm_disabled( - self, settings: SettingsWrapper, load_test_data: None + self, + settings: SettingsWrapper, + load_test_data: None, ) -> None: """ Tests that an ImproperlyConfigured exception is thrown, if firebase API is disabled in settings @@ -93,7 +95,9 @@ def test_is_valid(self, settings: SettingsWrapper, load_test_data: None) -> None @pytest.mark.django_db def test_is_invalid_when_no_translation( - self, settings: SettingsWrapper, load_test_data: None + self, + settings: SettingsWrapper, + load_test_data: None, ) -> None: """ Tests that :meth:`~integreat_cms.firebase_api.firebase_api_client.FirebaseApiClient.is_valid` is ``False``, @@ -110,7 +114,9 @@ def test_is_invalid_when_no_translation( @pytest.mark.django_db def test_is_invalid_when_no_title( - self, settings: SettingsWrapper, load_test_data: None + self, + settings: SettingsWrapper, + load_test_data: None, ) -> None: """ Tests that :meth:`~integreat_cms.firebase_api.firebase_api_client.FirebaseApiClient.is_valid` is ``False``, @@ -142,7 +148,9 @@ def test_firebase_api_200_success( :param caplog: Fixture for asserting log messages in tests (see :fixture:`pytest:caplog`) """ status = self.send_all_with_mocked_response( - settings, requests_mock, self.response_mock_data["200_success"] + settings, + requests_mock, + self.response_mock_data["200_success"], ) for record in caplog.records: assert ( @@ -168,7 +176,9 @@ def test_firebase_api_200_unexpected_api_response( :param caplog: Fixture for asserting log messages in tests (see :fixture:`pytest:caplog`) """ status = self.send_all_with_mocked_response( - settings, requests_mock, self.response_mock_data["200_no_name"] + settings, + requests_mock, + self.response_mock_data["200_no_name"], ) for record in caplog.records: assert "sent, but unexpected API response" in record.message @@ -192,7 +202,9 @@ def test_firebase_api_403_wrong_token( :param caplog: Fixture for asserting log messages in tests (see :fixture:`pytest:caplog`) """ status = self.send_all_with_mocked_response( - settings, requests_mock, self.response_mock_data["401_invalid_key"] + settings, + requests_mock, + self.response_mock_data["401_invalid_key"], ) for record in caplog.records: assert "Received invalid response from FCM for" in record.message @@ -215,7 +227,9 @@ def test_firebase_api_404( :param caplog: Fixture for asserting log messages in tests (see :fixture:`pytest:caplog`) """ status = self.send_all_with_mocked_response( - settings, requests_mock, self.response_mock_data["404"] + settings, + requests_mock, + self.response_mock_data["404"], ) for record in caplog.records: assert "Received invalid response from FCM for" in record.message @@ -241,7 +255,9 @@ def send_all_with_mocked_response( status_code = mocked_response.pop("status_code") settings.FCM_ENABLED = True requests_mock.post( - settings.FCM_URL, json=mocked_response, status_code=status_code + settings.FCM_URL, + json=mocked_response, + status_code=status_code, ) notification = PushNotification.objects.first() pns = FirebaseApiClient(notification) @@ -249,7 +265,10 @@ def send_all_with_mocked_response( @pytest.mark.django_db def test_region_notification_send( - self, settings: SettingsWrapper, load_test_data: None, requests_mock: Mocker + self, + settings: SettingsWrapper, + load_test_data: None, + requests_mock: Mocker, ) -> None: targets = set() @@ -273,7 +292,10 @@ def evaluate_request(request: _RequestObjectProxy, context: _Context) -> object: @pytest.mark.django_db def test_multiple_regions_notification_send( - self, settings: SettingsWrapper, load_test_data: None, requests_mock: Mocker + self, + settings: SettingsWrapper, + load_test_data: None, + requests_mock: Mocker, ) -> None: targets = set() diff --git a/tests/firebase_api/test_firebase_data_client.py b/tests/firebase_api/test_firebase_data_client.py index 27c76cf246..38ec5b1306 100644 --- a/tests/firebase_api/test_firebase_data_client.py +++ b/tests/firebase_api/test_firebase_data_client.py @@ -250,7 +250,7 @@ class TestFirebaseDataClient: "analyticsLabel": "berlin-en", "data": {}, }, - ] + ], } unlabeled_response_mock_data = { @@ -305,7 +305,7 @@ class TestFirebaseDataClient: "date": {"year": 2024, "month": 6, "day": 29}, "data": {}, }, - ] + ], } @pytest.mark.django_db diff --git a/tests/mt_api/deepl_api_test.py b/tests/mt_api/deepl_api_test.py index 0edf513d27..7a6a7848cb 100644 --- a/tests/mt_api/deepl_api_test.py +++ b/tests/mt_api/deepl_api_test.py @@ -3,21 +3,19 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Any, Final + from typing import Final from _pytest.logging import LogCaptureFixture - from django.db.models.base import ModelBase - from django.forms.models import ModelFormMetaclass - from django.http import HttpRequest from django.test.client import Client from pytest_django.fixtures import SettingsWrapper + from tests.mock import MockServer + import pytest from django.apps import apps from django.urls import reverse from integreat_cms.cms.models import Page -from tests.mock import MockServer from ..conftest import AUTHOR, EDITOR, MANAGEMENT, PRIV_STAFF_ROLES from ..utils import assert_message_in_log @@ -44,14 +42,15 @@ def setup_fake_deepl_api_server(mock_server: MockServer) -> None: "detected_source_language": "DE", "text": "This is your translation from DeepL", "billed_characters": 0, - } - ] + }, + ], }, ) def setup_deepl_supported_languages( - source_languages: list[str], target_languages: list[str] + source_languages: list[str], + target_languages: list[str], ) -> None: """ Setup supported languages for DeepL @@ -69,7 +68,9 @@ def setup_deepl_supported_languages( @pytest.mark.django_db @pytest.mark.parametrize( - "login_role_user", PRIV_STAFF_ROLES + [AUTHOR, MANAGEMENT, EDITOR], indirect=True + "login_role_user", + [*PRIV_STAFF_ROLES, AUTHOR, MANAGEMENT, EDITOR], + indirect=True, ) @pytest.mark.parametrize("error", api_errors) def test_deepl_bulk_mt_api_error( @@ -129,7 +130,9 @@ def test_deepl_bulk_mt_api_error( # Get the page objects including their translations from the database page_translations = get_content_translations( - Page, selected_ids, TARGET_LANGUAGE_SLUG + Page, + selected_ids, + TARGET_LANGUAGE_SLUG, ) # Check for a failure message diff --git a/tests/mt_api/google_translate_api_test.py b/tests/mt_api/google_translate_api_test.py index a8b1f1ec83..3bffa938d1 100644 --- a/tests/mt_api/google_translate_api_test.py +++ b/tests/mt_api/google_translate_api_test.py @@ -27,7 +27,8 @@ def setup_google_translate_supported_languages( - source_languages: list[str], target_languages: list[str] + source_languages: list[str], + target_languages: list[str], ) -> None: """ Setup supported languages for Google Translate @@ -35,15 +36,14 @@ def setup_google_translate_supported_languages( :param source_languages: The supported source languages :param target_languages: The supported target languages """ - apps.get_app_config("google_translate_api").supported_source_languages = ( - source_languages - ) - apps.get_app_config("google_translate_api").supported_target_languages = ( - target_languages - ) + apps.get_app_config( + "google_translate_api", + ).supported_source_languages = source_languages + apps.get_app_config( + "google_translate_api", + ).supported_target_languages = target_languages -# pylint:disable=too-few-public-methods class FakeClient: """ Fake client to replace translate_v2.Client @@ -60,7 +60,9 @@ def translate( def setup_fake_google_translate_api( # type: ignore[no-untyped-def] - self, request: HttpRequest, form_class: ModelFormMetaclass + self, + request: HttpRequest, + form_class: ModelFormMetaclass, ) -> None: """ Setup a fake for Google Translate API @@ -80,7 +82,9 @@ def setup_fake_google_translate_api( # type: ignore[no-untyped-def] @pytest.mark.django_db @pytest.mark.parametrize( - "login_role_user", PRIV_STAFF_ROLES + [AUTHOR, MANAGEMENT, EDITOR], indirect=True + "login_role_user", + [*PRIV_STAFF_ROLES, AUTHOR, MANAGEMENT, EDITOR], + indirect=True, ) def test_google_translate_error( login_role_user: tuple[Client, str], diff --git a/tests/mt_api/mt_api_test.py b/tests/mt_api/mt_api_test.py index 38547cb5b7..c7a632bd30 100644 --- a/tests/mt_api/mt_api_test.py +++ b/tests/mt_api/mt_api_test.py @@ -8,12 +8,11 @@ from typing import Any, Final from _pytest.logging import LogCaptureFixture - from django.db.models.base import ModelBase - from django.forms.models import ModelFormMetaclass - from django.http import HttpRequest from django.test.client import Client from pytest_django.fixtures import SettingsWrapper + from tests.mock import MockServer + from unittest.mock import patch import pytest @@ -26,7 +25,6 @@ from integreat_cms.google_translate_api.google_translate_api_client import ( GoogleTranslateApiClient, ) -from tests.mock import MockServer from ..conftest import ( ANONYMOUS, @@ -90,7 +88,7 @@ def mt_setup( content_role_id_combination = [ ( Page, - PRIV_STAFF_ROLES + [AUTHOR, MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, AUTHOR, MANAGEMENT, EDITOR], [28], ), ( @@ -106,7 +104,6 @@ def mt_setup( ] -# pylint:disable=too-many-locals, redefined-outer-name, too-many-positional-arguments @pytest.mark.django_db @pytest.mark.parametrize("provider_language_combination", provider_language_combination) @pytest.mark.parametrize("content_role_id_combination", content_role_id_combination) @@ -149,7 +146,9 @@ def test_bulk_mt( ) with patch.object( - GoogleTranslateApiClient, "__init__", setup_fake_google_translate_api + GoogleTranslateApiClient, + "__init__", + setup_fake_google_translate_api, ): response = client.post(machine_translation, data={"selected_ids[]": ids}) print(response.headers) @@ -168,7 +167,10 @@ def test_bulk_mt( response = client.get(tree) translations = get_content_translations( - content_type, ids, source_language_slug, target_language_slug + content_type, + ids, + source_language_slug, + target_language_slug, ) for translation in translations: @@ -197,7 +199,7 @@ def test_bulk_mt( # Check that used MT budget value in the region has been increased to the number of translated words translated_word_count = get_word_count( - [translation[source_language_slug] for translation in translations] + [translation[source_language_slug] for translation in translations], ) assert ( Region.objects.get(slug=REGION_SLUG).mt_budget_used @@ -218,7 +220,9 @@ def test_bulk_mt( @pytest.mark.django_db @pytest.mark.parametrize( - "login_role_user", PRIV_STAFF_ROLES + [AUTHOR, MANAGEMENT, EDITOR], indirect=True + "login_role_user", + [*PRIV_STAFF_ROLES, AUTHOR, MANAGEMENT, EDITOR], + indirect=True, ) @pytest.mark.parametrize("provider_language_combination", provider_language_combination) def test_bulk_mt_exceeds_limit( @@ -259,10 +263,13 @@ def test_bulk_mt_exceeds_limit( ) with patch.object( - GoogleTranslateApiClient, "__init__", setup_fake_google_translate_api + GoogleTranslateApiClient, + "__init__", + setup_fake_google_translate_api, ): response = client.post( - machine_translation, data={"selected_ids[]": selected_ids} + machine_translation, + data={"selected_ids[]": selected_ids}, ) print(response.headers) @@ -279,12 +286,15 @@ def test_bulk_mt_exceeds_limit( # Get the page objects including their translations from the database page_translations = get_content_translations( - Page, selected_ids, source_language_slug, target_language_slug + Page, + selected_ids, + source_language_slug, + target_language_slug, ) # Check for a failure message translations_str = iter_to_string( - [t[source_language_slug].title for t in page_translations] + [t[source_language_slug].title for t in page_translations], ) assert_message_in_log( f"ERROR The following pages could not be translated because they would exceed the remaining budget of 0 words: {translations_str}", @@ -299,7 +309,9 @@ def test_bulk_mt_exceeds_limit( @pytest.mark.django_db @pytest.mark.parametrize( - "login_role_user", PRIV_STAFF_ROLES + [AUTHOR, MANAGEMENT, EDITOR], indirect=True + "login_role_user", + [*PRIV_STAFF_ROLES, AUTHOR, MANAGEMENT, EDITOR], + indirect=True, ) @pytest.mark.parametrize("provider_language_combination", provider_language_combination) def test_bulk_mt_up_to_date( @@ -339,7 +351,9 @@ def test_bulk_mt_up_to_date( ) with patch.object( - GoogleTranslateApiClient, "__init__", setup_fake_google_translate_api + GoogleTranslateApiClient, + "__init__", + setup_fake_google_translate_api, ): response = client.post( machine_translation, @@ -367,7 +381,9 @@ def test_bulk_mt_up_to_date( @pytest.mark.django_db @pytest.mark.parametrize( - "login_role_user", PRIV_STAFF_ROLES + [AUTHOR, MANAGEMENT, EDITOR], indirect=True + "login_role_user", + [*PRIV_STAFF_ROLES, AUTHOR, MANAGEMENT, EDITOR], + indirect=True, ) @pytest.mark.parametrize("provider_language_combination", provider_language_combination) def test_bulk_mt_up_to_date_and_ready_for_mt( @@ -409,7 +425,9 @@ def test_bulk_mt_up_to_date_and_ready_for_mt( ) with patch.object( - GoogleTranslateApiClient, "__init__", setup_fake_google_translate_api + GoogleTranslateApiClient, + "__init__", + setup_fake_google_translate_api, ): response = client.post( machine_translation, @@ -457,7 +475,7 @@ def test_bulk_mt_up_to_date_and_ready_for_mt( content_role_id_data_combination = [ ( Page, - PRIV_STAFF_ROLES + [AUTHOR, MANAGEMENT, EDITOR], + [*PRIV_STAFF_ROLES, AUTHOR, MANAGEMENT, EDITOR], 4, { "title": "Neuer Titel", @@ -506,7 +524,8 @@ def test_bulk_mt_up_to_date_and_ready_for_mt( @pytest.mark.django_db @pytest.mark.parametrize("provider_language_combination", provider_language_combination) @pytest.mark.parametrize( - "content_role_id_data_combination", content_role_id_data_combination + "content_role_id_data_combination", + content_role_id_data_combination, ) def test_automatic_translation( load_test_data: None, @@ -517,7 +536,6 @@ def test_automatic_translation( mock_server: MockServer, caplog: LogCaptureFixture, ) -> None: - # pylint: disable=too-many-positional-arguments """ Check machine translation of the page/event/poi when automatic_translation checkbox in set on the form @@ -545,7 +563,8 @@ def test_automatic_translation( create_or_update = ( "update" if content_type.objects.filter( - id=content_id, translations__language__slug=target_language_slug + id=content_id, + translations__language__slug=target_language_slug, ).exists() else "create" ) @@ -562,8 +581,9 @@ def test_automatic_translation( data = copy.deepcopy(data) data.update( { - "mt_translations_to_" - + create_or_update: Language.objects.filter(slug=target_language_slug) + "mt_translations_to_" + create_or_update: Language.objects.filter( + slug=target_language_slug, + ) .first() .id, "status": ( @@ -571,11 +591,13 @@ def test_automatic_translation( if content_type is Page and role is AUTHOR else status.PUBLIC ), - } + }, ) with patch.object( - GoogleTranslateApiClient, "__init__", setup_fake_google_translate_api + GoogleTranslateApiClient, + "__init__", + setup_fake_google_translate_api, ): response = client.post( edit_content, @@ -585,7 +607,10 @@ def test_automatic_translation( if role in entitled_roles: # If the role should be allowed to access the view, we expect a successful result translations = get_content_translations( - content_type, [content_id], source_language_slug, target_language_slug + content_type, + [content_id], + source_language_slug, + target_language_slug, ) source_translation = translations[0][source_language_slug] target_translation = translations[0][target_language_slug] @@ -625,7 +650,9 @@ def test_automatic_translation( @pytest.mark.django_db @pytest.mark.parametrize( - "login_role_user", PRIV_STAFF_ROLES + [AUTHOR, MANAGEMENT, EDITOR], indirect=True + "login_role_user", + [*PRIV_STAFF_ROLES, AUTHOR, MANAGEMENT, EDITOR], + indirect=True, ) @pytest.mark.parametrize("provider_language_combination", provider_language_combination) def test_bulk_mt_no_source_language( @@ -662,10 +689,13 @@ def test_bulk_mt_no_source_language( }, ) with patch.object( - GoogleTranslateApiClient, "__init__", setup_fake_google_translate_api + GoogleTranslateApiClient, + "__init__", + setup_fake_google_translate_api, ): response = client.post( - machine_translation, data={"selected_ids[]": selected_ids} + machine_translation, + data={"selected_ids[]": selected_ids}, ) print(response.headers) @@ -682,7 +712,9 @@ def test_bulk_mt_no_source_language( # Get the page objects including their translations from the database page_translations = get_content_translations( - Page, selected_ids, target_language_slug + Page, + selected_ids, + target_language_slug, ) # Check for a failure message @@ -700,7 +732,9 @@ def test_bulk_mt_no_source_language( @pytest.mark.django_db @pytest.mark.parametrize( - "login_role_user", PRIV_STAFF_ROLES + [AUTHOR, MANAGEMENT, EDITOR], indirect=True + "login_role_user", + [*PRIV_STAFF_ROLES, AUTHOR, MANAGEMENT, EDITOR], + indirect=True, ) @pytest.mark.parametrize("provider_language_combination", provider_language_combination) def test_deepl_bulk_mt_no_target_language( @@ -737,10 +771,13 @@ def test_deepl_bulk_mt_no_target_language( }, ) with patch.object( - GoogleTranslateApiClient, "__init__", setup_fake_google_translate_api + GoogleTranslateApiClient, + "__init__", + setup_fake_google_translate_api, ): response = client.post( - machine_translation, data={"selected_ids[]": selected_ids} + machine_translation, + data={"selected_ids[]": selected_ids}, ) print(response.headers) @@ -757,7 +794,9 @@ def test_deepl_bulk_mt_no_target_language( # Get the page objects including their translations from the database page_translations = get_content_translations( - Page, selected_ids, target_language_slug + Page, + selected_ids, + target_language_slug, ) # Check for a failure message diff --git a/tests/mt_api/mt_provider_assignment_test.py b/tests/mt_api/mt_provider_assignment_test.py index a193d3c6f0..1ff6a31b8d 100644 --- a/tests/mt_api/mt_provider_assignment_test.py +++ b/tests/mt_api/mt_provider_assignment_test.py @@ -10,7 +10,6 @@ from pytest_django.fixtures import SettingsWrapper import pytest -from django.test.client import Client from django.urls import reverse from integreat_cms.cms.models import Language, LanguageTreeNode, Region @@ -24,7 +23,9 @@ def check_mt_provider( - region_slug: str, language_slug: str, mt_provider: str | None + region_slug: str, + language_slug: str, + mt_provider: str | None, ) -> None: """ Check whether the correct MT provider is assigned for the language @@ -37,7 +38,8 @@ def check_mt_provider( region = Region.objects.filter(slug=region_slug).first() language = Language.objects.filter(slug=language_slug).first() language_node = LanguageTreeNode.objects.filter( - region=region, language=language + region=region, + language=language, ).first() if mt_provider: @@ -176,7 +178,7 @@ def test_change_to_supporting_provider( }, ) - if role in HIGH_PRIV_STAFF_ROLES + [MANAGEMENT]: + if role in [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT]: assert response.status_code == 302 # Check the provider assignment was successfully changed check_mt_provider(REGION_SLUG, "en", "Google Translate") @@ -227,7 +229,7 @@ def test_change_to_not_supporting_provider( }, ) - if role in HIGH_PRIV_STAFF_ROLES + [MANAGEMENT]: + if role in [*HIGH_PRIV_STAFF_ROLES, MANAGEMENT]: assert response.status_code == 302 # Check the provider assignment was not changed check_mt_provider(REGION_SLUG, "en", "DeepL") diff --git a/tests/mt_api/utils.py b/tests/mt_api/utils.py index 1f3aecc34f..1d3ff7d9e5 100644 --- a/tests/mt_api/utils.py +++ b/tests/mt_api/utils.py @@ -17,7 +17,9 @@ def get_content_translations( - content_model: ModelBase, ids: list[int], *language_slugs: str + content_model: ModelBase, + ids: list[int], + *language_slugs: str, ) -> list[dict[str, Any]]: """ Load the translations for the given content model from the database @@ -37,9 +39,8 @@ def get_content_translations( def get_word_count( - translations: list[EventTranslation] | ( - list[PageTranslation] | list[POITranslation] - ), + translations: list[EventTranslation] + | (list[PageTranslation] | list[POITranslation]), ) -> int: """ Count the total number of words in the title, content and meta-description of translations diff --git a/tests/pdf/dummy_django_app/static_urls.py b/tests/pdf/dummy_django_app/static_urls.py index c85c14eead..3ce9b81a77 100644 --- a/tests/pdf/dummy_django_app/static_urls.py +++ b/tests/pdf/dummy_django_app/static_urls.py @@ -17,10 +17,10 @@ f"{settings.PDF_URL}".lstrip("/"), serve, {"document_root": settings.PDF_ROOT}, - ) + ), ], "pdf_files", - ) + ), ), ), ] diff --git a/tests/pdf/test_pdf_export.py b/tests/pdf/test_pdf_export.py index 04aabf5839..89155e3669 100644 --- a/tests/pdf/test_pdf_export.py +++ b/tests/pdf/test_pdf_export.py @@ -1,13 +1,16 @@ from __future__ import annotations import io +from typing import TYPE_CHECKING from urllib.parse import quote, urlencode import PyPDF3 import pytest -from django.test.client import Client from django.urls import reverse +if TYPE_CHECKING: + from django.test.client import Client + @pytest.mark.django_db # Override urls to serve PDF files @@ -91,7 +94,6 @@ def test_pdf_export( url: str, expected_filename: str, ) -> None: - # pylint: disable=too-many-locals,too-many-positional-arguments """ Test whether the PDF export works as expected @@ -119,31 +121,31 @@ def test_pdf_export( assert response.headers.get("Content-Type") == "application/pdf" # Compare file content result_pdf = PyPDF3.PdfFileReader( - io.BytesIO(b"".join(response.streaming_content)) - ) - # pylint: disable=consider-using-with - expected_pdf = PyPDF3.PdfFileReader( - open(f"tests/pdf/files/{expected_filename}", "rb") + io.BytesIO(b"".join(response.streaming_content)), ) - # Assert that both documents have same number of pages - assert result_pdf.numPages == expected_pdf.numPages - # Assert that the content is identical - for page_number in range(result_pdf.numPages): - result_page = result_pdf.getPage(page_number) - expected_page = expected_pdf.getPage(page_number) - assert result_page.artBox == expected_page.artBox - assert result_page.bleedBox == expected_page.bleedBox - assert result_page.cropBox == expected_page.cropBox - assert result_page.mediaBox == expected_page.mediaBox - assert result_page.extractText() == expected_page.extractText() - assert result_page.getContents() == expected_page.getContents() + with open(f"tests/pdf/files/{expected_filename}", "rb") as file: + expected_pdf = PyPDF3.PdfFileReader(file) + # Assert that both documents have same number of pages + assert result_pdf.numPages == expected_pdf.numPages + # Assert that the content is identical + for page_number in range(result_pdf.numPages): + result_page = result_pdf.getPage(page_number) + expected_page = expected_pdf.getPage(page_number) + assert result_page.artBox == expected_page.artBox + assert result_page.bleedBox == expected_page.bleedBox + assert result_page.cropBox == expected_page.cropBox + assert result_page.mediaBox == expected_page.mediaBox + assert result_page.extractText() == expected_page.extractText() + assert result_page.getContents() == expected_page.getContents() @pytest.mark.django_db # Override urls to serve PDF files @pytest.mark.urls("tests.pdf.dummy_django_app.static_urls") def test_pdf_export_invalid( - load_test_data: None, client: Client, admin_client: Client + load_test_data: None, + client: Client, + admin_client: Client, ) -> None: """ Test whether the PDF export throws the correct errors diff --git a/tests/summ_ai_api/summ_ai_test.py b/tests/summ_ai_api/summ_ai_test.py index e2109a987a..87be71c924 100644 --- a/tests/summ_ai_api/summ_ai_test.py +++ b/tests/summ_ai_api/summ_ai_test.py @@ -7,7 +7,6 @@ import aiohttp import pytest -from django.test.client import AsyncClient from django.urls import reverse from integreat_cms.cms.forms import PageTranslationForm @@ -46,12 +45,12 @@ if TYPE_CHECKING: from collections.abc import Callable - from typing import Any, Final + from typing import Final from _pytest.logging import LogCaptureFixture + from django.test.client import AsyncClient from pytest_django.fixtures import SettingsWrapper -# pylint: disable=global-statement # Mapping between roles and pages used in the tests # to avoid simultaneous translation of the same content by different users @@ -104,7 +103,9 @@ async def fails_on_first_attempt(msg: str) -> str: async def test_translate_text_field_successful_translation( - settings: SettingsWrapper, aiohttp_raw_server: Callable, caplog: LogCaptureFixture + settings: SettingsWrapper, + aiohttp_raw_server: Callable, + caplog: LogCaptureFixture, ) -> None: """ :param settings: The Django settings @@ -119,7 +120,7 @@ async def test_translate_text_field_successful_translation( "jobid": "9999", }, status=200, - ) + ), ) # Redirect call to the SUMM.AI API to the fake server settings.SUMM_AI_API_URL = ( @@ -131,7 +132,8 @@ async def test_translate_text_field_successful_translation( assert text_field.text == "ein Text" translated_text_field = await my_api_client.translate_text_field( - session, text_field + session, + text_field, ) assert translated_text_field is text_field @@ -154,7 +156,9 @@ async def test_translate_text_field_successful_translation( async def test_translate_text_field_hit_rate_limit( - settings: SettingsWrapper, aiohttp_raw_server: Callable, caplog: LogCaptureFixture + settings: SettingsWrapper, + aiohttp_raw_server: Callable, + caplog: LogCaptureFixture, ) -> None: """ see :func:`~test_translate_text_field_successful_translation` @@ -168,7 +172,7 @@ async def test_translate_text_field_hit_rate_limit( "error": "Too many requests. Please wait and resend your request.", }, status=429, - ) + ), ) settings.SUMM_AI_API_URL = ( f"{fake_server.scheme}://{fake_server.host}:{fake_server.port}" @@ -180,7 +184,9 @@ async def test_translate_text_field_hit_rate_limit( async def test_translate_text_field_ddos_defense( - settings: SettingsWrapper, aiohttp_raw_server: Callable, caplog: LogCaptureFixture + settings: SettingsWrapper, + aiohttp_raw_server: Callable, + caplog: LogCaptureFixture, ) -> None: """ see :func:`~test_translate_text_field_successful_translation` @@ -194,7 +200,7 @@ async def test_translate_text_field_ddos_defense( "error": "Too many requests. Please wait and resend your request.", }, status=529, - ) + ), ) settings.SUMM_AI_API_URL = ( f"{fake_server.scheme}://{fake_server.host}:{fake_server.port}" @@ -206,7 +212,9 @@ async def test_translate_text_field_ddos_defense( async def test_translate_text_field_internal_server_error( - settings: SettingsWrapper, aiohttp_raw_server: Callable, caplog: LogCaptureFixture + settings: SettingsWrapper, + aiohttp_raw_server: Callable, + caplog: LogCaptureFixture, ) -> None: """ see :func:`~test_translate_text_field_successful_translation` @@ -223,7 +231,7 @@ async def test_translate_text_field_internal_server_error( "jobid": "9999", }, status=500, - ) + ), ) settings.SUMM_AI_API_URL = ( f"{fake_server.scheme}://{fake_server.host}:{fake_server.port}" @@ -231,7 +239,7 @@ async def test_translate_text_field_internal_server_error( await my_api_client.translate_text_field(session, text_field) errors = tuple( - (record.message for record in caplog.records if record.levelname == "ERROR") + record.message for record in caplog.records if record.levelname == "ERROR" ) assert ( "SUMM.AI translation of failed because of : API has internal server error" @@ -241,7 +249,9 @@ async def test_translate_text_field_internal_server_error( async def test_translate_text_forbidden( - settings: SettingsWrapper, aiohttp_raw_server: Callable, caplog: LogCaptureFixture + settings: SettingsWrapper, + aiohttp_raw_server: Callable, + caplog: LogCaptureFixture, ) -> None: """ tests 403 response @@ -259,7 +269,7 @@ async def test_translate_text_forbidden( "jobid": "9999", }, status=403, - ) + ), ) # Redirect call to the SUMM.AI API to the fake server settings.SUMM_AI_API_URL = ( @@ -268,7 +278,7 @@ async def test_translate_text_forbidden( await my_api_client.translate_text_field(session, text_field) errors = tuple( - (record.message for record in caplog.records if record.levelname == "ERROR") + record.message for record in caplog.records if record.levelname == "ERROR" ) assert len(errors) >= 1 assert ( @@ -279,7 +289,9 @@ async def test_translate_text_forbidden( async def test_translate_text_with_empty_text_field( - settings: SettingsWrapper, aiohttp_raw_server: Callable, caplog: LogCaptureFixture + settings: SettingsWrapper, + aiohttp_raw_server: Callable, + caplog: LogCaptureFixture, ) -> None: """ see :func:`~test_translate_text_field_successful_translation` @@ -296,7 +308,7 @@ async def test_translate_text_with_empty_text_field( "jobid": "9999", }, status=200, - ) + ), ) settings.SUMM_AI_API_URL = ( f"{fake_server.scheme}://{fake_server.host}:{fake_server.port}" @@ -349,7 +361,8 @@ async def test_auto_translate_easy_german( }, ) response = await client.post( - translate_easy_german, data={"selected_ids[]": selected_ids} + translate_easy_german, + data={"selected_ids[]": selected_ids}, ) print(response.headers) if role in PRIV_STAFF_ROLES: @@ -423,7 +436,7 @@ async def test_summ_ai_error_handling( "error": "An error occurred", }, status=500, - ) + ), ) # Enable SUMM.AI in the test region await enable_summ_api(region_slug) @@ -447,7 +460,8 @@ async def test_summ_ai_error_handling( ready_for_mt_page_id = 14 response = await client.post( - translate_easy_german, data={"selected_ids[]": [ready_for_mt_page_id]} + translate_easy_german, + data={"selected_ids[]": [ready_for_mt_page_id]}, ) print(response.headers) @@ -482,7 +496,8 @@ def test_validate_response_valid() -> None: } assert ( SummAiApiClient(MockedRequest(), PageTranslationForm).validate_response( - response_data_translation_succeeded, 200 + response_data_translation_succeeded, + 200, ) is True ), "if translated_text found in reponse-data, validate_response should return True" @@ -499,12 +514,14 @@ def test_validate_response_invalid() -> None: } with pytest.raises(SummAiRuntimeError): SummAiApiClient(MockedRequest(), PageTranslationForm).validate_response( - response_data_translation_failed, 200 + response_data_translation_failed, + 200, ) async def test_unexpected_html( - settings: SettingsWrapper, aiohttp_raw_server: Callable + settings: SettingsWrapper, + aiohttp_raw_server: Callable, ) -> None: """ Test correct handling for an unexpected HTML response by SUMM.AI. @@ -532,7 +549,7 @@ async def test_unexpected_html( """, status=200, - ) + ), ) # Redirect call to the SUMM.AI API to the fake server settings.SUMM_AI_API_URL = ( @@ -547,7 +564,8 @@ async def test_unexpected_html( async def test_missing_translation( - settings: SettingsWrapper, aiohttp_raw_server: Callable + settings: SettingsWrapper, + aiohttp_raw_server: Callable, ) -> None: """ Test correct handling for a json response without the translated_text key by SUMM.AI. @@ -558,12 +576,14 @@ async def test_missing_translation( } with pytest.raises(SummAiRuntimeError): SummAiApiClient(MockedRequest(), PageTranslationForm).validate_response( - response_data_translation_missing, 200 + response_data_translation_missing, + 200, ) def test_check_rate_limit_exceeded( - settings: SettingsWrapper, caplog: LogCaptureFixture + settings: SettingsWrapper, + caplog: LogCaptureFixture, ) -> None: """ Test for check_rate_limit_exceeded method. Tests if return is True when @@ -577,31 +597,33 @@ def test_check_rate_limit_exceeded( assert ( SummAiApiClient(MockedRequest(), PageTranslationForm).check_rate_limit_exceeded( - 200 + 200, ) is False ), "if response-status not in (429, 529), check-rate-limit should return False" with pytest.raises(SummAiRateLimitingExceeded): SummAiApiClient(MockedRequest(), PageTranslationForm).check_rate_limit_exceeded( - 429 + 429, ) with pytest.raises(SummAiRateLimitingExceeded): SummAiApiClient(MockedRequest(), PageTranslationForm).check_rate_limit_exceeded( - 529 + 529, ) errors = tuple( - (record.message for record in caplog.records if record.levelname == "ERROR") + record.message for record in caplog.records if record.levelname == "ERROR" ) assert len(errors) >= 2 assert ( f"SUMM.AI translation is waiting for {settings.SUMM_AI_RATE_LIMIT_COOLDOWN}s because the rate limit has been exceeded" in errors - ), "after check_rate_limit_exceeded() with 429 or 529, a logging entry should appear" + ), ( + "after check_rate_limit_exceeded() with 429 or 529, a logging entry should appear" + ) assert ( SummAiApiClient(MockedRequest(), PageTranslationForm).check_rate_limit_exceeded( - 500 + 500, ) is False ), "if response-status not in (429, 529), check-rate-limit should return False" @@ -629,7 +651,6 @@ async def test_patient_task_queue_normal_deque() -> None: async def test_patient_task_queue_hit_rate_limit() -> None: - # pylint: disable=comparison-with-callable """ Test for PatientTaskQueue Class. It tests that when the maximum number of parallel requests to the summ_ai_api has been exceeded (rate limit exceeded), a defined time is waited until the next task object is returned via __anext__(). @@ -657,9 +678,9 @@ async def identity(x: T) -> T: task = await anext(task_generator) end = time.time() assert task == tasks[1], "PatientTaskQueue should return the second element" - assert ( - end - start < 0.5 - ), "PatientTaskQueue should return the second element very fast" + assert end - start < 0.5, ( + "PatientTaskQueue should return the second element very fast" + ) # Hit the rate limit and check whether the second task is rescheduled task_generator.hit_rate_limit(task) @@ -674,17 +695,16 @@ async def identity(x: T) -> T: task = await anext(task_generator) end = time.time() assert task == tasks[2], "PatientTaskQueue should return the third element" - assert ( - end - start < 0.5 - ), "PatientTaskQueue should return the third element very fast" + assert end - start < 0.5, ( + "PatientTaskQueue should return the third element very fast" + ) - assert ( - not task_generator - ), "PatientTaskQueue should be empty after three tasks have been removed" + assert not task_generator, ( + "PatientTaskQueue should be empty after three tasks have been removed" + ) async def test_patient_task_queue_max_retries() -> None: - # pylint: disable=comparison-with-callable """ Test for PatientTaskQueue Class. Tests that it stops early when requests are not successful after a cooldown, resulting in another cooldown repeatedly. @@ -726,19 +746,21 @@ async def identity(x: T) -> T: # Hit the rate limit, second retry task_generator.hit_rate_limit(task) task = await anext(task_generator) - assert ( - task == tasks[1] - ), "PatientTaskQueue should return the second element for the third time" + assert task == tasks[1], ( + "PatientTaskQueue should return the second element for the third time" + ) # Hit the rate limit, there should be no more retries task_generator.hit_rate_limit(task) with pytest.raises(StopAsyncIteration): task = await anext(task_generator) - assert ( - len(task_generator) == 2 - ), "PatientTaskQueue should have two tasks left that could not be completed" + assert len(task_generator) == 2, ( + "PatientTaskQueue should have two tasks left that could not be completed" + ) assert set(task_generator) == { tasks[1], tasks[2], - }, "PatientTaskQueue should have the second and third task left as they could not be completed" + }, ( + "PatientTaskQueue should have the second and third task left as they could not be completed" + ) diff --git a/tests/summ_ai_api/utils.py b/tests/summ_ai_api/utils.py index d175e76f2f..e8342806da 100644 --- a/tests/summ_ai_api/utils.py +++ b/tests/summ_ai_api/utils.py @@ -47,12 +47,14 @@ async def fake_summ_ai_server_rate_limited(request: Request) -> Response: if request.app["attempt"] < 2: # What if we get rate limited? return aiohttp.web.json_response( - data={"error": "rate limit exceeded"}, status=429 + data={"error": "rate limit exceeded"}, + status=429, ) if request.app["attempt"] == 2: # What if we get invalid JSON? return aiohttp.web.Response( - text='{"incomplete json response": ', status=200 + text='{"incomplete json response": ', + status=200, ) return aiohttp.web.json_response( data={ @@ -126,7 +128,6 @@ def get_changed_pages(settings: SettingsWrapper, ids: list[int]) -> list[dict]: class MockedRequest: - # pylint: disable=too-few-public-methods """ Helper class mocking request, used for creating a SummAiApiclient instance. Region-property is needed therefore. """ @@ -139,7 +140,6 @@ def __init__(self) -> None: class MockedRegion: - # pylint: disable=too-few-public-methods, missing-class-docstring def __init__(self) -> None: self.id = 1 self.slug = "augsburg" diff --git a/tests/textlab_api/textlab_api_test.py b/tests/textlab_api/textlab_api_test.py index 480b037b7c..d8dc83b3e3 100644 --- a/tests/textlab_api/textlab_api_test.py +++ b/tests/textlab_api/textlab_api_test.py @@ -4,19 +4,20 @@ from typing import TYPE_CHECKING import pytest -from django.http import HttpResponse from django.test.client import Client from django.urls import reverse from integreat_cms.cms.constants import status from integreat_cms.cms.models.pages.page import Page from integreat_cms.cms.models.regions.region import Region -from tests.mock import MockServer if TYPE_CHECKING: + from django.http import HttpResponse from django.test.client import Client from pytest_django.fixtures import SettingsWrapper + from tests.mock import MockServer + def update_page_content( admin_client: Client, @@ -24,7 +25,6 @@ def update_page_content( content: str, hix_ignore: bool = False, ) -> tuple[str, HttpResponse]: - edit_page = reverse( "edit_page", kwargs={ @@ -53,7 +53,6 @@ def create_page( content: str, hix_ignore: bool = False, ) -> HttpResponse: - new_page = reverse( "new_page", kwargs={ @@ -332,7 +331,7 @@ def test_hix_response_400_on_page_create( "tag": ["NN"], "wordcount": 1, "words": ["Jahrhundert"], - } + }, ], "term": { "check_words": 1, @@ -359,7 +358,7 @@ def test_hix_response_400_on_page_create( "tag": ["NE", "NE", "NN"], "wordcount": 3, "words": ["Frequently", "Asked", "Questions"], - } + }, ], "term": { "check_words": 1, @@ -408,7 +407,9 @@ def test_hix_score_update( Region.objects.filter(slug="augsburg").update(hix_enabled=True) edit_page, response = update_page_content( - admin_client, "Willkommen in Augsburg", "Neuer Inhalt" + admin_client, + "Willkommen in Augsburg", + "Neuer Inhalt", ) assert response.status_code == 302 @@ -458,7 +459,9 @@ def test_hix_disable_on_region( Region.objects.filter(slug="augsburg").update(hix_enabled=False) edit_page, response = update_page_content( - admin_client, "Willkommen in Augsburg", "Neuer Inhalt1" + admin_client, + "Willkommen in Augsburg", + "Neuer Inhalt1", ) assert response.status_code == 302 @@ -500,7 +503,10 @@ def test_ignore_hix_on_page_update( Region.objects.filter(slug="augsburg").update(hix_enabled=True) edit_page, response = update_page_content( - admin_client, "Willkommen in Augsburg", "Neuer Inhalt2", hix_ignore=True + admin_client, + "Willkommen in Augsburg", + "Neuer Inhalt2", + hix_ignore=True, ) assert response.status_code == 302 @@ -542,7 +548,9 @@ def test_hix_page_content_empty( Region.objects.filter(slug="augsburg").update(hix_enabled=True) edit_page, response = update_page_content( - admin_client, "Willkommen in Augsburg", "" + admin_client, + "Willkommen in Augsburg", + "", ) assert response.status_code == 302 @@ -593,7 +601,9 @@ def test_hix_no_content_changes( assert previous_content != "" edit_page, response = update_page_content( - admin_client, "Neuer Titel", previous_content + admin_client, + "Neuer Titel", + previous_content, ) assert response.status_code == 302 @@ -604,7 +614,7 @@ def test_hix_no_content_changes( assert page_translation.hix_score == previous_hix_score assert json.loads(page_translation.hix_feedback) == json.loads( - previous_hix_feedback + previous_hix_feedback, ) assert mock_server.requests_counter == 0 @@ -635,7 +645,9 @@ def test_hix_response_400_on_page_update( Region.objects.filter(slug="augsburg").update(hix_enabled=True) edit_page, response = update_page_content( - admin_client, "Willkommen in Augsburg", "Neuer Inhalt3" + admin_client, + "Willkommen in Augsburg", + "Neuer Inhalt3", ) assert response.status_code == 302 diff --git a/tests/utils.py b/tests/utils.py index d269d1857f..8034e47ede 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -14,7 +14,7 @@ from integreat_cms.core.signals.hix_signals import page_translation_save_handler if TYPE_CHECKING: - from typing import Generator + from collections.abc import Generator from _pytest.logging import LogCaptureFixture @@ -52,10 +52,9 @@ def assert_no_error_messages(caplog: LogCaptureFixture) -> None: :raises AssertionError: When the the logs contains error messages """ error_messages = get_error_messages(caplog) - assert ( - not error_messages - ), "The following error messages were found in the message log:\n\n" + "\n".join( - error_messages + assert not error_messages, ( + "The following error messages were found in the message log:\n\n" + + "\n".join(error_messages) ) diff --git a/tests/xliff/dummy_django_app/static_urls.py b/tests/xliff/dummy_django_app/static_urls.py index 87c437f3b2..c902698564 100644 --- a/tests/xliff/dummy_django_app/static_urls.py +++ b/tests/xliff/dummy_django_app/static_urls.py @@ -17,10 +17,10 @@ f"{settings.XLIFF_URL}".lstrip("/"), serve, {"document_root": settings.XLIFF_DOWNLOAD_DIR}, - ) + ), ], "xliff_files", - ) + ), ), ), ] diff --git a/tests/xliff/utils.py b/tests/xliff/utils.py index a01cefaf3a..1fb0b301b8 100644 --- a/tests/xliff/utils.py +++ b/tests/xliff/utils.py @@ -7,14 +7,13 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - import sys from typing import Any, Literal, NotRequired, TypedDict from _pytest.logging import LogCaptureFixture + from django.http import HttpResponse, HttpResponseNotFound, HttpResponseRedirect + from django.template.response import TemplateResponse + from django.test.client import Client -from django.http import HttpResponse, HttpResponseNotFound, HttpResponseRedirect -from django.template.response import TemplateResponse -from django.test.client import Client from django.urls import reverse from integreat_cms.cms.constants import translation_status @@ -51,7 +50,10 @@ def get_open_kwargs(file_name: str) -> OpenKwargs: def upload_files( - client: Client, url: str, file_1: str, file_2: str + client: Client, + url: str, + file_1: str, + file_2: str, ) -> HttpResponseNotFound | HttpResponseRedirect: """ Helper function to upload two XLIFF files @@ -64,9 +66,11 @@ def upload_files( :return: The upload response """ import_path = "tests/xliff/files/import" - with open(f"{import_path}/{file_1}", **get_open_kwargs(file_1)) as f1: - with open(f"{import_path}/{file_2}", **get_open_kwargs(file_2)) as f2: - return client.post(url, data={"xliff_file": [f1, f2]}, format="multipart") + with ( + open(f"{import_path}/{file_1}", **get_open_kwargs(file_1)) as f1, + open(f"{import_path}/{file_2}", **get_open_kwargs(file_2)) as f2, + ): + return client.post(url, data={"xliff_file": [f1, f2]}, format="multipart") def get_and_assert_200(client: Client, url: str) -> HttpResponse | TemplateResponse: @@ -104,7 +108,8 @@ def validate_xliff_import_response( # If the role should be allowed to access the view, we expect a successful result assert response.status_code == 302 page_tree = reverse( - "pages", kwargs={"region_slug": "augsburg", "language_slug": "en"} + "pages", + kwargs={"region_slug": "augsburg", "language_slug": "en"}, ) redirect_location = response.headers.get("Location") # If errors occur, we get redirected to the page tree @@ -135,7 +140,8 @@ def validate_xliff_import_response( if translation.version > 1: # If a translation already exists for this version, assert that the status is inherited previous_translation = page.translations.get( - language__slug="en", version=translation.version - 1 + language__slug="en", + version=translation.version - 1, ) assert previous_translation.status == translation.status else: diff --git a/tests/xliff/xliff_test.py b/tests/xliff/xliff_test.py index 5800e57bdf..80aadbd28b 100644 --- a/tests/xliff/xliff_test.py +++ b/tests/xliff/xliff_test.py @@ -51,7 +51,6 @@ def test_xliff_export( view: str, directory: str, ) -> None: - # pylint: disable=too-many-locals """ This test checks whether the xliff export works as expected @@ -66,17 +65,20 @@ def test_xliff_export( settings.XLIFF_EXPORT_VERSION = xliff_version client, role = login_role_user export_xliff = reverse( - view, kwargs={"region_slug": "augsburg", "language_slug": "en"} + view, + kwargs={"region_slug": "augsburg", "language_slug": "en"}, ) response = client.post( - export_xliff, data={"selected_ids[]": [1, 2, 3, 4, 5, 14, 15]} + export_xliff, + data={"selected_ids[]": [1, 2, 3, 4, 5, 14, 15]}, ) print(response.headers) - if role in STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR, OBSERVER]: + if role in [*STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR, OBSERVER]: # If the role should be allowed to access the view, we expect a successful result assert response.status_code == 302 page_tree = reverse( - "pages", kwargs={"region_slug": "augsburg", "language_slug": "en"} + "pages", + kwargs={"region_slug": "augsburg", "language_slug": "en"}, ) assert response.headers.get("Location") == page_tree response = client.get(page_tree) @@ -107,7 +109,8 @@ def test_xliff_export( zipped_file.extractall(path=tmp_path) for xliff_file in zipped_file.namelist(): assert filecmp.cmp( - f"{tmp_path}/{xliff_file}", f"{expected_result_dir}/{xliff_file}" + f"{tmp_path}/{xliff_file}", + f"{expected_result_dir}/{xliff_file}", ) # Check if existing translations are now "currently in translation" for page in Page.objects.filter(id__in=[1, 2]): @@ -153,11 +156,12 @@ def test_xliff_import( settings.LANGUAGE_CODE = "en" client, role = login_role_user upload_xliff = reverse( - "upload_xliff", kwargs={"region_slug": "augsburg", "language_slug": "en"} + "upload_xliff", + kwargs={"region_slug": "augsburg", "language_slug": "en"}, ) response = upload_files(client, upload_xliff, import_1["file"], import_2["file"]) # Check which role uploaded the files - if role in PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR]: + if role in [*PRIV_STAFF_ROLES, MANAGEMENT, EDITOR, AUTHOR]: validate_xliff_import_response( client, caplog, diff --git a/tools/_functions.sh b/tools/_functions.sh index 434574d389..594dfc33f3 100644 --- a/tools/_functions.sh +++ b/tools/_functions.sh @@ -503,7 +503,7 @@ function run_as_precommit { eval "$command \"$file\"" done - exit 0 + return fi done } diff --git a/tools/black.sh b/tools/black.sh deleted file mode 100755 index 4b5899e4f9..0000000000 --- a/tools/black.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# This script can be used to format the python code according to the black code style. - -# Import utility functions -# shellcheck source=./tools/_functions.sh -source "$(dirname "${BASH_SOURCE[0]}")/_functions.sh" - -require_installed - -# Run black as a pre-commit hook -run_as_precommit "black" "$@" - -# Run black -echo "Starting code formatting with black..." | print_info -black "${BASE_DIR}" -echo "✔ Code formatting finished" | print_success diff --git a/tools/code_style.sh b/tools/code_style.sh index a7a55deea5..b8d72e36a9 100755 --- a/tools/code_style.sh +++ b/tools/code_style.sh @@ -1,6 +1,6 @@ #!/bin/bash -# This script can be used to runs all of our code style tools: ruff, djlint, black, pylint, eslint and prettier +# This script can be used to runs all of our code style tools: ruff, mypy, djlint, eslint and prettier # Import utility functions # shellcheck source=./tools/_functions.sh @@ -12,15 +12,12 @@ ensure_not_root # Run ruff bash "${DEV_TOOL_DIR}/ruff.sh" || : -# Run black -bash "${DEV_TOOL_DIR}/black.sh" || : +# Run mypy +bash "${DEV_TOOL_DIR}/mypy.sh" || : # Run djlint bash "${DEV_TOOL_DIR}/djlint.sh" || : -# Run pylint -bash "${DEV_TOOL_DIR}/pylint.sh" || : - # Run eslint bash "${DEV_TOOL_DIR}/eslint.sh" || : diff --git a/tools/install.sh b/tools/install.sh index 0b1953521f..97f8f56463 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -155,7 +155,7 @@ echo "✔ Installed Python dependencies" | print_success # Install pre-commit-hooks if --pre-commit option is given if [[ -n "${PRE_COMMIT}" ]]; then echo "Installing pre-commit hooks..." | print_info - # Install pre-commit hook for black code style + # Install pre-commit hooks pre-commit install echo "✔ Installed pre-commit hooks" | print_success fi diff --git a/tools/pylint.sh b/tools/pylint.sh deleted file mode 100755 index 6a9c225f5b..0000000000 --- a/tools/pylint.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -# This script can be used to run pylint - -# Import utility functions -# shellcheck source=./tools/_functions.sh -source "$(dirname "${BASH_SOURCE[0]}")/_functions.sh" - -require_installed - -# Run pylint as a pre-commit hook -run_as_precommit "pylint --rcfile=pyproject.toml" "$@" - -# Run pylint -echo "Starting code linting with pylint..." | print_info -# Explicitly include cli which does not have a .py ending -pylint . integreat_cms/integreat-cms-cli -echo "✔ Linting finished" | print_success diff --git a/tools/ruff.sh b/tools/ruff.sh index 2267657060..e3b0ee614d 100755 --- a/tools/ruff.sh +++ b/tools/ruff.sh @@ -2,8 +2,6 @@ # This script can be used to format the python code with ruff. -# For the transitional phase we use both pylint and ruff. - # Import utility functions # shellcheck source=./tools/_functions.sh source "$(dirname "${BASH_SOURCE[0]}")/_functions.sh" @@ -11,10 +9,12 @@ source "$(dirname "${BASH_SOURCE[0]}")/_functions.sh" require_installed # Run ruff as a pre-commit hook -run_as_precommit "ruff check --fix" "$@" +run_as_precommit "ruff check" "$@" +run_as_precommit "ruff format --check" "$@" # Run ruff echo "Starting code linting and formatting with ruff..." | print_info -ruff check --fix "${BASE_DIR}" -# ruff format "${BASE_DIR}" -echo "✔ Code formatting finished" | print_success +ruff check --fix "${BASE_DIR}" || true +ruff format "${BASE_DIR}" + +echo "✔ Code formatting & linting finished" | print_success