Django custom forms.clean() method not working - django

I've been looking for an answer for a while, but i really don't know how this happens. I'm trying to make a form that receives 2 files, and I'm overriding the clean() method to check if the names and extensions are correct. This is what I have:
def clean(self):
cleaned_data = super(UploadForm, self).clean()
obs_filename = cleaned_data.get('obs').name.split('.')
nav_filename = cleaned_data.get('nav').name.split('.')
if obs_filename[0] != nav_filename[0] or [obs_filename[1], nav_filename[1]] != ['obs', 'nav']:
raise forms.ValidationError('Filenames do not match.')
if os.path.isfile(PROJECT_ROOT + '/data/unprocessed/' + '.'.join(obs_filename)) and os.path.isfile(PROJECT_ROOT + '/data/unprocessed/' + '.'.join(nav_filename)):
raise forms.ValidationError('Files already exist.')
return cleaned_data
The problem is, when I use this, the check for input doesn't work (the form submits without selecting any files). When I remove this code, it works fine.
The call to super(UploadForm, self).clean() is the same as in the django documentation for custom clean() functions. I'm using django 1.4. Any thoughts?
Thanks!

File fields are a bit more complicated; you have to make sure your files are bound to your form when you instantiate it. If you use class-based generic views, this should happen automatically, so if you're not seeing them then I'm guessing you're not. In which case, have a look at https://docs.djangoproject.com/en/1.4/ref/forms/api/#binding-uploaded-files. In short, when you instantiate your form, you have to get the files from request.FILES and pass these in as a separate argument to your form e.g. f = MyForm(request.POST, request.FILES).
Also make sure you have enctype="multipart/form-data" in your FORM tag in the HTML.

Related

Is there a way to remove hyperlinks from objects on django admin changelist that user does not have permission to change?

I am currently utilizing the has_change_permission hook in my custom django admin class to implement a simple form of row-level permissions and determine whether a non-superuser can edit a particular object, like so:
def has_change_permission(self, request, obj=None):
if obj is None or request.user.is_superuser or (obj and not obj.superuser_only): # (my model has a 'superuser_only' flag that gets set via fixtures, but its beyond the scope of this question)
return True
return False
This works well enough: all objects are shown to a user, but if they click on an object that they don't have permission to edit then they are taken to my 403 page, presumably because PermissionDenied is raised. However, giving them a hyperlink to a permission denied page doesn't seem ideal for this case; I would like to show the objects but not provide any hyperlinks to the edit page on the list page (in addition to raising PermissionDenied if they tried to manually use the URL for the object). Is there a straightforward hook for removing these hyperlinks without a horrendous hack? I'm an experienced django developer so if you can point me in the right direction (if any exists), I'll be able to implement the details or determine that its not worth it for now.
I was able to accomplish this in a fairly straightforward way by overwriting the default get_list_display_links function in the ModelAdmin class (in my admin.py file):
def get_list_display_links(self, request, list_display):
if request.user.is_superuser:
if self.list_display_links or not list_display:
return self.list_display_links
else:
# Use only the first item in list_display as link
return list(list_display)[:1]
else:
# Ensures that no hyperlinks appear in the change list for non-superusers
self.list_display_links = (None, )
return self.list_display_links
Note that my code maintains the hyperlink for superusers. You can easily just use the latter part of the function if you don't want to make this distinction.

Validation of modelformsets from modelforms

