Date Tags django

The other day I decided to add a small alphabetic filter to search among the broken packages in debian weather. Searching the net for a nice solution I’ve found few snippets, but none of them struck me as particularly flexible for my needs. I’ve also found a django module, but it seems to me overly complicated for such a simple thing.

I had a look at the code and I’ve generalized the _get_available_letters function that given a table and a filed gives you back a the list of letters used in the table for that specific field. I’ve generalized the code to integrate better with django relational model. Instead of acting directly on a table (using raw sql), I plug the raw sql statement UPPER(SUBSTR(%s, 1, 1)) in the django query using the extra function. The result is pretty neat as you don’t need to know the underlying model and you can use this method with an arbitrary queryset. This is of course possible thanks to django laziness in performing sql queries…

def alpha(request,obj,field):
    alphabet = u'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
    s = 'UPPER(SUBSTR(%s, 1, 1))' % field
    q = obj.distinct().extra(select={'_letter' : s}).values('_letter')
    letters_used = set([x['_letter'] for x in q])
    default_letters = set([x for x in alphabet])
    all_letters = list( default_letters | letters_used)
    all_letters.sort()
    alpha_lookup = request.GET.get('sw','')

    choices = [{
        'link': '?sw=%s' % letter,
        'title': letter,
        'active': letter == alpha_lookup,
        'has_entries': letter in letters_used,} for letter in all_letters]
    all_letters = [{
        'link': '?sw=all&page=all',
        'title': ('All'),
        'active': '' == alpha_lookup,
        'has_entries': True
    },]
    return (all_letters + choices)

This function also gets a request object in order to select the active letter. This is related to the template to display the result of this view.

   queryset = Entry.objects.all()

    #defaults pager + all letters
    element_by_page = None
    letter = request.GET.get('sw','all')

    if (letter != 'all') and (len(letter) == 1):
        queryset = queryset.filter(myfield__istartswith=letter)

    if (request.GET.get('page',None) != 'all') :
        element_by_page = ELEMENT_BY_PAGE

In my specific case I wanted to have by default all letters with pagination, but then to be able to switch off pagination and select a specific letter. I use two variables to control all this. The first variable,page, comes with the generic view list_details. It is usually a number from 0 to the last page and it is used to control pagination. I’ve added a value all to switch off pagination altogether setting element_by_page to None . The second variable one is sw that I use to select a specific letter to display.

    params = {'choices' : alpha(request,Entry.objects,"myfield")}

    return list_detail.object_list(
        request,
        paginate_by = element_by_page,
        queryset = queryset,
        template_name = 'details.html',
        extra_context = params)

If at the end of your view, you return a generic view as above, the only thing you need is to add a choises field in your template to display the alphabetic filter that will look something like this :

<link rel="stylesheet" href="{{ MEDIA_URL }}/css/alphabet.css" type="text/css" />
{% if choices %}
  <br class="clear" />
  <ul class="alphabetfilter">
    {% for choice in choices %}
      <li> {% if choice.has_entries %} <a href="{{ choice.link }}"> {% if choice.active %} <span class="selected">{{ choice.title }}</span> {% else %} <span class="inactive">{{ choice.title }}</span> {% endif %} </a> {% else %} <span class="inactive">{{ choice.title }}</span> {% endif %} </li>
    {% endfor %}
  </ul>
  <br class="clear" />
{% endif %}

This is pretty standard as it iterates over the list of letter linking the ones with content. You need to associate a small css to display the list horizontally. Put this is a file an embedd it where you want with an include statement: {% include "forecast/alphabet.html" %} .

The code for my application is here if you want to check out more details. You can have a look at the result debian here.