Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

hitcount_tags.py 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from collections import namedtuple
  4. from django import template
  5. from django.contrib.contenttypes.models import ContentType
  6. try:
  7. from django.core.urlresolvers import reverse
  8. except ImportError:
  9. from django.urls import reverse
  10. from hitcount.models import HitCount
  11. register = template.Library()
  12. def get_hit_count_from_obj_variable(context, obj_variable, tag_name):
  13. """
  14. Helper function to return a HitCount for a given template object variable.
  15. Raises TemplateSyntaxError if the passed object variable cannot be parsed.
  16. """
  17. error_to_raise = template.TemplateSyntaxError(
  18. "'%(a)s' requires a valid individual model variable "
  19. "in the form of '%(a)s for [model_obj]'.\n"
  20. "Got: %(b)s" % {'a': tag_name, 'b': obj_variable}
  21. )
  22. try:
  23. obj = obj_variable.resolve(context)
  24. except template.VariableDoesNotExist:
  25. raise error_to_raise
  26. try:
  27. ctype = ContentType.objects.get_for_model(obj)
  28. except AttributeError:
  29. raise error_to_raise
  30. hit_count, created = HitCount.objects.get_or_create(
  31. content_type=ctype, object_pk=obj.pk)
  32. return hit_count
  33. def return_period_from_string(arg):
  34. """
  35. Takes a string such as "days=1,seconds=30" and strips the quotes
  36. and returns a dictionary with the key/value pairs
  37. """
  38. period = {}
  39. if arg[0] == '"' and arg[-1] == '"':
  40. opt = arg[1:-1] # remove quotes
  41. else:
  42. opt = arg
  43. for o in opt.split(","):
  44. key, value = o.split("=")
  45. period[str(key)] = int(value)
  46. return period
  47. class GetHitCount(template.Node):
  48. def handle_token(cls, parser, token):
  49. args = token.contents.split()
  50. # {% get_hit_count for [obj] %}
  51. if len(args) == 3 and args[1] == 'for':
  52. return cls(obj_as_str=args[2])
  53. # {% get_hit_count for [obj] as [var] %}
  54. elif len(args) == 5 and args[1] == 'for' and args[3] == 'as':
  55. return cls(obj_as_str=args[2],
  56. as_varname=args[4],)
  57. # {% get_hit_count for [obj] within ["days=1,minutes=30"] %}
  58. elif len(args) == 5 and args[1] == 'for' and args[3] == 'within':
  59. return cls(obj_as_str=args[2],
  60. period=return_period_from_string(args[4]))
  61. # {% get_hit_count for [obj] within ["days=1,minutes=30"] as [var] %}
  62. elif len(args) == 7 and args[1] == 'for' and \
  63. args[3] == 'within' and args[5] == 'as':
  64. return cls(obj_as_str=args[2],
  65. as_varname=args[6],
  66. period=return_period_from_string(args[4]))
  67. else: # TODO - should there be more troubleshooting prior to bailing?
  68. raise template.TemplateSyntaxError(
  69. "'get_hit_count' requires "
  70. "'for [object] in [period] as [var]' (got %r)" % args
  71. )
  72. handle_token = classmethod(handle_token)
  73. def __init__(self, obj_as_str, as_varname=None, period=None):
  74. self.obj_variable = template.Variable(obj_as_str)
  75. self.as_varname = as_varname
  76. self.period = period
  77. def render(self, context):
  78. hit_count = get_hit_count_from_obj_variable(context, self.obj_variable, 'get_hit_count')
  79. if self.period: # if user sets a time period, use it
  80. try:
  81. hits = hit_count.hits_in_last(**self.period)
  82. except TypeError:
  83. raise template.TemplateSyntaxError(
  84. "'get_hit_count for [obj] within [timedelta]' requires "
  85. "a valid comma separated list of timedelta arguments. "
  86. "For example, ['days=5,hours=6']. "
  87. "Got these instead: %s" % self.period
  88. )
  89. else:
  90. hits = hit_count.hits
  91. if self.as_varname: # if user gives us a variable to return
  92. context[self.as_varname] = str(hits)
  93. return ''
  94. else:
  95. return str(hits)
  96. def get_hit_count(parser, token):
  97. """
  98. Returns hit counts for an object.
  99. - Return total hits for an object:
  100. {% get_hit_count for [object] %}
  101. - Get total hits for an object as a specified variable:
  102. {% get_hit_count for [object] as [var] %}
  103. - Get total hits for an object over a certain time period:
  104. {% get_hit_count for [object] within ["days=1,minutes=30"] %}
  105. - Get total hits for an object over a certain time period as a variable:
  106. {% get_hit_count for [object] within ["days=1,minutes=30"] as [var] %}
  107. The time arguments need to follow datetime.timedelta's limitations:
  108. Accepts days, seconds, microseconds, milliseconds, minutes,
  109. hours, and weeks.
  110. """
  111. return GetHitCount.handle_token(parser, token)
  112. register.tag('get_hit_count', get_hit_count)
  113. class WriteHitCountJavascriptVariables(template.Node):
  114. def handle_token(cls, parser, token):
  115. args = token.contents.split()
  116. if len(args) == 3 and args[1] == 'for':
  117. return cls(obj_variable=args[2])
  118. else:
  119. raise template.TemplateSyntaxError(
  120. 'insert_hit_count_js_variables requires this syntax: '
  121. '"insert_hit_count_js_variables for [object]"\n'
  122. 'Got: %s' % ' '.join(str(i) for i in args)
  123. )
  124. handle_token = classmethod(handle_token)
  125. def __init__(self, obj_variable):
  126. self.obj_variable = template.Variable(obj_variable)
  127. def render(self, context):
  128. hit_count = get_hit_count_from_obj_variable(context, self.obj_variable, 'insert_hit_count_js_variables')
  129. js = '<script type="text/javascript">\n' + \
  130. "var hitcountJS = {" + \
  131. "hitcountPK : '" + str(hit_count.pk) + "'," + \
  132. "hitcountURL : '" + str(reverse('hitcount:hit_ajax')) + "'};" + \
  133. "\n</script>"
  134. return js
  135. def insert_hit_count_js_variables(parser, token):
  136. """
  137. Injects JavaScript global variables into your template. These variables
  138. can be used in your JavaScript files to send the correctly mapped HitCount
  139. ID to the server (see: hitcount-jquery.js for an example).
  140. {% insert_hit_count_js_variables for [object] %}
  141. """
  142. return WriteHitCountJavascriptVariables.handle_token(parser, token)
  143. register.tag('insert_hit_count_js_variables', insert_hit_count_js_variables)
  144. class GetHitCountJavascriptVariables(template.Node):
  145. def handle_token(cls, parser, token):
  146. args = token.contents.split()
  147. if len(args) == 5 and args[1] == 'for' and args[3] == 'as':
  148. return cls(obj_variable=args[2], as_varname=args[4])
  149. else:
  150. raise template.TemplateSyntaxError(
  151. 'get_hit_count_js_variables requires this syntax: '
  152. '"get_hit_count_js_variables for [object] as [var_name]."\n'
  153. 'Got: %s' % ' '.join(str(i) for i in args)
  154. )
  155. handle_token = classmethod(handle_token)
  156. def __init__(self, obj_variable, as_varname):
  157. self.obj_variable = template.Variable(obj_variable)
  158. self.as_varname = as_varname
  159. def render(self, context):
  160. HitcountVariables = namedtuple('HitcountVariables', 'pk ajax_url hits')
  161. hit_count = get_hit_count_from_obj_variable(context, self.obj_variable, 'get_hit_count_js_variables')
  162. context[self.as_varname] = HitcountVariables(
  163. hit_count.pk, str(reverse('hitcount:hit_ajax')), str(hit_count.hits))
  164. return ''
  165. def get_hit_count_js_variables(parser, token):
  166. """
  167. Injects JavaScript global variables into your template. These variables
  168. can be used in your JavaScript files to send the correctly mapped HitCount
  169. ID to the server (see: hitcount-jquery.js for an example).
  170. {% get_hit_count_js_variables for [object] as [var_name] %}
  171. Will provide two variables:
  172. [var_name].pk = the hitcount pk to be sent via JavaScript
  173. [var_name].ajax_url = the relative url to post the ajax request to
  174. """
  175. return GetHitCountJavascriptVariables.handle_token(parser, token)
  176. register.tag('get_hit_count_js_variables', get_hit_count_js_variables)
  177. class WriteHitCountJavascript(template.Node):
  178. JS_TEMPLATE = """
  179. <script type="text/javascript">
  180. //<![CDATA[
  181. jQuery(document).ready(function($) {
  182. $.postCSRF("%s", {
  183. hitcountPK: "%s"
  184. });
  185. });
  186. //]]>
  187. </script>
  188. """
  189. JS_TEMPLATE_DEBUG = """
  190. <script type="text/javascript">
  191. //<![CDATA[
  192. jQuery(document).ready(function($) {
  193. $.postCSRF("%s", {
  194. hitcountPK: "%s"
  195. }).done(function(data) {
  196. console.log('django-hitcount: AJAX POST succeeded.');
  197. console.log(data);
  198. }).fail(function(data) {
  199. console.log('django-hitcount: AJAX POST failed.');
  200. console.log(data);
  201. });
  202. });
  203. //]]>
  204. </script>
  205. """
  206. def handle_token(cls, parser, token):
  207. args = token.contents.split()
  208. if len(args) == 3 and args[1] == 'for':
  209. return cls(obj_variable=args[2], debug=False)
  210. elif len(args) == 4 and args[1] == 'for' and args[3] == 'debug':
  211. return cls(obj_variable=args[2], debug=True)
  212. else:
  213. raise template.TemplateSyntaxError(
  214. 'insert_hit_count_js requires this syntax: '
  215. '"insert_hit_count_js for [object]"\n'
  216. '"insert_hit_count_js for [object] debug"'
  217. 'Got: %s' % ' '.join(str(i) for i in args)
  218. )
  219. handle_token = classmethod(handle_token)
  220. def __init__(self, obj_variable, debug):
  221. self.obj_variable = template.Variable(obj_variable)
  222. self.debug = debug
  223. def render(self, context):
  224. hit_count = get_hit_count_from_obj_variable(
  225. context,
  226. self.obj_variable,
  227. 'insert_hit_count_js'
  228. )
  229. template = self.JS_TEMPLATE_DEBUG if self.debug else self.JS_TEMPLATE
  230. return template % (str(reverse('hitcount:hit_ajax')), str(hit_count.pk))
  231. def insert_hit_count_js(parser, token):
  232. """
  233. Injects the JavaScript into your template that works with jquery.postcsrf.js.
  234. {% insert_hit_count_js_variables for [object] %}
  235. """
  236. return WriteHitCountJavascript.handle_token(parser, token)
  237. register.tag('insert_hit_count_js', insert_hit_count_js)