One view I have is using a modelform with custom field cleaning. One type of cleaning is checking if the user is trying to submit a change to a field that is already set to the value, and it works exactly how I want it to work by throwing a ValidationError. The problem of course is that I can only submit one form at a time, so I'd like to use a modelformset to submit multiple forms.
I know it is possible to override the modelformset's clean method, but I'm asking if it's possible to use the modelform's field cleaning methods on the modelformset?. Currently when I submit the modelformset with empty fields the is_valid() passes which seems strange to me...
I also would like to know "typically" where the custom modelformset validation code would go? I was thinking with the forms.py.
*Edit -- with answer. My httpResponseRedirect was allowing the form to be submitted without validation.
def mass_check_in(request):
# queryset
qs = Part.objects.none()
errlst=[]
c = {}
c.update(csrf(request))
# Creating a model_formset out of PartForm
PartFormSetFactory = modelformset_factory(model=Part,
form=PartForm,
formset=BasePartFormSet,
extra=2)
if request.method == 'POST':
PartFormSet = PartFormSetFactory(request.POST)
if PartFormSet.is_valid():
PartFormSet.save()
return http.HttpResponseRedirect('/current_count/')
else:
PartFormSet = PartFormSetFactory(queryset=qs, initial=[
{'serial_number':'placeholder',
},
{'serial_number':'placeholder'
}])
return render(request,'mass_check_in.html',{
'title':'Add Item',
'formset': PartFormSet,
'formset_errors': PartFormSet.non_form_errors(),
})
If you don't enter any data at all in one of the modelforms in your model formset, it will skip validation for that form; from the docs:
The formset is smart enough to ignore extra forms that were not changed.
You can actually disable this functionality though by forcing empty_permitted=False on the forms; see the accepted answer to this question for more: Django formsets: make first required?
Formsets do have their own clean method, but it's used to validate information between two or more forms in the formset, not for validating the individual forms themselves (which should be taken care of in the forms clean method - as you are doing now.
A formset has a clean method similar to the one on a Form class. This is where you define your own validation that works at the formset level:
Here's another similar question:
Django formset doesn't validate

Forms hierarchy issue in Django

I created a form for login, just like this:
class LoginForm(AuthenticationForm):
username = forms.CharField (label=_("Usuario"), max_length=30,
widget=forms.widgets.
TextInput(attrs={'id':'username','maxlength':'25'}))
password = forms.CharField (label=_("Password"), widget=forms.widgets.
PasswordInput(attrs={'id':'password','maxlength':'10'}))
I use it in this view:
def login(request):
if request.method == 'POST':
form = LoginForm(request.POST)
if form.is_valid():
...
After debugging I realize that the form.is_valid() method returns false cause the is_bound attr is false. Do I have to redefine something in my form or to modify my view???
Edit 1
I have followed this SO question about is_valid() method returning False:
form.is_valid() always returning false
but the problem is still there.
One issue to note about the currently accepted answer:
form = LoginForm(request=request, data=request.POST)
is that passing in request seems to enable Django's behavior of checking to see if a test cookie was successful before initiating a session. The problem is that if you haven't set the test cookie previously (it has to be set in a previous view request) it will fail and your login will fail. I recommend just passing the data keyword argument in like so:
form = LoginForm(data=request.POST)
Unless I'm missing something important (it didn't seem like the cookie check is absolutely necessary), this works better in most situations. You could alternatively call request.set_test_cookie() in the view that loads the login page, but that doesn't cover all scenarios.
The issue is actually similar to the one in the question you link to. The form you're inheriting from, django.contrib.auth.forms.AuthenticationForm, takes request as its first parameter, before the usual data param. This is why your form is reporting that it is not bound - as far as it's concerned, you're not passing in any data, so it has nothing to bind to.
So, in your view, you'll need to instantiate it like this:
form = LoginForm(request=request, data=request.POST)

redisplay django form after successfull call to is_valid() with custom error message

I have a django form that first validates its data through calling form.is_valid(). If its not, the form is redisplayed, with an error message regarding the invalid data.
Now if is_valid() is true, I try to save the data in an ldap backend. If the form.cleaned_data is not in correspondance with the ldap data type, I get an Exception from my ldap save method. Now what I would like to do in this case is to redisplay the form with an error message, just like the thing that happens after form.is_valid() returns false.
I tried reading some docs and also some django source, but could not find where I could hook into this.
An alternative would be to carefully build the form of (custom) form fields that would "guarantee" that the data is allready compliant to ldap syntax.
But I would like to make shure that I catch ldap syntax errors and display them in a convenient form. So if I could hook into that form redisplay mechanism would make me a happy little programmer :-)
Any ideas or hints?
Under your class for the form that extends forms.Form, add one of the following methods, assuming you have a is_valid_ldap_data() defined somewhere:
for a whole form:
def clean(self):
if !is_valid_ldap_data(self.cleaned_data.get("fieldname")):
raise forms.ValidationError("Invalid LDAP data type");
return self.cleaned_data
or for a single field:
def clean_fieldname(self):
if !is_valid_ldap_data(self.cleaned_data['fieldname'])):
raise forms.ValidationError("Invalid LDAP data type");
return self.cleaned_data['fieldname']
At your Form subclass implement custom field validation method
http://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-a-specific-field-attribute
Validation logic must go where it belongs. If form.is_valid() == True than form.cleaned_data must be valid. Just because code says so. You want to hide some of validation logic somewhere else -- and that is just bad practice.
It seems to me that you just have an additional step of validation.
You could, depending on which are your specific needs:
Put a validator on your fields, to check for simple things, or
Implement additional cleaning on your field or on multiple fields at once,
but in every case if the form is not valid for any reason (as in your case), is_valid should return False.

