Django form test non-field errors - python

I'd like to ask for a method to check if any kind of non-field errors was raised during form validation in Django. So far I found solution to do this during view testing. Well, it wasn't what I was looking for since I'm interested in doing it at form tests.
I know the way to look for fields' errors:
class FormTest(TestCase)
def test_fields_errors(self):
"""
Suppose form has one field which has to be filled
"""
form = TestForm()
form.full_clean()
self.assertFalse(form.is_valid())
self.assertIsInstance(
form.errors.as_data()['required_field'][0],
ValidationError
)
self.assertEquals(
form.errors['deadline_datetime'][0],
'This field must not be empty'
)
Is there any way to do this when we deal with non-field errors?

I answer my own question, but I also wanna start discussion if there is any better solution
With small help of debugger, I found that non-field errors appear in form.errors as well. But this time there wasn't any dict key named like non-field errors. All I found was a bit strange key __all__, but it allowed me to access demanded errors:
class FormTest(TestCase)
def test_fields_errors(self):
"""
Suppose form has two fields and either one has to be filled
"""
form = TestForm()
form.full_clean()
self.assertFalse(form.is_valid())
self.assertIsInstance(
form.errors.as_data()['__all__'][0],
ValidationError
)
assertEquals(
errors['__all__'][0],
'Fill at least one field'
)
So, I was able to find the error I was looking for during testing non-field errors raise.

Related

Passing data through Django forms

I feel I'm missing the obvious but I can't work it out!
I have written a custom form (for use outside of django admin), which I want to use to create / update instances of a number of model instances as well as hold conditional fields. However I seem to be losing my conditional data.
In my view I instansiate an instance of my form and pass it into the request context:-
view.py
form = MyForm(my_bool=True, pid=7)
render(request 'my_page.html', {'form': form})
forms.py
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
my_bool = kwargs.pop('my_bool', False)
self.pid = kwargs.pop('pid', None)
super(MyForm, self).__init__(*args, **kwargs)
if my_bool:
self.fields['textbox'] = forms.CharField(max_length=256)
That all works fine and the form renders as expected. Now when I submit the form it hits the below
view.py
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
# Do Stuff
My problem is that the form object in the above code does not contain my 'textbox' or 'pid' fields even though the form I submitted did. I'm certain whatever I'm doing wrong is extremely obvious but from a whole lot of googling I cant work out a simple way to instantiate a form, pass it some data about what fields I want to show as well as say the id of the model I eventually want it to update and then have access to the info I passed in the post part of the code.
The # Do Stuff part of my code is supposed to take the 'pid' I passed to the form and use that to fetch a product e.g. Product.objects.get(pk=pid), however without storing the pid in the session I cant work out how to access it from the postback.
If a validation error occurs in my form i.e. form.is_valid() returns false I render the request again passing the form I already have, that way I see the validation errors however any conditional fields I passed initially are missing and their values ignored.
Maybe I'm just going about it completely wrong and I should be doing this a different way. What I am trying to achieve is a content entry form whose fields change depending on the type of product it is passed, once filled in the form saves the data to that instance of the product.
Any help would be greatly appreciated, I've found Django to be extremely accommodating to anything I've thrown at it so far and this feels like such a common use-case that I must just be doing it wrong!
I'm on Django v1.11 & Python v3.6.
you are passing two arguments to create the form, namely my_bool and pid. But you are not passing those arguments in the POST view. In particular my_bool is None so the textbox field never gets generated. Changing
form = MyForm(request.POST)
to
form = MyForm(request.POST, my_bool=True, pid=7)
should do the trick.

How to update form with errors in Django?

I'm trying to write an is_valid method for my forms and I'm using this general strategy:
def is_valid(self):
form = super(UserCreateForm, self).is_valid()
for f, error in self.errors.iteritems():
if f!= '__all__':
self.fields[f].widget.attrs.update({'class': 'error', 'value': strip_tags(error)})
return form
I want to update the form fields' attributes if I get an error with helpful attributes. But I already have class attributes for the fields (using Bootstrap, so something like 'class':'form-control'); I want the error to replace them. However, when I fail the validation and actually get an error, Django complains saying can't concatenate str and errorList. I'm a bit new to Django, so I'm not sure what is going on here.
The way i do this is, have the the errors checked at all the individual divs and display bootstrap's .error class if i find a error.
Eg:{% if form.error %}
Like that you check it with every filed name.
Note: There might be better ways to do it, but i have been using this for a long time now and has worked fine.

How to properly validate a MultipleChoiceField in a Django form

