Skip to content

Instantly share code, notes, and snippets.

@alexlovelltroy
Last active December 19, 2015 17:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexlovelltroy/5993250 to your computer and use it in GitHub Desktop.
Save alexlovelltroy/5993250 to your computer and use it in GitHub Desktop.
Time-based graph of any django model with a date/datetime field
// * Somewhere in your html, you'll need a place to put the graph (flotcharts.org)
// <div class="graph-container">
// <div id="userGraph" class=graph-placeholder></div>
// </div>
// * You'll also need the data available to the js
// * This should probably go near the bottom. Like just before the </body> tag.
// <script>
// {% autoescape off %}
// GRAPHABLE_DATA = {{ report_as_json }}
// {% endautoescape %}
// </script>
sum = function(list, end) {
// This only works with an ordered list of lists
for(var s=0,i=end+1;i;s+=list[--i][1]);
return s;
}
$(document).ready(function() {
// put all your jQuery goodness in here.
// this uses flot with the time and tooltip plugins
var options = {
xaxis: { mode: "time", tickLength: 5 },
legend: {position: 'nw',},
grid: { hoverable: true},
tooltip: true,
tooltipOpts: {
content: "%y %s on %x",
xDateFormat: "%b %e",
},
series: {
lines: { show: true },
// points: { show: true }
},
};
user_totals = Array()
user_by_day = Array()
rolling_user = Array()
_.each(GRAPHABLE_DATA.Users, function(item,index,list) {user_totals.push([item[0] * 1000,sum(list,index)])});
_.each(GRAPHABLE_DATA.Users, function(item,index,list) {user_by_day.push([item[0] * 1000,item[1]])});
_.each(GRAPHABLE_DATA.rolling_Users, function(item,index,list) {rolling_user.push([item[0] * 1000,item[1]])});
total_users = user_totals[user_totals.length -1][1]
window.userPlot = $.plot("#userGraph", [
{data: user_totals, label:"Total Users"},
{data: user_by_day, label:"New Users"},
{data: rolling_uer, label: "New Users (weekly rolling average)"}
], options);
});
import datetime
import pytz
# This assumes that you've only got utc times in your database. Please don't put anything but utc in your database. I'll cry.
def list_frequency(mylist):
""" Turn a sequence with duplicates into a list of tuples matching item to frequency """
out = []
if len(mylist) < 1:
return out
counter = {}
for i in set(mylist):
counter[i] = mylist.count(i)
for x in counter:
out.append((x, counter[x]))
return sorted(out, key=lambda y: y[1], reverse=True)
def build_datestack(obj, date_field, qs=None):
""" Build a sorted list of tuples matching a date to the number of dated events on that date including dates with zero """
if qs:
obj_list = qs.order_by(date_field)
else:
obj_list = obj.objects.all().order_by(date_field)
first_date = getattr(obj_list[0], date_field).date()
last_date = getattr(obj_list[len(obj_list) -1], date_field).date()
date_list = sorted(list_frequency([getattr(x,date_field).date() for x in obj_list]), key=lambda y: y[0], reverse=True)
out = []
builder_date = first_date
data_date = date_list.pop()
while builder_date <= last_date:
if data_date[0] == builder_date:
out.append((builder_date, data_date[1]))
try:
data_date = date_list.pop()
except IndexError:
pass
else:
out.append((builder_date, 0))
builder_date = builder_date + datetime.timedelta(days=1)
return out
def rolling_average(datestack, days=7):
""" Build a new datestack based on the one made by build_datestack that computes a rolling average"""
# Assuming we've already summed everything, sorted everything, and zero days are included
out = []
for x in range(0, len(datestack)):
if x > days:
# calculate the sum
sample_set = datestack[x - days:x]
counter = 0
for date, count in sample_set:
counter = counter + count
out.append((date, counter/float(days)))
return out
def build_report(report_list, average=None):
""" Using a list of dictionaries to build datestacks to work with, and optionally a rolling average
[ dict(
obj = User, # A Django model
date_field = 'date_joined', # the date or datetime field to count
label = "Users", # A convenient label to identify what's being counted
), ]
In a view, you might add this to a context like this:
for key, value in report.iteritems():
context.update({key:value})
Or as a serialized json object like this:
dthandler = lambda obj: time.mktime(obj.timetuple()) if isinstance(obj, datetime.date) else None
context['report_as_json'] = json.dumps(report, default=dthandler)
"""
now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
report = {}
for each in report_list:
obj = each.get('obj', None)
date_field = each.get('date_field', None)
label = each.get('label', None)
qs = each.get('qs', None)
if qs is None:
qs = obj.objects.all()
result = build_datestack(obj, date_field, qs=qs)
report[label] = result
if average:
# Check for "True",
if isinstance(average, bool):
report['rolling_%s'] = rolling_average(result)
else:
try:
report['rolling_%s'] = rolling_average(result, days=int(average))
except TypeError:
# That's strange. I expected average to be boolean or an int. It wasn't I don't understand what to do
pass
report["%s_all_time" % label] = len(qs)
report["%s_last_30" % label] = len([x for x in qs if getattr(x, date_field) > now - datetime.timedelta(days=30)])
report["%s_last_60" % label] = len([x for x in qs if getattr(x, date_field) > now - datetime.timedelta(days=60)])
report["%s_last_90" % label] = len([x for x in qs if getattr(x, date_field) > now - datetime.timedelta(days=90)])
return report
@alexlovelltroy
Copy link
Author

Adding this and some other things to a new repository at django-telemetry

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment