7

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?

mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • I would very much like to up-vote this question, if you please accept an answer. – raratiru Oct 29 '17 at 18:13
  • @raratiru Okay, if it makes you happy, I picked one ;) Not sure if that's the actual solution I went with or not 7 years ago -- I usually accept the one I end up using. – mpen Oct 30 '17 at 01:26
  • Actually, that's the one I used! :-D – raratiru Oct 31 '17 at 06:03

2 Answers2

14

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
  • 90,183
  • 16
  • 117
  • 101
1

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
  • 1,995
  • 18
  • 28
  • 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. – mpen Aug 14 '10 at 02:19
  • i was speaking generally and perhaps not to the specific use of django forms. you did describe the results as a 'nasty error page'. re the second idea breaking DRY, I do explicitly mention that it's not DRY but a one-timer. to make reusable, you could encapsulate it in a function callable from anywhere. i.e. def checkModelUpdateOk(anyModel,someParams,...): okToUpdate=anyModel.complex_filter(someParams).count() (and so on, returning an error message or a green light) ...with someParams being a dict specifying the keys & values you are looking for (for your intended model). - j – jsh Aug 15 '10 at 19:49
  • The number of parameters nearly amounts to the length of the code :) Anywho, they're valid solutions, just not preferred. – mpen Aug 18 '10 at 01:18