Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django form: ask for confirmation before committing to db

Update: The solution can be found as a separate answer

I am making a Django form to allow users to add tvshows to my db. To do this I have a Tvshow model, a TvshowModelForm and I use the generic class-based views CreateTvshowView/UpdateTvshowView to generate the form.

Now comes my problem: lets say a user wants to add a show to the db, e.g. Game of Thrones. If a show by this title already exists, I want to prompt the user for confirmation that this is indeed a different show than the one in the db, and if no similar show exists I want to commit it to the db. How do I best handle this confirmation?

Some of my experiments are shown in the code below, but maybe I am going about this the wrong way. The base of my solution is to include a hidden field force, which should be set to 1 if the user gets prompted if he is sure he wants to commit this data, so that I can read out whether this thing is 1 to decide whether the user clicked submit again, thereby telling me that he wants to store it.

I would love to hear what you guy's think on how to solve this.

views.py

class TvshowModelForm(forms.ModelForm):
    force = forms.CharField(required=False, initial=0)
    def __init__(self, *args, **kwargs):
        super(TvshowModelForm, self).__init__(*args, **kwargs)

    class Meta:
        model = Tvshow
        exclude = ('user')

class UpdateTvshowView(UpdateView):
    form_class = TvshowModelForm
    model = Tvshow
    template_name = "tvshow_form.html"

    #Only the user who added it should be allowed to edit
    def form_valid(self, form):
        self.object = form.save(commit=False)
        #Check for duplicates and similar results, raise an error/warning if one is found     
        dup_list = get_object_duplicates(Tvshow, title = self.object.title)
        if dup_list:
            messages.add_message(self.request, messages.WARNING, 
'A tv show with this name already exists. Are you sure this is not the same one? Click submit again once you\'re sure this is new content'
               )
#            Experiment 1, I don't know why this doesn't work
#            form.fields['force'] = forms.CharField(required=False, initial=1)

#            Experiment 2, does not work: cleaned_data is not used to generate the new form
#            if form.is_valid():
#                form.cleaned_data['force'] = 1

#            Experiment 3, does not work: querydict is immutable
#            form.data['force'] = u'1'

        if self.object.user != self.request.user:
            messages.add_message(self.request, messages.ERROR, 'Only the user who added this content is allowed to edit it.')

        if not messages.get_messages(self.request):
            return super(UpdateTvshowView, self).form_valid(form)
        else:
            return super(UpdateTvshowView, self).form_invalid(form)
like image 549
tBuLi Avatar asked Jul 30 '12 19:07

tBuLi


4 Answers

Solution

Having solved this with the help of the ideas posted here as answers, in particular those by Alexander Larikov and Chris Lawlor, I would like to post my final solution so others might benefit from it.

It turns out that it is possible to do this with CBV, and I rather like it. (Because I am a fan of keeping everything OOP) I have also made the forms as generic as possible.

First, I have made the following forms:

class BaseConfirmModelForm(BaseModelForm):
    force = forms.BooleanField(required=False, initial=0)

    def clean_force(self):
        data = self.cleaned_data['force']
        if data:
            return data
        else:
            raise forms.ValidationError('Please confirm that this {} is unique.'.format(ContentType.objects.get_for_model(self.Meta.model)))

class TvshowModelForm(BaseModelForm):            
    class Meta(BaseModelForm.Meta):
        model = Tvshow
        exclude = ('user')

"""
To ask for user confirmation in case of duplicate title
"""
class ConfirmTvshowModelForm(TvshowModelForm, BaseConfirmModelForm):
    pass   

And now making suitable views. The key here was the discovery of get_form_class as opposed to using the form_class variable.

class EditTvshowView(FormView):       
    def dispatch(self, request, *args, **kwargs):
        try:
            dup_list = get_object_duplicates(self.model, title = request.POST['title'])  
            if dup_list:         
                self.duplicate = True
                messages.add_message(request, messages.ERROR, 'Please confirm that this show is unique.')
            else:
                self.duplicate = False
        except KeyError:
            self.duplicate = False
        return super(EditTvshowView, self).dispatch(request, *args, **kwargs)

    def get_form_class(self):
        return ConfirmTvshowModelForm if self.duplicate else TvshowModelForm

"""
Classes to create and update tvshow objects.
"""
class CreateTvshowView(CreateView, EditTvshowView):  
    pass

class UpdateTvshowView(EditTvshowView, UpdateObjectView):
    model = Tvshow  

I hope this will benefit others with similar problems.

like image 176
tBuLi Avatar answered Sep 19 '22 18:09

tBuLi


I will post it as an answer. In your form's clean method you can validate user's data in the way you want. It might look like that:

def clean(self):
    # check if 'force' checkbox is not set on the form
    if not self.cleaned_data.get('force'):
        dup_list = get_object_duplicates(Tvshow, title = self.object.title)
        if dup_list:
            raise forms.ValidationError("A tv show with this name already exists. "
                                        "Are you sure this is not the same one? "
                                        "Click submit again once you're sure this "
                                        "is new content")
like image 41
Alexander Larikov Avatar answered Sep 23 '22 18:09

Alexander Larikov


You could stick the POST data in the user's session, redirect to a confirmation page which contains a simple Confirm / Deny form, which POSTs to another view which processes the confirmation. If the update is confirmed, pull the POST data out of the session and process as normal. If update is cancelled, remove the data from the session and move on.

like image 23
Chris Lawlor Avatar answered Sep 19 '22 18:09

Chris Lawlor


I have to do something similar and i could do it using Jquery Dialog (to show if form data would "duplicate" things) and Ajax (to post to a view that make the required verification and return if there was a problem or not). If data was possibly duplicated, a dialog was shown where the duplicated entries appeared and it has 2 buttons: Confirm or Cancel. If someone hits in "confirm" you can continue with the original submit (for example, using jquery to submit the form). If not, you just close the dialog and let everything as it was.

I hope it helps and that you understand my description.... If you need help doing this, tell me so i can copy you an example.

like image 37
marianobianchi Avatar answered Sep 21 '22 18:09

marianobianchi