Leveraging HTML and Django Forms: Pagination of Filtered Results

Django’s forms are fantastic for parsing user input, but I’ve come up with a nice way to use them, in conjunction with HTML forms, for pagination, using the inbuilt Django pagination features.

It all stems from the fact that I’ve begun using forms quite heavily for GET purposes, rather than just for POST. Basically, anytime you have a URL that may have some parts of the query string that may need to be built, it’s simpler to use a form element, than to manually build up the url in your template.

Thus, where you may have something like:

<a href="{% url 'foo' %}?page={{ page }}">

It may be better do do something more like:

<form action="{% url 'foo' %}">
  <input type="hidden" name="page" value="{{ page }}">
</form>

Indeed, you can even use named buttons for submission, which will refer to the page. That is the key to the process outlined below.


Django comes with lots of “batteries”, including form handling and pagination. The Class Based Views (CBV) that deal with collections of objects will include pagination, although it is possible to use this pagination in your own views. For simplicity, we’ll stick with a simple ListView.

Let’s begin with that simple view: in our views.py:

from django.views.generic import ListView, DetailView

from .models import Person

person_list = ListView.as_view(
    queryset=Person.objects.all(),
    template_name='person/list.html',
    paginate_by=10,
)

person_detail = DetailView.as_view(
    queryset=Person.objects.all(),
    template_name='person/detail.html',
)

Essentially, that’s all you need to do. You could use implied template names, but I almost never do this. The takeaway from this block is that we are stating the queryset that our ListView will use as the base, the template it should render, and the number of items per page.

I’ve stubbed out the person_detail view, just so we can refer to it in our urlconf, and then in turn in the template. Because of the simplicity of it, we could have just done all of this in our urls.py.

Speaking of our urls.py, we have something like:

from django.conf.urls import url

import views

urlpatterns = [
    url(r'^people/$', views.person_list, name='person_list'),
    url(r'^people/(?P<pk>\d+)/', views.person_detail, name='person_detail'),
]

Then, in our template, we can render it as (ignoring the majority of the page):

<ul class="people">
  {% for object in object_list %}
    <li>
      <a href="{% url 'person_detail' pk=object.pk %}">
        {{ object }}
      </a>
    </li>
  {% endfor %}
</ul>

But this doesn’t give us our pagination. It will only show the first ten results, with no way to access the others. All we need to do to access the others is to append ?page=X, but, as we will see, there is another way.

Typically, your pagination block might look something like:

<ul class="pagination">
  <li>
    {% if page_obj.has_previous %}
      <a href="?page={{ page_obj.previous_page_number }}">
        prev
      </a>
    {% else %}
      <span>prev</span>
    {% endif %}
  </li>

  {% for page_number in paginator.page_range %}
    {% if page_number = page_obj.number %}
      <li class="active">
        <span>{{ page_number }}</span>
      </li>
    {% else %}
      <li>
        <a href="?page={{ page_number }}">
          {{ page_number }}
        </a>
      </li>
    {% endif %}
  {% endfor %}


  <li>
    {% if page_obj.has_next %}
      <a href="?page={{ page_obj.next_page_number }}">
        next
      </a>
    {% else %}
      <span>next</span>
    {% endif %}
  </li>
</ul>

Depending upon your CSS framework, if you use one, there may already be some pre-prepared styles to help you out with this.

This is all well and good, until you want paginated search results. Then, you can no longer rely on being able to rely on using ?page=N, as this would remove any search terms you were already using. Also, if you were using ajax to fetch and display stuff, you may need to use the whole URL, rather than just the query string.

Instead, we can use a Django form for searching, and just add in the pagination bits.

We will build a page that displays an optionally filtered list of people.

Our Person model will be deliberately simple:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=256)

Likewise, our form will be simple. All we need to do is have the form able to filter our queryset.

from django import forms

class PersonSearchForm(forms.Form):
    query = forms.CharField(label=_('Filter'), required=False)

    def filter_queryset(self, request, queryset):
        if self.cleaned_data['name']:
            return queryset.filter(name__icontains=self.cleaned_data['query'])
        return queryset

Finally, we will need to subclass a ListView. We’ll mixin from FormMixin, so we get the form-handling capabilities:

from django.views.generic.edit import FormMixin
from django.views.generic import ListView

class FilteredListView(FormMixin, ListView):
    def get_form_kwargs(self):
        return {
          'initial': self.get_initial(),
          'prefix': self.get_prefix(),
          'data': self.request.GET or None
        }

    def get(self, request, *args, **kwargs):
        self.object_list = self.get_queryset()

        form = self.get_form(self.get_form_class())

        if form.is_valid():
            self.object_list = form.filter_queryset(request, self.object_list)

        context = self.get_context_data(form=form, object_list=self.object_list)
        return self.render_to_response(context)

There’s a little bit to comment on there: we override the get_form_kwargs so we pull our form’s data from request.GET, instead of the default.

