Reputation: 1095
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".
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
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
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
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
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
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