This repository has been archived by the owner on Mar 15, 2018. It is now read-only.
/
helpers.py
397 lines (314 loc) · 11.9 KB
/
helpers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
import json as jsonlib
import pytz
from urlparse import urljoin
from django.conf import settings
from django.core.urlresolvers import reverse
from django.forms import CheckboxInput
from django.template import defaultfilters
from django.utils import translation
from django.utils.encoding import smart_unicode
import commonware.log
import jinja2
from babel.support import Format
from jingo import env, register
# Needed to make sure our own |f filter overrides jingo's one.
from jingo import helpers # noqa
from jingo_minify import helpers as jingo_minify_helpers
from six import text_type
from tower import ugettext as _
from mkt.translations.helpers import truncate
from mkt.translations.utils import get_locale_from_lang
from mkt.site.utils import append_tz
log = commonware.log.getLogger('z.mkt.site')
@jinja2.contextfunction
@register.function
def css(context, bundle, media=False, debug=None):
if debug is None:
debug = settings.TEMPLATE_DEBUG
# ?debug=true gives you unminified CSS for testing on -dev/prod.
if context['request'].GET.get('debug'):
debug = True
return jingo_minify_helpers.css(bundle, media, debug)
@jinja2.contextfunction
@register.function
def js(context, bundle, debug=None, defer=False, async=False):
if debug is None:
debug = settings.TEMPLATE_DEBUG
# ?debug=true gives you unminified JS for testing on -dev/prod.
if context['request'].GET.get('debug'):
debug = True
return jingo_minify_helpers.js(bundle, debug, defer, async)
@register.function
def no_results():
# This prints a "No results found" message. That's all. Carry on.
t = env.get_template('site/helpers/no_results.html').render()
return jinja2.Markup(t)
@jinja2.contextfunction
@register.function
def market_button(context, product, receipt_type=None, classes=None):
request = context['request']
purchased = False
classes = (classes or []) + ['button', 'product']
reviewer = receipt_type == 'reviewer'
data_attrs = {'manifest_url': product.get_manifest_url(reviewer),
'is_packaged': jsonlib.dumps(product.is_packaged)}
installed = None
if request.user.is_authenticated():
installed_set = request.user.installed_set
installed = installed_set.filter(addon=product).exists()
# Handle premium apps.
if product.has_premium():
# User has purchased app.
purchased = (request.user.is_authenticated() and
product.pk in request.user.purchase_ids())
# App authors are able to install their apps free of charge.
if (not purchased and
request.check_ownership(product, require_author=True)):
purchased = True
if installed or purchased or not product.has_premium():
label = _('Install')
else:
label = product.get_tier_name()
# Free apps and purchased apps get active install buttons.
if not product.is_premium() or purchased:
classes.append('install')
c = dict(product=product, label=label, purchased=purchased,
data_attrs=data_attrs, classes=' '.join(classes))
t = env.get_template('site/helpers/webapp_button.html')
return jinja2.Markup(t.render(c))
def product_as_dict(request, product, purchased=None, receipt_type=None,
src=''):
receipt_url = (reverse('receipt.issue', args=[product.app_slug]) if
receipt_type else product.get_detail_url('record'))
token_url = reverse('generate-reviewer-token', args=[product.app_slug])
src = src or request.GET.get('src', '')
reviewer = receipt_type == 'reviewer'
# This is the only info. we need to render the app buttons on the
# Reviewer Tools pages.
ret = {
'id': product.id,
'name': product.name,
'categories': product.categories,
'manifest_url': product.get_manifest_url(reviewer),
'recordUrl': helpers.urlparams(receipt_url, src=src),
'tokenUrl': token_url,
'is_packaged': product.is_packaged,
'src': src
}
if product.premium:
ret.update({
'price': product.get_price(region=request.REGION.id),
'priceLocale': product.get_price_locale(region=request.REGION.id),
})
if request.user.is_authenticated():
ret['isPurchased'] = purchased
# Jinja2 escape everything except this list so that bool is retained
# for the JSON encoding.
wl = ('categories', 'currencies', 'isPurchased', 'is_packaged', 'previews',
'price', 'priceLocale')
return dict([k, jinja2.escape(v) if k not in wl else v]
for k, v in ret.items())
@register.function
@jinja2.contextfunction
def mkt_breadcrumbs(context, product=None, items=None, crumb_size=40,
add_default=True, cls=None):
"""
Wrapper function for ``breadcrumbs``.
**items**
list of [(url, label)] to be inserted after Add-on.
**product**
Adds the App/Add-on name to the end of the trail. If items are
specified then the App/Add-on will be linked.
**add_default**
Prepends trail back to home when True. Default is True.
"""
if add_default:
crumbs = [(reverse('home'), _('Home'))]
else:
crumbs = []
if product:
if items:
url_ = product.get_detail_url()
else:
# The Product is the end of the trail.
url_ = None
crumbs += [(None, _('Apps')), (url_, product.name)]
if items:
crumbs.extend(items)
if len(crumbs) == 1:
crumbs = []
crumbs = [(u, truncate(label, crumb_size)) for (u, label) in crumbs]
t = env.get_template('site/helpers/breadcrumbs.html').render(
{'breadcrumbs': crumbs, 'cls': cls})
return jinja2.Markup(t)
@register.function
def form_field(field, label=None, tag='div', req=None, opt=False, hint=False,
tooltip=False, some_html=False, cc_startswith=None, cc_for=None,
cc_maxlength=None, grid=False, cls=None, validate=False):
attrs = {}
# Add a `required` attribute so we can do form validation.
# TODO(cvan): Write tests for kumar some day.
if validate and field.field.required:
attrs['required'] = ''
c = dict(field=field, label=label or field.label, tag=tag, req=req,
opt=opt, hint=hint, tooltip=tooltip, some_html=some_html,
cc_startswith=cc_startswith, cc_for=cc_for,
cc_maxlength=cc_maxlength, grid=grid, cls=cls, attrs=attrs)
t = env.get_template('site/helpers/simple_field.html').render(c)
return jinja2.Markup(t)
@register.filter
@jinja2.contextfilter
def timelabel(context, time):
t = env.get_template('site/helpers/timelabel.html').render({'time': time})
return jinja2.Markup(t)
@register.function
def mkt_admin_site_links():
return {
'addons': [
('Fake mail', reverse('zadmin.mail')),
],
'users': [
('Configure groups', reverse('admin:access_group_changelist')),
],
'settings': [
('View site settings', reverse('zadmin.settings')),
('Django admin pages', reverse('zadmin.home')),
],
'tools': [
('View request environment', reverse('mkt.env')),
('View elasticsearch settings', reverse('zadmin.elastic')),
('Purge data from memcache', reverse('zadmin.memcache')),
('Generate error', reverse('zadmin.generate-error')),
('Site Status', reverse('mkt.monitor')),
('Force Manifest Re-validation',
reverse('zadmin.manifest_revalidation'))
],
}
@register.function
@jinja2.contextfunction
def get_doc_template(context, template):
lang = getattr(context['request'], 'LANG', 'en-US')
if lang in settings.AMO_LANGUAGES:
try:
template = env.get_template('%s/%s.html' % (template, lang))
except jinja2.TemplateNotFound:
pass
else:
return jinja2.Markup(template.render())
template = env.get_template('%s/en-US.html' % template)
return jinja2.Markup(template.render())
@register.function
@jinja2.contextfunction
def get_doc_path(context, path, extension):
"""
Gets the path to a localizable document in the current language with
fallback to en-US.
"""
lang = getattr(context['request'], 'LANG', 'en-US')
if lang in settings.AMO_LANGUAGES:
try:
localized_file_path = '%s/%s.%s' % (path, lang, extension)
with open(localized_file_path):
return localized_file_path
except IOError:
return '%s/en-US.%s' % (path, extension)
@register.filter
def absolutify(url, site=None):
"""Takes a URL and prepends the SITE_URL"""
if url.startswith('http'):
return url
else:
return urljoin(site or settings.SITE_URL, url)
def _get_format():
lang = translation.get_language()
return Format(get_locale_from_lang(lang))
@register.filter
def babel_datetime(dt, format='medium'):
return _get_format().datetime(dt, format=format) if dt else ''
@register.filter
def babel_date(date, format='medium'):
return _get_format().date(date, format=format) if date else ''
@register.filter
def is_choice_field(value):
try:
return isinstance(value.field.widget, CheckboxInput)
except AttributeError:
pass
@register.filter
def numberfmt(num, format=None):
return _get_format().decimal(num, format)
@register.function
@jinja2.contextfunction
def page_title(context, title):
title = smart_unicode(title)
base_title = _('Firefox Marketplace')
return u'%s | %s' % (title, base_title)
@register.filter
def timesince(time):
if not time:
return u''
ago = defaultfilters.timesince(time)
# L10n: relative time in the past, like '4 days ago'
return _(u'{0} ago').format(ago)
@register.function
def url(viewname, *args, **kwargs):
"""Helper for Django's ``reverse`` in templates."""
host = kwargs.pop('host', '')
src = kwargs.pop('src', '')
url = '%s%s' % (host, reverse(viewname, args=args, kwargs=kwargs))
if src:
url = helpers.urlparams(url, src=src)
return url
@register.filter
def impala_paginator(pager):
t = env.get_template('site/impala_paginator.html')
return jinja2.Markup(t.render({'pager': pager}))
@register.filter
def json(s):
return jsonlib.dumps(s)
@register.function
@jinja2.contextfunction
def media(context, url, key='MEDIA_URL'):
"""Get a MEDIA_URL link with a cache buster querystring."""
if 'BUILD_ID' in context:
build = context['BUILD_ID']
else:
if url.endswith('.js'):
build = context['BUILD_ID_JS']
elif url.endswith('.css'):
build = context['BUILD_ID_CSS']
else:
build = context['BUILD_ID_IMG']
return urljoin(context[key], helpers.urlparams(url, b=build))
@register.function
@jinja2.contextfunction
def static(context, url):
"""Get a STATIC_URL link with a cache buster querystring."""
return media(context, url, 'STATIC_URL')
@register.filter
def f(string, *args, **kwargs):
"""This overrides jingo.helpers.f to convert input to unicode if needed.
This is needed because of
https://github.com/jbalogh/jingo/pull/54#issuecomment-36728948
"""
if not isinstance(string, text_type):
string = text_type(string)
return string.format(*args, **kwargs)
def strip_controls(s):
"""
Strips control characters from a string.
"""
# Translation table of control characters.
control_trans = dict((n, None) for n in xrange(32) if n not in [10, 13])
rv = unicode(s).translate(control_trans)
return jinja2.Markup(rv) if isinstance(s, jinja2.Markup) else rv
@register.function
@jinja2.contextfunction
def prefer_signin(context):
return 'has_logged_in' in context['request'].COOKIES
@register.filter
def isotime(t):
"""Date/Time format according to ISO 8601"""
if not hasattr(t, 'tzinfo'):
return
return append_tz(t).astimezone(pytz.utc).strftime("%Y-%m-%dT%H:%M:%SZ")