We also override get, so we filter results if the form validates (which it will if there was data provided). We delegate responsibility for the actual filtering to the form class.

Everything else is just standard.

We will want to actually use this view:

people_list = FilteredListView.as_view(
    form_class=PersonSearchForm,
    template_name='person/list.html',
    queryset=Person.objects.all(),
    paginate_by=10
)

Now we need to render this.

<form id="person-list-filter" action="{% url 'person_list' %}">
  <input name="{{ form.query.html_name }}" value="{{ form.query.value }}" type="search">
  <button type="submit" name="page" value="1">{% trans 'Search' %}</button>
</form>

<div class="results">
  {% include 'person/list-results.html' %}
</div>

You may notice that the search button will result in page=1 being used. This is deliberate.

Our person/list-results.html is just the same as what our person/list.html looked like before, with the addition of the pagination template inclusion.

{% include 'pagination.html' with form_target='person-list-filter' %}

<ul class="people">
  {% for object in object_list %}
    <li>
      <a href="{% url 'person_detail' pk=object.pk %}">
        {{ object }}
      </a>
    </li>
  {% endfor %}
</ul>

Our pagination.html is very similar to how our other template above looked too, but using <button> elements instead of <a>, and we will disable those that should not be clickable. Also, the buttons contain an attribute indicating which form they should be bound to.

<ul class="pagination">
  <li>
    <button
      form="{{ form_target }}"
      {% if page_obj.has_previous %}
        name="page"
        value="{{ page_obj.previous_page_number }}"
        type="submit"
      {% else %}
        disabled="disabled"
      {% endif %}>
      prev
    </button>
  </li>

  {% for page_number in paginator.page_range %}
    <li class="{% if page_number == page_obj.number %}active{% endif %}">
      <button
        name="page"
        value="{{ page_number }}"
        type="submit"
        form="{{ form_target }}"
        {% if page_number == page_obj.number %}
          disabled="disabled"
        {% endif %}>
        {{ page_number }}
      </button>
    </li>
  {% endfor %}

  <li>
    <button
      form="{{ form_target }}"
      {% if page_obj.has_next %}
        name="page"
        value="{{ page_obj.next_page_number }}"
        type="submit"
      {% else %}
        disabled="disabled"
      {% endif %}>
      next
    </button>
  </li>
</ul>

We are getting close now. This will be enough to have clicking on the next/previous or page number buttons resubmitting our search form, resulting in the page reloading with the correct results.

But we can do a bit better. We can easily load the results using AJAX, and just insert them into the page.

We just need one additional method on our View class:

class FilteredListView(FormMixin, ListView):
    # ...

    def get_template_names(self):
        if self.request.is_ajax():
            return [self.ajax_template_name]
        return [self.template_name]

    # ...

and one addition to our view declaration:

people_list = FilteredListView.as_view(
    form_class=PersonSearchForm,
    template_name='person/list.html',
    ajax_template_name='person/list-results.html',
    queryset=Person.objects.all(),
    paginate_by=10,
)

I’ll use jQuery, is it makes for easier to follow code:

// Submit handler for our form: submit it using AJAX instead.
$('#person-list-filter').on('submit', function(evt) {
  evt.preventDefault();

  var form = evt.target;

  $.ajax({
    url: form.action,
    data: $(form).serialize(),
    success: function(data) {
      $('#results').html(data)
    }
  });
});

// Because we are using buttons, which ajax submit will not send,
// we need to add a hidden field with the relevant page number
// when we send our request.
$('#person-list-filter').on('click', '[name=page]', function(evt) {
  var $button = $(evt.target).closest('button');
  var $form = $button[0].form;

  if (!$form.find('[type=hidden][name=page]')) {
    $form.append('<input type="hidden" name="page">');
  }

  $form.find('[type=hidden][name=page]').val($button.val());

  $form.submit();
});

That should do nicely.


There is another thing that we need to think about. If we leave the next/prev buttons, then we need to handle multiple clicks on those buttons, which fetch the subsequent page, and possibly cancel the existing AJAX request.

I do have a solution for this, too, although it complicates things a fair bit. First, we need to add some attributes to the next/prev buttons:

<ul class="pagination">
  <li>
    <button
      form="{{ form_target }}"
      {% if page_obj.has_previous %}
        name="page"
        value="{{ page_obj.previous_page_number }}"
        type="submit"
        data-increment="-1"
        data-stop-at="1"
      {% else %}
        disabled="disabled"
      {% endif %}>
      prev
    </button>
  </li>

  {% for page_number in paginator.page_range %}
    <li class="{% if page_number == page_obj.number %}active{% endif %}">
      <button
        name="page"
        value="{{ page_number }}"
        type="submit"
        form="{{ form_target }}"
        {% if page_number == page_obj.number %}
          disabled="disabled"
        {% endif %}>
        {{ page_number }}
      </button>
    </li>
  {% endfor %}

  <li>
    <button
      form="{{ form_target }}"
      {% if page_obj.has_next %}
        name="page"
        value="{{ page_obj.next_page_number }}"
        type="submit"
        data-increment="1"
        data-stop-at="{{ paginator.num_pages }}"
      {% else %}
        disabled="disabled"
      {% endif %}>
      next
    </button>
  </li>
