# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from collections import namedtuple
from django import template
from django.contrib.contenttypes.models import ContentType
try:
from django.core.urlresolvers import reverse
except ImportError:
from django.urls import reverse
from hitcount.models import HitCount
register = template.Library()
def get_hit_count_from_obj_variable(context, obj_variable, tag_name):
"""
Helper function to return a HitCount for a given template object variable.
Raises TemplateSyntaxError if the passed object variable cannot be parsed.
"""
error_to_raise = template.TemplateSyntaxError(
"'%(a)s' requires a valid individual model variable "
"in the form of '%(a)s for [model_obj]'.\n"
"Got: %(b)s" % {'a': tag_name, 'b': obj_variable}
)
try:
obj = obj_variable.resolve(context)
except template.VariableDoesNotExist:
raise error_to_raise
try:
ctype = ContentType.objects.get_for_model(obj)
except AttributeError:
raise error_to_raise
hit_count, created = HitCount.objects.get_or_create(
content_type=ctype, object_pk=obj.pk)
return hit_count
def return_period_from_string(arg):
"""
Takes a string such as "days=1,seconds=30" and strips the quotes
and returns a dictionary with the key/value pairs
"""
period = {}
if arg[0] == '"' and arg[-1] == '"':
opt = arg[1:-1] # remove quotes
else:
opt = arg
for o in opt.split(","):
key, value = o.split("=")
period[str(key)] = int(value)
return period
class GetHitCount(template.Node):
def handle_token(cls, parser, token):
args = token.contents.split()
# {% get_hit_count for [obj] %}
if len(args) == 3 and args[1] == 'for':
return cls(obj_as_str=args[2])
# {% get_hit_count for [obj] as [var] %}
elif len(args) == 5 and args[1] == 'for' and args[3] == 'as':
return cls(obj_as_str=args[2],
as_varname=args[4],)
# {% get_hit_count for [obj] within ["days=1,minutes=30"] %}
elif len(args) == 5 and args[1] == 'for' and args[3] == 'within':
return cls(obj_as_str=args[2],
period=return_period_from_string(args[4]))
# {% get_hit_count for [obj] within ["days=1,minutes=30"] as [var] %}
elif len(args) == 7 and args[1] == 'for' and \
args[3] == 'within' and args[5] == 'as':
return cls(obj_as_str=args[2],
as_varname=args[6],
period=return_period_from_string(args[4]))
else: # TODO - should there be more troubleshooting prior to bailing?
raise template.TemplateSyntaxError(
"'get_hit_count' requires "
"'for [object] in [period] as [var]' (got %r)" % args
)
handle_token = classmethod(handle_token)
def __init__(self, obj_as_str, as_varname=None, period=None):
self.obj_variable = template.Variable(obj_as_str)
self.as_varname = as_varname
self.period = period
def render(self, context):
hit_count = get_hit_count_from_obj_variable(context, self.obj_variable, 'get_hit_count')
if self.period: # if user sets a time period, use it
try:
hits = hit_count.hits_in_last(**self.period)
except TypeError:
raise template.TemplateSyntaxError(
"'get_hit_count for [obj] within [timedelta]' requires "
"a valid comma separated list of timedelta arguments. "
"For example, ['days=5,hours=6']. "
"Got these instead: %s" % self.period
)
else:
hits = hit_count.hits
if self.as_varname: # if user gives us a variable to return
context[self.as_varname] = str(hits)
return ''
else:
return str(hits)
def get_hit_count(parser, token):
"""
Returns hit counts for an object.
- Return total hits for an object:
{% get_hit_count for [object] %}
- Get total hits for an object as a specified variable:
{% get_hit_count for [object] as [var] %}
- Get total hits for an object over a certain time period:
{% get_hit_count for [object] within ["days=1,minutes=30"] %}
- Get total hits for an object over a certain time period as a variable:
{% get_hit_count for [object] within ["days=1,minutes=30"] as [var] %}
The time arguments need to follow datetime.timedelta's limitations:
Accepts days, seconds, microseconds, milliseconds, minutes,
hours, and weeks.
"""
return GetHitCount.handle_token(parser, token)
register.tag('get_hit_count', get_hit_count)
class WriteHitCountJavascriptVariables(template.Node):
def handle_token(cls, parser, token):
args = token.contents.split()
if len(args) == 3 and args[1] == 'for':
return cls(obj_variable=args[2])
else:
raise template.TemplateSyntaxError(
'insert_hit_count_js_variables requires this syntax: '
'"insert_hit_count_js_variables for [object]"\n'
'Got: %s' % ' '.join(str(i) for i in args)
)
handle_token = classmethod(handle_token)
def __init__(self, obj_variable):
self.obj_variable = template.Variable(obj_variable)
def render(self, context):
hit_count = get_hit_count_from_obj_variable(context, self.obj_variable, 'insert_hit_count_js_variables')
js = '"
return js
def insert_hit_count_js_variables(parser, token):
"""
Injects JavaScript global variables into your template. These variables
can be used in your JavaScript files to send the correctly mapped HitCount
ID to the server (see: hitcount-jquery.js for an example).
{% insert_hit_count_js_variables for [object] %}
"""
return WriteHitCountJavascriptVariables.handle_token(parser, token)
register.tag('insert_hit_count_js_variables', insert_hit_count_js_variables)
class GetHitCountJavascriptVariables(template.Node):
def handle_token(cls, parser, token):
args = token.contents.split()
if len(args) == 5 and args[1] == 'for' and args[3] == 'as':
return cls(obj_variable=args[2], as_varname=args[4])
else:
raise template.TemplateSyntaxError(
'get_hit_count_js_variables requires this syntax: '
'"get_hit_count_js_variables for [object] as [var_name]."\n'
'Got: %s' % ' '.join(str(i) for i in args)
)
handle_token = classmethod(handle_token)
def __init__(self, obj_variable, as_varname):
self.obj_variable = template.Variable(obj_variable)
self.as_varname = as_varname
def render(self, context):
HitcountVariables = namedtuple('HitcountVariables', 'pk ajax_url hits')
hit_count = get_hit_count_from_obj_variable(context, self.obj_variable, 'get_hit_count_js_variables')
context[self.as_varname] = HitcountVariables(
hit_count.pk, str(reverse('hitcount:hit_ajax')), str(hit_count.hits))
return ''
def get_hit_count_js_variables(parser, token):
"""
Injects JavaScript global variables into your template. These variables
can be used in your JavaScript files to send the correctly mapped HitCount
ID to the server (see: hitcount-jquery.js for an example).
{% get_hit_count_js_variables for [object] as [var_name] %}
Will provide two variables:
[var_name].pk = the hitcount pk to be sent via JavaScript
[var_name].ajax_url = the relative url to post the ajax request to
"""
return GetHitCountJavascriptVariables.handle_token(parser, token)
register.tag('get_hit_count_js_variables', get_hit_count_js_variables)
class WriteHitCountJavascript(template.Node):
JS_TEMPLATE = """
"""
JS_TEMPLATE_DEBUG = """
"""
def handle_token(cls, parser, token):
args = token.contents.split()
if len(args) == 3 and args[1] == 'for':
return cls(obj_variable=args[2], debug=False)
elif len(args) == 4 and args[1] == 'for' and args[3] == 'debug':
return cls(obj_variable=args[2], debug=True)
else:
raise template.TemplateSyntaxError(
'insert_hit_count_js requires this syntax: '
'"insert_hit_count_js for [object]"\n'
'"insert_hit_count_js for [object] debug"'
'Got: %s' % ' '.join(str(i) for i in args)
)
handle_token = classmethod(handle_token)
def __init__(self, obj_variable, debug):
self.obj_variable = template.Variable(obj_variable)
self.debug = debug
def render(self, context):
hit_count = get_hit_count_from_obj_variable(
context,
self.obj_variable,
'insert_hit_count_js'
)
template = self.JS_TEMPLATE_DEBUG if self.debug else self.JS_TEMPLATE
return template % (str(reverse('hitcount:hit_ajax')), str(hit_count.pk))
def insert_hit_count_js(parser, token):
"""
Injects the JavaScript into your template that works with jquery.postcsrf.js.
{% insert_hit_count_js_variables for [object] %}
"""
return WriteHitCountJavascript.handle_token(parser, token)
register.tag('insert_hit_count_js', insert_hit_count_js)