adding errors to Django form errors.__all__

How do I add errors to the top of a form after I cleaned the data? I have an object that needs to make a REST call to an external app (google maps) as a pre-save condition, and this can fail, which means I need my users to correct the data in the form. So I clean the data and then try to save and add to the form errors if the save doesn't work:
if request.method == "POST":
#clean form data
try:
profile.save()
return HttpResponseRedirect(reverse("some_page", args=[some.args]))
except ValueError:
our_form.errors.__all__ = [u"error message goes here"]
return render_to_response(template_name, {"ourform": our_form,},
context_instance=RequestContext(request))
This failed to return the error text in my unit-tests (which were looking for it in {{form.non_field_errors}}), and then when I run it through the debugger, the errors had not been added to the forms error dict when they reach the render_to_response line, nor anywhere else in the our_form tree. Why didn't this work? How am I supposed to add errors to the top of a form after it's been cleaned?
You really want to do this during form validation and raise a ValidationError from there... but if you're set on doing it this way you'll want to access _errors to add new messages. Try something like this:
from django.forms.util import ErrorList
our_form._errors["field_name"] = ErrorList([u"error message goes here"])
Non field errors can be added using the constant NON_FIELD_ERRORS dictionary key (which is __all__ by default):
from django import forms
errors = my_form._errors.setdefault(forms.forms.NON_FIELD_ERRORS, forms.util.ErrorList())
errors.append("My error here")
In Django 1.7 or higher, I would do:
form.add_error(field_name, "Some message")
The method add_error was added in 1.7. The form variable is the form I want to manipulate and field_name is the specific field name or None if I want an error that is not associated with a specific field.
In Django 1.6 I would do something like:
from django.forms.forms import NON_FIELD_ERRORS
errors = form._errors.setdefault(field_name, form.error_class())
errors.append("Some message")
In the code above form is the form I want to manipulate and field_name is the field name for which I want to add an error. field_name can be set to NON_FIELD_ERRORS to add an error not associated with a specific field. I use form.error_class() to generate the empty list of error messages. This is how Django 1.6 internally creates an empty list rather than instantiate ErrorList() directly.
You should raise the validationerror.
Why not put the verification within the form's clean method
class ProfileForm(forms.Form):
def clean(self):
try:
#Make a call to the API and verify it works well
except:
raise forms.ValidationError('Your address is not locatable by Google Maps')
that way, you just need the standard form.is_valid() in the view.
You're almost there with your original solution. Here is a base Form class I built which allows me to do the same thing, i.e. add non-field error messages to the form:
from django import forms
from django.forms.util import ErrorDict
from django.forms.forms import NON_FIELD_ERRORS
class MyBaseForm(forms.Form):
def add_form_error(self, message):
if not self._errors:
self._errors = ErrorDict()
if not NON_FIELD_ERRORS in self._errors:
self._errors[NON_FIELD_ERRORS] = self.error_class()
self._errors[NON_FIELD_ERRORS].append(message)
class MyForm(MyBaseForm):
....
All my forms extend this class and so I can simply call the add_form_error() method to add another error message.
I'm not sure how horrible of a hack this is (I've only really worked on two Django projects up until this point) but if you do something like follows you get a separate error message that is not associated with a specific field in the model:
form = NewPostForm()
if something_went_horribly_wrong():
form.errors[''] = "You broke it!"
If the validation pertains to the data layer, then you should indeed not use form validation. Since Django 1.2 though, there exists a similar concept for Django models and this is certainly what you shoud use. See the documentation for model validation.