Héléna
Héléna

Reputation: 1095

django 1.8- if form entry query result does't match database, display alert message on same page, instead of "None" or raise exception page

I am grateful to the answers below, but sorry I still didn't resolve this issue maybe I didn't understand them correctly. Therefore I put a bounty for this for clearer answer.

After user entering some information in the form, these information works as a query to filter the database to get the result, if there is no corresponding record in the database, how could I have an alert displaying on the current page or redirected page alerting users "No corresponding data".

enter image description here

Take an example as picture: if user enters "EU" and "India", for sure there is no corresponding record in the database. And the form allows the user to leave the fields blank.

I used to use raise ValidationError, if query result doesn't match database, it will go to a yellow "Exception" page which is not user-friendly. I want to display an error message on the SAME form page right after submitting it :

views.py

from django.contrib import messages

class InputFormView(FormView):
template_name = 'entryform.html'
form_class = EntryForm

def get_success_url(self):
    params = {
        'department': self.request.POST.get('company'),
        'person': self.request.POST.get('region')
    }
    return ''.join([reverse('final'), '?', urllib.urlencode(params.items())])

class FinalView(ListView):
    context_object_name = 'XXX'
    template_name = 'XXX.html'
    model = Final

    def get_queryset(self):
        form = InputForm(self.request.GET)        
        if form.is_valid():
            department = form.cleaned_data['department']
            person = form.cleaned_data['person']

            if department !="" and person !="":
                if Final.objects.filter(department=department,person=person).exists():
                    queryset=Final.objects.filter(department=department,person=person)
                    return queryset
                else:
                    msg="no corresponding data exists!"
                    form.add_error('department', msg)
                    form.add_error('person', msg)

            elif department =="" and person !="":
                if Final.objects.filter(person=person).exists():
                    queryset=Final.objects.filter(person=person)
                    return queryset
                else:
                    msg="no corresponding data exists!"
                    form.add_error('department', msg)
                    form.add_error('person', msg)

            elif ........


        else:     #if form not valid
            messages.error(request, "Error")

    def get_context_data(self,**kwargs):
        query_set = self.get_queryset()
        if query_set is not None:
            context["sales"] = self.get_queryset().aggregate(Sum('sales'))

html

 <form method="post">{% csrf_token %}
        {% csrf_token %}
      {{ formset.management_form }}
      {{ formset.errors }}
      {{ formset.non_field_errors }}
      {{ formset.non_form_errors }}
      {{ form.non_field_errors }}     
     ......                   
        <!--select department-->
        <div class="field" >
            <label> Select department:
            {{ form.department }}
                {% for department in form.department.choices %}                    
                     <option value="department" name= "department" id="id_department">{{department}} </option>
                {% endfor %}
            </label>
        </div>     

..........same for person.....                    

        <!--submit-->
        <div class="button" id="btnShow"><input type="submit" value="Submit" /></div>

        </div>
 </form>

If I don't use the ValidationError method, it will redirect to result page showing everything as "None". But I want to display an alert message. I saw there was an ajax example online, which is a little bit complicated. Is there any easier way to realize it?

Thanks in advance.

Thanks.

Upvotes: 7

Views: 1830

Answers (5)

Daniel Backman
Daniel Backman

Reputation: 5241

If I understand correctly the FinalView should just filter on the GET parameters?

Do you really need the form if that's the case? I it seem like you want to present "no corresponding data exists!" if no result is yielded? generic Listview automatically populates self.object_list (or the context_object_name) from get_queryset so a simple check object_list.exists() should be enough in code or template to render the error msg...

To do a simple filter I'll provide an example of a technique I usually use transformed for your example:

