Django: form validation: accepting multiple values for one field - django

I'm looking to create a form field that takes multiple values for a given field, validates them, and stores them as a list.
For example, one can run the following curl command and post several POST parameters called 'email'
curl -X POST -d email=test#example.com -d email=test2#example.com http://url/here/
In my view, I can execute the following to get a list of emails directly from the POST data.
email = request.POST.getlist('email')
However, I'd like to take advantage of form validation to clean all of the emails specified in the POST data.
Ideally, I'd like to run form.is_valid() and then access the the cleaned_data['email'] key on the form would return a list of valid email addresses.
I've looked into using MultipleChoice fields, and similar fields (because they accept multiple inputs with the same name), but those fields require that you define the choices beforehand. I've also considered using formsets, but those seem overly-complicated for what I'm trying to do in this case.
Does anyone know of any fields that behave in this way? Thanks for reading.

I'm looking for something similar and I found this: http://djangosnippets.org/snippets/497/
from django import newforms as forms
class SeparatedValuesField(forms.Field):
"""
A Django newforms field which takes another newforms field during
initialization and validates every item in a separated list with
this field class. Please use it like this::
from django.newforms import EmailField
emails = SeparatedValuesField(EmailField)
You would be able to enter a string like 'john#doe.com,guido#python.org'
because every email address would be validated when clean() is executed.
This of course also applies to any other Field class.
You can define the sepator (default: ',') during initialization with the
``separator`` parameter like this::
from django.newforms import EmailField
emails = SeparatedValuesField(EmailField, separator='###')
If validation succeeds it returns the original data, though the already
splitted value list can be accessed with the get_list() method.
>>> f = SeparatedValuesField(forms.EmailField)
>>> f.clean('foo#bar.com,bar#foo.com')
'foo#bar.com,bar#foo.com'
>>> f.get_list()
['foo#bar.com', 'bar#foo.com']
>>> f.clean('foobar,foo#bar.com,bar#foo.com')
Traceback (most recent call last):
...
ValidationError: <unprintable ValidationError object>
>>> u = SeparatedValuesField(forms.URLField)
>>> u.clean('http://foo.bar.com,http://foobar.com')
'http://foo.bar.com,http://foobar.com'
>>> u.clean('http:foo.bar.com')
Traceback (most recent call last):
...
ValidationError: <unprintable ValidationError object>
>>> f = SeparatedValuesField(forms.EmailField, separator='###')
>>> f.clean('foo#bar.com###bar#foo.com')
'foo#bar.com###bar#foo.com'
>>> f.clean('foobar###foo#bar.com###bar#foo.com')
Traceback (most recent call last):
...
ValidationError: <unprintable ValidationError object>
"""
def __init__(self, base_field=None, separator=',', *args, **kwargs):
super(SeparatedValuesField, self).__init__(*args, **kwargs)
self.base_field = base_field
self.separator = separator
def clean(self, data):
if not data:
raise forms.ValidationError('Enter at least one value.')
self.value_list = data.split(self.separator)
if self.base_field is not None:
base_field = self.base_field()
for value in self.value_list:
base_field.clean(value)
return data
def get_list(self):
return self.value_list
def _test():
import doctest
doctest.testmod()
if __name__ == "__main__":
_test()
It's not totally satisfactory though: for a list of emails for example, if only one email is not valid, the whole field is not valid.
One could probably rewrite the clean method so that it returns only the valid 'base_fields' instead of throwing a ValidationError altogether.

In 2018, there was still no satisfactory answer to this question, so I wrote my own field which behaves like the OP wanted. Check it out here.
To solve the original problem:
forms.py:
import django.forms as forms
from multivaluefield import MultiValueField
class MultiEmailForm(forms.Form):
emails = MultiValueField(forms.EmailField(), "email")
View code:
form = MultiEmailForm(request.POST)
if form.is_valid:
emails = form.cleaned_data["emails"]
# do something with emails
else:
errors = form.errors
# do something with errors

django-multi-email-field can do this: https://github.com/fle/django-multi-email-field
Sample form code:
from django import forms
from multi_email_field.forms import MultiEmailField
class SendMessageForm(forms.Form):
emails = MultiEmailField()
Sample view code:
assert form.is_valid()
print(form.cleaned_data["emails"])

Related

Validate get_queryset GET parameters

