views:

51

answers:

2

I'm having some issues with double-posting on my site. I figure a simple unique constraint across all the relevant fields will solve the issue on a database level, but then it just produces a nasty error page for the user. Is there a way I can turn this into a pretty form error instead? Like a non_field_error? Or what approach should I take?

+3  A: 

Maybe something like this will help you:

class YourForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = self.cleaned_data
        your_unique_key = cleaned_data['your_unique_key']

        if your_unique_key and YourModel.objects.get(your_unique_key=your_unique_key):
            raise forms.ValidationError("not unique")

        # Always return the full collection of cleaned data.
        return cleaned_data

The clean() method will allow you to access all fields of the form which might be useful if you have a combined unique key. Otherwise a (sightly shorter) clean_your_unique_key() might suit you better.

And please note that under rare circumstances (race conditions) the form validation might not report a duplicate entry (but it's of course reported by the database engine). But for most applications the provided example will be the easier and more maintainable one, so I still recommend this approach.

tux21b
+1  A: 

as far as a 'nasty error page' for the user, Django lets you customize your own 500,404 and probably other pages. general info on that:

In order to use the Http404 exception to its fullest, you should create a template that is displayed when a 404 error is raised. This template should be called 404.html and located in the top level of your template tree.

-- http://docs.djangoproject.com/en/dev/topics/http/views/

another nice way, not as DRY as tux21b's solution but perhaps a little easier to understand for a one-time solution, might be to catch the error intelligently. one way is to do so without even bothering to violate the constraint - a simple query should verify whether the user is about to do something illegal.

okToUpdate=MyModel.objects.filter(parameters=values...).count()  

if okToUpdate>0:  # an object already exists  
    errorExists=True  
    errors={customError:customMessage}  

...  

if errorExists:  
     return render_to_response(errors,'customErrorPage.html')  

else:  
    # return whatever you normally would return  

you then use render_to_response to render a custom error page.

(another way is to allow the database violation to occur, then catch that error and do the same thing... i theorize that a DB gets slightly less stress doing a lookup than handling an exception but it's up to you how you like to do things).

JB

jsh
Uhh..dressing up the 500 page doesn't make it any better. They're still taken away from the page they should be on, and really, that's misleading because in fact one of their posts *did* go through, so it isn't really an "error" so much as a warning, if it even deserves to be that. Regarding your second solution.... that's not a lot better. It breaks DRY. This sort of thing should really be in the form, the same place where the validation occurs.
Mark
jsh
The number of parameters nearly amounts to the length of the code :) Anywho, they're valid solutions, just not preferred.
Mark