</ul>

And our click handler changes a bit too:

$('#person-list-filter').on('click', 'button[name=page]', function() {
  var page = parseInt(this.value, 10);
  var $form = $(this.form);
  // Only update the value of the hidden form.
  if (!$form.find('[name=page][type=hidden]')) {
    $form.insert('<input name=page type=hidden>');
  }
  $form.find('[name=page][type=hidden]').val(page);
  // Increment any prev/next buttons values by their increment amount,
  // and set the disabled flag on any that have reached their stop-at
  $form.find('[data-increment]').each(function() {
    this.value = parseInt(this.dataset.increment, 10) + page;
    // We want to disable the button if we get to the 'stop-at' value,
    // but this needs to happen after any submit events have occurred.
    if (this.dataset.stopAt) {
      setTimeout(function() {
        this.disabled = (this.value == this.dataset.stopAt);
      }.bind(this), 0);
    }
  });

  $form.submit();
});

Since this was posted, I have written a number of pages that use this pattern. Some of the improvements that could be made are listed below:

It’s possible to have these results automatically update as the user types. Obviously, this only makes sense if we have AJAX submission happening!

$('#person-list-filter').on('keyup', function() {
  this.submit();
})

If you have lots and lots of results, you probably won’t want to show every button. Often you will see the first few, and a couple either side of the current page (and sometimes the last few). This is almost possible to do with pure CSS, but not quite. I do have a solution for this, but it’s probably worthy of a complete post of its own.

Another situation that is likely to happen is this:

  • User clicks on a page other than page 1 of results. Let’s say page N.
  • User enters text in search field which results in fewer than N pages of results being available.
  • User gets error message.

We can fix this with an overridden method:

class FilteredListView(FormMixin, ListView):
    # ...

    def paginate_queryset(self, queryset, page_size):
        try:
            return super(FilteredListView, self).paginate_queryset(queryset, page_size)
        except Http404:
            self.kwargs['page'] = 'last'
            return super(FilteredListView, self).paginate_queryset(queryset, page_size)

    # ...

You’ll also need to add in a get_prefix() method if you are using an old Django, but really you should just upgrade.


Updated: I’ve added in some more error checking into the templates, to prevent exceptions when attempting to render previous and next page links (thanks inoks).

Updated: I’ve changed to use the preferred urlpattern syntax. (thanks knbk).

Updated: Delegate to the form for filtering. Add discussion of other extensions. Add button[form] attributes.

...

    Tags:

More Cleartext Passwords

Update: I’ve since heard back from my health insurer. They are working to mitigate the risks outlined in the complaint below.

As such, and as an indicator of good faith on my behalf, I’ve removed links and mentions of them. Should they not follow through, this post will be restored, and more venom added!


More plain-text-shenanigans.

I complained some time ago to my health insurer:

I’m writing to complain again about your security practices.

You clearly store passwords in plain text: evidenced by the fact that the password reset process sends me my actual password, instead of doing a password reset, like every responsible company does.

Given the recent spate of security breaches, you really should consider improving this.

I received the following reply:

Dear Mr Schinckel,

Thank you for your email. Your feedback regarding the security of our Online Member Services has been noted.

Please rest assured that as a fund, we ensure that we adhere to the Privacy Act. The security of our members and their information is paramount to us, and systems operate in accordance with relevant legislation and guidelines. Our systems and processes are regularly reviewed and audited to ensure full adherence.

I didn’t do anything about it, but the email I received today made me think about it again.

So, here is the content of my complaint to them.


Consider this a formal complaint.

It also occurs to me that what you are claiming in your privacy policy is false.

[link to privacy policy, and content of privacy policy removed]

You clearly have not taken “reasonable steps to protect all personal information … from unauthorised access”. A simple, very reasonable step would be to store only hashes of passwords, generated using a per-password salt, instead of storing passwords in clear text.

Your storage of passwords in clear text, and then the further sending of those over email, which is inherently not encrypted, means that any administrator of any mail server between your server and the recipient would be able to view the username and password of a member, and masquerade as them.

Even if you encrypted passwords in a manner that means you were able to recover them, in order to send them over email, means that you are still open to attackers appropriating passwords, either in transit, or after gaining access to your systems. This includes having an encrypted database, or encrypted columns within the database.

You clearly have not ensured “that appropriate technical and organisational security measures, consistent with standard industry practice, are in place to attempt to safeguard the security…”, as what your password recovery feature indicates, you are storing passwords in clear text. As a professional Web Developer, I can assure you this is not in line with best practices with regard to software development.

