# combo - content management system # Copyright (C) 2014 Entr'ouvert # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . from __future__ import absolute_import import datetime import json import time from django import template from django.conf import settings from django.core import signing from django.core.exceptions import PermissionDenied from django.template import VariableDoesNotExist from django.template.base import TOKEN_BLOCK, TOKEN_VAR, TOKEN_COMMENT from django.template.defaultfilters import stringfilter from django.utils import dateparse from combo.data.models import Page, Placeholder from combo.public.menu import get_menu_context from combo.utils import NothingInCacheException, flatten_context from combo.apps.dashboard.models import DashboardCell, Tile register = template.Library() def skeleton_text(context, placeholder_name, content=''): return '{%% block placeholder-%s %%}{%% block %s %%}%s{%% endblock %%}{%% endblock %%}' % ( placeholder_name, placeholder_name, content) @register.inclusion_tag('combo/placeholder.html', takes_context=True) def placeholder(context, placeholder_name, **options): placeholder = Placeholder(key=placeholder_name, cell=context.get('cell'), **options) # make sure render_skeleton is available in context context['render_skeleton'] = context.get('render_skeleton') if context.get('placeholder_search_mode'): if placeholder.name: # only include placeholders with a name context['placeholders'].append(placeholder) if not context['traverse_cells']: return context context['render'] = True context['placeholder'] = placeholder if not placeholder.render: context['render'] = False return context page_cells = [] if 'page_cells' in context: # page cells are not precomputed when rendering a single cell in an # ajax call page_cells = context.get('page_cells') elif not context.get('render_skeleton'): page_cells = context['page'].get_cells() if 'page' in context else [] context['cells'] = [x for x in page_cells if x.placeholder == placeholder_name and (context.get('render_skeleton') or x.is_relevant(context) and x.is_visible(context['request'].user))] if context.get('render_skeleton') and not context['cells']: context['skeleton'] = skeleton_text(context, placeholder_name) else: context['skeleton'] = '' return context @register.simple_tag(takes_context=True) def render_cell(context, cell): if context.get('render_skeleton') and cell.is_user_dependant(context): context = flatten_context(context) return template.loader.get_template('combo/deferred-cell.html').render(context) in_dashboard = False if DashboardCell.is_enabled(): # check if cell is actually a dashboard tile try: tile = Tile.get_by_cell(cell) except Tile.DoesNotExist: pass else: if context['request'].user != tile.user: raise PermissionDenied() in_dashboard = True context = flatten_context(context) context['in_dashboard'] = in_dashboard if 'placeholder' in context and context['placeholder'].force_synchronous: context['synchronous'] = True try: return cell.render(context) except NothingInCacheException: return template.loader.get_template('combo/deferred-cell.html').render(context) except: if context.get('placeholder_search_mode'): return '' raise @register.tag def skeleton_extra_placeholder(parser, token): try: tag_name, placeholder_name = token.split_contents() except ValueError: raise template.TemplateSyntaxError( "%r tag requires exactly one argument" % token.contents.split()[0] ) tokens_copy = parser.tokens[:] text = [] while True: token = tokens_copy.pop(0) if token.contents == 'end_skeleton_extra_placeholder': break if token.token_type == TOKEN_VAR: text.append('{{') elif token.token_type == TOKEN_BLOCK: text.append('{%') elif token.token_type == TOKEN_COMMENT: text.append('{# ') text.append(token.contents) if token.token_type == TOKEN_VAR: text.append('}}') elif token.token_type == TOKEN_BLOCK: text.append('%}') elif token.token_type == TOKEN_COMMENT: text.append(' #}') nodelist = parser.parse(('end_skeleton_extra_placeholder',)) parser.delete_first_token() return ExtraPlaceholderNode(nodelist, placeholder_name, ''.join(text)) class ExtraPlaceholderNode(template.Node): def __init__(self, nodelist, placeholder_name, content): self.nodelist = nodelist self.placeholder_name = placeholder_name self.content = content def render(self, context): if not context.get('render_skeleton'): return self.nodelist.render(context) return skeleton_text(context, self.placeholder_name, content=self.content) @register.inclusion_tag('combo/menu.html', takes_context=True) def show_menu(context, level=0, current_page=None, depth=1, ignore_visibility=True, reduce_depth=False): if reduce_depth: depth -= 1 new_context = { 'page': context['page'], 'render_skeleton': context.get('render_skeleton'), 'request': context['request']} return get_menu_context(new_context, level=level, current_page=current_page, depth=depth, ignore_visibility=ignore_visibility) @register.simple_tag(takes_context=True) def page_absolute_url(context, page): return context['request'].build_absolute_uri(page.get_online_url()) @register.filter(name='strptime') @stringfilter def strptime(date_string, date_format): try: return datetime.datetime.strptime(date_string, date_format) except ValueError: return None @register.filter def parse_date(date_string): try: return dateparse.parse_date(date_string) except (ValueError, TypeError): return None @register.filter def parse_datetime(date_string): if isinstance(date_string, time.struct_time): return datetime.datetime.fromtimestamp(time.mktime(date_string)) try: return dateparse.parse_datetime(date_string) except (ValueError, TypeError): return None @register.filter def parse_time(time_string): try: return dateparse.parse_time(time_string) except (ValueError, TypeError): return None @register.filter def shown_because_admin(cell, request): if not (request.user and request.user.is_superuser): return False if cell.public: return False cell_groups = cell.groups.all() if not cell_groups: return False return not(set(cell_groups).intersection(request.user.groups.all())) @register.filter(name='has_role') def has_role(user, groupname): if not user or user.is_anonymous(): return False return user.groups.filter(name=groupname).exists() @register.filter(name='get') def get(obj, key): try: return obj.get(key) except AttributeError: return None @register.filter(name='get_group') def get_group(group_list, group_name): ret = [] for group in group_list: if getattr(group, 'grouper', Ellipsis) == group_name: # Django >= 1.11, namedtuple ret.extend(group.list) elif not hasattr(group, 'grouper') and group['grouper'] == group_name: ret.extend(group['list']) return ret @register.filter(name='is_empty_placeholder') def is_empty_placeholder(page, placeholder_name): return len([x for x in page.get_cells() if x.placeholder == placeholder_name]) == 0 @register.filter(name='list') def as_list(obj): return list(obj) @register.filter(name='as_json') def as_json(obj): return json.dumps(obj) @register.filter def signed(obj): return signing.dumps(obj) @register.filter def name_id(user): if user: user_name_id = user.get_name_id() if user_name_id: return user_name_id # it is important to raise this so get_templated_url is aborted and no call # is tried with a missing user argument. raise VariableDoesNotExist('name_id') @register.assignment_tag def get_page(page_slug): return Page.objects.get(slug=page_slug)