I have a MultipleChoiceField representing US states, and passing a GET request to my form like ?state=AL%2CAK results in the error:
Select a valid choice. AL,AK is not one of the available choices.
However, these values are definitely listed in the fields choices, as they're rendered in the form field correctly.
I've tried specifying a custom clean_state() method in my form, to convert the value to a list, but that has no effect. Printing the cleaned_data['state'] seems to show it's not even being called with the data from request.GET.
What's causing this error?
from django import forms
class MyForm(forms.Form):
state = forms.MultipleChoiceField(
required=False,
choices=[('AL','Alabama'),('AK','Alaska')],
)
MultipleChoiceFields don't pass all of the selected values in a list, they pass several different values for the same key instead.
In other words, if you select 'AL' and 'AK' your querystring should be ?state=AL&state=AK instead of ?state=AL%2CAK.
Without seeing your custom clean_state() method I can't tell you what's going wrong with it, but if the state field isn't valid because the querystring is wrong then 'state' won't be in cleaned_data (because cleaned_data only holds valid data).
Hopefully that helps. If you're still stuck try adding a few more details and I can try to be more specific.

How to append error message to form.non_field_errors in django?

I have a form with several fields. I have separate validation checks for each field, done via the forms validation. I however also need to check if few fields are filled in before redirecting the user to a different view. I was hoping I could somehow append the error to forms.non_field_errors as it is not for a particular field , but I am not sure what the right syntax for this would be. I have checked online and found..
form.errors['__all__'] = form.error_class(["error msg"])
This displays the error message, but it seems to mess up the other pages as well and displyas the error message if I click on anything else.
I tried
form._errors[NON_FIELD_ERRORS] = form.error_class()
This causes a 'NoneType' object has no attribute 'setdefault' error for me.
I have tried
form.non_field_errors().append("Please complete your profile in order to access the content.")
This doesn't seem to do anything and I cant see the error message on the view.
What would be the best way to do this? Ideally I dont' want to do it in the form's clean method. I feel like I should be able to append an error to the form in the view.
Call full_clean(), this should initialize form._errors. This step is critical, if you don't do it, it won't work.
Make the error list, it takes a list of messages, instanciate it as such: error_list = form.error_class(['your error messages'])
Assign the error list to NON_FIELD_ERRORS, you have to import NON_FIELD_ERRORS from django.forms.forms, then assign as such: form._errors[NON_FIELD_ERRORS] = error_list
Here is a demonstration from a shell:
In [1]: from bet.forms import BetForm
In [2]: from django.forms.forms import NON_FIELD_ERRORS
In [3]: form = BetForm()
In [4]: form.full_clean()
In [5]: form._errors[NON_FIELD_ERRORS] = form.error_class(['your error messages'])
In [6]: form.non_field_errors()
Out[6]: [u'your error messages']
This is a bit out-dated but i recently ran into the same question and wanted to shed some further light on this for future readers.
As of Django 1.6+ the errors dictionary is stored as form.errors and not form._errors
If you instantiate form.is_valid(), it is the equivalent of running full_clean()
NON_FIELD_ERRORS isn't necessary to import, you can simply refer to its default dictionary key of __all__
Example:
if form.is_valid():
form.errors['__all__'] = form.error_class(['Your Error Here!'])
On form there is method add_error.
class ExampleForm(forms.Form) :
def clean(self) :
self.add_error(None, "The __all__ error message")
return super().clean()
The first param of add_error() function is about the refered fields.
If the field is None the add_error() function will considere that the error is a form_error.
The clean() method is a hook called by _clean_form().
Then the _clean_form() function is called by full_clean(). See more: full_clean() source
self._errors.setdefault('__all__', ErrorList()).extend([""])

Django inline formset custom validation

Okay. I am probably just overlooking something silly. But I am having an issue with an inline form in django. I have this:
class BaseModelAFormset(forms.models.BaseInlineFormSet):
def clean(self):
for form in self.forms:
print 'I am getting here'
ModelAFormset = inlineformset_factory(ModelB,ModelA, extra=1, formset=BaseModelAFormset)
My models are not really named ModelA and ModelB. I did that as an example.
Anyway, I have this formset on a page and have 2 forms in it plus a third blank one. (So two of the three forms are filled in with objects.
I hit save and I'd expect to see two "I am getting here" statements, but I only see one.
In the end, I am doing a count check for validation, but if the second form is never checked, my count check won't do much good. So the print statement is an intermediate step towards that. Anyone have any idea why this isn't looping right?

Categories