Custom Filter: Filter output based on a Python list

157 views
Skip to first unread message

+Emmanuel

unread,
Oct 2, 2013, 6:33:19 AM10/2/13
to django...@googlegroups.com
Am working on a Django project that retrieves a student's details from the database, filters them based on the 'period of examination' (this is implemented as a python list in a custom filter) and displays the output.

Here is my custom filter:

@register.filter
def periodofexamination(periodofexam):
    periodofexam = ['DECEMBER 2011', 'MAY 2011', 'DECEMBER 2010']
    for period in periodofexam:
        return period

The template for displaying the output:

{% extends 'base.html' %}
{% load results_extras %}
{% block title %}My Results{% endblock %}

{% block content %}
<h3> My Tentative Examination Results</h3>
<div class="alert alert-info">The results displayed below are tentative and for information purposes only.</div>

    <table class='table table-striped table-bordered'>
    <thead><th>Course Code</th><th>Course Name</th><th>Course Work</th><th>Exam</th><th>Grade</th><th>GPA</th><th>Year</th><th>Exam Period</th></thead>
{% for query in queryset %}
        
    <tr><td>{{ query.coursecode}}</td><td>{{query.coursename}}</td><td>{{query.coursework}}</td><td>{{query.exam}}</td><td>{{query.exam|grade}}</td><td>{{query.exam|gpa}}</td><td>{{query.academicyear}}</td><td>{{query.examperiod}}</td></tr>
   
{% endfor %}
</table>
{% endblock %}

The output:
See the attached file (screenshot.png)

The challenge:
I would like to be able have the student results for a given exam period to appear each in their own table, for instance, all the results for exam period 'December 2011' should be in one table, all the results for exam period 'December 2010' should be in a different table. Currently, all the results appear in one table.
How do I implement this using a custom filter?
Thanks.
screenshot.png

Leonardo Giordani

unread,
Oct 2, 2013, 7:13:25 AM10/2/13
to django...@googlegroups.com
I'd cycle in the template through a list of exam periods, printing a table for each cycle.
You have to pass the list of exam periods in the context of your view.

Try and let me know.

Regards,
Leo


Leonardo Giordani
Author of The Digital Cat
My profile on About.me - My GitHub page - My Coderwall profile


2013/10/2 +Emmanuel <elus...@gmail.com>

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/bed2d4a9-2439-41bd-81a0-be2c4964ca6b%40googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

+Emmanuel

unread,
Oct 2, 2013, 7:41:53 AM10/2/13
to django...@googlegroups.com
That's what I have been trying to do for quite sometime now! I can't seem to get the code right.

Leonardo Giordani

unread,
Oct 2, 2013, 8:00:40 AM10/2/13
to django...@googlegroups.com
Can you perhaps paste the view you are using to render the template? So I can try and help your with some code
2013/10/2 +Emmanuel <elus...@gmail.com>

+Emmanuel

unread,
Oct 2, 2013, 9:03:15 AM10/2/13
to django...@googlegroups.com
Here's the view:
def results_page(request):
    queryset = Results.objects.all().filter(reg_number = 'DBA/20020/82/DU')
    return render_to_response('results.html', locals(), context_instance = RequestContext(request))

Please note that, so far, there's nothing wrong with the view. Using the custom filter at the output level is where the issue is.
What I want to do is something close to this (note the bold items):

{% for query in queryset|periodofexamination %}
    <table class='table table-striped table-bordered'>
    <thead><th>Course Code</th><th>Course Name</th><th>Course Work</th><th>Exam</th><th>Grade</th><th>GPA</th><th>Year</th><th>Exam Period</th></thead>
   
    <tr><td>{{ query.coursecode}}</td><td>{{query.coursename}}</td><td>{{query.coursework}}</td><td>{{query.exam}}</td><td>{{query.exam|grade}}</td><td>{{query.exam|gpa}}</td><td>{{query.academicyear}}</td><td>{{query.examperiod}}</td></tr>
   
</table>
{% endfor %}
{% endblock %}

Leonardo Giordani

unread,
Oct 2, 2013, 9:32:22 AM10/2/13
to django...@googlegroups.com
Ok, now I get the point. I STRONGLY suggest to avoid performing big computations while rendering templates, so I think it is better for you to try something like

def results_page(request):
    context = {}
    context.update(locals())
    context['periods'] = {}
    for period in ['DECEMBER 2011', 'MAY 2011', 'DECEMBER 2010']:
        context['periods'][period] = Results.objects.all().filter(\
               reg_number = 'DBA/20020/82/DU').filter(<further_filter_by_period>)
    return render_to_response('results.html', context, context_instance = RequestContext(request))


and in template

{% for period,results in periods.items %}
    <table class='table table-striped table-bordered'>
    [...]
    Here you can use {{period}}, which is in turn 'DECEMBER 2011', 'MAY 2011', and so on, and
    {{result}} that encompasses the values extracted from the DB.
    [...]
    </table>
{% endfor %}

I included a filter(<further_filter_by_period>) since I do not know the models, so I do not know how you can filter out by examination period.

Please note that I would not pass the whole locals() dict to the view, but carefully select what I need in the template.

Speaking about your filter

