]> git.openstreetmap.org Git - osqa.git/blob - forum/templatetags/extra_tags.py
Converts all instant notifications to the new style emails.
[osqa.git] / forum / templatetags / extra_tags.py
1 import time
2 import os
3 import posixpath
4 import datetime
5 import math
6 import re
7 import logging
8 from django import template
9 from django.utils.encoding import smart_unicode
10 from django.utils.safestring import mark_safe
11 from forum.models import Question, Answer, QuestionRevision, AnswerRevision, NodeRevision
12 from django.utils.translation import ugettext as _
13 from django.utils.translation import ungettext
14 from django.utils import simplejson
15 from forum import settings
16 from django.template.defaulttags import url as default_url
17 from forum import skins
18
19 register = template.Library()
20
21 GRAVATAR_TEMPLATE = ('<img class="gravatar" width="%(size)s" height="%(size)s" '
22                      'src="http://www.gravatar.com/avatar/%(gravatar_hash)s'
23                      '?s=%(size)s&amp;d=identicon&amp;r=PG" '
24                      'alt="%(username)s\'s gravatar image" />')
25
26 @register.simple_tag
27 def gravatar(user, size):
28     """
29     Creates an ``<img>`` for a user's Gravatar with a given size.
30
31     This tag can accept a User object, or a dict containing the
32     appropriate values.
33     """
34     try:
35         gravatar = user['gravatar']
36         username = user['username']
37     except (TypeError, AttributeError, KeyError):
38         gravatar = user.gravatar
39         username = user.username
40     return mark_safe(GRAVATAR_TEMPLATE % {
41         'size': size,
42         'gravatar_hash': gravatar,
43         'username': template.defaultfilters.urlencode(username),
44     })
45
46 MAX_FONTSIZE = 18
47 MIN_FONTSIZE = 12
48 @register.simple_tag
49 def tag_font_size(max_size, min_size, current_size):
50     """
51     do a logarithmic mapping calcuation for a proper size for tagging cloud
52     Algorithm from http://blogs.dekoh.com/dev/2007/10/29/choosing-a-good-font-size-variation-algorithm-for-your-tag-cloud/
53     """
54     #avoid invalid calculation
55     if current_size == 0:
56         current_size = 1
57     try:
58         weight = (math.log10(current_size) - math.log10(min_size)) / (math.log10(max_size) - math.log10(min_size))
59     except:
60         weight = 0
61     return MIN_FONTSIZE + round((MAX_FONTSIZE - MIN_FONTSIZE) * weight)
62
63
64 LEADING_PAGE_RANGE_DISPLAYED = TRAILING_PAGE_RANGE_DISPLAYED = 5
65 LEADING_PAGE_RANGE = TRAILING_PAGE_RANGE = 4
66 NUM_PAGES_OUTSIDE_RANGE = 1
67 ADJACENT_PAGES = 2
68 @register.inclusion_tag("paginator.html")
69 def cnprog_paginator(context):
70     """
71     custom paginator tag
72     Inspired from http://blog.localkinegrinds.com/2007/09/06/digg-style-pagination-in-django/
73     """
74     if (context["is_paginated"]):
75         " Initialize variables "
76         in_leading_range = in_trailing_range = False
77         pages_outside_leading_range = pages_outside_trailing_range = range(0)
78
79         if (context["pages"] <= LEADING_PAGE_RANGE_DISPLAYED):
80             in_leading_range = in_trailing_range = True
81             page_numbers = [n for n in range(1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
82         elif (context["page"] <= LEADING_PAGE_RANGE):
83             in_leading_range = True
84             page_numbers = [n for n in range(1, LEADING_PAGE_RANGE_DISPLAYED + 1) if n > 0 and n <= context["pages"]]
85             pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
86         elif (context["page"] > context["pages"] - TRAILING_PAGE_RANGE):
87             in_trailing_range = True
88             page_numbers = [n for n in range(context["pages"] - TRAILING_PAGE_RANGE_DISPLAYED + 1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
89             pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
90         else:
91             page_numbers = [n for n in range(context["page"] - ADJACENT_PAGES, context["page"] + ADJACENT_PAGES + 1) if n > 0 and n <= context["pages"]]
92             pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
93             pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
94
95         extend_url = context.get('extend_url', '')
96         return {
97             "base_url": context["base_url"],
98             "is_paginated": context["is_paginated"],
99             "previous": context["previous"],
100             "has_previous": context["has_previous"],
101             "next": context["next"],
102             "has_next": context["has_next"],
103             "page": context["page"],
104             "pages": context["pages"],
105             "page_numbers": page_numbers,
106             "in_leading_range" : in_leading_range,
107             "in_trailing_range" : in_trailing_range,
108             "pages_outside_leading_range": pages_outside_leading_range,
109             "pages_outside_trailing_range": pages_outside_trailing_range,
110             "extend_url" : extend_url
111         }
112
113 @register.inclusion_tag("pagesize.html")
114 def cnprog_pagesize(context):
115     """
116     display the pagesize selection boxes for paginator
117     """
118     if (context["is_paginated"]):
119         return {
120             "base_url": context["base_url"],
121             "pagesize" : context["pagesize"],
122             "is_paginated": context["is_paginated"]
123         }
124
125    
126 @register.simple_tag
127 def get_score_badge(user):
128     BADGE_TEMPLATE = '<span class="score" title="%(reputation)s %(reputationword)s">%(reputation)s</span>'
129     if user.gold > 0 :
130         BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgesword)s">'
131         '<span class="badge1">&#9679;</span>'
132         '<span class="badgecount">%(gold)s</span>'
133         '</span>')
134     if user.silver > 0:
135         BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgesword)s">'
136         '<span class="silver">&#9679;</span>'
137         '<span class="badgecount">%(silver)s</span>'
138         '</span>')
139     if user.bronze > 0:
140         BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgesword)s">'
141         '<span class="bronze">&#9679;</span>'
142         '<span class="badgecount">%(bronze)s</span>'
143         '</span>')
144     BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
145     return mark_safe(BADGE_TEMPLATE % {
146         'reputation' : user.reputation,
147         'gold' : user.gold,
148         'silver' : user.silver,
149         'bronze' : user.bronze,
150                 'badgesword' : _('badges'),
151                 'reputationword' : _('reputation points'),
152     })
153     
154 @register.simple_tag
155 def get_score_badge_by_details(rep, gold, silver, bronze):
156     BADGE_TEMPLATE = '<span class="reputation-score" title="%(reputation)s %(repword)s">%(reputation)s</span>'
157     if gold > 0 :
158         BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgeword)s">'
159         '<span class="badge1">&#9679;</span>'
160         '<span class="badgecount">%(gold)s</span>'
161         '</span>')
162     if silver > 0:
163         BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgeword)s">'
164         '<span class="badge2">&#9679;</span>'
165         '<span class="badgecount">%(silver)s</span>'
166         '</span>')
167     if bronze > 0:
168         BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgeword)s">'
169         '<span class="badge3">&#9679;</span>'
170         '<span class="badgecount">%(bronze)s</span>'
171         '</span>')
172     BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
173     return mark_safe(BADGE_TEMPLATE % {
174         'reputation' : rep,
175         'gold' : gold,
176         'silver' : silver,
177         'bronze' : bronze,
178                 'repword' : _('reputation points'),
179                 'badgeword' : _('badges'),
180     })      
181     
182 @register.simple_tag
183 def get_age(birthday):
184     current_time = datetime.datetime(*time.localtime()[0:6])
185     year = birthday.year
186     month = birthday.month
187     day = birthday.day
188     diff = current_time - datetime.datetime(year,month,day,0,0,0)
189     return diff.days / 365
190
191 @register.simple_tag
192 def get_total_count(up_count, down_count):
193     return up_count + down_count
194
195 @register.simple_tag
196 def format_number(value):
197     strValue = str(value)
198     if len(strValue) <= 3:
199         return strValue
200     result = ''
201     first = ''
202     pattern = re.compile('(-?\d+)(\d{3})')
203     m = re.match(pattern, strValue)
204     while m != None:
205         first = m.group(1)
206         second = m.group(2)
207         result = ',' + second + result
208         strValue = first + ',' + second
209         m = re.match(pattern, strValue)
210     return first + result
211
212 @register.simple_tag
213 def convert2tagname_list(question):
214     question['tagnames'] = [name for name in question['tagnames'].split(u' ')]
215     return ''
216
217 @register.simple_tag
218 def diff_date(date, limen=2):
219     if not date:
220         return _('unknown')
221         
222     now = datetime.datetime.now()#datetime(*time.localtime()[0:6])#???
223     diff = now - date
224     days = diff.days
225     hours = int(diff.seconds/3600)
226     minutes = int(diff.seconds/60)
227
228     if days > 2:
229         if date.year == now.year:
230             return date.strftime("%b %d at %H:%M")
231         else:
232             return date.strftime("%b %d '%y at %H:%M")
233     elif days == 2:
234         return _('2 days ago')
235     elif days == 1:
236         return _('yesterday')
237     elif minutes >= 60:
238         return ungettext('%(hr)d hour ago','%(hr)d hours ago',hours) % {'hr':hours}
239     else:
240         return ungettext('%(min)d min ago','%(min)d mins ago',minutes) % {'min':minutes}
241
242 @register.simple_tag
243 def get_latest_changed_timestamp():
244     try:
245         from time import localtime, strftime
246         from os import path
247         root = settings.SITE_SRC_ROOT
248         dir = (
249             root,
250             '%s/forum' % root,
251             '%s/templates' % root,
252         )
253         stamp = (path.getmtime(d) for d in dir)
254         latest = max(stamp)
255         timestr = strftime("%H:%M %b-%d-%Y %Z", localtime(latest))
256     except:
257         timestr = ''
258     return timestr
259
260 @register.simple_tag
261 def media(url):
262     url = skins.find_media_source(url)
263     if url:
264         url = '///' + settings.FORUM_SCRIPT_ALIAS + '/m/' + url
265         return posixpath.normpath(url)
266
267 class ItemSeparatorNode(template.Node):
268     def __init__(self,separator):
269         sep = separator.strip()
270         if sep[0] == sep[-1] and sep[0] in ('\'','"'):
271             sep = sep[1:-1]
272         else:
273             raise template.TemplateSyntaxError('separator in joinitems tag must be quoted')
274         self.content = sep
275     def render(self,context):
276         return self.content
277
278 class JoinItemListNode(template.Node):
279     def __init__(self,separator=ItemSeparatorNode("''"), items=()):
280         self.separator = separator
281         self.items = items
282     def render(self,context):
283         out = []
284         empty_re = re.compile(r'^\s*$')
285         for item in self.items:
286             bit = item.render(context)
287             if not empty_re.search(bit):
288                 out.append(bit)
289         return self.separator.render(context).join(out)
290
291 @register.tag(name="joinitems")
292 def joinitems(parser,token):
293     try:
294         tagname,junk,sep_token = token.split_contents()
295     except ValueError:
296         raise template.TemplateSyntaxError("joinitems tag requires 'using \"separator html\"' parameters")
297     if junk == 'using':
298         sep_node = ItemSeparatorNode(sep_token)
299     else:
300         raise template.TemplateSyntaxError("joinitems tag requires 'using \"separator html\"' parameters")
301     nodelist = []
302     while True:
303         nodelist.append(parser.parse(('separator','endjoinitems')))
304         next = parser.next_token()
305         if next.contents == 'endjoinitems':
306             break
307
308     return JoinItemListNode(separator=sep_node,items=nodelist)
309
310 class BlockMediaUrlNode(template.Node):
311     def __init__(self,nodelist):
312         self.items = nodelist 
313     def render(self,context):
314         prefix = '///' + settings.FORUM_SCRIPT_ALIAS + 'm/'
315         url = ''
316         if self.items:
317             url += '/'     
318         for item in self.items:
319             url += item.render(context)
320
321         url = skins.find_media_source(url)
322         url = prefix + url
323         out = posixpath.normpath(url)
324         return out.replace(' ','')
325
326 @register.tag(name='blockmedia')
327 def blockmedia(parser,token):
328     try:
329         tagname = token.split_contents()
330     except ValueError:
331         raise template.TemplateSyntaxError("blockmedia tag does not use arguments")
332     nodelist = []
333     while True:
334         nodelist.append(parser.parse(('endblockmedia')))
335         next = parser.next_token()
336         if next.contents == 'endblockmedia':
337             break
338     return BlockMediaUrlNode(nodelist)
339
340 class FullUrlNode(template.Node):
341     def __init__(self, default_node):
342         self.default_node = default_node
343
344     def render(self, context):
345         domain = settings.APP_URL
346         #protocol = getattr(settings, "PROTOCOL", "http")
347         path = self.default_node.render(context)
348         return "%s%s" % (domain, path)
349
350 @register.tag(name='fullurl')
351 def fullurl(parser, token):
352     default_node = default_url(parser, token)
353     return FullUrlNode(default_node)
354
355 @register.simple_tag
356 def fullmedia(url):
357     domain = settings.APP_URL
358     #protocol = getattr(settings, "PROTOCOL", "http")
359     path = media(url)
360     return "%s%s" % (domain, path)
361
362 class UserVarNode(template.Node):
363     def __init__(self, tokens):
364         self.tokens = tokens
365
366     def render(self, context):
367         return "{{ %s }}" % self.tokens
368
369 @register.tag(name='user_var')
370 def user_var(parser, token):
371     tokens = " ".join(token.split_contents()[1:])
372     return UserVarNode(tokens)
373
374
375 class SimpleVarNode(template.Node):
376     def __init__(self, name, value):
377         self.name = name
378         self.value = template.Variable(value)
379
380     def render(self, context):
381         context[self.name] = self.value.resolve(context)
382         return ''
383
384 class BlockVarNode(template.Node):
385     def __init__(self, name, block):
386         self.name = name
387         self.block = block
388
389     def render(self, context):
390         source = self.block.render(context)
391         context[self.name] = source.strip()
392         return ''
393
394
395 @register.tag(name='var')
396 def do_var(parser, token):
397     tokens = token.split_contents()[1:]
398
399     if not len(tokens) or not re.match('^\w+$', tokens[0]):
400         raise template.TemplateSyntaxError("Expected variable name")
401
402     if len(tokens) == 1:
403         nodelist = parser.parse(('endvar',))
404         parser.delete_first_token()
405         return BlockVarNode(tokens[0], nodelist)
406     elif len(tokens) == 3:
407         return SimpleVarNode(tokens[0], tokens[2])
408
409     raise template.TemplateSyntaxError("Invalid number of arguments")
410
411 class DeclareNode(template.Node):
412     dec_re = re.compile('^\s*(\w+)\s*(:?=)\s*(.*)$')
413
414     def __init__(self, block):
415         self.block = block
416
417     def render(self, context):
418         source = self.block.render(context)
419
420         for line in source.splitlines():
421             m = self.dec_re.search(line)
422             if m:
423                 clist = list(context)
424                 clist.reverse()
425                 d = {}
426                 d['_'] = _
427                 d['os'] = os
428                 for c in clist:
429                     d.update(c)
430                 try:
431                     context[m.group(1).strip()] = eval(m.group(3).strip(), d)
432                 except Exception, e:
433                     logging.error("Error in declare tag, when evaluating: %s" % m.group(3).strip())
434                     raise
435         return ''
436
437 @register.tag(name='declare')
438 def do_declare(parser, token):
439     nodelist = parser.parse(('enddeclare',))
440     parser.delete_first_token()
441     return DeclareNode(nodelist)