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
24 register = template.Library()
26 GRAVATAR_TEMPLATE = ('<img class="gravatar" width="%(size)s" height="%(size)s" '
27 'src="https://secure.gravatar.com/avatar/%(gravatar_hash)s'
28 '?s=%(size)s&d=%(default)s&r=%(rating)s" '
29 'alt="%(username)s\'s gravatar image" />')
32 def gravatar(user, size):
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 % {
41 'gravatar_hash': gravatar,
42 'default': settings.GRAVATAR_DEFAULT_IMAGE,
43 'rating': settings.GRAVATAR_ALLOWED_RATING,
44 'username': template.defaultfilters.urlencode(username),
49 def get_score_badge(user):
50 return _get_score_badge(user)
52 def _get_score_badge(user):
53 if user.is_suspended():
54 return _("(suspended)")
56 repstr = decorated_int(user.reputation, "")
58 BADGE_TEMPLATE = '<span class="score" title="%(reputation)s %(reputationword)s">%(repstr)s</span>'
60 BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgesword)s">'
61 '<span class="badge1">●</span>'
62 '<span class="badgecount">%(gold)s</span>'
65 BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgesword)s">'
66 '<span class="silver">●</span>'
67 '<span class="badgecount">%(silver)s</span>'
70 BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgesword)s">'
71 '<span class="bronze">●</span>'
72 '<span class="badgecount">%(bronze)s</span>'
74 BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
75 return mark_safe(BADGE_TEMPLATE % {
76 'reputation' : user.reputation,
79 'silver' : user.silver,
80 'bronze' : user.bronze,
81 'badgesword' : _('badges'),
82 'reputationword' : _('reputation points'),
85 # Usage: {% get_accept_rate node.author %}
87 def get_accept_rate(user):
88 # If the Show Accept Rate feature is not activated this tag should return a blank string
89 if not settings.SHOW_USER_ACCEPT_RATE:
92 # Freeze accept rate for users
93 freeze_accept_rate_for_users_users = settings.FREEZE_ACCEPT_RATE_FOR.value
94 if user.username in list(freeze_accept_rate_for_users_users):
99 # We get the number of all user's answers.
100 total_answers_count = Answer.objects.filter(author=user).count()
102 # We get the number of the user's accepted answers.
103 accepted_answers_count = Answer.objects.filter(author=user, state_string__contains="(accepted)").count()
105 # In order to represent the accept rate in percentages we divide the number of the accepted answers to the
106 # total answers count and make a hundred multiplication.
108 accept_rate = (float(accepted_answers_count) / float(total_answers_count) * 100)
109 except ZeroDivisionError:
112 # If the user has more than one accepted answers the rate title will be in plural.
113 if accepted_answers_count > 1:
114 accept_rate_number_title = _('%(user)s has %(count)d accepted answers') % {
115 'user' : smart_unicode(user.username),
116 'count' : int(accepted_answers_count)
118 # If the user has one accepted answer we'll be using singular.
119 elif accepted_answers_count == 1:
120 accept_rate_number_title = _('%s has one accepted answer') % smart_unicode(user.username)
121 # This are the only options. Otherwise there are no accepted answers at all.
124 accept_rate_number_title = ""
126 accept_rate_number_title = _('%s has no accepted answers') % smart_unicode(user.username)
129 <span title="%(accept_rate_title)s" class="accept_rate">%(accept_rate_label)s:</span>
130 <span title="%(accept_rate_number_title)s">%(accept_rate)d%</span>
132 'accept_rate_label' : _('accept rate'),
133 'accept_rate_title' : _('Rate of the user\'s accepted answers'),
134 'accept_rate' : 100 if freeze else int(accept_rate),
135 'accept_rate_number_title' : u'%s' % accept_rate_number_title,
138 return mark_safe(html_output)
141 def get_age(birthday):
142 current_time = datetime.datetime(*time.localtime()[0:6])
144 month = birthday.month
146 diff = current_time - datetime.datetime(year, month, day, 0, 0, 0)
147 return diff.days / 365
150 def diff_date(date, limen=2):
154 now = datetime.datetime.now()
157 hours = int(diff.seconds/3600)
158 minutes = int(diff.seconds/60)
160 if date.year != now.year:
161 return dateformat.format(date, 'd M \'y, H:i')
163 return dateformat.format(date, 'd M, H:i')
166 return _('2 days ago')
168 return _('yesterday')
170 return ungettext('%(hr)d ' + _("hour ago"), '%(hr)d ' + _("hours ago"), hours) % {'hr':hours}
171 elif diff.seconds >= 60:
172 return ungettext('%(min)d ' + _("min ago"), '%(min)d ' + _("mins ago"), minutes) % {'min':minutes}
174 return ungettext('%(sec)d ' + _("sec ago"), '%(sec)d ' + _("secs ago"), diff.seconds) % {'sec':diff.seconds}
178 url = skins.find_media_source(url)
180 # Create the URL prefix.
181 url_prefix = settings.FORCE_SCRIPT_NAME + '/m/'
183 # Make sure any duplicate forward slashes are replaced with a single
185 url_prefix = re.sub("/+", "/", url_prefix)
187 url = url_prefix + url
191 def get_tag_font_size(tag):
192 occurrences_of_current_tag = tag.used_count
194 # Occurrences count settings
195 min_occurs = int(settings.TAGS_CLOUD_MIN_OCCURS)
196 max_occurs = int(settings.TAGS_CLOUD_MAX_OCCURS)
199 min_font_size = int(settings.TAGS_CLOUD_MIN_FONT_SIZE)
200 max_font_size = int(settings.TAGS_CLOUD_MAX_FONT_SIZE)
202 # Calculate the font size of the tag according to the occurrences count
203 weight = (math.log(occurrences_of_current_tag)-math.log(min_occurs))/(math.log(max_occurs)-math.log(min_occurs))
204 font_size_of_current_tag = min_font_size + int(math.floor((max_font_size-min_font_size)*weight))
206 return font_size_of_current_tag
208 class ItemSeparatorNode(template.Node):
209 def __init__(self, separator):
210 sep = separator.strip()
211 if sep[0] == sep[-1] and sep[0] in ('\'', '"'):
214 raise template.TemplateSyntaxError('separator in joinitems tag must be quoted')
217 def render(self, context):
220 class BlockMediaUrlNode(template.Node):
221 def __init__(self, nodelist):
222 self.items = nodelist
224 def render(self, context):
225 prefix = settings.APP_URL + 'm/'
229 for item in self.items:
230 url += item.render(context)
232 url = skins.find_media_source(url)
235 return out.replace(' ', '')
237 @register.tag(name='blockmedia')
238 def blockmedia(parser, token):
240 tagname = token.split_contents()
242 raise template.TemplateSyntaxError("blockmedia tag does not use arguments")
245 nodelist.append(parser.parse(('endblockmedia')))
246 next = parser.next_token()
247 if next.contents == 'endblockmedia':
249 return BlockMediaUrlNode(nodelist)
254 domain = settings.APP_BASE_URL
255 #protocol = getattr(settings, "PROTOCOL", "http")
257 return "%s%s" % (domain, path)
260 class SimpleVarNode(template.Node):
261 def __init__(self, name, value):
263 self.value = template.Variable(value)
265 def render(self, context):
266 context[self.name] = self.value.resolve(context)
269 class BlockVarNode(template.Node):
270 def __init__(self, name, block):
274 def render(self, context):
275 source = self.block.render(context)
276 context[self.name] = source.strip()
280 @register.tag(name='var')
281 def do_var(parser, token):
282 tokens = token.split_contents()[1:]
284 if not len(tokens) or not re.match('^\w+$', tokens[0]):
285 raise template.TemplateSyntaxError("Expected variable name")
288 nodelist = parser.parse(('endvar',))
289 parser.delete_first_token()
290 return BlockVarNode(tokens[0], nodelist)
291 elif len(tokens) == 3:
292 return SimpleVarNode(tokens[0], tokens[2])
294 raise template.TemplateSyntaxError("Invalid number of arguments")
296 class DeclareNode(template.Node):
297 dec_re = re.compile('^\s*(\w+)\s*(:?=)\s*(.*)$')
299 def __init__(self, block):
302 def render(self, context):
303 source = self.block.render(context)
305 for line in source.splitlines():
306 m = self.dec_re.search(line)
308 clist = list(context)
314 d['reverse'] = reverse
315 d['settings'] = settings
316 d['smart_str'] = smart_str
317 d['smart_unicode'] = smart_unicode
318 d['force_unicode'] = force_unicode
322 command = m.group(3).strip()
323 context[m.group(1).strip()] = eval(command, d)
325 logging.error("Error in declare tag, when evaluating: %s" % m.group(3).strip())
328 @register.tag(name='declare')
329 def do_declare(parser, token):
330 nodelist = parser.parse(('enddeclare',))
331 parser.delete_first_token()
332 return DeclareNode(nodelist)
334 # Usage: {% random 1 999 %}
335 # Generates random number in the template
336 class RandomNumberNode(template.Node):
337 # We get the limiting numbers
338 def __init__(self, int_from, int_to):
339 self.int_from = int(int_from)
340 self.int_to = int(int_to)
342 # We generate the random number using the standard python interface
343 def render(self, context):
344 return str(random.randint(self.int_from, self.int_to))
346 @register.tag(name="random")
347 def random_number(parser, token):
348 # Try to get the limiting numbers from the token
350 tag_name, int_from, int_to = token.split_contents()
352 # If we had no success -- raise an exception
353 raise template.TemplateSyntaxError, "%r tag requires exactly two arguments" % token.contents.split()[0]
355 # Call the random Node
356 return RandomNumberNode(int_from, int_to)