Your claim of non-responsibility “for any breach of security caused by third parties” is laughable, given that any attacker that gains access to your member database automatically has access to all details of all members, and can store passwords, and access those member’s details at any time in the future unless they change their passwords.

You state that “[redacted] does not use any form of encryption … to protect information a member sends from their computer … and emails”. Did it not occur to you that the same is true of the reverse? That information you send over email is likewise not encrypted?

Claiming that your systems operate “in accordance with relevant legislation” is a cop-out. All it does is show that the legislation is out of date.

Please take my complaints seriously. Please fix your security.

Matt.

Plain-text bad-news

For the past few years, I’ve run in the City-Bay, Adelaide’s largest Fun Run.

This year, when entering, I needed to reset my password.

Imagine my surprise when they sent me my password in plain text.

Then, imagine my surprise, or lack of it, when they sent me:

Dear Matthew,

We would like to advise that we have removed access to our online entry system after customers reported data missing from their records. Although investigations are underway, we would like to advise no financial information of race entrants is stored on our system and is therefore not at risk.

We recommend that you change the password you have used on our website (once it is deemed safe to reopen). As an added precaution we also advise that you change your password on any other website it is used.

Thank you for your understanding and continued interest in our event,

Kind Regards,

City-Bay Fun Run team

www.city-bay.org.au

Well, you know, if you store passwords, in plain text, then you are just asking for trouble.

Review Django Essentials

Django Essentials. Note it appears the name of this book has been changed from “Getting started with Django”.

I’ll be clear from the outset: I have some pretty strong issues about the first part of this book, and I’m going to be quite specific with the things that I think are wrong with it. Having said that, the later chapters are far better than the earlier ones.

I am not sure, however, that it’s any more accessible than the official documentation. There’s probably a market for a more thorough tutorial than the one on the Django website, however, I’m not sure this book, as it stands, is that tutorial.

How could this book be better?

I think it gets bogged down providing detail in areas that are just not that important at that point in time. I also think it misses a good overview of the product that is being built: indeed it’s never clear, even after completing the book, exactly what the product is supposed to do.

In my opinion, the code examples are hard to read. This is a combination of the styling of the source code, and the layout. That bold, blue is quite jarring in comparison to the rest of the text, and the repeated lack of PEP8 compliance, especially when coupled with reading it on a narrow device, make it hard to follow the code. Multiple code blocks (which should be in separate files) flow together, making it hard to see where one stops and the next begins.

The book fails early on to push some basic Python standards and best practices. In some cases these are addressed later on, however it is not obvious what is gained by not starting from this point. Similarly, there are some security issues that should never have passed through editing. Again, these are addressed later, but I feel that the damage has already been done. Friends don’t let friends store passwords in plain text; and very little is gained by disabling the CSRF protection.

But it’s not just the source code that seems lacking. The technical translation at times varies between the obtuse and the absurd. Early chapters in particular (the ones that I think are more important when teaching basic concepts) contain sentences or paragraphs that required me to re-read several times in order for me to be able to translate it into something that made sense to me. And I’ve been writing Django code for about 6 years (and Python code for probably another 6 before it).

Would I recommend it?

After hitting the plain-text-password section, I said no. I actually have a couple of guys much newer to Django than me at work, and I did not want them to read the book at that point.

However, after I’d cooled down, and actually started to draft this review, I re-read the start, and read the rest. There is some good information, but I’m not sure that it’s presented in a way that is better than the official documentation, or some other resources out there.

So, I’m really not sure I’d recommend it to a beginner. There are too many things early in the book that set up for future failures (or at least, unlearning). And I’m not sure I’d recommend it to an intermediate developer. It’s not that it’s bad (with the caveats below), it’s just not as good as what you can read on the Django website.

Some of the more important specific issues that I feel are wrong with this book follow. These are often things that beginners struggle with. You’ll notice less stuff about the later chapters. That’s because they are better.


Code standards.

Throughout the book, there are inconstencies with how individual models and modules are named. Whilst this seems pedantic, computers are pedantic, when it comes to textual source code. It does matter if you use Work_manager in one place, and the Workmanager in another.

Further, in Python, we always (unless the project we are working on has different standards) use snake_case for module names, TitleCase for class names, and snake_case again for variables, methods and functions, and ANGRY_SNAKE_CASE for constants. There’s just no reason to go against these guidelines.

Okay, I may have made up the name ANGRY_SNAKE_CASE.

Finally, Python code should be compliant to PEP8. I’m not sure that a single line of code in this book would pass through a PEP8 checker.


MVC/MVT

The section on “The MVC Framework” (tip: Django isn’t) seems superfluous. It would be far better to avoid this term, and instead describe the typical flow of data that one might see in a request-response cycle handled by Django:

  1. The client sends a request to the server
  2. The server passes the request to the correct view function (according to the url)
  3. The view function performs the required work, and returns an HttpResponse object.
  4. The HttpResponse object is sent back to the server.

