]> git.openstreetmap.org Git - osqa.git/blob - forum/templatetags/extra_tags.py
Remove footer link to squatted domain
[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 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="https://secure.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     return _get_score_badge(user)
50
51 def _get_score_badge(user):
52     if user.is_suspended():
53         return _("(suspended)")
54
55     repstr = decorated_int(user.reputation, "")
56
57     BADGE_TEMPLATE = '<span class="score" title="%(reputation)s %(reputationword)s">%(repstr)s</span>'
58     if user.gold > 0 :
59         BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgesword)s">'
60         '<span class="badge1">&#9679;</span>'
61         '<span class="badgecount">%(gold)s</span>'
62         '</span>')
63     if user.silver > 0:
64         BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgesword)s">'
65         '<span class="silver">&#9679;</span>'
66         '<span class="badgecount">%(silver)s</span>'
67         '</span>')
68     if user.bronze > 0:
69         BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgesword)s">'
70         '<span class="bronze">&#9679;</span>'
71         '<span class="badgecount">%(bronze)s</span>'
72         '</span>')
73     BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
74     return mark_safe(BADGE_TEMPLATE % {
75     'reputation' : user.reputation,
76     'repstr': repstr,
77     'gold' : user.gold,
78     'silver' : user.silver,
79     'bronze' : user.bronze,
80     'badgesword' : _('badges'),
81     'reputationword' : _('reputation points'),
82     })
83
84 # Usage: {% get_accept_rate node.author %}
85 @register.simple_tag
86 def get_accept_rate(user):
87     # If the Show Accept Rate feature is not activated this tag should return a blank string
88     if not settings.SHOW_USER_ACCEPT_RATE:
89         return ""
90
91     # Freeze accept rate for users
92     freeze_accept_rate_for_users_users = settings.FREEZE_ACCEPT_RATE_FOR.value
93     if user.username in list(freeze_accept_rate_for_users_users):
94         freeze = True
95     else:
96         freeze = False
97
98     # We get the number of all user's answers.
99     total_answers_count = Answer.objects.filter(author=user).count()
100
101     # We get the number of the user's accepted answers.
102     accepted_answers_count = Answer.objects.filter(author=user, state_string__contains="(accepted)").count()
103
104     # In order to represent the accept rate in percentages we divide the number of the accepted answers to the
105     # total answers count and make a hundred multiplication.
106     try:
107         accept_rate = (float(accepted_answers_count) / float(total_answers_count) * 100)
108     except ZeroDivisionError:
109         accept_rate = 0
110
111     # If the user has more than one accepted answers the rate title will be in plural.
112     if accepted_answers_count > 1:
113         accept_rate_number_title = _('%(user)s has %(count)d accepted answers') % {
114             'user' :  smart_unicode(user.username),
115             'count' : int(accepted_answers_count)
116         }
117     # If the user has one accepted answer we'll be using singular.
118     elif accepted_answers_count == 1:
119         accept_rate_number_title = _('%s has one accepted answer') % smart_unicode(user.username)
120     # This are the only options. Otherwise there are no accepted answers at all.
121     else:
122         if freeze:
123             accept_rate_number_title = ""
124         else:
125             accept_rate_number_title = _('%s has no accepted answers') % smart_unicode(user.username)
126
127     html_output = """
128     <span title="%(accept_rate_title)s" class="accept_rate">%(accept_rate_label)s:</span>
129     <span title="%(accept_rate_number_title)s">%(accept_rate)d&#37;</span>
130     """ % {
131         'accept_rate_label' : _('accept rate'),
132         'accept_rate_title' : _('Rate of the user\'s accepted answers'),
133         'accept_rate' : 100 if freeze else int(accept_rate),
134         'accept_rate_number_title' : u'%s' % accept_rate_number_title,
135     }
136
137     return mark_safe(html_output)
138
139 @register.simple_tag
140 def get_age(birthday):
141     current_time = datetime.datetime(*time.localtime()[0:6])
142     year = birthday.year
143     month = birthday.month
144     day = birthday.day
145     diff = current_time - datetime.datetime(year, month, day, 0, 0, 0)
146     return diff.days / 365
147
148 @register.simple_tag
149 def diff_date(date, limen=2):
150     if not date:
151         return _('unknown')
152
153     now = datetime.datetime.now()
154     diff = now - date
155     days = diff.days
156     hours = int(diff.seconds/3600)
157     minutes = int(diff.seconds/60)
158
159     if date.year != now.year:
160         return dateformat.format(date, 'd M \'y, H:i')
161     elif days > 2:
162         return dateformat.format(date, 'd M, H:i')
163
164     elif days == 2:
165         return _('2 days ago')
166     elif days == 1:
167         return _('yesterday')
168     elif minutes >= 60:
169         return ungettext('%(hr)d ' + _("hour ago"), '%(hr)d ' + _("hours ago"), hours) % {'hr':hours}
170     elif diff.seconds >= 60:
171         return ungettext('%(min)d ' + _("min ago"), '%(min)d ' + _("mins ago"), minutes) % {'min':minutes}
172     else:
173         return ungettext('%(sec)d ' + _("sec ago"), '%(sec)d ' + _("secs ago"), diff.seconds) % {'sec':diff.seconds}
174
175 @register.simple_tag
176 def media(url):
177     url = skins.find_media_source(url)
178     if url:
179         # Create the URL prefix.
180         url_prefix = settings.FORCE_SCRIPT_NAME + '/m/'
181
182         # Make sure any duplicate forward slashes are replaced with a single
183         # forward slash.
184         url_prefix = re.sub("/+", "/", url_prefix)
185
186         url = url_prefix + url
187         return url
188
189 @register.simple_tag
190 def get_tag_font_size(tag):
191     occurrences_of_current_tag = tag.used_count
192
193     # Occurrences count settings
194     min_occurs = int(settings.TAGS_CLOUD_MIN_OCCURS)
195     max_occurs = int(settings.TAGS_CLOUD_MAX_OCCURS)
196
197     # Font size settings
198     min_font_size = int(settings.TAGS_CLOUD_MIN_FONT_SIZE)
199     max_font_size = int(settings.TAGS_CLOUD_MAX_FONT_SIZE)
200
201     # Calculate the font size of the tag according to the occurrences count
202     weight = (math.log(occurrences_of_current_tag)-math.log(min_occurs))/(math.log(max_occurs)-math.log(min_occurs))
203     font_size_of_current_tag = min_font_size + int(math.floor((max_font_size-min_font_size)*weight))
204
205     return font_size_of_current_tag
206
207 class ItemSeparatorNode(template.Node):
208     def __init__(self, separator):
209         sep = separator.strip()
210         if sep[0] == sep[-1] and sep[0] in ('\'', '"'):
211             sep = sep[1:-1]
212         else:
213             raise template.TemplateSyntaxError('separator in joinitems tag must be quoted')
214         self.content = sep
215
216     def render(self, context):
217         return self.content
218
219 class BlockMediaUrlNode(template.Node):
220     def __init__(self, nodelist):
221         self.items = nodelist
222
223     def render(self, context):
224         prefix = settings.APP_URL + 'm/'
225         url = ''
226         if self.items:
227             url += '/'
228         for item in self.items:
229             url += item.render(context)
230
231         url = skins.find_media_source(url)
232         url = prefix + url
233         out = url
234         return out.replace(' ', '')
235
236 @register.tag(name='blockmedia')
237 def blockmedia(parser, token):
238     try:
239         tagname = token.split_contents()
240     except ValueError:
241         raise template.TemplateSyntaxError("blockmedia tag does not use arguments")
242     nodelist = []
243     while True:
244         nodelist.append(parser.parse(('endblockmedia')))
245         next = parser.next_token()
246         if next.contents == 'endblockmedia':
247             break
248     return BlockMediaUrlNode(nodelist)
249
250
251 @register.simple_tag
252 def fullmedia(url):
253     domain = settings.APP_BASE_URL
254     #protocol = getattr(settings, "PROTOCOL", "http")
255     path = media(url)
256     return "%s%s" % (domain, path)
257
258
259 class SimpleVarNode(template.Node):
260     def __init__(self, name, value):
261         self.name = name
262         self.value = template.Variable(value)
263
264     def render(self, context):
265         context[self.name] = self.value.resolve(context)
266         return ''
267
268 class BlockVarNode(template.Node):
269     def __init__(self, name, block):
270         self.name = name
271         self.block = block
272
273     def render(self, context):
274         source = self.block.render(context)
275         context[self.name] = source.strip()
276         return ''
277
278
279 @register.tag(name='var')
280 def do_var(parser, token):
281     tokens = token.split_contents()[1:]
282
283     if not len(tokens) or not re.match('^\w+$', tokens[0]):
284         raise template.TemplateSyntaxError("Expected variable name")
285
286     if len(tokens) == 1:
287         nodelist = parser.parse(('endvar',))
288         parser.delete_first_token()
289         return BlockVarNode(tokens[0], nodelist)
290     elif len(tokens) == 3:
291         return SimpleVarNode(tokens[0], tokens[2])
292
293     raise template.TemplateSyntaxError("Invalid number of arguments")
294
295 class DeclareNode(template.Node):
296     dec_re = re.compile('^\s*(\w+)\s*(:?=)\s*(.*)$')
297
298     def __init__(self, block):
299         self.block = block
300
301     def render(self, context):
302         source = self.block.render(context)
303
304         for line in source.splitlines():
305             m = self.dec_re.search(line)
306             if m:
307                 clist = list(context)
308                 clist.reverse()
309                 d = {}
310                 d['_'] = _
311                 d['os'] = os
312                 d['html'] = html
313                 d['reverse'] = reverse
314                 d['settings'] = settings
315                 d['smart_str'] = smart_str
316                 d['smart_unicode'] = smart_unicode
317                 d['force_unicode'] = force_unicode
318                 for c in clist:
319                     d.update(c)
320                 try:
321                     command = m.group(3).strip()
322                     context[m.group(1).strip()] = eval(command, d)
323                 except Exception, e:
324                     logging.error("Error in declare tag, when evaluating: %s" % m.group(3).strip())
325         return ''
326
327 @register.tag(name='declare')
328 def do_declare(parser, token):
329     nodelist = parser.parse(('enddeclare',))
330     parser.delete_first_token()
331     return DeclareNode(nodelist)
332
333 # Usage: {% random 1 999 %}
334 # Generates random number in the template
335 class RandomNumberNode(template.Node):
336     # We get the limiting numbers
337     def __init__(self, int_from, int_to):
338         self.int_from = int(int_from)
339         self.int_to = int(int_to)
340
341     # We generate the random number using the standard python interface
342     def render(self, context):
343         return str(random.randint(self.int_from, self.int_to))
344
345 @register.tag(name="random")
346 def random_number(parser, token):
347     # Try to get the limiting numbers from the token
348     try:
349         tag_name, int_from, int_to = token.split_contents()
350     except ValueError:
351         # If we had no success -- raise an exception
352         raise template.TemplateSyntaxError, "%r tag requires exactly two arguments" % token.contents.split()[0]
353
354     # Call the random Node
355     return RandomNumberNode(int_from, int_to)