I am building an application that allows user to view certain records from the database. Since I want the users to be able to filter the number of records per page via the paginate_by attribute I also want to validate that input. Here is a snipped of my code.
def get_queryset(self):
q = self.request.GET.get('paginate_by')
if q is None:
return Syslog.objects.all()
elif ( int(q) > 0):
return Syslog.objects.all()
else:
raise PermissionDenied
Firstly I am getting the queryset and more specifically the paginate_by parameter and I am trying to validate it. When a user provide a positive integer or the home page the view returns the queryset. If the user provide a negative number a PermissionDenied is returned. The problem is that when the user provide a string, it throws a 500 Server Error.
What I am trying to do is to check if the provided GET parameter is positive integer or None (for home page), and if it is not to render a custom error template.
Regards,
Jordan
Looks like you're using Django Rest Framework. In that case DRF has pagination built in which i would recommend using instead. DRF Pagination
I have figure it out. I had to rewrite the If loop in order to handle the 500 Server error that I was getting. Since the q parameter is a string when it checks it against if ( int(q) > 0 ) it throws and error because the int() method expects some sort of string.
>>> var="15"
>>> print(int(var))
15
>>>
>>> var="string"
>>> print(int(var))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'string'
>>>
The first example shows a correct conversion between a string and int, while the second throws an error and that was my problem after all.
Because it raise an error, I wrapped everything in a try:... except: statement to handle it correctly.
Here is the code I ended up.
def get_queryset(self):
query = Syslog.objects.all()
q = self.request.GET.get('paginate_by')
try:
if ( q is None ) or ( int(q) > 0 ):
return query
else:
raise Http404
except:
raise Http404

passing value to get_context_data from Django template

I am working on a Django template associated with a particular model. Within the view for this template, I am trying to access a record from a different model (Terms containing scientific terms), based on the parameter I would pass in the template.
I tried being using get_context_data to query a random term in the database, and then use a custom filter tag to replace the term to the one I want from within the template. Django is smarter, though and wouldn't let me do that.
Currently, I am trying to define the context within my views.py
class ArticlesView(DetailView):
model = models.Articles
template_name = 'web/articles-details.html'
context_object_name = 'Articles_details'
def get_context_data(self, *args, **kwargs):
ctx = super(ArticlesView, self).get_context_data(*args, **kwargs)
ctx['featured'] = Articles.objects.filter(featured=True)
ctx['term','arg'] = Articles.objects.filter('slug'=='arg')
return ctx
In the above code, the 'featured' context works fine, but not the 'term' one. It is clearly wrong; I know that... but I can't figure out what the correct syntax would be, and how I would provide the parameter from within the template. (I am trying to print out just the slug of the 'scientific term' in this example).
Any thoughts?
I know that I can set a ForeignKey within my models to connect them. The problem with that is that I would have at least 4-5 'scientific terms' on any given page, and therefore, I would have to add at least 5 related terms for each page, and then add them in a particular manner... a lot of repetition and messy.
Thanks for your assistance, in advance.
What you're doing with the context won't quite work as you perhaps think. To illustrate what you've done with that context dictionary;
>>> context = dict()
>>> context['slug', 'arg'] = 'test'
>>> context
{('slug', 'arg'): 'test'}
>>> context['slug']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'slug'
>>> context['arg']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'arg'
>>> context['slug', 'arg']
'test'
So in your template if you did something like {{ slug, arg }} you'd get your object, but I don't think that's valid in the template syntax.
So based on what you've got I suspect you'd want to do something like;
arg = 'my-slug'
ctx['term'] = Articles.objects.filter(slug=arg)
ctx['arg'] = arg
That would give you the arg used to match the slug on the Articles model with the queryset assigned to term.

ModelForm clean_xxxx() works for CharField, not for URLField. Django 1.5

