diff --git a/Makefile b/Makefile index dd9ff01..474a699 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ test-python: clean ## Run test suite. quality: clean ## Run quality test. $(TOX) pycodestyle ./eox_tagging $(TOX) pylint ./eox_tagging --rcfile=./setup.cfg - $(TOX) isort --check-only --diff ./eox_tagging + $(TOX) isort --check-only --diff ./eox_tagging --skip ./eox_tagging/migrations build-docs: make docs_requirements diff --git a/eox_tagging/api/v1/serializers.py b/eox_tagging/api/v1/serializers.py index fd104b4..ff962c5 100644 --- a/eox_tagging/api/v1/serializers.py +++ b/eox_tagging/api/v1/serializers.py @@ -1,8 +1,9 @@ """ Serializers for tags and related objects. """ + from django.core.exceptions import ValidationError -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from rest_framework import serializers from eox_tagging.api.v1 import fields @@ -19,19 +20,36 @@ class TagSerializer(serializers.ModelSerializer): """Serializer for tag objects.""" - target_id = serializers.CharField(source='target_object', write_only=True) - owner_id = serializers.CharField(source='owner_object', required=False, write_only=True) - owner_type = serializers.CharField(source='owner_object_type', write_only=True, required=False) - target_type = serializers.CharField(source='target_object_type', write_only=True) + target_id = serializers.CharField(source="target_object", write_only=True) + owner_id = serializers.CharField( + source="owner_object", required=False, write_only=True + ) + owner_type = serializers.CharField( + source="owner_object_type", write_only=True, required=False + ) + target_type = serializers.CharField(source="target_object_type", write_only=True) meta = serializers.SerializerMethodField() access = fields.EnumField(enum=AccessLevel, required=False) status = fields.EnumField(enum=Status, required=False) class Meta: """Meta class.""" + model = Tag - fields = ('meta', 'key', 'tag_value', 'tag_type', 'access', 'activation_date', 'expiration_date', - 'target_id', 'owner_id', 'owner_type', 'target_type', 'status') + fields = ( + "meta", + "key", + "tag_value", + "tag_type", + "access", + "activation_date", + "expiration_date", + "target_id", + "owner_id", + "owner_type", + "target_type", + "status", + ) def get_meta(self, instance): """Getter of read-only field that returns technical information.""" @@ -61,8 +79,10 @@ def create(self, validated_data): try: target_object = get_object_from_edxapp(target_type, **data) - except Exception: - raise serializers.ValidationError({"Target": _(f"Error getting {target_type} object.")}) + except Exception as exc: + raise serializers.ValidationError( + {"Target": _(f"Error getting {target_type} object.")} + ) from exc if owner_type and owner_type.lower() == "user": owner_object = self.context.get("request").user diff --git a/eox_tagging/api/v1/test/test_viewset.py b/eox_tagging/api/v1/test/test_viewset.py index 92f027b..8cd14f6 100644 --- a/eox_tagging/api/v1/test/test_viewset.py +++ b/eox_tagging/api/v1/test/test_viewset.py @@ -52,7 +52,7 @@ def decorated_function(*args, **kwargs): "validate_expiration_date": {"exist": True}, }, ]) -class TestTagViewSet(TestCase): # pylint: disable=too-many-instance-attributes +class TestTagViewSet(TestCase): """Test class for tags viewset.""" patch_permissions = patch( diff --git a/eox_tagging/api/v1/viewset.py b/eox_tagging/api/v1/viewset.py index 86e9a0d..2cf1a19 100644 --- a/eox_tagging/api/v1/viewset.py +++ b/eox_tagging/api/v1/viewset.py @@ -203,7 +203,7 @@ def audit_method(*args, **kwargs): # pylint: disable=unused-argument ], responses={status.HTTP_404_NOT_FOUND: "Not found"}, ) -class TagViewSet(viewsets.ModelViewSet): # pylint: disable=too-many-ancestors +class TagViewSet(viewsets.ModelViewSet): """Viewset for listing and creating Tags.""" serializer_class = TagSerializer diff --git a/eox_tagging/models.py b/eox_tagging/models.py index b9855a4..c84d4ad 100644 --- a/eox_tagging/models.py +++ b/eox_tagging/models.py @@ -202,7 +202,7 @@ class Tag(models.Model): objects = TagQuerySet().as_manager() - class Meta: # pylint: disable=too-few-public-methods + class Meta: """Meta class. """ verbose_name = "tag" verbose_name_plural = "tags" @@ -299,7 +299,7 @@ def clean_fields(self): # pylint: disable=arguments-differ return self.validator.validate_fields_integrity() - def full_clean(self, exclude=None, validate_unique=False): + def full_clean(self, exclude=None, validate_unique=False): # pylint: disable=arguments-differ """ Call clean_fields(), clean(), and validate_unique() -not implemented- on the model. Raise a ValidationError for any errors that occur. diff --git a/eox_tagging/validators.py b/eox_tagging/validators.py index 689f29d..fb7265c 100644 --- a/eox_tagging/validators.py +++ b/eox_tagging/validators.py @@ -85,8 +85,10 @@ def __force_configuration_values(self): if key.startswith(pattern): try: self.instance.set_attribute(key.replace(pattern, ""), value) - except Exception: - raise ValidationError(f"EOX_TAGGING | The field {key} with value `{value}` is wrongly configured") + except Exception as exc: + raise ValidationError( + f"EOX_TAGGING | The field {key} with value `{value}` is wrongly configured" + ) from exc del self.current_tag_definitions[key] def __validate_required(self): @@ -126,16 +128,18 @@ def __validate_configuration(self): for _key in value: # Validations must exist as a class method try: getattr(self, f"validate_{_key}") - except AttributeError: + except AttributeError as exc: raise ValidationError( f"EOX_TAGGING | The field {key} with value `{_key}` is wrongly configured." - ) + ) from exc # Validate key existence clean_key = re.sub(regex, "", key) try: self.instance.get_attribute(clean_key) - except AttributeError: - raise ValidationError(f"EOX_TAGGING | The field `{key}` is wrongly configured.") + except AttributeError as exc: + raise ValidationError( + f"EOX_TAGGING | The field `{key}` is wrongly configured." + ) from exc def __validate_configuration_types(self): """Function that validate the correct type for pairs in configuration.""" @@ -170,13 +174,17 @@ def __validate_model(self, field_name): """Function that validates the instances in GFK fields calling the integrity validators.""" try: model_name = self.instance.get_attribute(field_name, name=True) - except AttributeError: - raise ValidationError(f"EOX_TAGGING | The field '{field_name}' is wrongly configured.") + except AttributeError as exc: + raise ValidationError( + f"EOX_TAGGING | The field '{field_name}' is wrongly configured." + ) from exc try: if model_name: self.model_validations[model_name](field_name) - except KeyError: - raise ValidationError(f"EOX_TAGGING | Could not find integrity validation for field '{field_name}'") + except KeyError as exc: + raise ValidationError( + f"EOX_TAGGING | Could not find integrity validation for field '{field_name}'" + ) from exc # Integrity validators def __validate_proxy_model(self, object_name): @@ -189,10 +197,10 @@ def __validate_proxy_model(self, object_name): opaque_key = self.instance.get_attribute(object_name).opaque_key try: CourseOverview.get_from_id(opaque_key) - except Exception: + except Exception as exc: raise ValidationError( f"EOX_TAGGING | Could not find opaque key: '{opaque_key}' for relation '{object_name}'" - ) + ) from exc def __validate_user_integrity(self, object_name): """ @@ -209,9 +217,11 @@ def __validate_user_integrity(self, object_name): try: get_edxapp_user(**data) - except Exception: - raise ValidationError(f"EOX_TAGGING | Could not find ID: {self.instance.get_attribute(object_name).id} \ - for relation '{object_name}'") + except Exception as exc: + raise ValidationError( + f"EOX_TAGGING | Could not find ID: {self.instance.get_attribute(object_name).id} \ + for relation '{object_name}'" + ) from exc def __validate_enrollment_integrity(self, object_name): """ @@ -232,11 +242,11 @@ def __validate_enrollment_integrity(self, object_name): f"EOX_TAGGING | Enrollment for user '{data['username']}' and courseID '{data['course_id']}' \ does not exist" ) - except Exception: + except Exception as exc: raise ValidationError( f"EOX_TAGGING | Error getting enrollment for user '{data['username']}' \ and courseID '{data['course_id']}'" - ) + ) from exc def __validate_site_integrity(self, object_name): """ @@ -249,8 +259,10 @@ def __validate_site_integrity(self, object_name): try: Site.objects.get(id=site_id) - except ObjectDoesNotExist: - raise ValidationError(f"EOX_TAGGING | Site '{site_id}' does not exist") + except ObjectDoesNotExist as exc: + raise ValidationError( + f"EOX_TAGGING | Site '{site_id}' does not exist" + ) from exc def __validate_certificate_integrity(self, object_name): """ @@ -263,8 +275,10 @@ def __validate_certificate_integrity(self, object_name): try: GeneratedCertificate.objects.get(id=certificate_id) - except ObjectDoesNotExist: - raise ValidationError(f"EOX_TAGGING | Certificate '{certificate_id}' does not exist") + except ObjectDoesNotExist as exc: + raise ValidationError( + f"EOX_TAGGING | Certificate '{certificate_id}' does not exist" + ) from exc # Other validations def validate_no_updating(self): @@ -303,9 +317,11 @@ def validate_opaque_key(self, field, value): opaque_key_to_validate = getattr(all_opaque_keys, value) # Validation method for OpaqueKey: opaque_key_to_validate getattr(opaque_key_to_validate, "from_string")(field_value) - except InvalidKeyError: + except InvalidKeyError as exc: # We don't recognize this key - raise ValidationError(f"The key '{field_value}' for '{field}' is not an opaque key") + raise ValidationError( + f"The key '{field_value}' for '{field}' is not an opaque key" + ) from exc def validate_in(self, field, values): """ @@ -374,11 +390,11 @@ def validate_between(self, field, value): for datetime_str in value: try: datetime_obj.append(datetime.datetime.strptime(datetime_str, DATETIME_FORMAT_VALIDATION)) - except TypeError: + except TypeError as exc: raise ValidationError( f"EOX_TAGGING | The DateTime field '{datetime_str}' \ must follow the format '{DATETIME_FORMAT_VALIDATION}'." - ) + ) from exc if field_value < datetime_obj[0] or field_value > datetime_obj[-1]: raise ValidationError( @@ -396,10 +412,10 @@ def __compare_equal_dates(self, field_value, value): """ try: datetime_str = datetime.datetime.strptime(value, DATETIME_FORMAT_VALIDATION) - except TypeError: + except TypeError as exc: raise ValidationError( f"EOX_TAGGING | The DateTime field '{value}' must follow the format '{DATETIME_FORMAT_VALIDATION}'." - ) + ) from exc if field_value != datetime_str: raise ValidationError(f"EOX_TAGGING | The DateTime field '{field_value}' must be equal to '{str(value)}'.") diff --git a/setup.cfg b/setup.cfg index 457bb8b..b92fc47 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,14 +22,9 @@ statistics = True [pylint] ignore = migrations,CVS - -[pylint.FORMAT] max-line-length = 120 [pylint.messages_control] -disable = raise-missing-from, fixme, too-many-public-methods, too-few-public-methods - -[MESSAGES CONTROL] enable = line-too-long, syntax-error, @@ -64,18 +59,15 @@ enable = undefined-all-variable, invalid-all-object, no-name-in-module, - unbalance-tuple-unpacking, + unbalanced-tuple-unpacking, unpacking-non-sequence, bad-except-order, raising-bad-type, misplaced-bare-raise, raising-non-exception, - nonimplemented-raised, + notimplemented-raised, catching-non-exception, - slots-on-old-class, - super-on-old-class, bad-super-call, - missing-super-argument, no-member, not-callable, assignment-from-no-return, @@ -99,19 +91,14 @@ enable = logging-too-few-args, bad-format-character, truncated-format-string, - mixed-fomat-string, + mixed-format-string, format-needs-mapping, missing-format-string-key, too-many-format-args, too-few-format-args, bad-str-strip-call, - model-unicode-not-callable, - super-method-not-called, - non-parent-method-called, - test-inherits-tests, - translation-of-non-string, - redefined-variable-type, - cyclical-import, + non-parent-init-called, + cyclic-import, unreachable, dangerous-default-value, pointless-statement, @@ -128,20 +115,15 @@ enable = signature-differs, abstract-method, super-init-not-called, - relative-import, import-self, misplaced-future, - invalid-encoded-data, global-variable-undefined, redefined-outer-name, redefined-builtin, - redefined-in-handler, undefined-loop-variable, cell-var-from-loop, duplicate-except, - nonstandard-exception, binary-op-exception, - property-on-old-class, bad-format-string-key, unused-format-string-key, bad-format-string, @@ -158,14 +140,13 @@ enable = astroid-error, parse-error, method-check-failed, - django-not-available, raw-checker-failed, - django-not-available-placeholder, empty-docstring, invalid-characters-in-docstring, missing-docstring, wrong-spelling-in-comment, wrong-spelling-in-docstring, + unused-import, unused-variable, unused-argument, exec-used, @@ -173,19 +154,14 @@ enable = bad-classmethod-argument, bad-mcs-classmethod-argument, bad-mcs-method-argument, - bad-whitespace, consider-iterating-dictionary, consider-using-enumerate, - literal-used-as-attribute, multiple-imports, multiple-statements, - old-style-class, - simplifiable-range, singleton-comparison, superfluous-parens, unidiomatic-typecheck, unneeded-not, - wrong-assert-type, simplifiable-if-statement, no-classmethod-decorator, no-staticmethod-decorator, @@ -200,9 +176,6 @@ enable = broad-except, logging-not-lazy, redundant-unittest-assert, - model-missing-unicode, - model-has-unicode, - model-no-explicit-unicode, protected-access, deprecated-module, deprecated-method, @@ -218,72 +191,22 @@ enable = trailing-newlines, trailing-whitespace, unexpected-line-ending-format, - mixed-indentation, bad-option-value, unrecognized-inline-option, useless-suppression, bad-inline-option, deprecated-pragma, - unused-import, disable = - bad-continuation, invalid-name, - misplaced-comparison-constant, file-ignored, bad-indentation, - lowercase-l-suffix, unused-wildcard-import, global-statement, no-else-return, - apply-builtin, - backtick, - basestring-builtin, - buffer-builtin, - cmp-builtin, - cmp-method, - coerce-builtin, - coerce-method, - delslice-method, - dict-iter-method, - dict-view-method, duplicate-code, - execfile-builtin, - file-builtin, - filter-builtin-not-iterating, fixme, - getslice-method, - hex-method, - import-star-module-level, - indexing-exception, - input-builtin, - intern-builtin, locally-disabled, - locally-enabled, logging-format-interpolation, - long-builtin, - long-suffix, - map-builtin-not-iterating, - metaclass-assignment, - next-method-called, - no-absolute-import, - no-init, - no-self-use, - nonzero-method, - oct-method, - old-division, - old-ne-operator, - old-octal-literal, - old-raise-syntax, - parameter-unpacking, - print-statement, - raising-string, - range-builtin-not-iterating, - raw_input-builtin, - reduce-builtin, - reload-builtin, - round-builtin, - setslice-method, - standarderror-builtin, suppressed-message, too-few-public-methods, too-many-ancestors, @@ -294,13 +217,6 @@ disable = too-many-locals, too-many-public-methods, too-many-return-statements, - unichr-builtin, - unicode-builtin, - unpacking-in-except, - using-cmp-argument, - xrange-builtin, - zip-builtin-not-iterating, - raise-missing-from, [isort] default_section = THIRDPARTY diff --git a/tox.ini b/tox.ini index ae32879..bee6736 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,19 @@ [tox] -envlist = py{38,310}-django{32} +envlist = py{38,310,311}-django{32,42} [testenv] envdir= # Use the same environment for all commands running under a specific python version py38: {toxworkdir}/py38 py310: {toxworkdir}/py310 + py311: {toxworkdir}/py311 passenv = TEST_INTEGRATION TEST_DATA deps = - django32: -r requirements/django.txt + django32: -r requirements/django32.txt + django42: -r requirements/django42.txt -r requirements/test.txt commands = {posargs}