Validate get_queryset GET parameters - django

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

Related

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.

Ensure User Entered Integer

I am new to Django, and I recently created a system where users can look up a record based on a number. It's just a simple search. The system numbers have leading zeros, and I want the system to recognize the numbers with or without the zeros. I have been able to implement this system and I am converting the number the user specifies with the following code:
def get_queryset(self):
queryset = super(SearchResultsView, self).get_queryset()
search_query = int(self.request.GET.get("q"))
if search_query:
queryset = Book.objects.filter(Q(request_number__icontains=search_query)).distinct()
The code above works fine, as long as the user enters a number. If they typo and include letters, I get invalid literal for Base10. I understand the error, a letter is not an INT. I have spent most of the afternoon looking for how to prevent this error and I can't find what I'm looking for. I have tried to do something like:
if search_query:
try:
queryset = Book.objects.filter(Q(request_number__icontains=search_query)).distinct()
except ValueError:
q = 0000000
return queryset
But the letters are still interpreted and then I receive the invalid literal for Base10 error again. How can I prevent the letters from causing a problem with my query based on a number?
I have also figured out that if I remove the conversion to INT for the search query, the letters no longer cause a problem and the system returns nothing as I would expect it to so I have a work around. Just wondering how I could get the system to do both, accept the letters and also then prevent the invalid literal error and allow the system to turn the input into integers. Thanks in advance for your helpful suggestions.
As Daniel Roseman suggested, I tried to use the following form, but it doesn't seem to catch the error either...
class RequestNumberSearch(forms.Form):
q = forms.IntegerField(required=True)
def __init__(self, user, *args, **kwargs):
super(RequestNumberSearch, self).__init__(*args, **kwargs)
self.fields['q'].widget.attrs['class'] = 'name2'
def clean_q(self):
data = self.cleaned_data['q']
if q != int:
raise forms.ValidationError("Please enter valid numbers!")
return data
You are trying to cast the query to int before checking it.
search_query = self.request.GET.get("q")
if search_query.isdigit(): # check is digit
queryset = Book.objects.filter(Q(request_number__icontains=search_query)).distinct()
return queryset
elif ... : # another check
...
else:
return 'query is erroneous'

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)

Django ValidationError

According to https://docs.djangoproject.com/en/dev/ref/forms/validation/
# Good
ValidationError(
_('Invalid value: %(value)s'),
params={'value': '42'},
)
# Bad
ValidationError(_('Invalid value: %s') % value)
The docs doesnt really explain why it is bad / good. Can someone give a concrete example?
Furthermore, when I inspect form.errors, I get something like 'Invalid: %(value)s'. How do I get the params from the Validation error and interpolate them into the error msg?
Edited
So is this considered good?
ValidationError(
_('Invalid value: %(value)s') % {'value': '42'},
)
I think the real question is: why pass the variables separately via the params argument? Why not interpolate directly into the error msg (ignore named or positional interpolation for now)???
Edited
Ok, From the source # https://github.com/django/django/blob/stable/1.5.x/django/forms/forms.py
I don't think there is any way to retrieve ValidationError's params since the Form does not even save the ValidationError object itself. See code below.
class ValidationError(Exception):
"""An error while validating data."""
def __init__(self, message, code=None, params=None):
import operator
from django.utils.encoding import force_text
"""
ValidationError can be passed any object that can be printed (usually
a string), a list of objects or a dictionary.
"""
if isinstance(message, dict):
self.message_dict = message
# Reduce each list of messages into a single list.
message = reduce(operator.add, message.values())
if isinstance(message, list):
self.messages = [force_text(msg) for msg in message]
else:
self.code = code
self.params = params
message = force_text(message)
self.messages = [message]
class Form:
....
def _clean_fields(...):
....
except ValidationError as e:
self._errors[name] = self.error_class(e.messages) # Save messages ONLY
if name in self.cleaned_data:
del self.cleaned_data[name]
If you have multiple parameters, they might appear in a different order when you translate the error message.
Named arguments allow you to change the order in which the arguments appear, without changing params. With a tuple of arguments, the order is fixed.
Note that you are linking to the development version of the Django docs. The validation error is not interpolating the parameters because you are using Django 1.5 or earlier. If you try your code in the 1.6 beta, then the parameters are interpolated into the error message.
ValidationError is caught by the form validation routine and though it can just show a message, it's better to save the possibility of getting params of error; eg. field name, value that caused error and so on. It's stated just before the example you've provided.
In order to make error messages flexible and easy to override

Django: form validation: accepting multiple values for one field

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"])