Depending upon the view, it may do any or all of the following:

  • Process data provided by the client using a Form
  • Load and/or save data to/from the database
  • Render an HTML template or return a JSON (or XML) response.
  • Perform any other action that is required

The whole concept of a Controller doesn’t really make sense in the context of a web page, although purely within the client-side of a Single-Page-Application it could.


Installation.

I’ve written about installation before, notably discussing how every project should be installed into a new virtualenv. Indeed, I even install every command-line application in it’s own environment. And, most of the experienced Pythonistas I have come across always use a new virtualenv for each project, both in development and in deployment. So it was worriesome to see a non-best-practice approach used for installation.

Although this is addressed later in the book (in the chapter on deployment), I fail to understand the benefit of not mentioning it now. There are so many reasons to use virtualenv in development, and none I can think of for avoiding it.


Security

There are two things in this book that set off alarm bells for me, with respect to security. I’ve mentioned them above, but I’ll go into a little more detail.

The more minor error is the disabling of CSRF checking. The inbuilt Django CSRF protection ensures a range of attacks are ineffective, and the mental cost of using this protection is fairly low: in any view that you are POSTing back to the server, you need to include the CSRF token. This is usually done as a form field, using the csrf_token template tag.

Disabling it is almost never a good idea.

Suggesting that you disable it “just for now” as the only thing you change in the initial settings file is even worse. A beginning programmer may begin routinely disabling CSRF protection as they start a new project, and not re-enabling it. Bad form.

The severe error is storing user passwords in plain text. This flaw is so basic that, even though it is “fixed” later in the book, as is CSRF protection, by then I feel it is too late. Even hinting that either of these things is acceptable to do as an interim measure (do you have any idea how much “interim” or temporary code I have in production still, years after it was written?) makes me really struggle to continue reading.

However, I am glad I did.


URL routing and regular expressions

This book contains a reasonable explanation of regular expressions, but I think it would have been better suited to have a more concrete set of examples of how regular expressions can be used for URL routing in Django. For instance:

r'^tasks/$'

r'^task/([0-9a-z]+)/$'

r'^posts/(?P<year>\d{4})/(?P<month>\d{2})/$'

You could use a series of examples, like these, to describe some of the key rules of regular expressions, and at the same time discuss parameters. Alternatively, you could skip regular expressions at all at this point in time, and use simple strings.

When discussing URL routing, the following paragraph is a great example of a failure to explain what is essentially a simple process.

“After having received a request from a web client, the controller goes through the list of URLs linearly and checks whether the URL is correct with regular expressions. If it is not in conformity, the controller keeps checking the rest of the list. If it is in conformity, the controller will call the method of the corresponding view by sending the parameters in the URL.”

Phrased in a simpler manner:

“The URL resolver looks at each pattern in the order it is defined. If the regular expression of the url route matches the request’s path, the matching view method is called with the request object and any other parameters defined, otherwise it is passed on to the next route.”


Templates

This book presents a reasonable discussion of the Django template language. There are some parts that made me do a double-take (legacy of templates? Oh, you mean inheritance), and there are lots of important typos, missing characters, or just plain wrong source code.

And then there’s render_to_response.

Back in the day, we used to use a function called render_to_response(), which required you to manually pass a RequestContext instance to it: we have since moved on to render(). There is no need really to mention render_to_response() in anything other than a footnote: “You might see older code that uses…”

Talking about the context itself is good, but I think it should be more explicit: “You pass three arguments to render(): the request object, the template path and a dict containing the variables from your view that you want available in the rendering context”.

Oh, and later in the book, locals() is passed as the context. The Zen of Python: explicit is better than implicit. Yes, in the box immediately afterward, it is suggested that you don’t do this.

Doing something, and then suggesting that you don’t do it is counterproductive.


Models

Django’s ORM gets some criticism at times. I find it’s mostly good enough for my needs, indeed, it sometimes does a better job of writing queries than me. However, it is an Object Relational Mapper, and discussing how that works is simple terms would probably be useful. It’s not strictly necessary to have a strong background in relational databases and SQL to use correctly, but understanding some of the implications of how accessing things from the ORM can cause issues, or indeed, how the data is even represented in the database can only be a positive.

“To make a connection between databases and SQL, we can say that a model is represented by a table in the database, and a model property is represented by a field in the table.”

Cumbersome language again, and not totally wrong, but probably slightly misleading. Perhaps:

”…a Model class is represented by a table in the database, and an instance of that Model is represented by a row/tuple. Fields on the model (which appear as special attributes) are the columns of that row.”

A discussion of south is also somewhat welcome. Even though the soon to be released Django 1.7 contains a superior (but written by the same person) implementation of migrations, it’s certainly still worth understanding a little about how south works.

