diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 538e9623..23a14ffa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,12 +19,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: ['3.7', '3.8', '3.9', '3.10'] - django: ['3.2', '4.1'] - exclude: - - python: '3.7' - django: '4.1' - + python: ['3.8', '3.9', '3.10', '3.11'] + django: ['3.2', '4.2'] services: postgres: image: postgres:12 diff --git a/setup.cfg b/setup.cfg index e8ba31cc..a5af536b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,17 +14,17 @@ classifiers = Development Status :: 5 - Production/Stable Framework :: Django Framework :: Django :: 3.2 - Framework :: Django :: 4.1 + Framework :: Django :: 4.2 Intended Audience :: Developers Operating System :: Unix Operating System :: MacOS Operating System :: Microsoft :: Windows Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 Topic :: Software Development :: Libraries :: Python Modules [options] @@ -36,10 +36,10 @@ scripts = bin/patch_content_types bin/use_external_components install_requires = - django>=3.2.0,<4.2 + django>=3.2.0 django-filter>=2.0 django-solo - djangorestframework~=3.12.0 + djangorestframework>=3.11.0 djangorestframework_camel_case>=1.2.0 django-rest-framework-condition drf-yasg>=1.20.0 @@ -50,7 +50,6 @@ install_requires = notifications-api-common>=0.2.2 oyaml PyJWT>=2.0.0 - pyyaml requests coreapi tests_require = @@ -96,15 +95,13 @@ release = test=pytest [isort] +profile = black combine_as_imports = true -default_section = THIRDPARTY -include_trailing_comma = true -line_length = 88 -multi_line_output = 3 -force_grid_wrap = 0 -use_parentheses = True -ensure_newline_before_comments = True -skip = env,.tox,.history,.eggs +skip = + env + node_modules + .tox +skip_glob = **/migrations/** known_django=django known_first_party=vng_api_common sections=FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER @@ -113,7 +110,7 @@ sections=FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER testpaths = tests DJANGO_SETTINGS_MODULE=testapp.settings -[pep8] +[pycodestyle] max-line-length=88 exclude=env,.tox,doc diff --git a/testapp/settings.py b/testapp/settings.py index 81bee495..4ca121d6 100644 --- a/testapp/settings.py +++ b/testapp/settings.py @@ -4,6 +4,7 @@ SITE_ID = 1 + DEBUG = os.getenv("DEBUG", "no").lower() in ["yes", "true", "1"] BASE_DIR = os.path.abspath(os.path.dirname(__file__)) @@ -12,6 +13,8 @@ ALLOWED_HOSTS = ["*"] +USE_TZ = True + DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", diff --git a/tox.ini b/tox.ini index e347d0a8..5b3abe02 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,6 @@ [tox] envlist = - py37-django32 - py{38,39,310}-django{32,41} + py{38,39,310,311}-django{32,42} isort docs black @@ -9,15 +8,15 @@ skip_missing_interpreters = true [gh-actions] python = - 3.7: py37 3.8: py38 3.9: py39 3.10: py310 + 3.11: py311 [gh-actions:env] DJANGO = 3.2: django32 - 4.1: django41 + 4.2: django42 [testenv] extras = @@ -25,13 +24,13 @@ extras = coverage deps = django32: Django~=3.2.0 - django41: Django~=4.1.0 + django42: Django~=4.2.0 passenv = PGUSER PGPORT PGPASSWORD commands = - py.test tests \ + pytest tests \ --cov --cov-report xml \ {posargs} @@ -54,6 +53,6 @@ extras = tests docs commands= - py.test check_sphinx.py -v \ + pytest check_sphinx.py -v \ --tb=auto \ {posargs} diff --git a/vng_api_common/authorizations/validators.py b/vng_api_common/authorizations/validators.py index c5637a19..895d941d 100644 --- a/vng_api_common/authorizations/validators.py +++ b/vng_api_common/authorizations/validators.py @@ -15,20 +15,14 @@ class UniqueClientIDValidator: message = _( "The clientID(s) {client_id} are already used in application(s) {app_id}" ) + requires_context = True - def set_context(self, serializer_field): - """ - This hook is called by the serializer instance, - prior to the validation call being made. - """ - # Determine the existing instance, if this is an update operation. - self.instance = getattr(serializer_field.parent, "instance", None) - - def __call__(self, value: List[str]): + def __call__(self, value: List[str], serializer_field): + instance = getattr(serializer_field.parent, "instance", None) qs = Applicatie.objects.all() - if self.instance: - qs = qs.exclude(id=self.instance.id) + if instance: + qs = qs.exclude(id=instance.id) existing = qs.filter(client_ids__overlap=value).values_list( "uuid", "client_ids" diff --git a/vng_api_common/caching/etags.py b/vng_api_common/caching/etags.py index 4d99a613..bcf81afc 100644 --- a/vng_api_common/caching/etags.py +++ b/vng_api_common/caching/etags.py @@ -157,7 +157,8 @@ def mark_affected(cls, obj: models.Model, using=None) -> None: connection = transaction.get_connection(using) func = MethodCallback(etag_update.calculate_new_value) - for _, _func in connection.run_on_commit: + for run_on_commit in connection.run_on_commit: + _func = run_on_commit[1] if func == _func: logger.debug( "Update for model instance %r with pk %s was already scheduled", diff --git a/vng_api_common/inspectors/query.py b/vng_api_common/inspectors/query.py index 9fdea531..8b84e278 100644 --- a/vng_api_common/inspectors/query.py +++ b/vng_api_common/inspectors/query.py @@ -1,10 +1,5 @@ from django.db import models - -try: - from django.utils.encoding import force_str -except ImportError: # Django < 4.0 - from django.utils.encoding import force_text as force_str - +from django.utils.encoding import force_str from django.utils.translation import gettext as _ from django_filters.filters import BaseCSVFilter, ChoiceFilter diff --git a/vng_api_common/validators.py b/vng_api_common/validators.py index 64308c77..964fc828 100644 --- a/vng_api_common/validators.py +++ b/vng_api_common/validators.py @@ -206,20 +206,18 @@ def __call__(self, url: str): class InformatieObjectUniqueValidator(validators.UniqueTogetherValidator): + requires_context = True + def __init__(self, parent_field, field: str): self.parent_field = parent_field self.field = field super().__init__(None, (parent_field, field)) - def set_context(self, serializer_field): - serializer = serializer_field.parent - super().set_context(serializer) - - self.queryset = serializer.Meta.model._default_manager.all() - self.parent_object = serializer.context["parent_object"] - - def __call__(self, informatieobject: str): - attrs = {self.parent_field: self.parent_object, self.field: informatieobject} + def __call__(self, informatieobject: str, serializer): + attrs = { + self.parent_field: serializer.context["parent_object"], + self.field: informatieobject, + } super().__call__(attrs) @@ -232,17 +230,12 @@ class ObjectInformatieObjectValidator: "Het informatieobject is in het DRC nog niet gerelateerd aan dit object." ) code = "inconsistent-relation" + requires_context = True - def set_context(self, serializer): - """ - This hook is called by the serializer instance, - prior to the validation call being made. - """ - self.parent_object = serializer.context["parent_object"] - self.request = serializer.context["request"] - - def __call__(self, informatieobject: str): - object_url = self.parent_object.get_absolute_api_url(self.request) + def __call__(self, informatieobject: str, serializer): + object_url = serializer.context["parent_object"].get_absolute_api_url( + self.request + ) # dynamic so that it can be mocked in tests easily client = get_client(informatieobject) @@ -311,40 +304,33 @@ class UniekeIdentificatieValidator: message = _("Deze identificatie bestaat al binnen de organisatie") code = "identificatie-niet-uniek" + requires_context = True def __init__(self, organisatie_field: str, identificatie_field="identificatie"): self.organisatie_field = organisatie_field self.identificatie_field = identificatie_field - def set_context(self, serializer): - """ - This hook is called by the serializer instance, - prior to the validation call being made. - """ - # Determine the existing instance, if this is an update operation. - self.instance = getattr(serializer, "instance", None) - self.model = serializer.Meta.model - - def __call__(self, attrs: dict): + def __call__(self, attrs: dict, serializer): + instance = getattr(serializer, "instance", None) identificatie = attrs.get(self.identificatie_field) if not identificatie: - if self.instance: + if instance: # In case of a partial update - identificatie = self.instance.identificatie + identificatie = instance.identificatie else: # identification is being generated, and the generation checks for # uniqueness return organisatie = attrs.get(self.organisatie_field) - pk = self.instance.pk if self.instance else None + pk = instance.pk if instance else None # if we're updating an instance, setting the current values will not # trigger an error because the instance-to-be-updated is excluded from # the queryset. If either bronorganisatie or identificatie changes, # and it already exists, it will raise a validation error combination_exists = ( - self.model.objects + serializer.Meta.model.objects # in case of an update, exclude the current object. for a create, this # will be None .exclude(pk=pk) @@ -370,22 +356,15 @@ class IsImmutableValidator: message = _("Dit veld mag niet gewijzigd worden.") code = "wijzigen-niet-toegelaten" + requires_context = True - def set_context(self, serializer_field): - """ - This hook is called by the serializer instance, - prior to the validation call being made. - """ - # Determine the existing instance, if this is an update operation. - self.serializer_field = serializer_field - self.instance = getattr(serializer_field.parent, "instance", None) - - def __call__(self, new_value): + def __call__(self, new_value, serializer_field): + instance = getattr(serializer_field.parent, "instance", None) # no instance -> it's not an update - if not self.instance: + if not instance: return - current_value = getattr(self.instance, self.serializer_field.field_name) + current_value = getattr(instance, serializer_field.field_name) if new_value != current_value: raise serializers.ValidationError(self.message, code=self.code)