From dd0edc2d56c2c234be32fa76e3bd5b6b4f38a69f Mon Sep 17 00:00:00 2001 From: Dirk Alders Date: Sun, 26 Jan 2020 20:43:19 +0100 Subject: [PATCH] Initial mycreole implementation --- __init__.py | 103 ++++++++++++++++++++++++++++ admin.py | 3 + apps.py | 5 ++ context.py | 11 +++ help.py | 118 +++++++++++++++++++++++++++++++++ migrations/__init__.py | 0 models.py | 3 + templates/mycreole/upload.html | 12 ++++ templatetags/__init__.py | 0 templatetags/mycreole.py | 16 +++++ tests.py | 3 + urls.py | 9 +++ views.py | 84 +++++++++++++++++++++++ 13 files changed, 367 insertions(+) create mode 100644 __init__.py create mode 100644 admin.py create mode 100644 apps.py create mode 100644 context.py create mode 100644 help.py create mode 100644 migrations/__init__.py create mode 100644 models.py create mode 100644 templates/mycreole/upload.html create mode 100644 templatetags/__init__.py create mode 100644 templatetags/mycreole.py create mode 100644 tests.py create mode 100644 urls.py create mode 100644 views.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..0942a76 --- /dev/null +++ b/__init__.py @@ -0,0 +1,103 @@ +import creole +from django.conf import settings +from django.urls.base import reverse +from .help import MYCREOLE_HELP +import importlib +import os +import shutil + + +def get_full_path(rel_path): + try: + return os.path.join(settings.MYCREOLE_ROOT, *rel_path.split('/')) + except AttributeError: + raise AttributeError("You need to define a root directory for mycreole in settings.py: MYCREOLE_ROOT") + + +def delete_attachment_target_path(attachment_target_path): + shutil.rmtree(get_full_path(attachment_target_path), True) + + +def mycreole_help_pagecontent(): + return creole.creole2html(MYCREOLE_HELP) + + +def render_simple(text): + return creole.creole2html(text) + + +def render(request, text, attachment_target_path, next_anchor=''): + def get_attachment_name(text, state): + end_idx = text.index(']]' if state == '[' else '}}') + try: + split_idx = text.index('|') + except ValueError: + split_idx = len(text) + return text[:min(end_idx, split_idx)] + + # Call the additional filters before rendering + try: + ext_filters = settings.MYCREOLE_EXT_FILTERS + except AttributeError: + pass # No filters defined + else: + for function_string in ext_filters: + m_name, f_name = function_string.rsplit('.', 1) + try: + mod = importlib.import_module(m_name) + except ModuleNotFoundError: + pass + else: + try: + func = getattr(mod, f_name) + except AttributeError: + pass + else: + text = func(text) + + if not attachment_target_path.endswith('/'): + attachment_target_path += '/' + try: + render_txt = '' + while len(text) > 0: + try: + link_pos = text.index('[[attachment:') + except ValueError: + link_pos = len(text) + try: + embed_pos = text.index('{{attachment:') + except ValueError: + embed_pos = len(text) + pos = min(link_pos, embed_pos) + render_txt += text[:pos] + text = text[pos + 13:] + if link_pos != embed_pos: + if link_pos < embed_pos: + state = '[' + else: + state = '{' + attachment_name = get_attachment_name(text, state) + text = text[len(attachment_name):] + rel_path = attachment_target_path + attachment_name + if os.path.isfile(get_full_path(rel_path)): + render_txt += 2 * state + url_attachment(rel_path) + else: + render_txt += '[[%s|Upload]]' % url_upload(request, rel_path, next_anchor) + text = text[text.index(']]' if state == '[' else '}}') + 2:] + return creole.creole2html(render_txt) + except: + return creole.creole2html(text) + + +def url_upload(request, rel_path, next_anchor=''): + nxt = request.GET.get('next', request.get_full_path()) + return reverse('mycreole-upload', kwargs={'rel_path': rel_path}) + '?next=%s' % nxt + ('' if not next_anchor else '#%s' % next_anchor) + + +def url_attachment(rel_path): + return reverse('mycreole-attachment', kwargs={'rel_path': rel_path}) + + +def url_manage_uploads(request, rel_path): + nxt = request.GET.get('next', request.get_full_path()) + return reverse('mycreole-manageuploads', kwargs={'rel_path': rel_path}) + '?next=%s' % nxt diff --git a/admin.py b/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps.py b/apps.py new file mode 100644 index 0000000..edccf61 --- /dev/null +++ b/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class MycreoleConfig(AppConfig): + name = 'mycreole' diff --git a/context.py b/context.py new file mode 100644 index 0000000..298ca0a --- /dev/null +++ b/context.py @@ -0,0 +1,11 @@ +from users.context import menubar as user_menubar +import themes + + +def context_adaption(context, request, title, **kwargs): + context.set_additional_title(title) + user_menubar(context[context.MENUBAR], request) + context[context.NAVIGATIONBAR].append_entry(*themes.empty_entry_parameters(request)) + context[context.ACTIONBAR].append_entry(*themes.empty_entry_parameters(request)) + for key in kwargs: + context[key] = kwargs[key] diff --git a/help.py b/help.py new file mode 100644 index 0000000..961d8e9 --- /dev/null +++ b/help.py @@ -0,0 +1,118 @@ +MYCREOLE_HELP = """\ += Creole Markup + +The Fields //"Description"// and //"Name"// of Tasks and Projects are interpreted as creole. \ +See http://www.wikicreole.org for more details. + +== Text Markup +| =Name | =Syntax | =Result | +| Normal | {{{text}}} | text | +| Bold | {{{**text**}}} | **text** | +| Italics | {{{//text//}}} | //text// | +| Underline | {{{__text__}}} | __text__ | +| Raw Link | {{{https://python.org}}} | https://python.org | +| Text Link | {{{[[https://python.org|Python]]}}} | [[https://python.org|Python]] | +| Image | {{{ {{/media/theme/logo.png|logo}} }}} | {{/media/theme/logo.png|logo}} | +| Attachment Text Link | {{{[[attachment:file.ext|Python]]}}} | | +| Attachment Image | {{{ {{attachment:logo.png|logo}} }}} | | + +== Paragraph Markup +=== Bullet List +{{{ +* Entry One +* Entry Two +** Subentry Two.One +}}} +results in: +* Entry One +* Entry Two +** Subentry Two.One + +=== Numbered List +{{{ +# Entry One +# Entry Two +## Subentry Two.One +}}} +results in: +# Entry One +# Entry Two +## Subentry Two.One + +=== Headings +{{{ = Large Heading }}} +results in: += Large Heading + +{{{ == Medium Heading }}} +results in: +== Medium Heading + +{{{ === Small Heading }}} +results in: +=== Small Heading + +=== Line Break +{{{ +Multiple +line text +}}} +results in: + +Multiple +line text + +=== No Line Break +{{{ +Single \\ +Line +}}} +results in: + +Single \ +Line + +=== Paragraph +{{{ +Part 1 + +Part 2 +}}} +results in: + +Part 1 + +Part 2 + +=== Horizontal Line +{{{ +---- +}}} +results in: +---- + +=== Table +{{{ +| =Header 1 | =Header 2 | =Header 3 | +| body row 1 | column 2 | column 3 | +| body row 2 | - | - | + +}}} +results in: + +| =Header 1 | =Header 2 | =Header 3 | +| body row 1 | column 2 | column 3 | +| body row 2 | - | - | + +=== Unprocessed (raw) Text +{{{ +{{{ +unprocessde data!
+}}\0} +}}} +results in: + +{{{ +unprocessde data!
+}}} +""" \ No newline at end of file diff --git a/migrations/__init__.py b/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models.py b/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/templates/mycreole/upload.html b/templates/mycreole/upload.html new file mode 100644 index 0000000..e0819f2 --- /dev/null +++ b/templates/mycreole/upload.html @@ -0,0 +1,12 @@ +{% extends "themes/"|add:settings.page_theme|add:"/base.html" %} +{% load i18n %} + +{% block content %} +
+ {% csrf_token %} + + + + +
+{% endblock content %} diff --git a/templatetags/__init__.py b/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/templatetags/mycreole.py b/templatetags/mycreole.py new file mode 100644 index 0000000..5ec163b --- /dev/null +++ b/templatetags/mycreole.py @@ -0,0 +1,16 @@ +from django import template +from django.utils.safestring import mark_safe +import mycreole + +register = template.Library() + + +@register.simple_tag(name='render_creole_simple') +def render_creole_simple(text): + return mark_safe(mycreole.render_simple(text)) + + +@register.simple_tag(name='render_creole', takes_context=True) +def render_creole(context, text, attachment_target_path, next_anchor=''): + request = context['request'] + return mark_safe(mycreole.render(request, text, attachment_target_path, next_anchor)) diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/urls.py b/urls.py new file mode 100644 index 0000000..53c0837 --- /dev/null +++ b/urls.py @@ -0,0 +1,9 @@ +from django.urls import path +from . import views + + +urlpatterns = [ + path('attachment/', views.mycreole_attachment, name='mycreole-attachment'), + path('upload/', views.mycreole_upload, name='mycreole-upload'), + path('manageuploads/', views.mycreole_manageuploads, name='mycreole-manageuploads'), +] diff --git a/views.py b/views.py new file mode 100644 index 0000000..1a6d0fb --- /dev/null +++ b/views.py @@ -0,0 +1,84 @@ +from .context import context_adaption +from django.conf import settings +from django.shortcuts import render, redirect, HttpResponse +from django.http import HttpResponseNotFound, HttpResponseForbidden +import importlib +import mimetypes +import logging +import mycreole +import os +import themes +from django.contrib import messages + +logger = logging.getLogger('APP') + + +def get_method(import_string): + class_data = import_string.split(".") + module_path = ".".join(class_data[:-1]) + class_str = class_data[-1] + # + module = importlib.import_module(module_path) + return getattr(module, class_str) + + +def access(access_type, request, rel_path): + def log_warning(access_type, e): + logger.warning('Could not import %s_access method for checking access rights: %s - %s', access_type, type(e).__name__, e) + + try: + method_name = settings.MYCREOLE_ATTACHMENT_ACCESS[access_type] + except (AttributeError, KeyError) as e: + log_warning(access_type, e) + return False + else: + if method_name in [True, None]: + return True + elif method_name is False: + return False + else: + try: + return get_method(method_name)(request, rel_path) + except AttributeError as e: + log_warning(access_type, e) + return False + + +def mycreole_attachment(request, rel_path): + full_path = mycreole.get_full_path(rel_path) + if access('read', request, rel_path): + if os.path.isfile(full_path): + mimetypes.init() + mime_type = mimetypes.types_map.get(os.path.splitext(full_path)[1]) + data = open(full_path, 'rb').read() + return HttpResponse(data, content_type=mime_type) + else: + return HttpResponseNotFound(rel_path) + else: + return HttpResponseForbidden(rel_path) + + +def mycreole_upload(request, rel_path): + if access('modify', request, rel_path): + if not request.POST: + context = themes.Context(request) + context_adaption(context, request, 'Upload %s' % rel_path, next=request.GET.get('next', '/')) + return render(request, 'mycreole/upload.html', context=context) + else: + full_path = mycreole.get_full_path(rel_path) + try: + os.makedirs(os.path.dirname(full_path), exist_ok=True) + except PermissionError: + raise PermissionError("Ensure that we have access to MYCREOLE_ROOT=%s" % repr(settings.MYCREOLE_ROOT)) + else: + with open(full_path, 'wb') as fh: + fh.write(request.FILES['file'].read()) + return redirect(request.POST.get('next', '/')) + else: + messages.error(request, "Upload: Access denied!") + return redirect(request.GET.get('next', '/')) + + +def mycreole_manageuploads(request, rel_path): + messages.error(request, 'Manage Uploads: Not yet implemented!') + return redirect(request.GET.get('next', '/'))