However, there is one false statement when discussing south:

“Never perform the Django syncdb command. After running syncdb --migrate for the first time, never run it again. Use migrate afterwards.”

This is a broken statement. If you were to add a new app that did not have migrations, then without a syncdb command, the tables for it’s models would not be created.

This chapter suddenly gets a whole lot worse as soon as a model is defined with a plain-text password field, but I’ve already discussed that.


Django.contrib.admin

I spend a lot of time trying to talk people out of using the admin module as anything other than a top-level admin tool. Really, this is a tool that is fantastic for viewing and maniplating data in the early stages of development, and great for emergency access for a developer or trusted admin to view or change data on a live system, but trying to push too much into it is problematic. I say that as someone who has a project that relies far too much on the admin.

It’s also hard to not discuss the admin, as it really is a great tool, but it’s really important to understand it’s limitations.

I quote Django core contributor Russ Keith-Magee:

“Django’s admin is not meant to be the interface for your website”


QuerySets

Interestingly, the chapters on QuerySets and Forms are actually far better than those preceeding. The source code isn’t formatted any better, but it really does seem that the translations make (mostly) more sense.

I do think the manner of adding data to the database is bunkum, however. Given that we just covered the admin interface, it would make sense to use this to add some data, before looking at QuerySets. And we could delve into manage.py shell in order to illustrate how QuerySets, their various methods, and some model methods actually work.

And while we are on anti-patterns: queryset[:1].get() is pointless. You might as well just use queryset[0]. It is exactly the same SQL, and easier to read.


Forms

And then we get to Forms. I’m a really big fan of Django’s form handling: it’s something that makes dealing with user input much safer, and simpler. And this chapter explains that, but, from an educational perspective, I’m cautious that showing someone the wrong way to do something first is counter-productive.

Sure, I understand that it makes a point, and having done something a laborious, error-prone way for some time, and then being shown a safer, faster, easier method is eye-popping, but I fear that for some percentage of readers, they will get a takeaway that not using Forms is a valid choice.

Even beginning with a ModelForm is probably a nice approach, as you can get a lot of functionality with almost no code at all.


CBV

The section on Class Based Views is okay too. These are something else that are often hard to understand, and the initial official documentation on them was sadly lacking. Once you have your head around how they work they can be really powerful, and this book takes the right approach in suggesting caution about not always using them. Similarly, it is great that these were not used as a starting point.

However, I find that the explanations and descriptions are not always clear. Certainly as an experienced Django user I can read and understand what is going on, but as a beginner I think this chapter would be hard to follow. Perhaps a simple discussion about what the different CBV are used for, and how the ViewClass.as_view() pattern works, and why it is required, and then some examples.

Perhaps a better approach would have been to have written Model and Form classes earlier, and then writing the function-based views to CRUD those objects, and then rewriting the exact same views using CBV.


Django.contrib.auth

Although far less impressive that the admin, I think that auth is a more important module. Especially now given we can easily swap out auth.User, to get the desired user functionality, I think this is something that should be given more weight. It doesn’t need to necessarily come before the chapter about the admin, but it should be discussed, or at least introduced, before anything is done with a User-ish model.

I think this book does not do justice to Django.contrib.auth. There are lots of views and forms that can (and should) be used to save writing your own code, which is more likely to have bugs. Also, even if the basic User model is used in the example, a discussion of how easy it is to swap out, and get “email as username” functionality is certainly deserved.


AJAX

I’m probably 50-50 on the AJAX chapter. I guess I understand why you’d want to include a chapter on it, but I worry that this chapter maybe doesn’t do enough. If it’s an introduction to AJAX I’m not sure it seems up to that.

I do often use jQuery, but it’s probably not too tricky to rewrite the code to delete an object using vanilla Javascript. And if you are going to use jQuery, you should get the idioms right.

var cases = $('nav ul li').each(function() {
  $(this).addClass('nav_item');
})

Can easily be written:

$('nav ul li').addClass('nav_item');

And we probably shouldn’t use $(foo).html($(foo).html() + bar), when really we want to use $(foo).append(bar).

Also, I don’t think that using csrf_exempt is a great idea: the official documentation has details about how to use AJAX and still keep CSRF protection.


Thanks to:

  • Maior in #django for proofreading.

Dear ABC, please unbreak iView

Dear ABC,

The latest iOS iView apps use a custom media player, rather than the system supplied one. This actually degrades performance and usability in a few areas.

Specifically, when the stream is still loading, and you select AirPlay, then it will sometimes not actually send to the target device (and will not play on the local device either). This did not (to my recollection) occur with the old version.

Even more annoying, when attempting to scrub to a later point within the programme, it would more often than not just revert back to the previous point (prior to scrubbing). This happened about 60% of the time I was using the app last night. Again, this is something I have not seen when using either the old iview app, or other apps that use the default media player controls (but is something I see on apps that use custom players, such as the AU commercial channels’ apps).

