48 |
48 |
from combo.public.menu import get_menu_context
|
49 |
49 |
from combo.utils import NothingInCacheException, flatten_context
|
50 |
50 |
from combo.utils.date import make_date, make_datetime
|
51 |
51 |
from combo.apps.dashboard.models import DashboardCell, Tile
|
52 |
52 |
|
53 |
53 |
|
54 |
54 |
register = template.Library()
|
55 |
55 |
|
|
56 |
|
56 |
57 |
def skeleton_text(context, placeholder_name, content=''):
|
57 |
58 |
return '{%% block placeholder-%s %%}{%% block %s %%}%s{%% endblock %%}{%% endblock %%}' % (
|
58 |
59 |
placeholder_name, placeholder_name, content)
|
59 |
60 |
|
|
61 |
|
60 |
62 |
@register.inclusion_tag('combo/placeholder.html', takes_context=True)
|
61 |
63 |
def placeholder(context, placeholder_name, **options):
|
62 |
64 |
placeholder = Placeholder(key=placeholder_name, cell=context.get('cell'), **options)
|
63 |
65 |
# make sure render_skeleton is available in context
|
64 |
66 |
context['render_skeleton'] = context.get('render_skeleton')
|
65 |
67 |
if context.get('placeholder_search_mode'):
|
66 |
68 |
if placeholder.name:
|
67 |
69 |
# only include placeholders with a name
|
... | ... | |
92 |
94 |
(context.get('render_skeleton') or x.is_relevant(context) and
|
93 |
95 |
x.is_visible(user=context['request'].user, check_validity_info=False))]
|
94 |
96 |
if context.get('render_skeleton'):
|
95 |
97 |
context['skeleton'] = skeleton_text(context, placeholder_name)
|
96 |
98 |
else:
|
97 |
99 |
context['skeleton'] = ''
|
98 |
100 |
return context
|
99 |
101 |
|
|
102 |
|
100 |
103 |
@register.simple_tag(takes_context=True)
|
101 |
104 |
def render_cell(context, cell):
|
102 |
105 |
if context.get('render_skeleton') and cell.is_user_dependant(context):
|
103 |
106 |
context = flatten_context(context)
|
104 |
107 |
return template.loader.get_template('combo/deferred-cell.html').render(context)
|
105 |
108 |
|
106 |
109 |
in_dashboard = False
|
107 |
110 |
if DashboardCell.is_enabled():
|
... | ... | |
123 |
126 |
return cell.render(context)
|
124 |
127 |
except NothingInCacheException:
|
125 |
128 |
return template.loader.get_template('combo/deferred-cell.html').render(context)
|
126 |
129 |
except:
|
127 |
130 |
if context.get('placeholder_search_mode'):
|
128 |
131 |
return ''
|
129 |
132 |
raise
|
130 |
133 |
|
|
134 |
|
131 |
135 |
@register.tag
|
132 |
136 |
def skeleton_extra_placeholder(parser, token):
|
133 |
137 |
try:
|
134 |
138 |
tag_name, placeholder_name = token.split_contents()
|
135 |
139 |
except ValueError:
|
136 |
140 |
raise template.TemplateSyntaxError(
|
137 |
141 |
"%r tag requires exactly one argument" % token.contents.split()[0]
|
138 |
142 |
)
|
... | ... | |
169 |
173 |
self.placeholder_name = placeholder_name
|
170 |
174 |
self.content = content
|
171 |
175 |
|
172 |
176 |
def render(self, context):
|
173 |
177 |
if not context.get('render_skeleton'):
|
174 |
178 |
return self.nodelist.render(context)
|
175 |
179 |
return skeleton_text(context, self.placeholder_name, content=self.content)
|
176 |
180 |
|
|
181 |
|
177 |
182 |
@register.inclusion_tag('combo/menu.html', takes_context=True)
|
178 |
183 |
def show_menu(context, level=0, current_page=None, depth=1, ignore_visibility=True, reduce_depth=False):
|
179 |
184 |
if reduce_depth:
|
180 |
185 |
depth -= 1
|
181 |
186 |
new_context = {
|
182 |
187 |
'page': context['page'],
|
183 |
188 |
'render_skeleton': context.get('render_skeleton'),
|
184 |
189 |
'request': context['request']}
|
185 |
190 |
return get_menu_context(new_context, level=level, current_page=current_page,
|
186 |
191 |
depth=depth, ignore_visibility=ignore_visibility)
|
187 |
192 |
|
|
193 |
|
188 |
194 |
@register.simple_tag(takes_context=True)
|
189 |
195 |
def page_absolute_url(context, page):
|
190 |
196 |
return context['request'].build_absolute_uri(page.get_online_url())
|
191 |
197 |
|
|
198 |
|
192 |
199 |
@register.filter(name='strptime')
|
193 |
200 |
@stringfilter
|
194 |
201 |
def strptime(date_string, date_format):
|
195 |
202 |
try:
|
196 |
203 |
return datetime.datetime.strptime(date_string, date_format)
|
197 |
204 |
except ValueError:
|
198 |
205 |
return None
|
199 |
206 |
|
|
207 |
|
200 |
208 |
@register.filter
|
201 |
209 |
def parse_date(date_string):
|
202 |
210 |
try:
|
203 |
211 |
return make_date(date_string)
|
204 |
212 |
except ValueError:
|
205 |
213 |
pass
|
206 |
214 |
# fallback to Django function
|
207 |
215 |
try:
|
208 |
216 |
return dateparse.parse_date(date_string)
|
209 |
217 |
except (ValueError, TypeError):
|
210 |
218 |
return None
|
211 |
219 |
|
|
220 |
|
212 |
221 |
@register.filter(expects_localtime=True, is_safe=False)
|
213 |
222 |
def date(value, arg=None):
|
214 |
223 |
if arg is None:
|
215 |
224 |
return parse_date(value) or ''
|
216 |
225 |
if not isinstance(value, (datetime.datetime, datetime.date, datetime.time)):
|
217 |
226 |
value = parse_datetime(value) or parse_date(value)
|
218 |
227 |
return defaultfilters.date(value, arg=arg)
|
219 |
228 |
|
|
229 |
|
220 |
230 |
@register.filter
|
221 |
231 |
def parse_datetime(datetime_string):
|
222 |
232 |
try:
|
223 |
233 |
return make_datetime(datetime_string)
|
224 |
234 |
except ValueError:
|
225 |
235 |
pass
|
226 |
236 |
# fallback to Django function
|
227 |
237 |
try:
|
228 |
238 |
return dateparse.parse_datetime(datetime_string)
|
229 |
239 |
except (ValueError, TypeError):
|
230 |
240 |
return None
|
231 |
241 |
|
|
242 |
|
232 |
243 |
@register.filter(name='datetime', expects_localtime=True, is_safe=False)
|
233 |
244 |
def datetime_(value, arg=None):
|
234 |
245 |
if arg is None:
|
235 |
246 |
return parse_datetime(value) or ''
|
236 |
247 |
if not isinstance(value, (datetime.datetime, datetime.date, datetime.time)):
|
237 |
248 |
value = parse_datetime(value)
|
238 |
249 |
return defaultfilters.date(value, arg=arg)
|
239 |
250 |
|
|
251 |
|
240 |
252 |
@register.filter
|
241 |
253 |
def parse_time(time_string):
|
242 |
254 |
# if input is a datetime, extract its time
|
243 |
255 |
try:
|
244 |
256 |
dt = parse_datetime(time_string)
|
245 |
257 |
if dt:
|
246 |
258 |
return dt.time()
|
247 |
259 |
except (ValueError, TypeError):
|
248 |
260 |
pass
|
249 |
261 |
# fallback to Django function
|
250 |
262 |
try:
|
251 |
263 |
return dateparse.parse_time(time_string)
|
252 |
264 |
except (ValueError, TypeError):
|
253 |
265 |
return None
|
254 |
266 |
|
|
267 |
|
255 |
268 |
@register.filter(expects_localtime=True, is_safe=False)
|
256 |
269 |
def time(value, arg=None):
|
257 |
270 |
if arg is None:
|
258 |
271 |
parsed = parse_time(value)
|
259 |
272 |
return parsed if parsed is not None else '' # because bool(midnight) == False
|
260 |
273 |
if not isinstance(value, (datetime.datetime, datetime.date, datetime.time)):
|
261 |
274 |
value = parse_time(value)
|
262 |
275 |
return defaultfilters.date(value, arg=arg)
|
263 |
276 |
|
|
277 |
|
264 |
278 |
@register.filter
|
265 |
279 |
def shown_because_admin(cell, request):
|
266 |
280 |
if not (request.user and request.user.is_superuser):
|
267 |
281 |
return False
|
268 |
282 |
if cell.public:
|
269 |
283 |
return False
|
270 |
284 |
cell_groups = cell.groups.all()
|
271 |
285 |
if not cell_groups:
|
272 |
286 |
return False
|
273 |
287 |
return not(set(cell_groups).intersection(request.user.groups.all()))
|
274 |
288 |
|
|
289 |
|
275 |
290 |
@register.filter(name='has_role')
|
276 |
291 |
def has_role(user, groupname):
|
277 |
292 |
if not user or user.is_anonymous:
|
278 |
293 |
return False
|
279 |
294 |
return user.groups.filter(name=groupname).exists()
|
280 |
295 |
|
|
296 |
|
281 |
297 |
@register.filter(name='get')
|
282 |
298 |
def get(obj, key):
|
283 |
299 |
try:
|
284 |
300 |
return obj.get(key)
|
285 |
301 |
except AttributeError:
|
286 |
302 |
return None
|
287 |
303 |
|
|
304 |
|
288 |
305 |
@register.filter
|
289 |
306 |
def split(string, separator=' '):
|
290 |
307 |
return (string or '').split(separator)
|
291 |
308 |
|
|
309 |
|
292 |
310 |
@register.filter
|
293 |
311 |
def strip(string, chars=None):
|
294 |
312 |
if not string:
|
295 |
313 |
return ''
|
296 |
314 |
if chars:
|
297 |
315 |
return force_text(string).strip(force_text(chars))
|
298 |
316 |
else:
|
299 |
317 |
return force_text(string).strip()
|
... | ... | |
327 |
345 |
for group in group_list:
|
328 |
346 |
if getattr(group, 'grouper', Ellipsis) == group_name:
|
329 |
347 |
# Django >= 1.11, namedtuple
|
330 |
348 |
ret.extend(group.list)
|
331 |
349 |
elif not hasattr(group, 'grouper') and group['grouper'] == group_name:
|
332 |
350 |
ret.extend(group['list'])
|
333 |
351 |
return ret
|
334 |
352 |
|
|
353 |
|
335 |
354 |
@register.filter(name='is_empty_placeholder')
|
336 |
355 |
def is_empty_placeholder(page, placeholder_name):
|
337 |
356 |
return len([x for x in page.get_cells() if x.placeholder == placeholder_name]) == 0
|
338 |
357 |
|
|
358 |
|
339 |
359 |
@register.filter(name='list')
|
340 |
360 |
def as_list(obj):
|
341 |
361 |
return list(obj)
|
342 |
362 |
|
|
363 |
|
343 |
364 |
@register.filter(name='as_json')
|
344 |
365 |
def as_json(obj):
|
345 |
366 |
return json.dumps(obj)
|
346 |
367 |
|
|
368 |
|
347 |
369 |
@register.filter
|
348 |
370 |
def signed(obj):
|
349 |
371 |
return signing.dumps(obj)
|
350 |
372 |
|
|
373 |
|
351 |
374 |
@register.filter
|
352 |
375 |
def name_id(user):
|
353 |
376 |
if user and user.is_authenticated:
|
354 |
377 |
user_name_id = user.get_name_id()
|
355 |
378 |
if user_name_id:
|
356 |
379 |
return user_name_id
|
357 |
380 |
# it is important to raise this so get_templated_url is aborted and no call
|
358 |
381 |
# is tried with a missing user argument.
|
359 |
382 |
raise VariableDoesNotExist('name_id')
|
360 |
383 |
|
|
384 |
|
361 |
385 |
@register.simple_tag
|
362 |
386 |
def get_page(page_slug):
|
363 |
387 |
return Page.objects.get(slug=page_slug)
|
364 |
388 |
|
|
389 |
|
365 |
390 |
@register.filter
|
366 |
391 |
def startswith(string, substring):
|
367 |
392 |
return string and force_text(string).startswith(force_text(substring))
|
368 |
393 |
|
369 |
394 |
|
370 |
395 |
@register.filter
|
371 |
396 |
def endswith(string, substring):
|
372 |
397 |
return string and force_text(string).endswith(force_text(substring))
|
... | ... | |
376 |
401 |
if isinstance(value, six.string_types):
|
377 |
402 |
# replace , by . for French users comfort
|
378 |
403 |
value = value.replace(',', '.')
|
379 |
404 |
try:
|
380 |
405 |
return float(value)
|
381 |
406 |
except (ValueError, TypeError):
|
382 |
407 |
return ''
|
383 |
408 |
|
|
409 |
|
384 |
410 |
def get_as_datetime(s):
|
385 |
411 |
result = parse_datetime(s)
|
386 |
412 |
if not result:
|
387 |
413 |
result = parse_date(s)
|
388 |
414 |
if result:
|
389 |
415 |
result = datetime.datetime(year=result.year, month=result.month, day=result.day)
|
390 |
416 |
return result
|
391 |
417 |
|
|
418 |
|
392 |
419 |
@register.filter(expects_localtime=True, is_safe=False)
|
393 |
420 |
def add_days(value, arg):
|
394 |
421 |
value = parse_date(value) # consider only date, not hours
|
395 |
422 |
if not value:
|
396 |
423 |
return ''
|
397 |
424 |
arg = parse_float(arg)
|
398 |
425 |
if not arg:
|
399 |
426 |
return value
|
400 |
427 |
result = value + datetime.timedelta(days=float(arg))
|
401 |
428 |
return result
|
402 |
429 |
|
|
430 |
|
403 |
431 |
@register.filter(expects_localtime=True, is_safe=False)
|
404 |
432 |
def add_hours(value, arg):
|
405 |
433 |
value = parse_datetime(value)
|
406 |
434 |
if not value:
|
407 |
435 |
return ''
|
408 |
436 |
arg = parse_float(arg)
|
409 |
437 |
if not arg:
|
410 |
438 |
return value
|
411 |
439 |
return value + datetime.timedelta(hours=float(arg))
|
412 |
440 |
|
|
441 |
|
413 |
442 |
@register.filter(expects_localtime=True, is_safe=False)
|
414 |
443 |
def age_in_days(value, today=None):
|
415 |
444 |
value = parse_date(value)
|
416 |
445 |
if not value:
|
417 |
446 |
return ''
|
418 |
447 |
if today is not None:
|
419 |
448 |
today = parse_date(today)
|
420 |
449 |
if not today:
|
421 |
450 |
return ''
|
422 |
451 |
else:
|
423 |
452 |
today = datetime.date.today()
|
424 |
453 |
return (today - value).days
|
425 |
454 |
|
|
455 |
|
426 |
456 |
@register.filter(expects_localtime=True, is_safe=False)
|
427 |
457 |
def age_in_hours(value, now=None):
|
428 |
458 |
# consider value and now as datetimes (and not dates)
|
429 |
459 |
value = parse_datetime(value)
|
430 |
460 |
if not value:
|
431 |
461 |
return ''
|
432 |
462 |
if now is not None:
|
433 |
463 |
now = parse_datetime(now)
|
434 |
464 |
if not now:
|
435 |
465 |
return ''
|
436 |
466 |
else:
|
437 |
467 |
now = datetime.datetime.now()
|
438 |
468 |
return int((now - value).total_seconds() / 3600)
|
439 |
469 |
|
|
470 |
|
440 |
471 |
def age_in_years_and_months(born, today=None):
|
441 |
472 |
'''Compute age since today as the number of years and months elapsed'''
|
442 |
473 |
born = make_date(born)
|
443 |
474 |
if not born:
|
444 |
475 |
return ''
|
445 |
476 |
if today is not None:
|
446 |
477 |
today = make_date(today)
|
447 |
478 |
if not today:
|
... | ... | |
453 |
484 |
months = today.month - born.month
|
454 |
485 |
if before:
|
455 |
486 |
years -= 1
|
456 |
487 |
months += 12
|
457 |
488 |
if today.day < born.day:
|
458 |
489 |
months -= 1
|
459 |
490 |
return years, months
|
460 |
491 |
|
|
492 |
|
461 |
493 |
@register.filter(expects_localtime=True, is_safe=False)
|
462 |
494 |
def age_in_years(value, today=None):
|
463 |
495 |
try:
|
464 |
496 |
return age_in_years_and_months(value, today)[0]
|
465 |
497 |
except ValueError:
|
466 |
498 |
return ''
|
467 |
499 |
|
|
500 |
|
468 |
501 |
@register.filter(expects_localtime=True, is_safe=False)
|
469 |
502 |
def age_in_months(value, today=None):
|
470 |
503 |
try:
|
471 |
504 |
years, months = age_in_years_and_months(value, today)
|
472 |
505 |
except ValueError:
|
473 |
506 |
return ''
|
474 |
507 |
return years * 12 + months
|
475 |
508 |
|
... | ... | |
490 |
523 |
if isinstance(value, six.string_types):
|
491 |
524 |
# replace , by . for French users comfort
|
492 |
525 |
value = value.replace(',', '.')
|
493 |
526 |
try:
|
494 |
527 |
return Decimal(value).quantize(Decimal('1.0000')).normalize()
|
495 |
528 |
except (ArithmeticError, TypeError):
|
496 |
529 |
return default
|
497 |
530 |
|
|
531 |
|
498 |
532 |
@register.filter(is_safe=False)
|
499 |
533 |
def decimal(value, arg=None):
|
500 |
534 |
if not isinstance(value, Decimal):
|
501 |
535 |
value = parse_decimal(value)
|
502 |
536 |
if arg is None:
|
503 |
537 |
return value
|
504 |
538 |
return defaultfilters.floatformat(value, arg=arg)
|
505 |
539 |
|
|
540 |
|
506 |
541 |
@register.filter
|
507 |
542 |
def add(term1, term2):
|
508 |
543 |
'''replace the "add" native django filter'''
|
509 |
544 |
|
510 |
545 |
if term1 is None:
|
511 |
546 |
term1 = ''
|
512 |
547 |
if term2 is None:
|
513 |
548 |
term2 = ''
|
... | ... | |
517 |
552 |
if term1_decimal is not None and term2_decimal is not None:
|
518 |
553 |
return term1_decimal + term2_decimal
|
519 |
554 |
if term1 == '' and term2_decimal is not None:
|
520 |
555 |
return term2_decimal
|
521 |
556 |
if term2 == '' and term1_decimal is not None:
|
522 |
557 |
return term1_decimal
|
523 |
558 |
return defaultfilters.add(term1, term2)
|
524 |
559 |
|
|
560 |
|
525 |
561 |
@register.filter
|
526 |
562 |
def subtract(term1, term2):
|
527 |
563 |
return parse_decimal(term1) - parse_decimal(term2)
|
528 |
564 |
|
|
565 |
|
529 |
566 |
@register.filter
|
530 |
567 |
def multiply(term1, term2):
|
531 |
568 |
return parse_decimal(term1) * parse_decimal(term2)
|
532 |
569 |
|
|
570 |
|
533 |
571 |
@register.filter
|
534 |
572 |
def divide(term1, term2):
|
535 |
573 |
try:
|
536 |
574 |
return parse_decimal(term1) / parse_decimal(term2)
|
537 |
575 |
except DecimalInvalidOperation:
|
538 |
576 |
return ''
|
539 |
577 |
except DecimalDivisionByZero:
|
540 |
578 |
return ''
|
541 |
579 |
|
|
580 |
|
542 |
581 |
@register.filter
|
543 |
582 |
def ceil(value):
|
544 |
583 |
'''the smallest integer value greater than or equal to value'''
|
545 |
584 |
return decimal(math.ceil(parse_decimal(value)))
|
546 |
585 |
|
|
586 |
|
547 |
587 |
@register.filter
|
548 |
588 |
def floor(value):
|
549 |
589 |
return decimal(math.floor(parse_decimal(value)))
|
550 |
590 |
|
|
591 |
|
551 |
592 |
@register.filter(name='abs')
|
552 |
593 |
def abs_(value):
|
553 |
594 |
return decimal(abs(parse_decimal(value)))
|
554 |
595 |
|
|
596 |
|
555 |
597 |
_json_script_escapes = {
|
556 |
598 |
ord('>'): '\\u003E',
|
557 |
599 |
ord('<'): '\\u003C',
|
558 |
600 |
ord('&'): '\\u0026',
|
559 |
601 |
}
|
560 |
602 |
|
561 |
603 |
|
562 |
604 |
@register.filter(is_safe=True)
|