passerelle/passerelle/base/templatetags/passerelle.py

303 lines
10 KiB
Python

# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2019 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/>.
import collections
import json
import re
from django import template
from django.contrib.auth import get_permission_codename
from django.contrib.contenttypes.models import ContentType
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.template.defaultfilters import stringfilter
from django.utils.html import format_html, mark_safe
from django.utils.translation import gettext as _
from passerelle.utils import get_trusted_services
from ..models import AccessRight, ResourceLog
register = template.Library()
@register.inclusion_tag('passerelle/includes/access-rights-table.html', takes_context=True)
def access_rights_table(context, resource, permission):
resource_type = ContentType.objects.get_for_model(resource)
rights = AccessRight.objects.filter(
resource_type=resource_type, resource_pk=resource.id, codename=permission
)
context['permission'] = permission
context['access_rights_list'] = rights
context['resource_type'] = resource_type.id
context['resource_pk'] = resource.id
context['trusted_services'] = get_trusted_services()
return context
@register.filter(name='resource_type')
def as_resource_type(resource):
return ContentType.objects.get_for_model(resource).id
@register.inclusion_tag('passerelle/includes/resource-logs-table.html', takes_context=True)
def resource_logs_table(context, resource):
request = context.get('request')
page = request.GET.get('page', 1)
connector = resource.get_connector_slug()
context['connector'] = connector
context['slug'] = resource.slug
qs = ResourceLog.objects.filter(appname=connector, slug=resource.slug).order_by('-timestamp')
paginator = Paginator(qs, 10)
try:
logrecords = paginator.page(page)
except PageNotAnInteger:
logrecords = paginator.page(1)
except (EmptyPage,):
logrecords = paginator.page(paginator.num_pages)
context['logrecords'] = logrecords
return context
@register.inclusion_tag('passerelle/includes/resource-jobs-table.html', takes_context=True)
def resource_jobs_table(context, resource):
request = context.get('request')
page = request.GET.get('page', 1)
qs = resource.jobs_set().order_by('-creation_timestamp')
paginator = Paginator(qs, 10)
try:
jobs = paginator.page(page)
except PageNotAnInteger:
jobs = paginator.page(1)
except (EmptyPage,):
jobs = paginator.page(paginator.num_pages)
context['jobs'] = jobs
return context
@register.filter
def can_edit(obj, user):
return user.has_perm(get_permission_codename('change', obj._meta), obj=obj)
@register.filter
def can_delete(obj, user):
return user.has_perm(get_permission_codename('delete', obj._meta), obj=obj)
@register.filter
@stringfilter
def censor(string):
return re.sub(r'://([^/]*):([^/]*?)@', r'://\1:***@', string)
def render_json_schema(schema):
if not isinstance(schema, dict):
if schema is True:
return mark_safe('<em>%s</em>') % _('always valid')
if schema is False:
return mark_safe('<em>%s</em>') % _('always invalid')
return format_html('<tt>{!r}</tt>', schema)
def many_of(name, schemas):
s = format_html('<b>{}</b>', name)
parts = [render_json_schema(schema) for schema in schemas]
if any('\n' in part for part in parts):
s += '<ul>'
for part in parts:
s += format_html('<li>{0}</li>\n', part)
s += '</ul>'
else:
s += ' [ ' + ' | '.join(parts) + ' ]'
return mark_safe(s)
def html_type(s):
return '<span class="type">%s</span>' % s
if 'anyOf' in schema:
return many_of('anyOf', schema['anyOf'])
if 'oneOf' in schema and not schema.get('type') == 'object':
return many_of('oneOf', schema['oneOf'])
if 'allOf' in schema:
return many_of('allOf', schema['allOf'])
original_schema = schema
schema = schema.copy()
schema.pop('$schema', None)
schema.pop('$id', None)
title = schema.pop('title', None)
description = schema.pop('description', None)
typ = schema.pop('type', None)
if typ == 'null':
return mark_safe(html_type('null'))
if typ == 'string':
enum = schema.pop('enum', [])
min_length = schema.pop('minLength', '')
max_length = schema.pop('maxLength', '')
pattern = schema.pop('pattern', '')
pattern_description = schema.pop('pattern_description', '')
if enum:
enum = mark_safe(' | '.join([format_html('<tt>{}</tt>', json.dumps(el)) for el in enum]))
s = 'string'
if max_length or min_length:
s += format_html('[{0}:{1}]', min_length, max_length)
s = html_type(s)
if enum:
s += ' %s' % enum
if pattern_description:
s += format_html(' <em>{}</em>', pattern_description)
elif pattern:
s += format_html(' /<tt>{}</tt>/', pattern)
if schema:
s += format_html('\n{!r}', schema)
return mark_safe(s)
if typ == 'integer':
if not schema:
return mark_safe(html_type('integer'))
if typ == 'number':
if not schema:
return mark_safe(html_type('number'))
if typ == 'array':
s = html_type('array') + ' '
if 'items' in schema:
s += render_json_schema(schema['items'])
return mark_safe(s)
if typ == 'object':
s = html_type('object')
unflatten = schema.pop('unflatten', False)
merge_extra = schema.pop('merge_extra', False)
properties = schema.pop('properties', {})
pattern_properties = schema.pop('patternProperties', {})
required_keys = schema.pop('required', [])
additional_properties = schema.pop('additionalProperties', True)
one_of = schema.pop('oneOf', [])
if unflatten:
s += format_html(', <em class="unflatten">{}</em>', _('unflatten'))
if merge_extra:
s += format_html(', <em class="merge-extra">{}</em>', _('merge extra'))
if not additional_properties:
s += format_html(
', <em class="additional-properties-false">{}</em>', _('no additional properties')
)
if title:
s += format_html(', <em class="title">{}</em>', title)
if schema:
s += format_html('<tt class="raw">{!r}</tt>', schema)
if description:
s += format_html('\n<p class="description">{}</p>', description)
s += ' '
def render_property_schema(key, html, sub):
nonlocal s
required = key in required_keys
sub_description = sub.pop('description', '')
sub_title = sub.pop('title', '')
s += format_html('<li>{0}', html)
if required:
s += format_html('<span title="{}" class="required">*</span>', _('required'))
if description or sub:
s += ' :'
if sub_title:
s += format_html(' <em>{0}</em>', sub_title)
elif sub_description and '\n' not in sub_description:
s += format_html(' <em>{0}</em>', sub_description)
if sub_title or '\n' in sub_description:
s += format_html('\n<p class="description">{}</p>', sub_description)
if sub:
s += format_html('\n{0}', render_json_schema(sub))
s += '</li>'
if properties or pattern_properties:
s += '\n<ul>'
if properties:
keys = properties
if not isinstance(properties, collections.OrderedDict):
keys = sorted(properties, key=lambda key: key.lower())
for key in keys:
sub = properties.get(key, {}).copy()
render_property_schema(key, format_html('<tt>{0}</tt>', key), sub)
if one_of and one_of[0].get('required'):
s += many_of('oneOf', one_of)
if pattern_properties:
s += format_html('<li><span>{0}</span>', _('Pattern properties'))
s += '\n<ul>'
for key, sub in pattern_properties.items():
if key:
pattern_key = format_html('/<tt>{0}</tt>/', key)
else:
pattern_key = format_html('<em>any</em>')
render_property_schema(key, pattern_key, sub)
s += '</ul>'
if properties or pattern_properties:
s += '</ul>'
return mark_safe(s)
if typ == 'boolean':
if not schema:
return mark_safe(html_type('boolean'))
enum = schema.pop('enum', [])
if enum and not schema:
return mark_safe(' | '.join([format_html('<tt>{}</tt>', json.dumps(el)) for el in enum]))
required_keys = schema.pop('required', [])
if required_keys and not schema:
return format_html('<em>{0} {1!r}</em>', _('required'), ', '.join(required_keys))
return format_html('<em>{0} {1!r}</em>', _('unknown validation'), original_schema)
@register.simple_tag(takes_context=False)
def render_body_schemas(body_schemas):
if not body_schemas:
return ''
s = mark_safe('<ul>')
for key in body_schemas:
if key == 'application/json':
s += mark_safe('<li><tt>application/json</tt> : <span class="json-schema">')
s += render_json_schema(body_schemas['application/json'])
s += mark_safe('</span></li>')
else:
s += format_html('<li><tt>{0}</tt></li>', key)
s += mark_safe('<ul>')
return mark_safe(s)
@register.filter(name='get')
def get(obj, key):
try:
return obj.get(key)
except AttributeError:
try:
return obj[key]
except (IndexError, KeyError, TypeError):
return None
@register.filter
def json_dumps(d):
return json.dumps(d, indent=2)