Finally, after interacting with the controls, they do not fade after a period of time. Again, this is a significant regression compared to the previous version(s), and standard media controls.

Regards,

Matt.

(Note: I’ve also posted this as a complaint on http://www.abc.net.au/contact/contactabc.htm).

tox and coverage.py

Tox makes it really easy to run multiple tests on your project: against different versions of python or different versions of a related library.

It’s still lacking proper matrix testing: you need to manually define each environment, but apparently, that is going to change:

However, that’s not what I’m writing about today.

Today is about coverage testing, using coverage.py.

It’s possible, using tox, to get coverage.py to run:

[testenv]
commands=
  coverage run setup.py test
  coverage report
deps=
  coverage

However, this will generate a coverage report for just that environment. It would be better if you generated a coverage report for the whole project (although you may want per-environment coverage testing too).

So, we can abuse the fact that the tox envlist will be created and processed in the order they appear:

[tox]
envlist = clean,py27,py34,stats

[testenv]
commands=
  coverage run -a setup.py test
deps=
  coverage

[testenv:clean]
commands=
  coverage erase

[testenv:stats]
commands=
  coverage report
  covarage html

You’ll then get a nice html report in htmlcov/, and a printed coverage report in your console.

Multiple Homebrew Pythons

The Homebrew project brings a really nice package installer to OS X, allowing you to install command line programs in a really simple way. For instance, to install the latest version of Python 3, you would do:

$ brew install python3

Because not all projects are as aware of old versions as python, when brew upgrades a package, it removes the old versions linked binaries and support files. This is actually not a good thing for python: it means you can no longer access the older interpreter.

Python keeps version-named interpreters, and then just symlinks the most recently installed to the python executable. Thus, it’s not uncommon to see, for python2:

$ ls -1 /usr/bin/python*
/usr/bin/python
/usr/bin/python-config
/usr/bin/python2.5
/usr/bin/python2.5-config
/usr/bin/python2.6
/usr/bin/python2.6-config
/usr/bin/python2.7
/usr/bin/python2.7-config
/usr/bin/pythonw
/usr/bin/pythonw2.5
/usr/bin/pythonw2.6
/usr/bin/pythonw2.7

This means, if you want to run an older version (for instance, say you use tox and want to do some testing against a range of versions), you can just use:

$ python2.5
Python 2.5.6 (r256:Unversioned directory, Mar  9 2014, 22:15:03) 
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 

But Homebrew breaks this.

However, if you have the old versions already installed, you can easily recreate the symlinks. Indeed, here is a script that will visit all homebrew installed python3 versions, creating

cd /usr/local/Cellar/python3/

for VERSION in `ls`
do
  cd $VERSION
  
  find bin -name \*3\* -exec ln -s -f `pwd`/{} /usr/local/{} \;
  find lib -name \*python\* -maxdepth 1 -exec ln -s -F `pwd`/{} /usr/local/{} \;
  find share -name python\* -exec ln -s -f `pwd`/{} /usr/local/{} \;
  find Frameworks -name 3.\* -exec ln -s -f `pwd`/{} /usr/local/{} \;
  
  cd ..
done

It worked for me for python3 with the following versions installed:

  • 3.2.3
  • 3.3.3
  • 3.4.1

Now I just need to figure out how to get Homebrew to download and build specific versions of packages.

Adding JSON operators to PostgreSQL

Don’t use the code in this post. Instead, read: Adding JSON(B) operators to PostgreSQL

Notably, the new JSONB data type in postgres is missing some of the features that the hstore data type has had for some time.

hstore - text     : delete key from left operand
hstore - text[]   : delete keys from left operand
hstore - hstore   : delete matching pairs from left operand

However, using two awesome features of Postgres, it’s possible to add these operators in.

Firstly, python as a language in postgres:

matt=# CREATE LANGUAGE plpythonu;

Then, you can write functions in python, that import standard system libraries, like json.

CREATE OR REPLACE FUNCTION json_subtract(json_object json, key text) RETURNS json AS $body$
import json
data = json.loads(json_object)
data.pop(key, None)
return json.dumps(data)
$body$ LANGUAGE plpythonu;

Finally, you can now overload the operator to get the syntactic sugar you want:

CREATE OPERATOR - (
  LEFTARG = json,
  RIGHTARG = text,
  PROCEDURE = json_subtract
);

Now, you can use the same syntax as for hstore:

matt=# SELECT '{"a":1, "b":2}'::json - 'a'::text;
 ?column?
----------
 {"b": 2}
(1 row)

It’s possible to repeat these for the other subtraction operators:

CREATE OR REPLACE FUNCTION json_subtract(json_object json, keys text[]) RETURNS json AS $body$
import json
data = json.loads(json_object)
for key in keys:
    data.pop(key, None)
return json.dumps(data)
$body$ LANGUAGE plpythonu;

CREATE OPERATOR - (
  LEFTARG = json,
  RIGHTARG = text[],
  PROCEDURE = json_subtract
);

CREATE OR REPLACE FUNCTION json_subtract(json_object json, pairs json) RETURNS json AS $body$
import json
data = json.loads(json_object)
pairs_data = json.loads(pairs)
for key,value in pairs_data.items():
  if key in data and data[key] == value:
    data.pop(key)
return json.dumps(data)
$body$ LANGUAGE plpythonu;

CREATE OPERATOR - (
  LEFTARG = json,
  RIGHTARG = json,
  PROCEDURE = json_subtract
);

I’ll leave it as an exercise to write functions for the other operators.

In-Database Audit Trail

I’ve been thinking about audit trails, object versioning and the like a bit lately. We have situations where it’s actually important to be able to know what changes were made, when, and by whom.

The tool we are currently using in Django for this is django-reversion, but I’m not that happy with it. Part of the problem is that it adds a fair amount of overhead into each request, which we have noticed, and also that querying the data it produces is not simple, unless you are just looking for changes made to a specific object.

For our case, we want to know all the changes made to a set of objects (that may not all be of the same type), made within a given range, where there is (eventually) a foreign key relationship back to our Company model.

Part of the problem is just that: a custom solution could store the Company relationship explicitly, but also, we really care about what the changes were, rather than what the object looks like after the changes. Well, technically, we want both.

However, it has gotten me thinking about other ways to solve this problem.

In most DBMSs, it’s possible to get a trigger function to run when an update happens to each row, which makes it possible to get access to this information. Indeed, googling “Postgres audit trigger” pops up some hits that are highly relevant. I looked at Audit trigger 91plus, since it’s “improved”. It has some really nice features like storing the audit data in an hstore column, which means you can query it.

However, one place where this type of trigger breaks down is that it’s not generally possible to get the “application user” associated with a request, only the “database user”, which for our system (and most other web applications) is fixed for all access.

One way to get around this might be to, at the start of every database transaction, inject a call that creates a temporary table, with the extra bits of data that you want to log, and then use that in the trigger function.

CREATE TEMP TABLE IF NOT EXISTS
  "_app_user" (user_id integer, ip_address inet);

Then we need to add (or update) the one row that will contain our data. We must ensure that we only ever have one row in this table.

UPDATE _app_user SET user_id=%s, ip_address=%s;
INSERT INTO _app_user (user_id, ip_address)
  SELECT %s, %s WHERE NOT EXISTS (SELECT * FROM _app_user);

This code will ensure that the first statement (UPDATE) will affect all rows in the table (of which there will be at most one), and the second statement (INSERT ... SELECT ... WHERE NOT EXISTS ...) will only create a new row if there are no rows currently in the table.

It’s up to you to then pass the correct data to this. I’m currently looking at doing this using Django middleware, although I suspect this may fall down using the newer transaction handling, as otherwise we could have just ensured our middleware ran after the TransactionMiddleware. It may be possible to do it with a custom database backend, but it needs to somehow get access to the request object (which contains the user, and the ip address). Obviously, you could log other data about the request, too.

The final part of the puzzle is to inject this data into the row that will be used for the audit table entry. I modified the table definition so it included columns for the data I wanted: app_user_id and app_ip_address.

Then, inside the actual trigger function, after the audit_row object has been created, but before it is written to the table, we inject the data we want.

We need to be a little careful, as it’s possible the table does not exist:

BEGIN
  PERFORM 
    n.nspname, c.relname 
  FROM
    pg_catalog.pg_class c 
  LEFT JOIN 
    pg_catalog.pg_namespace n
  ON n.oid = c.relnamespace
  WHERE
    n.nspname like 'pg_temp_%' 
  AND
    c.relname = '_app_user';

  IF FOUND THEN
    FOR r IN SELECT * FROM _app_user LIMIT 1 LOOP
      audit_row.app_user_id = r.user_id;
      audit_row.app_ip_address = r.ip_address;
    END LOOP;
    END IF;
END;

This checks to see if the _app_user table exists in any of the valid temporary table namespaces, and if so, grabs the first (and only, from above) entry, using the values to update the row.

This function then works: if there is a temporary table with this name, it uses these fields when creating the audit, if not, it creates the audit row with empty values. This would mean that some audit statements may not contain proper user data, but in the case of Django, it’s possible to make changes outside of the request-response cycle. You could require that a user starting a shell session authenticates with a valid django username+password, but that still leaves management commands. I guess you could have a system account, but leaving these entries blank is like an explicit system user.

I haven’t got any production code using anything like this: I’d still want to test that it works as expected as part of the request, and would want to build up some method of querying it. There’s probably no reason you couldn’t do the table definition as a Django model (and indeed, have the function definition as a migration).