How can I remove whitespace, prior to validation of a URLField?
Using "clean_[fieldname]()" would seem to be the documented way from https://docs.djangoproject.com/en/dev/ref/forms/validation/ , but it does not work for the URLField. I've reduced it to a basic test case which can be run in the django shell:
class XXXTestModel(models.Model):
url = models.URLField('URL',null=True,blank=True)
name = models.CharField(max_length=200)
class XXXTestForm(ModelForm):
def clean_url(self):
return self.cleaned_data['url'].strip()
def clean_name(self):
return self.cleaned_data['name'].strip()
class Meta:
model = XXXTestModel
fields = (
'url',
)
Tested from the Django shell with:
>>> django.VERSION
(1, 5, 1, 'final', 0)
>>> from xxx import XXXTestForm,XXXTestModel
>>> data = dict(url=' http://www.example.com/ ',name=' example ')
>>> f=XXXTestForm(data)
>>> f.is_valid();f.errors
False
{'url': [u'Enter a valid URL.']}
>>> f.cleaned_data
{'name': example'}
There are a number of close dupes of this question on stack overflow, but none of the answers guide toward a solution.
The issue here is how the django.forms.URLField works.
django.forms.Field.clean is defined as:
def clean(self, value):
"""
Validates the given value and returns its "cleaned" value as an
appropriate Python object.
Raises ValidationError for any errors.
"""
value = self.to_python(value)
self.validate(value)
self.run_validators(value)
return value
Note that to_python is performed before any validation. This is the issue here - django.forms.URLField can't understand the value you're giving it, so the value it produces fails the set of validators already defined as part of django.forms.URLField (namely, django.core.validators.URLValidator).
The reason it fails is django tries to "normalize" the URL. This includes things such as adding "http://" where needed. When given your example url, " http://www.example.com ", django uses urlparse.urlsplit to get it "parts" of the url. The leading space, however, messes it up and the entire value becomes part of the path. As such, django finds no scheme, and reconstitutes the URL as "http:// http://www.example.com ". This is then given to django.core.validators.URLValidator, which obviously fails.
To avoid this, we'll need to define our own URLField for our form
from django import forms
class StrippedURLField(forms.URLField):
def to_python(self, value):
return super(StrippedURLField, self).to_python(value and value.strip())
Using this ensures the process will all go as expected, and we wont need a clean_url method. (note: you should use clean_* where possible, but here it is not)
class XXXTestForm(forms.ModelForm):
url = StrippedURLField(blank=True, null=True)

How to serialize cleaned_data if it contains models?

I'm trying to serialize some form data so that I can stuff it into a hidden field until the user is ready to submit the whole form (think of a wizard).
I'm trying this:
print simplejson.dumps(vehicle_form.cleaned_data)
But I keep getting errors like this:
<VehicleMake: Honda> is not JSON serializable
Really I just need it to output the PK for "Honda".
This doesn't work either:
print serializers.serialize('json', vehicle_form.cleaned_data)
Gives:
'str' object has no attribute '_meta'
Presumably because it's iterating over the keys, which are all strings, whereas I think it expects a queryset, which I don't have.
So how do I do this?
Okay, so far I've come up with this:
from django.utils.simplejson import JSONEncoder, dumps, loads
from django.utils.functional import curry
from django.db.models import Model
from django.db.models.query import QuerySet
from django.core.serializers import serialize
class DjangoJSONEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, Model):
return obj.pk
elif isinstance(obj, QuerySet):
return loads(serialize('json', obj, ensure_ascii=False))
return JSONEncoder.default(self, obj)
json_encode = curry(dumps, cls=DjangoJSONEncoder)
json_decode = loads
Based on the answers I found [here][1]. Now I'm trying this:
json = json_encode(vehicle_form.cleaned_data)
data = json_decode(json)
vehicle = Vehicle(**data)
The first 2 lines work perfectly, but the 3rd results in an exception:
Cannot assign "3": "Vehicle.model" must be a "VehicleModel" instance.
Getting close! Not sure how to deal with this one though...
This is a bit of a hack, but I don't know of a better way:
try:
vehicle_data = simplejson.loads(request.POST['vehicle_data'])
except ValueError:
vehicle_data = []
vehicle_data.append(vehicle_form.raw_data())
request.POST['vehicle_data'] = simplejson.dumps(vehicle_data)
It grabs the JSON data from hidden field in your form and decodes it into a Python dict. If it doesn't exist, it starts a new list. Then it appends the new raw/uncleaned data and re-encodes it and dumps it into the hidden field.
For this to work you need to either make a copy of the POST data (request.POST.copy()) so that it becomes mutable, or hack it like I did: request.POST._mutable = True
In my form template I put this:
<input type="hidden" name="vehicle_data" value="{{request.POST.vehicle_data}}" />
And lastly, to access the raw data for a form I added these methods:
from django.forms import *
def _raw_data(self):
return dict((k,self.data[self.add_prefix(k)]) for k in self.fields.iterkeys())
def _raw_value(self, key, value=None):
if value is None:
return self.data[self.add_prefix(key)]
self.data[self.add_prefix(key)] = value
Form.raw_data = _raw_data
Form.raw_value = _raw_value
ModelForm.raw_data = _raw_data
ModelForm.raw_value = _raw_value
Since .data returns too much data (in fact, it just returns the POST data you initially passed in), plus it's got the prefixes, which I didn't want.

Help understanding a Django view

