Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

custom validations before standard ones #905

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 47 additions & 8 deletions osidb/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ class NullStrFieldsMixin(models.Model):
# mixin as we would not allow the null values for the Char/Text fields anymore
def clean(self):
super().clean()
self.convert_to_python()

def convert_to_python(self):
str_based_fields = [
field
for field in self._meta.get_fields()
Expand Down Expand Up @@ -792,24 +794,57 @@ def alert(
# alerts of the same name, object_id and age have the same meaning
pass

def convert_to_python(self, exclude=None):
"""
run mass to_python conversion without any validations which is necessary to be able
to run custom validations before the standard ones as they convert on top of validating

for more details on why is this needed and how it was created see
https://github.com/django/django/blob/stable/4.2.x/django/db/models/base.py#L1457
https://github.com/django/django/blob/stable/4.2.x/django/db/models/base.py#L1504
"""
# parent conversions first
# in case it is available
if hasattr(super(), "convert_to_python"):
super().convert_to_python()

if exclude is None:
exclude = set()

for f in self._meta.fields:
if f.name in exclude:
continue

# ValidationError may be raised here
setattr(self, f.attname, f.to_python(getattr(self, f.attname)))

def validate(self, raise_validation_error=True, dry_run=False):
"""
Run standard Django validations first, potentially raising ValidationError.
These ensure minimal necessary data quality and thus cannot be suppressed.
Run custom validations first and then standard Django validations as the
custom ones offer more specific errors. This may raise ValidationError.

Then custom validations are run, either raising ValidationError exceptions
for error level invalidities or storing the alerts for warning level ones.
Custom validations either raise ValidationError exceptions for error level
invalidities or store the alerts for warning level ones.

For error level invalidities the default behavior may be changed by setting
raise_validation_error option to false, resulting in suppressing all the
exceptions and instead storing them as error level alerts.
exceptions and instead storing them as error level alerts. The standard
validations ensure minimal necessary data quality and cannot be suppressed.

When dry_run is true no changes in alert table will be made, this option
does not prevent validations from raising errors.
"""
# standard validations
# exclude meta attributes
self.full_clean(exclude=["meta_attr"])
# convert the field values without validating
self.convert_to_python(exclude=["meta_attr"])
# but perform the full validation for the array fields
# as the conversion does not behave the same way here
self.full_clean(
exclude=[
f.name
for f in self._meta.fields
if not isinstance(f, fields.ArrayField)
]
)

# custom validations
for validation_name in [
Expand All @@ -831,6 +866,10 @@ def validate(self, raise_validation_error=True, dry_run=False):
**(e.params or {}),
)

# standard validations
# exclude meta attributes
self.full_clean(exclude=["meta_attr"])

def save(self, *args, **kwargs):
"""
Save with validate call parametrized by raise_validation_error
Expand Down
9 changes: 2 additions & 7 deletions osidb/models/affect.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,17 +175,12 @@ def __str__(self):
def save(self, *args, **kwargs):
if self.purl and not self.ps_component:
try:
# try to parse the PS component from the PURL but do not raise any
# error on failure as that will be done as part of the validations
self.ps_component = PackageURL.from_string(self.purl).name
except ValueError:
pass

# TODO: if ps_component is still missing, we must catch an error now
# because the error message in AlertMixin's standard validations is
# not helpful and changing the order of validations is not possible now
# (this should be removed once custom validations run before standard validations)
if not self.ps_component:
self._validate_purl_and_ps_component()

super().save(*args, **kwargs)

def _validate_ps_module_old_flaw(self, **kwargs):
Expand Down
Loading