How to raise multiple ValidationError in Django - python

I saw this post: How to raise multiple ValidationError on Django?
However I have some questions. In the accepted answer, andilabs writes:
raise ValidationError({
'field_name_1': ["Field not allowed to change"],
'field_name_2': ["Field not allowed to change"],
})
Do the values have to be in a List even though it is just one string? If so, anyone know why? Or where in the documentation it says so? I have not found it in https://docs.djangoproject.com/en/3.0/ref/forms/validation/#raising-multiple-errors.
The below code works for me and in my html template I can do {{ form.user.errors }} to have it show up in a div on submission. For who wants to know what context I am using it in, I am using it in a Form view, and inside it I have a def clean(self) method, where I override the parent clean(). Some code for reference:
class RegisterForm(forms.Form):
user = forms.CharField()
**the fields and stuff**
def clean(self):
error = {}
cleaned = super().clean()
if 'user' not in cleaned_data:
error['user'] = ['Username is empty']
**check if other fields are not there, check password length minimum, etc.**
if len(error) != 0:
raise ValidationError(error)
return cleaned

From the Doctring of the __init__ method for ValidationError in django.core.exceptions:
"""
The `message` argument can be a single error, a list of errors, or a
dictionary that maps field names to lists of errors. What we define as
an "error" can be either a simple string or an instance of
ValidationError with its message attribute set, and what we define as
list or dictionary can be an actual `list` or `dict` or an instance
of ValidationError with its `error_list` or `error_dict` attribute set.
"""
Link to source code

Related

Django rest Framework error messages. Does not work

I am new to Django and I am working on a small project, I want an error message to be shown if the user let the field empty. the code that I wrote is not working. Can anyone help me ?
def validate_name(school: School):
if school.name is None:
raise APIException(detail='Name is mandatory.')
class SchoolService(object):
#staticmethod
def validate_create(school: School):
validate_name(school)
Django Rest Framework provides default messages for such common wanted behaviours.
You do not even need to add anything to your field as fields are required by default, unless you explicitly specify required=False
If the user does not fill that field, DRF will automatically return a json object mentioning the field is required and should be filled.
see docs
In your serializer class try adding the validation this way
from django.core.exceptions import ValidationError
def validate_name(self, value):
if value is None:
raise serializers.ValidationError('Name is mandatory')
return value

Correct way to validate GET parameters in django

I'm building a social website that uses django templates/dynamic pages (no SPA technology in place).
I have some ajax calls that check the users news feed or new messages.
Example GET web request of those looks as follows:
GET /feeds/check/?last_feed=3&feed_source=all&_=1500749662203 HTTP/1.1
This is how I receive it in the view:
#login_required
#ajax_required
def check(request):
last_feed = request.GET.get('last_feed')
feeds = Feed.get_feeds_after(last_feed)
It all works, but I want to protect it so the function get_feeds_after does not crash when a malicious user sets the GET parameter to last_feed="123malicious4556". Currently it crashes because in the Feed model the function does this:
#staticmethod
def get_feeds_after(feed):
feeds = Feed.objects.filter(parent=None, id__gt=float(feed))
return feeds
and crashes with the error:
ValueError at /feeds/check/
invalid literal for float(): 2fff2
I currently solve this by directly performing checks on the GET variable and handling exception on int() casting:
def check(request):
last_feed = request.GET.get('last_feed')
try:
feed_source = int(request.GET.get('last_feed'))
except ValueError:
return HttpResponse(0)
My question is what is the best django-recommended way to address this?
I know django has special support forms validation. But this does not seem quite right here, as the GET calls are more of an api rather than forms so it seems like a bad idea to define forms for those GET parameters.
Thanks
All you actually need are the form fields which do all basic validation for you.
If you need custom validation you can write your own validator or even better your own custom form field.
To use the field alone without the form you can do like that for example:
evalType = forms.CharField().clean(request.GET.get('eval-type'))
Because calling this way is not very human friendly I prefer to write a function to deal with it:
def cleanParam(params, paramName, FieldType, *args, **kwargs):
field = FieldType(*args, **kwargs)
cleaned = field.clean(params.get(paramName))
return cleaned
Which we use this way:
evalType = cleanParam(request.GET, 'eval-type', forms.CharField)
This will save you a form class. But I don't think it's very ugly to create a django form for that. A bit too much for the problem but no great concern IMHO.
The advantage of having a form is that you declare the fields you expect in your api call and can check all at once then see the result of is_valid().
I hope this helps.
You can also use a django Validator, which is
a callable that takes a value and raises a ValidationError if it does not meet some criteria.
In your specific case, you can use a combination of validate_slug() - which does not raise a ValidationError only if the passed input is composed of letters, numbers, underscores or hyphens - and int() functions in this way:
from django.core.validators import validate_slug
...
last_feed = request.GET.get("last_feed", "")
try:
validate_slug(last_feed)
last_feed = int(last_feed)
...
except ValueError:
print("invalid literal passed to int()")
except ValidationError:
print("invalid input passed to validate_slug()")
...
Request params can be validated with the DRF serializer.
Create a serializer with all the parameters that are required to be validated.
from rest_framework import serializers
class OrderIDSerializer(serializers.Serializer):
order_id = serializers.CharField(required=True)
def validate(self, attrs):
order_id = attrs.get('order_id')
if not Order.objects.filter(
id=order_id).exists():
raise error
attrs['order_id'] = order_id
return attrs
Import serializer in the views:
from order.serializers.order_serializer import OrderIDSerializer
serializer = OrderIDSerializer(data=request.query_params)
if serializer.is_valid():
order_id = serializer.data['order_id']
// your logic after the validation

In Django how to avoid boilerplate code for getting model instance by pk in a view