I am trying to follow the code listed on https://github.com/alex/django-ajax-validation/blob/master/ajax_validation/views.py
I have been able to understand a small chunk of it. I have added comments stating my understanding of what is happening.
I would really appreciate some assistance on questions I listed in comments next to the lines I couldn't quite follow.
def validate(request, *args, **kwargs):
# I thing it is some sort of initializations but I cannot really understand what's happening
form_class = kwargs.pop('form_class')
defaults = {
'data': request.POST
}
extra_args_func = kwargs.pop('callback', lambda request, *args, **kwargs: {})
kwargs = extra_args_func(request, *args, **kwargs)
defaults.update(kwargs)
form = form_class(**defaults)
if form.is_valid(): #straightforward, if there is no error then the form is valid
data = {
'valid': True,
}
else:
# if we're dealing with a FormSet then walk over .forms to populate errors and formfields
if isinstance(form, BaseFormSet): #I cannot really understand what is BaseFromSet
errors = {}
formfields = {}
for f in form.forms: # I am guessing that this is for when there are multiple form submitted for validation
for field in f.fields.keys(): # I think he is looping over all fields and checking for error. what does add_prefix () return? and what is formfields[]?
formfields[f.add_prefix(field)] = f[field]
for field, error in f.errors.iteritems():
errors[f.add_prefix(field)] = error
if form.non_form_errors():
errors['__all__'] = form.non_form_errors() # what is the '__all__'?
else:
errors = form.errors
formfields = dict([(fieldname, form[fieldname]) for fieldname in form.fields.keys()])
# if fields have been specified then restrict the error list
if request.POST.getlist('fields'): # I am having a hard time understanding what this if statement does.
fields = request.POST.getlist('fields') + ['__all__']
errors = dict([(key, val) for key, val in errors.iteritems() if key in fields])
final_errors = {} # here the author of this code totally lost me.
for key, val in errors.iteritems():
if '__all__' in key:
final_errors[key] = val
elif not isinstance(formfields[key].field, forms.FileField):
html_id = formfields[key].field.widget.attrs.get('id') or formfields[key].auto_id
html_id = formfields[key].field.widget.id_for_label(html_id)
final_errors[html_id] = val
data = {
'valid': False or not final_errors,
'errors': final_errors,
}
json_serializer = LazyEncoder() # Why does the result have to be returned in json?
return HttpResponse(json_serializer.encode(data), mimetype='application/json')
validate = require_POST(validate) # a decorator that requires a post to submit
LazyEncoder
class LazyEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, Promise):
return force_unicode(obj)
return obj
form_class = kwargs.pop('form_class')
This is simply pulling the keyword argument, form_class, that was passed in via the URL conf.
(r'^SOME/URL/$', 'ajax_validation.views.validate',
{'form_class': ContactForm}, # this keyword argument.
'contact_form_validate')
BaseFormSet is simply the formset class doing the work behind the scenes. When you don't know, search the source! grep -ri "baseformset" . It's an invaluable tool.
Take a look at at django.forms.formsets to see how formset_factory produces new "formset" classes based on the BaseFormSet, hence the factory part!
I am guessing that this is for when there are multiple form submitted for validation
Yes, that's exactly what a formset is for (dealing with multiple forms)
I think he is looping over all fields and checking for error. what does add_prefix () return? and what is formfields[]?
Yes, that would be looping through the field names.
add_prefix() is for prefixing form field names with a specific form. Because a formset repeats form elements multiple times, each field needs a unique prefix, such as 0-field1, 1-field1, etc.
formfields is just an empty dictionary defined a few lines above.
what is the 'all'?
__all__ is defined at the top of django.forms.forms
NON_FIELD_ERRORS = '__all__'
It's just what non field specific errors (such as constraints across 2 fields) are stored under in the errors dictionary as opposed to errors[fieldname].
I am having a hard time understanding what this if statement does.
The author has left a note:
# if fields have been specified then restrict the error list
if request.POST.getlist('fields'):
It's checking if you specified any specific fields to validate in your URLConf, this is not django but ajax_validation.
You can see that he's overwriting his errors dictionary based on only the fields specified, thus passing on the validation only for those fields.
errors = dict([(key, val) for key, val in errors.iteritems() if key in fields])
here the author of this code totally lost me.
The author has mapped a custom errors and fields dictionary to specific field names with prefixes, (as opposed to the usual FormSet with each form having its own errors dictionary, unaware of the formset itself) which he presumably uses in the AJAX response to validate all fields.
Normally, you can iterate over a formset and go through the errors on a form by form basis, but not so if you need to validate all of them through ajax.
The line pulling html_id should be straight forward most of the time, but it's there because form widgets CAN add interesting things to the end of the ID's based on whether or not the widget is a radio select for example.
From source comments :
# RadioSelect is represented by multiple <input type="radio"> fields,
# each of which has a distinct ID. The IDs are made distinct by a "_X"
# suffix, where X is the zero-based index of the radio field. Thus,
# the label for a RadioSelect should reference the first one ('_0').
Why does the result have to be returned in json?
Because it's an ajax request and javascript easily eats json.
2- could you go through these lines of code...
extra_args_func = kwargs.pop('callback', lambda request, *args, **kwargs: {})
Either return a keyword argument named 'callback' (which if passed in, is supposed to be a function that accepts request and return a dictionary), and if it wasn't, return a lambda function that only returns an empty dictionary.
I'm not sure what the specific use is for the extra context. You could use it to run arbitrary snippets of code without modifying or subclassing ajax_validation...
It might help you to run this code, and put a debugger breakpoint in somewhere so you can step through and examine the variables and methods. You can do this by simply putting this line where you want to break:
import pdb; pdb.set_trace()
and you will be dumped into the debugger in the console.