@register.filter
def periodofexamination(periodofexam):
    periodofexam = ['DECEMBER 2011', 'MAY 2011', 'DECEMBER 2010']
    for period in periodofexam:
        return period

I think you misunderstood the filter role, which is, indeed, to filter, things. Here, the input periodofexam variable is not filtered, but overwritten. Furthermore, you use return in a for loop, so the result of your filter is 'DECEMBER 2011' irrespective of the input value.

Please check the above code, I wrote it directly in the mail composer, so bugs are certainly lurking.

Let me know if you succeed in solving the problem

Leo



2013/10/2 +Emmanuel <elus...@gmail.com>

+Emmanuel

unread,
Oct 3, 2013, 3:59:02 PM10/3/13
to django...@googlegroups.com
Thanks for the update and the code. Been trying to make sense of the code and trying to get it to work. This is my first project so I still have some challenges:

  • My model contains a field examperiod = models.CharField(max_length = 100) which I need to use to filter. How exactly do I apply that in <further_filter_by_period>. I can't seem to get it to work.
  • The code you provided to be used in the template is a little difficult for me to comprehend. Previously, I could use query.coursecode, query.examcode, etc to populate the specific cells in the table. I can't seem to figure out how to do that with the new piece of code.
Thanks for the help :)

Leonardo Giordani

unread,
Oct 4, 2013, 3:23:38 AM10/4/13
to django...@googlegroups.com
Emmanuel,

for examperiod I'd simply write

[...]
for period in ['DECEMBER 2011', 'MAY 2011', 'DECEMBER 2010']:
        context['periods'][period] = Results.objects.all().filter(\
               reg_number = 'DBA/20020/82/DU').filter(examperiod=period)
[...]

Just be careful when dealing with such comparisons since values must be exactly the same; to state it clearly: this works always if the period strings ("DECEMBER 2011", ...) are machine-generated and do not come from a form (user input).

However, check this page which contains a very detailed analysis of Django query system.

Speaking about the templates: first of all do not take my code as the source of the truth, things can be done in several ways.
Given that, I try to give you a bigger picture that hopefully helps you understanding how to step further.

When introducing Django I always stress the separation between views and templates, which is the base concept of the whole framework (MVT - Model/View/Template).
Views are Python functions. Their purpose is to produce data and to return data, just like any function in this world =)
Jokes apart, views are MOST OF THE TIME used to fetch data from the DB through models and to return templates rendered in HTTP responses, so that a browser can show them.

So, putting it simply: views extract data, templates show them (this is somehow simplistic, but is a good starting point).

Templates are text files that are joined with a dictionary through the Django template language. Most notably you can insert in the template elements of the dictionary just inserting "{{ key }}" where "key" is the dictionary key you want to fetch. If the value you get from the dictionary is an object or another dictionary, you can use the dotted notation to access its attributes or keys. So if you write in a template {{ obj }} the template expects you to pass a dictionary which contains the key 'obj' and some value that can be printed. If you write {{ obj.name }} the template expects you to pass a dictionary that contains the key 'obj', which in turn is something that can be accessed through the 'name' attribute lookup; for example, a Python object with a 'name' attribute.

Context is the dictionary that the view passes to the template when calling render_to_response().

So my advice was to select what you pass in the context, since passing locals() is overkill; you pass the template every variable you have, and this can result in passing a very big dictionary! Moreover, it is not clear to another programmer (that is you in a month) what the template really needs. If you template is made to represent a set of objects in table just extract the set of objects and pass them to the view. To give you an example:

MODEL
class Exam(models.Model):
    label = models.CharField(max_length=30)

VIEW
def someview(request):
    my_list_of_objects = Exam.objects.filter(label='somelabel')
    context = {'objects':my_list_of_objects}
    return render_to_response('results.html', context, context_instance = RequestContext(request))

TEMPLATE
[...]
<table>
{% for obj in objects %}
        <td><tr>{{obj.label}}</tr></td>
{% endfor %}
</table>
[...]

This can also be written (and is perfectly correct):

VIEW
def someview(request):
    my_list_of_objects = [exam.label for exam in Exam.objects.filter(label='somelabel')]
    context = {'object':my_list_of_object}
    return render_to_response('results.html', context, context_instance = RequestContext(request))

TEMPLATE
[...]
<table>
{% for obj in objects %}
        <td><tr>{{obj}}</tr></td>
{% endfor %}
</table>
[...]

(Please note that in the second example I left the previous nomenclature for clarity's sake - in a real case I'd call the values 'labels' and not 'objects')

Hope this helps you. Regards,

Leo

Leonardo Giordani
Author of The Digital Cat
My profile on About.me - My GitHub page - My Coderwall profile


2013/10/3 +Emmanuel <elus...@gmail.com>

+Emmanuel

unread,
Oct 5, 2013, 4:41:57 PM10/5/13
to django...@googlegroups.com
Leo,
Finally got it working and was able to display the data in the respective table as desired. Thank you very much.

I appreciate you patience in helping me understand the 'why' and not just 'how' I should do it in a particular way; and for the advice on Django best practices. I can't thank you enough!

Leonardo Giordani

unread,
Oct 5, 2013, 5:43:19 PM10/5/13
to django...@googlegroups.com

You are welcome! I hope you succeed in your work.

Cheers,

Leo

Reply all
Reply to author
Forward
0 new messages