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()
|
... | ... | |
331 |
349 |
for group in group_list:
|
332 |
350 |
if getattr(group, 'grouper', Ellipsis) == group_name:
|
333 |
351 |
# Django >= 1.11, namedtuple
|
334 |
352 |
ret.extend(group.list)
|
335 |
353 |
elif not hasattr(group, 'grouper') and group['grouper'] == group_name:
|
336 |
354 |
ret.extend(group['list'])
|
337 |
355 |
return ret
|
338 |
356 |
|
|
357 |
|
339 |
358 |
@register.filter(name='is_empty_placeholder')
|
340 |
359 |
def is_empty_placeholder(page, placeholder_name):
|
341 |
360 |
return len([x for x in page.get_cells() if x.placeholder == placeholder_name]) == 0
|
342 |
361 |
|
|
362 |
|
343 |
363 |
@register.filter(name='list')
|
344 |
364 |
def as_list(obj):
|
345 |
365 |
return list(obj)
|
346 |
366 |
|
|
367 |
|
347 |
368 |
@register.filter(name='as_json')
|
348 |
369 |
def as_json(obj):
|
349 |
370 |
return json.dumps(obj)
|
350 |
371 |
|
|
372 |
|
351 |
373 |
@register.filter
|
352 |
374 |
def signed(obj):
|
353 |
375 |
return signing.dumps(obj)
|
354 |
376 |
|
|
377 |
|
355 |
378 |
@register.filter
|
356 |
379 |
def name_id(user):
|
357 |
380 |
if user and user.is_authenticated:
|
358 |
381 |
user_name_id = user.get_name_id()
|
359 |
382 |
if user_name_id:
|
360 |
383 |
return user_name_id
|
361 |
384 |
# it is important to raise this so get_templated_url is aborted and no call
|
362 |
385 |
# is tried with a missing user argument.
|
363 |
386 |
raise VariableDoesNotExist('name_id')
|
364 |
387 |
|
|
388 |
|
365 |
389 |
@register.simple_tag
|
366 |
390 |
def get_page(page_slug):
|
367 |
391 |
return Page.objects.get(slug=page_slug)
|
368 |
392 |
|
|
393 |
|
369 |
394 |
@register.filter
|
370 |
395 |
def startswith(string, substring):
|
371 |
396 |
return string and force_text(string).startswith(force_text(substring))
|
372 |
397 |
|
373 |
398 |
|
374 |
399 |
@register.filter
|
375 |
400 |
def endswith(string, substring):
|
376 |
401 |
return string and force_text(string).endswith(force_text(substring))
|
... | ... | |
380 |
405 |
if isinstance(value, six.string_types):
|
381 |
406 |
# replace , by . for French users comfort
|
382 |
407 |
value = value.replace(',', '.')
|
383 |
408 |
try:
|
384 |
409 |
return float(value)
|
385 |
410 |
except (ValueError, TypeError):
|
386 |
411 |
return ''
|
387 |
412 |
|
|
413 |
|
388 |
414 |
def get_as_datetime(s):
|
389 |
415 |
result = parse_datetime(s)
|
390 |
416 |
if not result:
|
391 |
417 |
result = parse_date(s)
|
392 |
418 |
if result:
|
393 |
419 |
result = datetime.datetime(year=result.year, month=result.month, day=result.day)
|
394 |
420 |
return result
|
395 |
421 |
|
|
422 |
|
396 |
423 |
@register.filter(expects_localtime=True, is_safe=False)
|
397 |
424 |
def add_days(value, arg):
|
398 |
425 |
value = parse_date(value) # consider only date, not hours
|
399 |
426 |
if not value:
|
400 |
427 |
return ''
|
401 |
428 |
arg = parse_float(arg)
|
402 |
429 |
if not arg:
|
403 |
430 |
return value
|
404 |
431 |
result = value + datetime.timedelta(days=float(arg))
|
405 |
432 |
return result
|
406 |
433 |
|
|
434 |
|
407 |
435 |
@register.filter(expects_localtime=True, is_safe=False)
|
408 |
436 |
def add_hours(value, arg):
|
409 |
437 |
value = parse_datetime(value)
|
410 |
438 |
if not value:
|
411 |
439 |
return ''
|
412 |
440 |
arg = parse_float(arg)
|
413 |
441 |
if not arg:
|
414 |
442 |
return value
|
415 |
443 |
return value + datetime.timedelta(hours=float(arg))
|
416 |
444 |
|
|
445 |
|
417 |
446 |
@register.filter(expects_localtime=True, is_safe=False)
|
418 |
447 |
def age_in_days(value, today=None):
|
419 |
448 |
value = parse_date(value)
|
420 |
449 |
if not value:
|
421 |
450 |
return ''
|
422 |
451 |
if today is not None:
|
423 |
452 |
today = parse_date(today)
|
424 |
453 |
if not today:
|
425 |
454 |
return ''
|
426 |
455 |
else:
|
427 |
456 |
today = datetime.date.today()
|
428 |
457 |
return (today - value).days
|
429 |
458 |
|
|
459 |
|
430 |
460 |
@register.filter(expects_localtime=True, is_safe=False)
|
431 |
461 |
def age_in_hours(value, now=None):
|
432 |
462 |
# consider value and now as datetimes (and not dates)
|
433 |
463 |
value = parse_datetime(value)
|
434 |
464 |
if not value:
|
435 |
465 |
return ''
|
436 |
466 |
if now is not None:
|
437 |
467 |
now = parse_datetime(now)
|
438 |
468 |
if not now:
|
439 |
469 |
return ''
|
440 |
470 |
else:
|
441 |
471 |
now = datetime.datetime.now()
|
442 |
472 |
return int((now - value).total_seconds() / 3600)
|
443 |
473 |
|
|
474 |
|
444 |
475 |
def age_in_years_and_months(born, today=None):
|
445 |
476 |
'''Compute age since today as the number of years and months elapsed'''
|
446 |
477 |
born = make_date(born)
|
447 |
478 |
if not born:
|
448 |
479 |
return ''
|
449 |
480 |
if today is not None:
|
450 |
481 |
today = make_date(today)
|
451 |
482 |
if not today:
|
... | ... | |
457 |
488 |
months = today.month - born.month
|
458 |
489 |
if before:
|
459 |
490 |
years -= 1
|
460 |
491 |
months += 12
|
461 |
492 |
if today.day < born.day:
|
462 |
493 |
months -= 1
|
463 |
494 |
return years, months
|
464 |
495 |
|
|
496 |
|
465 |
497 |
@register.filter(expects_localtime=True, is_safe=False)
|
466 |
498 |
def age_in_years(value, today=None):
|
467 |
499 |
try:
|
468 |
500 |
return age_in_years_and_months(value, today)[0]
|
469 |
501 |
except ValueError:
|
470 |
502 |
return ''
|
471 |
503 |
|
|
504 |
|
472 |
505 |
@register.filter(expects_localtime=True, is_safe=False)
|
473 |
506 |
def age_in_months(value, today=None):
|
474 |
507 |
try:
|
475 |
508 |
years, months = age_in_years_and_months(value, today)
|
476 |
509 |
except ValueError:
|
477 |
510 |
return ''
|
478 |
511 |
return years * 12 + months
|
479 |
512 |
|
... | ... | |
494 |
527 |
if isinstance(value, six.string_types):
|
495 |
528 |
# replace , by . for French users comfort
|
496 |
529 |
value = value.replace(',', '.')
|
497 |
530 |
try:
|
498 |
531 |
return Decimal(value).quantize(Decimal('1.0000')).normalize()
|
499 |
532 |
except (ArithmeticError, TypeError):
|
500 |
533 |
return default
|
501 |
534 |
|
|
535 |
|
502 |
536 |
@register.filter(is_safe=False)
|
503 |
537 |
def decimal(value, arg=None):
|
504 |
538 |
if not isinstance(value, Decimal):
|
505 |
539 |
value = parse_decimal(value)
|
506 |
540 |
if arg is None:
|
507 |
541 |
return value
|
508 |
542 |
return defaultfilters.floatformat(value, arg=arg)
|
509 |
543 |
|
|
544 |
|
510 |
545 |
@register.filter
|
511 |
546 |
def add(term1, term2):
|
512 |
547 |
'''replace the "add" native django filter'''
|
513 |
548 |
|
514 |
549 |
if term1 is None:
|
515 |
550 |
term1 = ''
|
516 |
551 |
if term2 is None:
|
517 |
552 |
term2 = ''
|
... | ... | |
521 |
556 |
if term1_decimal is not None and term2_decimal is not None:
|
522 |
557 |
return term1_decimal + term2_decimal
|
523 |
558 |
if term1 == '' and term2_decimal is not None:
|
524 |
559 |
return term2_decimal
|
525 |
560 |
if term2 == '' and term1_decimal is not None:
|
526 |
561 |
return term1_decimal
|
527 |
562 |
return defaultfilters.add(term1, term2)
|
528 |
563 |
|
|
564 |
|
529 |
565 |
@register.filter
|
530 |
566 |
def subtract(term1, term2):
|
531 |
567 |
return parse_decimal(term1) - parse_decimal(term2)
|
532 |
568 |
|
|
569 |
|
533 |
570 |
@register.filter
|
534 |
571 |
def multiply(term1, term2):
|
535 |
572 |
return parse_decimal(term1) * parse_decimal(term2)
|
536 |
573 |
|
|
574 |
|
537 |
575 |
@register.filter
|
538 |
576 |
def divide(term1, term2):
|
539 |
577 |
try:
|
540 |
578 |
return parse_decimal(term1) / parse_decimal(term2)
|
541 |
579 |
except DecimalInvalidOperation:
|
542 |
580 |
return ''
|
543 |
581 |
except DecimalDivisionByZero:
|
544 |
582 |
return ''
|
545 |
583 |
|
|
584 |
|
546 |
585 |
@register.filter
|
547 |
586 |
def ceil(value):
|
548 |
587 |
'''the smallest integer value greater than or equal to value'''
|
549 |
588 |
return decimal(math.ceil(parse_decimal(value)))
|
550 |
589 |
|
|
590 |
|
551 |
591 |
@register.filter
|
552 |
592 |
def floor(value):
|
553 |
593 |
return decimal(math.floor(parse_decimal(value)))
|
554 |
594 |
|
|
595 |
|
555 |
596 |
@register.filter(name='abs')
|
556 |
597 |
def abs_(value):
|
557 |
598 |
return decimal(abs(parse_decimal(value)))
|
558 |
599 |
|
|
600 |
|
559 |
601 |
_json_script_escapes = {
|
560 |
602 |
ord('>'): '\\u003E',
|
561 |
603 |
ord('<'): '\\u003C',
|
562 |
604 |
ord('&'): '\\u0026',
|
563 |
605 |
}
|
564 |
606 |
|
565 |
607 |
|
566 |
608 |
@register.filter(is_safe=True)
|