Here is example code:
def someview(request):
try:
instance = SomeModel.objects.get(id=request.GET.get('id'))
except SomeModel.DoesNotExist:
instance = None
except ValueError:
# This error may occur if user manually enter invalid (non-integer)
# id value (intentionally or not) in a browser address bar, e.g.
# http://example.com/?id=2_foo instead of http://example.com/?id=2
# This raises ValueError: invalid literal for int() with base 10: '2_'
instance = None
...
Is there a best practice to get a model instance by pk without writing this boilerplate code over and over? Should I use some predefined shortcut in Django or just roll my own?
I was sure that I should use Django's DetailView or SingleObjectMixin but curiously enough it doesn't handle the ValueError exception from my example https://github.com/django/django/blob/master/django/views/generic/detail.py#L50
Is it implied that I have to specify correct integer regexp for pk kwarg in urlconf? Ok, likely. But what if I get pk from request querystring?
UPD I have special logic to do with instance either it's None or not.
You can also use Django's built in shorcut get_object_or_404() that it's designed for this specifically. That function will raise an Http404 exception in case the object doesn't exist. If you want to get None instead of raising the exception, you can create a helper function to accomplish it very easily:
def get_object_or_none(klass, *args, **kwargs):
try:
return get_object_or_404(klass, *args, **kwargs)
except Http404:
return None
Hope this helps!
The first part of your try/except block can be simplified by using django-annoying:
from annoying.functions import get_object_or_None
instance = get_object_or_None(SomeModel, id=request.GET.get('id'))
FYI, you can also just extract get_object_or_None from the package (see source).
There are many generic class based views that might be helpful, in your case DetailView could work.
from django.views.generic.detail import DetailView
class SomeModelDetailView(DetailView):
model = SomeModel
You can overwrite get_object method to change default behaviour.
def get_object(self, queryset=None):
return SomeModel.objects.get(pk=self.get_pk())
And lastly if object is none you should probably display custom 404 page.

How to pass field attribute value to Django form field error message

I want to change form validation error message "This field is required" to include field's label, e.g. "Name field is required". Does Django provide a simple way to refer to field attributes in error messages?
errors = {
'required': _('{label} field is required')
}
class MyForm(forms.Form):
name = forms.CharField(label=_('Name'), error_messages=errors, max_length=80)
age = forms.IntegerField(label=_('Age'), error_messages=errors)
I know I can override form's __init__ method to construct error messages as I want, but I would like a cleaner way.
It is possible to use Python string formatting in the error messages, but you probably need to use a custom Field sub-class.
For the specific error message you want to customise - a required field - Django's Field class just raises the error without attempting any string substitutions:
def validate(self, value):
if value in validators.EMPTY_VALUES and self.required:
raise ValidationError(self.error_messages['required'])
(ValidationError does not do any either)
Some other errors allow some specific vars in the error string (to find them just search for raise ValidationError in django/forms/fields.py) but one problem Django has in giving you what you want is the field instance doesn't know its own name. Unless you passed in a label kwarg the field instance doesn't know its label either, so at the point where the exception is raised Django has nothing to give you.
In your case, you are passing in custom labels for all fields, so you could try the following mixin to ease making the custom field classes you need:
class RequiredErrorMsgMixin(object):
def validate(self, value):
if value in validators.EMPTY_VALUES and self.required:
raise ValidationError(
self.error_messages['required'].format(label=self.label)
)
class CustomCharField(RequiredErrorMsgMixin, CharField):
pass
class CustomIntField(RequiredErrorMsgMixin, IntField):
pass
errors = {
'required': _('{label} field is required')
}
class MyForm(forms.Form):
name = CustomCharField(label=_('Name'), error_messages=errors, max_length=80)
age = CustomIntegerField(label=_('Age'), error_messages=errors)

Is there another way to change the error message on a ModelForm for a validate_unique error?

Disregarding the actual method I use below for parsing the error messages since it needs lots of improvement to make it more general-purpose, is parsing the error messages raised like this the only way of changing the error message displayed?
Specifically, I've removed one of the fields from a ModelForm. When validate_unique is run I remove that field from validation as described in this answer on SO. The error message that Django displays on the form when validate_unique is run says: 'X with this Y and Z already exists.' where Z is the field I manually removed from the ModelForm. I want to change this error message because mentioning Z, the not-displayed field, is confusing to the user who has no way of changing Z on this form.
This feels fragile and hacky.
def validate_unique(self):
exclude = self._get_validation_exclusions()
exclude.remove('a_field_not_shown_on_form')
try:
self.instance.validate_unique(exclude=exclude)
except ValidationError, e:
if '__all__' in e.message_dict:
for idx, err in enumerate(e.message_dict['__all__']):
for unique_together in self.instance._meta.unique_together:
if 'a_field_not_shown_on_form' not in unique_together:
continue
if err.lower() == '{} with this {} and {} already exists.'.format(self.instance.__class__.__name__,
unique_together[0],
unique_together[1]).lower():
e.message_dict['__all__'][idx] = '{} with this {} already exists in {}'.format(self.instance.__class__.__name__,
unique_together[0].capitalize(),
self.instance.complex.name)
self._update_errors(e.message_dict)
from django.utils.text import capfirst
class YourModel(models.Model):
# fields
def unique_error_message(self, model_class, unique_check):
opts = model_class._meta
model_name = capfirst(opts.verbose_name)
# A unique field
field_name = self._meta.unique_together[0]
field_label = capfirst(opts.get_field(field_name).verbose_name)
# Insert the error into the error dict, very sneaky
return _(u"%(model_name)s with this %(field_label)s already exists.") % {
'model_name': unicode(model_name),
'field_label': unicode(field_label)
}

Categories