class FinalView(ListView):

    def get_queryset(self):
        qs = super(FinalView, self).get_queryset()
        filter_fields = ('department', 'person')  # the fields to filter on
        query_dict = {}  # filter query
        for param in self.request.GET:  # iterate all params
            if param in filter_fields:  # is param a filter we want to use?
                value = self.request.GET.get(param)
                if value:  # Have value? otherwise ignore
                    query_dict[param] = value
        return qs.filter(**query_dict)  # Execute filter

    def get_context_data(self, **kwargs):
        kwargs = super(FinalView, self).get_context_data(**kwargs)
        if self.object_list.exists():  # Did we get objects? (this can also be done directly in template)
           kwargs['error_msg'] = 'no corresponing data exist!'
       else:
           kwargs["sales"] = self.object_list.aggregate(Sum('sales'))
       # Re-populate form if we want that
       kwargs['form'] = InputForm(initial=self.request.GET)
       return kwargs

Don't know if it fits your requirements. But is an alternative solution.

Elaboration of forms in django: The forms in django is used (among other things)to validate input data and create the appropriate python types from the fields. (I.e IntegerField will be an integer etc). Roles for forms

In this case the data is used to filter a queryset. The data it self is valid but not the result using the data.

The "roles" for the ListView and form are important: The view filters the queryset, the django form validates input data.

The view should handle if we're not happy with the result after executing the filter, the django form should handle if the input data is bad. (I.e bad format, empty fields or department must no be empty if person is filled etc).

Based on the question we know the input to FinalView will be strings or empty. (The self.request.GET is always strings or empty), the need of a django form here might just complicate things?

Upvotes: 2

Sayse
Sayse

Reputation: 43320

Let me just start by saying that this answer is only reiterating what Daniel Roseman and Sebastian Wozny said in their answers so I'd encourage you to accept one of those over this.

Your get_queryset has one self-titled job, and that is to retrieve the queryset your form uses so it has no business doing any error handling at all.

That should be done in the form's clean method

def clean(self):
    cleaned_data = super(MyForm, self).clean()
    region = cleaned_data.get('region')
    country = cleaned_data.get('country')

    if not Result.objects.filter(region=region, country=country).exists():
         self.add_error(ValidationError('No corresponding data exists'))

Now what will happen, if your form isn't valid, you can then return to the same template with this form and it will contain errors about the fields that aren't valid.

Upvotes: 2

user1797792
user1797792

Reputation: 1189

Why not something like this?

views.py

if form.is_valid():
        region = form.cleaned_data['region']
        start_date=form.cleaned_data['start_date']
        end_date=form.cleaned_data['end_date']
        ....          

        queryset=Result.objects.filter(region=region,date__range=[start_date,end_date])

        try:
            result = Result.objects.get(region=region,supply_chain=supply_chain)
        except Result.DoesNotExist:
            result = None

template.html

{% if not result %} 
    <strong>No corresponding data exists</strong>
{% else %}
    {# display data #}
{% endif %}

Upvotes: 2

Sebastian Wozny
Sebastian Wozny

Reputation: 17506

If you're stuck with django <1.7 you can use self._errors.add(thanks to @Sayse). If you're on django 1.7 or newer you can use Form.add_error():

This method allows adding errors to specific fields from within the Form.clean() method, or from outside the form altogether; for instance from a view.

The field argument is the name of the field to which the errors should be added. If its value is None the error will be treated as a non-field error as returned by Form.non_field_errors().

The error argument can be a simple string, or preferably an instance of ValidationError. See Raising ValidationError for best practices when defining form errors.

You should check if there is no corresponding record in the database in the clean method of the form or before you call form.is_valid() in the view, and attach the error to the field:

form.addError("region", ValidationError('No corresponding data exists'))

PS: To turn of the "yellow exception page" turn off DEBUG in your settings.

Upvotes: 3

Daniel Roseman
Daniel Roseman

Reputation: 599856

All this logic belongs inside the form itself. If you put it in the clean method, then the validation error will be caught by the existing Django logic and you can display the error in the template with {{ form.non_field_errors }}.

Upvotes: 3

Related Questions