diff --git a/extensions/tests/test_manifest.py b/extensions/tests/test_manifest.py
index e3bd23e4..33715835 100644
--- a/extensions/tests/test_manifest.py
+++ b/extensions/tests/test_manifest.py
@@ -400,11 +400,8 @@ class ValidateManifestFields(TestCase):
data['blender_version_min'] = '2.9'
with self.assertRaises(ValidationError) as e:
ManifestValidator(data)
-
self.assertEqual(1, len(e.exception.messages))
-
self.assertIn('blender_version_min', e.exception.messages[0])
- self.assertIn('4.2.0', e.exception.messages[0])
data['blender_version_min'] = '4.1.0'
with self.assertRaises(ValidationError) as e:
@@ -419,12 +416,8 @@ class ValidateManifestFields(TestCase):
data['blender_version_min'] = '4.2.0-beta'
with self.assertRaises(ValidationError) as e:
ManifestValidator(data)
- self.assertEqual(
- e.exception.messages,
- [
- 'Manifest value error: blender_version_min
should be at least "4.2.0"',
- ],
- )
+ self.assertIn('blender_version_min', e.exception.messages[0])
+ self.assertIn('should follow', e.exception.messages[0])
def test_blender_version_max(self):
data = {
@@ -443,6 +436,52 @@ class ValidateManifestFields(TestCase):
self.assertEqual(1, len(e.exception.messages))
self.assertIn('blender_version_max', e.exception.messages[0])
+ def test_non_semver_versions(self):
+ data = {
+ **self.mandatory_fields,
+ **self.optional_fields,
+ }
+
+ for field, value in (
+ ('blender_version_min', '4.2'),
+ ('blender_version_min', '4.2.1+alpha'),
+ ('blender_version_min', '5'),
+ ('blender_version_max', '4.3'),
+ ('blender_version_max', '4.3.0+alpha'),
+ ('blender_version_max', '5'),
+ ('schema_version', '1.0'),
+ ('schema_version', '1'),
+ ('schema_version', '1.0.0.0'),
+ ):
+ value_original = data[field]
+ data[field] = value
+
+ with self.assertRaises(ValidationError) as e:
+ ManifestValidator(data)
+ self.assertEqual(1, len(e.exception.messages))
+ self.assertIn(field, e.exception.messages[0])
+
+ data[field] = value_original
+
+ def test_version(self):
+ data = {
+ **self.mandatory_fields,
+ **self.optional_fields,
+ }
+
+ data['version'] = '1.2.0'
+ ManifestValidator(data)
+
+ data['version'] = '1.2.0+alpha'
+ ManifestValidator(data)
+
+ for bad_version in ['1.2', '1.2.0.0', '1']:
+ data['version'] = bad_version
+ with self.assertRaises(ValidationError) as e:
+ ManifestValidator(data)
+ self.assertEqual(1, len(e.exception.messages))
+ self.assertIn('version', e.exception.messages[0])
+
def test_licenses(self):
data = {
**self.mandatory_fields,
diff --git a/files/validators.py b/files/validators.py
index 73056ce3..d1431aff 100644
--- a/files/validators.py
+++ b/files/validators.py
@@ -1,5 +1,6 @@
from semantic_version import Version
import logging
+import re
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator, validate_unicode_slug
@@ -20,6 +21,10 @@ import files.models
logger = logging.getLogger(__name__)
+# Define the regex pattern for major.minor.patch (do not include any additional label).
+# https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
+version_regex_pattern = re.compile(r'^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$')
+
@deconstructible
class FileMIMETypeValidator:
@@ -536,7 +541,35 @@ class VersionValidator:
)
-class SchemaVersionValidator(VersionValidator):
+class SemVerNoLabelValidator(StringValidator):
+ """These version are a sub-set of the semantic version
+
+ They only support major.minor.patch.
+ """
+
+ @staticmethod
+ def is_version_string(version):
+ return version_regex_pattern.match(version)
+
+ @classmethod
+ def validate(cls, *, name: str, value: str, manifest: dict) -> str:
+ """Return error message if cannot validate, otherwise returns nothing"""
+ err_message = mark_safe(
+ f'Manifest value error: {name}
should follow a '
+ f'semantic version '
+ f'with only major.minor.patch '
+ f'e.g., "{cls.example}"'
+ )
+
+ if super().validate(name=name, value=value, manifest=manifest):
+ return err_message
+
+ # Make sure we only have major.minor.patch.
+ if not cls.is_version_string(version=value):
+ return err_message
+
+
+class SchemaVersionValidator(SemVerNoLabelValidator):
example = '1.0.0'
@classmethod
@@ -558,7 +591,7 @@ class SchemaVersionValidator(VersionValidator):
)
-class VersionMinValidator(VersionValidator):
+class VersionMinValidator(SemVerNoLabelValidator):
example = '4.2.0'
@classmethod
@@ -574,7 +607,7 @@ class VersionMinValidator(VersionValidator):
)
-class VersionMaxValidator(VersionValidator):
+class VersionMaxValidator(SemVerNoLabelValidator):
example = '4.3.0'
@classmethod
@@ -591,15 +624,7 @@ class VersionMaxValidator(VersionValidator):
# assuming that VersionMinValidator has caught this and reported properly
return
- try:
- max = Version(value)
- except (ValueError, TypeError):
- return mark_safe(
- f'Manifest value error: {name}
should follow a '
- f'semantic version. '
- f'e.g., "{cls.example}"'
- )
-
+ max = Version(value)
if max <= min:
return mark_safe(
'Manifest value error: blender_version_max
should be greater than '