]> git.openstreetmap.org Git - osqa.git/blob - forum/templatetags/extra_tags.py
672d7a645b24f1ceb6bbec9d783bc6c3cccbf6a8
[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 import random
9 from django import template
10 from django.utils.encoding import smart_unicode, force_unicode, smart_str
11 from django.utils.safestring import mark_safe
12 from django.utils import dateformat
13 from forum.models import Question, Answer, QuestionRevision, AnswerRevision, NodeRevision
14 from django.utils.translation import ugettext as _
15 from django.utils.translation import ungettext
16 from django.utils import simplejson
17 from forum import settings
18 from django.template.defaulttags import url as default_url
19 from forum import skins
20 from forum.utils import html
21 from extra_filters import decorated_int
22 from django.core.urlresolvers import reverse
23
24 register = template.Library()
25
26 GRAVATAR_TEMPLATE = ('<img class="gravatar" width="%(size)s" height="%(size)s" '
27 'src="http://www.gravatar.com/avatar/%(gravatar_hash)s'
28 '?s=%(size)s&amp;d=%(default)s&amp;r=%(rating)s" '
29 'alt="%(username)s\'s gravatar image" />')
30
31 @register.simple_tag
32 def gravatar(user, size):
33     try:
34         gravatar = user['gravatar']
35         username = user['username']
36     except (TypeError, AttributeError, KeyError):
37         gravatar = user.gravatar
38         username = user.username
39     return mark_safe(GRAVATAR_TEMPLATE % {
40     'size': size,
41     'gravatar_hash': gravatar,
42     'default': settings.GRAVATAR_DEFAULT_IMAGE,
43     'rating': settings.GRAVATAR_ALLOWED_RATING,
44     'username': template.defaultfilters.urlencode(username),
45     })
46
47
48 @register.simple_tag
49 def get_score_badge(user):
50     if user.is_suspended():
51         return _("(suspended)")
52
53     repstr = decorated_int(user.reputation, "")
54
55     BADGE_TEMPLATE = '<span class="score" title="%(reputation)s %(reputationword)s">%(repstr)s</span>'
56     if user.gold > 0 :
57         BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgesword)s">'
58         '<span class="badge1">&#9679;</span>'
59         '<span class="badgecount">%(gold)s</span>'
60         '</span>')
61     if user.silver > 0:
62         BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgesword)s">'
63         '<span class="silver">&#9679;</span>'
64         '<span class="badgecount">%(silver)s</span>'
65         '</span>')
66     if user.bronze > 0:
67         BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgesword)s">'
68         '<span class="bronze">&#9679;</span>'
69         '<span class="badgecount">%(bronze)s</span>'
70         '</span>')
71     BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
72     return mark_safe(BADGE_TEMPLATE % {
73     'reputation' : user.reputation,
74     'repstr': repstr,
75     'gold' : user.gold,
76     'silver' : user.silver,
77     'bronze' : user.bronze,
78     'badgesword' : _('badges'),
79     'reputationword' : _('reputation points'),
80     })
81
82 # Usage: {% get_accept_rate node.author %}
83 @register.simple_tag
84 def get_accept_rate(user):
85     # We get the number of all user's answers.
86     total_answers_count = Answer.objects.filter(author=user).count()
87
88     # We get the number of the user's accepted answers.
89     accepted_answers_count = Answer.objects.filter(author=user, state_string__contains="(accepted)").count()
90
91     # In order to represent the accept rate in percentages we divide the number of the accepted answers to the
92     # total answers count and make a hundred multiplication.
93     try:
94         accept_rate = (float(accepted_answers_count) / float(total_answers_count) * 100)
95     except ZeroDivisionError:
96         accept_rate = 0
97
98     # If the user has more than one accepted answers the rate title will be in plural.
99     if accepted_answers_count > 1:
100         accept_rate_number_title = _('%(user)s has %(count)d accepted answers') % {
101             'user' :  user.username,
102             'count' : int(accepted_answers_count)
103         }
104     # If the user has one accepted answer we'll be using singular.
105     elif accepted_answers_count == 1:
106         accept_rate_number_title = _('%s has one accepted answer') % user.username
107     # This are the only options. Otherwise there are no accepted answers at all.
108     else:
109         accept_rate_number_title = _('%s has no accepted answers') % smart_unicode(user.username)
110
111     html_output = """
112     <span title="%(accept_rate_title)s" class="accept_rate">%(accept_rate_label)s:</span>
113     <span title="%(accept_rate_number_title)s">%(accept_rate)d&#37;</span>
114     """ % {
115         'accept_rate_label' : _('accept rate'),
116         'accept_rate_title' : _('Rate of the user\'s accepted answers'),
117         'accept_rate' : int(accept_rate),
118         'accept_rate_number_title' : u'%s' % accept_rate_number_title,
119     }
120
121     return mark_safe(html_output)
122
123 @register.simple_tag
124 def get_age(birthday):
125     current_time = datetime.datetime(*time.localtime()[0:6])
126     year = birthday.year
127     month = birthday.month
128     day = birthday.day
129     diff = current_time - datetime.datetime(year, month, day, 0, 0, 0)
130     return diff.days / 365
131
132 @register.simple_tag
133 def diff_date(date, limen=2):
134     if not date:
135         return _('unknown')
136
137     now = datetime.datetime.now()
138     diff = now - date
139     years = diff.years
140     days = diff.days
141     hours = int(diff.seconds/3600)
142     minutes = int(diff.seconds/60)
143
144     if years > 0 or days > 2:
145         if date.year == now.year:
146             return dateformat.format(date, 'd M, H:i')
147         else:
148             return dateformat.format(date, 'd M \'y, H:i')
149
150     elif days == 2:
151         return _('2 days ago')
152     elif days == 1:
153         return _('yesterday')
154     elif minutes >= 60:
155         return ungettext('%(hr)d ' + _("hour ago"), '%(hr)d ' + _("hours ago"), hours) % {'hr':hours}
156     elif diff.seconds >= 60:
157         return ungettext('%(min)d ' + _("min ago"), '%(min)d ' + _("mins ago"), minutes) % {'min':minutes}
158     else:
159         return ungettext('%(sec)d ' + _("sec ago"), '%(sec)d ' + _("secs ago"), diff.seconds) % {'sec':diff.seconds}
160
161 @register.simple_tag
162 def media(url):
163     url = skins.find_media_source(url)
164     if url:
165         # Create the URL prefix.
166         url_prefix = settings.FORCE_SCRIPT_NAME + '/m/'
167
168         # Make sure any duplicate forward slashes are replaced with a single
169         # forward slash.
170         url_prefix = re.sub("/+", "/", url_prefix)
171
172         url = url_prefix + url
173         return url
174
175 class ItemSeparatorNode(template.Node):
176     def __init__(self, separator):
177         sep = separator.strip()
178         if sep[0] == sep[-1] and sep[0] in ('\'', '"'):
179             sep = sep[1:-1]
180         else:
181             raise template.TemplateSyntaxError('separator in joinitems tag must be quoted')
182         self.content = sep
183
184     def render(self, context):
185         return self.content
186
187 class BlockMediaUrlNode(template.Node):
188     def __init__(self, nodelist):
189         self.items = nodelist
190
191     def render(self, context):
192         prefix = settings.APP_URL + 'm/'
193         url = ''
194         if self.items:
195             url += '/'
196         for item in self.items:
197             url += item.render(context)
198
199         url = skins.find_media_source(url)
200         url = prefix + url
201         out = url
202         return out.replace(' ', '')
203
204 @register.tag(name='blockmedia')
205 def blockmedia(parser, token):
206     try:
207         tagname = token.split_contents()
208     except ValueError:
209         raise template.TemplateSyntaxError("blockmedia tag does not use arguments")
210     nodelist = []
211     while True:
212         nodelist.append(parser.parse(('endblockmedia')))
213         next = parser.next_token()
214         if next.contents == 'endblockmedia':
215             break
216     return BlockMediaUrlNode(nodelist)
217
218
219 @register.simple_tag
220 def fullmedia(url):
221     domain = settings.APP_BASE_URL
222     #protocol = getattr(settings, "PROTOCOL", "http")
223     path = media(url)
224     return "%s%s" % (domain, path)
225
226
227 class SimpleVarNode(template.Node):
228     def __init__(self, name, value):
229         self.name = name
230         self.value = template.Variable(value)
231
232     def render(self, context):
233         context[self.name] = self.value.resolve(context)
234         return ''
235
236 class BlockVarNode(template.Node):
237     def __init__(self, name, block):
238         self.name = name
239         self.block = block
240
241     def render(self, context):
242         source = self.block.render(context)
243         context[self.name] = source.strip()
244         return ''
245
246
247 @register.tag(name='var')
248 def do_var(parser, token):
249     tokens = token.split_contents()[1:]
250
251     if not len(tokens) or not re.match('^\w+$', tokens[0]):
252         raise template.TemplateSyntaxError("Expected variable name")
253
254     if len(tokens) == 1:
255         nodelist = parser.parse(('endvar',))
256         parser.delete_first_token()
257         return BlockVarNode(tokens[0], nodelist)
258     elif len(tokens) == 3:
259         return SimpleVarNode(tokens[0], tokens[2])
260
261     raise template.TemplateSyntaxError("Invalid number of arguments")
262
263 class DeclareNode(template.Node):
264     dec_re = re.compile('^\s*(\w+)\s*(:?=)\s*(.*)$')
265
266     def __init__(self, block):
267         self.block = block
268
269     def render(self, context):
270         source = self.block.render(context)
271
272         for line in source.splitlines():
273             m = self.dec_re.search(line)
274             if m:
275                 clist = list(context)
276                 clist.reverse()
277                 d = {}
278                 d['_'] = _
279                 d['os'] = os
280                 d['html'] = html
281                 d['reverse'] = reverse
282                 d['smart_str'] = smart_str
283                 d['smart_unicode'] = smart_unicode
284                 d['force_unicode'] = force_unicode
285                 for c in clist:
286                     d.update(c)
287                 try:
288                     command = m.group(3).strip()
289                     context[m.group(1).strip()] = eval(command, d)
290                 except Exception, e:
291                     logging.error("Error in declare tag, when evaluating: %s" % m.group(3).strip())
292                     raise
293         return ''
294
295 @register.tag(name='declare')
296 def do_declare(parser, token):
297     nodelist = parser.parse(('enddeclare',))
298     parser.delete_first_token()
299     return DeclareNode(nodelist)
300
301 # Usage: {% random 1 999 %}
302 # Generates random number in the template
303 class RandomNumberNode(template.Node):
304     # We get the limiting numbers
305     def __init__(self, int_from, int_to):
306         self.int_from = int(int_from)
307         self.int_to = int(int_to)
308
309     # We generate the random number using the standard python interface
310     def render(self, context):
311         return str(random.randint(self.int_from, self.int_to))
312
313 @register.tag(name="random")
314 def random_number(parser, token):
315     # Try to get the limiting numbers from the token
316     try:
317         tag_name, int_from, int_to = token.split_contents()
318     except ValueError:
319         # If we had no success -- raise an exception
320         raise template.TemplateSyntaxError, "%r tag requires exactly two arguments" % token.contents.split()[0]
321
322     # Call the random Node
323     return RandomNumberNode(int_from, int_to)