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