combo/combo/public/templatetags/combo.py

263 lines
8.9 KiB
Python

# 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 <http://www.gnu.org/licenses/>.
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)