def keys(request):
    """Lists API keys. Compatible with jQuery DataTables."""
    iDisplayStart = parse_int_param(request, 'iDisplayStart')
    iDisplayLength = parse_int_param(request, 'iDisplayLength')
    sEcho = parse_int_param(request, 'sEcho')
    iSortCol_0 = parse_int_param(request, 'iSortCol_0')
    sSortDir_0 = request.GET.get('sSortDir_0', 'asc')
    sSearch = request.GET.get('sSearch')

    columns = ['key', 'email', 'calls', 'latest_call', 'issued_on']
    qry = Key.objects
    if sSearch not in (None, ''):
        qry = qry.filter(Q(key__icontains=sSearch)
                         | Q(email__icontains=sSearch)
                         | Q(name__icontains=sSearch)
                         | Q(org_name__icontains=sSearch)
                         | Q(org_url__icontains=sSearch))
    qry = qry.values('key', 'email', 'issued_on').annotate(calls=Sum('reports__calls'),
                                                           latest_call=Max('reports__date'))
    qry = qry.filter(calls__isnull=False)
    qry = exclude_internal_keys(qry)
    # TODO: Add multi-column sorting
    if iSortCol_0 not in (None, ''):
        sort_col_field = columns[iSortCol_0]
        sort_spec = '{dir}{col}'.format(dir='-' if sSortDir_0 == 'desc' else '',
                                        col=sort_col_field)
        qry = qry.order_by(sort_spec)

    result = {
        'iTotalRecords': Key.objects.count(),
        'iTotalDisplayRecords': qry.count(),
        'sEcho': sEcho,
        'aaData': [[k['key'],
                    '<a href="{0}">{1}</a>'.format(reverse('key_analytics', args=(k['key'], )), k['email']),
                    k['calls'],
                    k['latest_call'].isoformat(),
                    k['issued_on'].date().isoformat()]
                   for k in qry[iDisplayStart:iDisplayStart+iDisplayLength]]
    }
    return HttpResponse(content=json.dumps(result), status=200, content_type='application/json')
Lists API keys. Compatible with jQuery DataTables.