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")
def keys_issued_monthly(request):
    year = parse_int_param(request, "year")
    if year is None:
        return HttpResponseBadRequest("You must specify a year parameter.")
    ignore_inactive = parse_bool_param(request, "ignore_inactive", False)
    ignore_internal_keys = parse_bool_param(request, "ignore_internal_keys", True)

    qry = Key.objects.filter(issued_on__gte=datetime.date(year, 1, 1), issued_on__lte=datetime.date(year, 12, 31))
    if ignore_internal_keys:
        qry = exclude_internal_keys(qry)
    if ignore_inactive:
        qry = qry.filter(status="A")

    daily_aggs = qry.values("issued_on").annotate(issued=Count("pk"))
    monthly = dict(((m, {"month": m, "issued": 0}) for m in range(1, 13)))
    for daily in daily_aggs:
        month = daily["issued_on"].month
        monthly[month]["issued"] += daily["issued"]

    agg = qry.aggregate(issued=Count("pk"))

    result = {"year": year, "issued": agg["issued"], "monthly": monthly.values()}
    return HttpResponse(content=json.dumps(result), status=200, content_type="application/json")
def keys_issued_yearly(request):
    ignore_inactive = parse_bool_param(request, "ignore_inactive", False)
    ignore_internal_keys = parse_bool_param(request, "ignore_internal_keys", True)

    date_extents = _keys_issued_date_range()
    if date_extents["earliest"] and date_extents["latest"]:
        earliest_year = date_extents["earliest"].year
        latest_year = date_extents["latest"].year

        qry = Key.objects
        if ignore_internal_keys:
            qry = exclude_internal_keys(qry)
        if ignore_inactive:
            qry = qry.filter(status="A")

        result = {"earliest_year": earliest_year, "latest_year": latest_year, "yearly": []}
        for year in range(earliest_year, latest_year + 1):
            yr_fro = datetime.date(year, 1, 1)
            yr_to = datetime.date(year, 12, 31)
            yr_agg = qry.filter(issued_on__gte=yr_fro, issued_on__lte=yr_to).aggregate(issued=Count("pk"))
            result["yearly"].append({"year": year, "issued": yr_agg["issued"]})
    else:
        result = {"earliest_year": None, "latest_year": None, "yearly": []}
    return HttpResponse(content=json.dumps(result), status=200, content_type="application/json")
def keys_issued(request):
    begin_date = parse_date_param(request, "begin_date")
    end_date = parse_date_param(request, "end_date")
    ignore_inactive = parse_bool_param(request, "ignore_inactive", False)
    ignore_internal_keys = parse_bool_param(request, "ignore_internal_keys", True)

    qry = Key.objects
    if ignore_internal_keys:
        qry = exclude_internal_keys(qry)
    if begin_date:
        qry = qry.filter(issued_on__gte=begin_date)
    if end_date:
        qry = qry.filter(issued_on__lte=end_date)
    if ignore_inactive:
        qry = qry.filter(status="A")
    qry = qry.aggregate(issued=Count("pk"))

    result = {"issued": qry["issued"]}
    if begin_date is not None:
        result["begin_date"] = begin_date.isoformat()
    if end_date is not None:
        result["end_date"] = end_date.isoformat()

    return HttpResponse(content=json.dumps(result), status=200, content_type="application/json")