From Marshmallow#validation, I know I can register validators on specific fields in a Schema. If a validator fails, errors in :
data, errors = MySchema().load({"some":"data})
will include error information for any field which has failed validators :
errors
# => some error message for the field that failed
My question : Is it possible to validate at the Schema level (rather than at individual field level) and still return an error in the above way?
As an arbitrary example, say I wanted to validate that you tried to MySchema().load() n distinct keys.
I currently have a #pre_load method which checks the structure of the input and raise ValidationError('message') if the data is ill-formed, but I would like to return it as result.errors like field validation does. What are my options?
You can use validates_schema decorator to run validations on whole object:
class MySchema(marshmallow.Schema):
# ...
#marshmallow.validates_schema(skip_on_field_errors=True)
def validate_object(self, data):
if data['foo'] < data['bar']:
raise marshmallow.ValidationError(
'Value should not be less than bar',
['foo'] # name of field to report error for
)
Although if you want to report multiple errors for different fields independently, Marshmallow at this moment does not support reporting multiple different errors for different fields, and you need to put separate validations into separate methods:
class MySchema(Schema):
# ...
#validates_schema
def validate_foo(self, data):
pass
#validates_schema(skip_on_field_errors=True)
def validate_bar(self, data):
pass
Related
I would like to know the difference between these two methods of checking for unique-ness.
I have two fields which I do not want there to be duplicates of - key_name and developer_email - i.e. a developer can not have two keys with the same name. But two developers can have a key with the same name as each other.
I found out about unique_together so I added this to KeyDefinitions in models.py:
class Meta:
verbose_name = "Key Definition"
#constraints = [
# models.UniqueConstraint(fields=['key_name', 'developer_email'], name='unique key names for each user')
#]
unique_together = [['key_name', 'developer_email']]
However, when I tested this by inputting a duplicate key_name and hitting "OK" on my form it just gives me back the form filled in and no errors. Although an entry hasn't been added to the database which tells me that this code is doing something.
I wanted an error to be raised so I found out about validate_unique and added it to the same model like so:
def validate_unique(self, exclude=None):
# I feel like this function makes constraints/unique_together option in Meta obsolete ?!
qs = KeyDefinition.objects.filter(key_name=self.key_name, developer_email=self.developer_email) # need to filter by developer too
print(qs)
if qs:
raise ValidationError (
{'key_name' : ['This Key Name already exists']}
)
return super().validate_unique(exclude)
The above code gave me what I wanted - i.e. an error message under the input field if a duplicate has been detected.
Now, my question is this - why use the constraints/unique_together options in the Meta class if we can just use the validate_unique function to make sure a duplicate isn't submitted? Is it because there's a chance someone could add an entry manually to the database? If so, why doesn't Django handle this instead of me overriding a function - I just don't know if this is the best way to handle this situation.
The Optional validator allows for both empty values and if the value is not present (from the docs):
class wtforms.validators.Optional(strip_whitespace=True)
Allows empty input and stops the validation chain from continuing.
If input is empty, also removes prior errors (such as processing
errors) from the field.
I have some additional validators on a field, and I would like if those validators ran even if the input is an empty string. The builtin Optional validator makes the rest of the validators skipped if the input was an empty string. Is there a built in or any other way to achieve this?
Edit: More specifics about my usecase
I am using this form to validate PUT requests. Let's say I have User entities with usernames as ID and middlenames as an optional field. Then the validator for the fields would look something like:
class UserUpdateForm(Form):
username = fields.StringField('username', [
validators.Optional(),
validators.Length(min=5, max=500)
])
middlename = fields.StringField('middlename', [
validators.Optional()
])
So I would allow for PUT requests that does not have a username or middlename parameter, and those would leave the fields untouched. However, when the parameter is present and is an empty string, I would like the username field validation fail because of the Length validator, but I would allow the middlename field to be set to the empty string.
From another perspective: I would like to distinguish non-present parameters and empty string parameters.
I took a look at the source of the Optional validator:
class Optional(object):
...
def __call__(self, form, field):
if not field.raw_data or isinstance(field.raw_data[0], string_types) and not self.string_check(field.raw_data[0]):
field.errors[:] = []
raise StopValidation()
As you can see in and not self.string_check(field.raw_data[0]), empty strings are explicitly considered here. I wonder what would happen if I sent two values like a=foo&a=&b=bar.
Anyway, the quick solution for me was to implement a new validator:
class OptionalButNotEmpty(object):
"""
Allows missing but not empty input and stops the validation chain from continuing.
"""
# Code is a modified version of `Optional` (https://github.com/wtforms/wtforms/blob/master/wtforms/validators.py#L148)
field_flags = ('optional', )
def __call__(self, form, field):
if not field.raw_data:
raise wtforms.validators.StopValidation()
I have been using the #validates decorator in sqlalchemy.orm from flask to validate fields, and all has gone well as long as all of the fields are independent of one another such as:
#validates('field_one')
def validates_field_one(self, key, value):
#field one validation
#validates('field_two')
def validates_field_two(self, key, value):
#field two validation
However, now I need to do some validation that will require access to field_one and field_two simultaneously. It looks like validates accepts multiple arguments to the validates decorator, however, it will simply run the validation function once for each argument, as such:
#validates('field_one', 'field_two')
def validates_fields(self, keys, values):
#field validation
Results in a work flow of validate field_one and then validate field_two. However, I would like to validate both at the same time(a trivial example of which would be assert that the value of field_one is not the value of field_two, an example of which would be disallowing self-loops in a graph where field_one and field_two refer to nodes and it is performing validation on an edge). How would be the best way to go about doing that?
Order the fields in the order they were defined on the model. Then check if the last field is the one being validated. Otherwise just return the value unchecked. If the validator is validating one of the earlier fields, some of them will not be set yet.
#validates('field_one', 'field_two')
def validates_fields(self, key, value):
if key == 'field_two':
assert self.field_one != value
return value
See this example.
Adding another answer here, as the accepted one didn't quite meet my use case for using another field to validate and modify relationship/collection fields, which are not really compatible with #validates. In this case you can use an event listener for the before_flush event to achieve what you're looking for:
#event.listens_for(Session, 'before_flush')
def validate_and_modify_relationships(session, flush_context, instances):
"""
Complex validation that cannot be performed with #valdiates
"""
# new records only (for updates only, use session.dirty)
for instance in session.new:
if isinstance(instance, MyData):
if instance.some_other_value:
instance.some_relation = []
More details here: Flask-SQLAlchemy validation: prevent adding to relationship based on other field
I have subclassed a text-field form field in Django to create my own custom widget for a field. I was wondering if it's possible to check if all other fields of the form are valid (I want its server side behavior to vary based on the validation of other fields)
See comment
Something like:
class CustomField(TextInput):
def __init__(self, *args, **kwargs):
...
super(CustomField, self).__init__(*args, **kwargs)
input_type = 'hidden'
def value_from_datadict(self, data, files, name):
aws_file_key = data.get(name, None)
_media_bucket = boto.connect_s3(settings.AWS_ACCESS_KEY_ID,
settings.AWS_SECRET_ACCESS_KEY)\
.lookup(settings.AWS_MEDIA_STORAGE_BUCKET_NAME)
try:
key = _media_bucket.get_key(aws_file_key)
except:
print 'Failed to get key.'
key = None
if key and aws_file_key:
fh = tempfile.TemporaryFile()
key.get_contents_to_file(fh)
fh.seek(0)
files = SimpleUploadedFile(key.name, fh.read())
### IF FORM IS VALID DELETE KEY, OTHERWISE, KEEP IT.
if code_to_check_if_valid:
_media_bucket.delete_key(key)
fh.close()
return files
...... etc.
If you want to validate a certain field depending on the values of other fields, you need to to it at the form level and overwrite the field's clean method. Here's the docs on the subject - they are very good.
class CustomForm(forms.Form):
custom_field = CustomField()
def clean(self):
cleaned_data = super(CustomForm, self).clean()
custom_field = cleaned_data.get("custom_field")
...
If you look at the flow of how forms are validated, you will see that the clean method is run if all the other fields validate independently, so at this stage, the form can be considered valid:
These methods are run in the order given above, one field at a time. That is, for each field in the form (in the order they are declared in the form definition), the Field.clean() method (or its override) is run, then clean_<fieldname>(). Finally, once those two methods are run for every field, the Form.clean() method, or its override, is executed.
The final clean method is actually run regardless of if there's an error so you have to iterate through the cleaned_data to make sure there are no errors
The clean() method for the Form class or subclass is always run. If that method raises a ValidationError, cleaned_data will be an empty dictionary.
The previous paragraph means that if you are overriding Form.clean(), you should iterate through self.cleaned_data.items(), possibly considering the _errors dictionary attribute on the form as well. In this way, you will already know which fields have passed their individual validation requirements.
The clean methods for individual fields are called in the same order as the form declaration order or explicitly specified order. [django source code]
Although I wouldn't recommend this approach over using the clean method for multi-field validation, if your custom field is the last field in the order, you can expect self._errors to indicate whether all other fields have passed validation or not. However at this stage, non-field errors won't be available.
As the built in validator in django accepts even if just spaces are input into a field. I want a validation in which if only spaces are fed then it must raise a validation error. I have a field like this:
name = forms.CharField(max_length=200)
Using validators outside i.e. writing a function like this works.
def validate_spaces(value):
if value.strip() == '':
raise ValidationError(u"You must provide more than just whitespace.")
But, I was thinking if it could be done using the form.clean() methods, i.e. without writing any extra functions outside. Any help will be much appreciated.
You can add custom behaviour to form.clean() this way:
class YourForm(forms.Form):
# Everything as before.
...
def clean_name(self):
data = self.cleaned_data['name']
if data.strip() == '':
raise forms.ValidationError(u"You must provide more than just whitespace.")
# Always return the cleaned data, whether you have changed it or
# not.
return data
However, if you want to create a type of field that automatically gets this type of validation, you add a new class like this
class NoSpacesCharField(forms.CharField):
def validate(self, value):
# Use the parent's handling of required fields, etc.
super(NoSpacesCharField, self).validate(value)
if value.strip() == '':
raise ValidationError(u"You must provide more than just whitespace.")
Then uses NoSpacesCharField like you would ordinarily use forms.CharField.
I'm not currently in a position to test this code so there might be the odd kink in it, but it should get you most of the way there. For further info on form validation in Django see https://docs.djangoproject.com/en/dev/ref/forms/validation/