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()
Related
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 am getting the form with through a post request:
form = ReportDataForm2(req.POST)
I am trying to strip spaces from all fields by:
for element in form:
form[element] = form[element].strip()
But this does not seem to do anything.
I also tried stripping at the point where I am receiving the data:
id = form.cleaned_data['id'].strip()
Not working either.
I am new to Django so don't know how forms are treated.
How about this:
You extend the CharField class, create your own Field, and use that anytime you want to have stripped field?
class StrippedCharField(CharField):
"""Newforms CharField that strips trailing and leading spaces."""
def clean(self, value):
if value is not None:
value = value.strip()
return super(StrippedCharField, self).clean(value)
There's been a long discussion on whether form data stripping should be handled by Django or not. The discussion was resurrected recently but the actual thread started about 7 years ago
I haven't tested the code above myself. The solution was pulled from this forum discussion
A good place to validate your form fields is the clean_<fieldname>() method. <fieldname> is the name of the field you want to validate. For example, if you want to validate a field called name, you will define a clean_name() method to validate it.
An elaborate example:
class MyForm(forms.Form):
text = forms.CharField(...)
...
def clean_text(self):
stripped_text = self.cleaned_data['text'].strip()
# do some other validation if you want...
return stripped_text
Apart from stripping, you can do all sorts of validation there.
See docs for more on validation.
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/
Thanks to Insin for answering a previous question related to this one.
His answer worked and works well, however, I'm perplexed at the provision of 'cleaned_data', or more precisely, how to use it?
class RegistrationFormPreview(FormPreview):
preview_template = 'workshops/workshop_register_preview.html'
form_template = 'workshops/workshop_register_form.html'
def done(self, request, cleaned_data):
# Do something with the cleaned_data, then redirect
# to a "success" page.
registration = Registration(cleaned_data)
registration.user = request.user
registration.save()
# an attempt to work with cleaned_data throws the error: TypeError
# int() argument must be a string or a number, not 'dict'
# obviously the fk are python objects(?) and not fk_id
# but how to proceed here in an easy way?
# the following works fine, however, it seems to be double handling the POST data
# which had already been processed in the django.formtools.preview.post_post
# method, and passed through to this 'done' method, which is designed to
# be overidden.
'''
form = self.form(request.POST) # instansiate the form with POST data
registration = form.save(commit=False) # save before adding the user
registration.user = request.user # add the user
registration.save() # and save.
'''
return HttpResponseRedirect('/register/success')
For quick reference, here's the contents of the post_post method:
def post_post(self, request):
"Validates the POST data. If valid, calls done(). Else, redisplays form."
f = self.form(request.POST, auto_id=AUTO_ID)
if f.is_valid():
if self.security_hash(request, f) != request.POST.get(self.unused_name('hash')):
return self.failed_hash(request) # Security hash failed.
return self.done(request, f.cleaned_data)
else:
return render_to_response(self.form_template,
{'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state},
context_instance=RequestContext(request))
I've never tried what you're doing here with a ModelForm before, but you might be able to use the ** operator to expand your cleaned_data dictionary into the keyword arguments expected for your Registration constructor:
registration = Registration (**cleaned_data)
The constructor to your model classes take keyword arguments that Django's Model meta class converts to instance-level attributes on the resulting object. The ** operator is a calling convention that tells Python to expand your dictionary into those keyword arguments.
In other words...
What you're doing currently is tantamount to this:
registration = Registration ({'key':'value', ...})
Which is not what you want because the constructor expects keyword arguments as opposed to a dictionary that contains your keyword arguments.
What you want to be doing is this
registration = Registration (key='value', ...)
Which is analogous to this:
registration = Registration (**{'key':'value', ...})
Again, I've never tried it, but it seems like it would work as long as you aren't doing anything fancy with your form, such as adding new attributes to it that aren't expected by your Registration constructor. In that case you'd likely have to modify the items in the cleaned_data dictionary prior to doing this.
It does seem like you're losing out on some of the functionality inherent in ModelForms by going through the form preview utility, though. Perhaps you should take your use case to the Django mailing list and see if there's a potential enhancement to this API that could make it work better with ModelForms.
Edit
Short of what I've described above, you can always just extract the fields from your cleaned_data dictionary "by hand" and pass those into your Registration constructor too, but with the caveat that you have to remember to update this code as you add new fields to your model.
registration = Registration (
x=cleaned_data['x'],
y=cleaned_data['y'],
z=cleaned_data['z'],
...
)