Browse Source

Initial mycreole implementation

master
Dirk Alders 4 years ago
parent
commit
dd0edc2d56
13 changed files with 367 additions and 0 deletions
  1. 103
    0
      __init__.py
  2. 3
    0
      admin.py
  3. 5
    0
      apps.py
  4. 11
    0
      context.py
  5. 118
    0
      help.py
  6. 0
    0
      migrations/__init__.py
  7. 3
    0
      models.py
  8. 12
    0
      templates/mycreole/upload.html
  9. 0
    0
      templatetags/__init__.py
  10. 16
    0
      templatetags/mycreole.py
  11. 3
    0
      tests.py
  12. 9
    0
      urls.py
  13. 84
    0
      views.py

+ 103
- 0
__init__.py View File

@@ -0,0 +1,103 @@
1
+import creole
2
+from django.conf import settings
3
+from django.urls.base import reverse
4
+from .help import MYCREOLE_HELP
5
+import importlib
6
+import os
7
+import shutil
8
+
9
+
10
+def get_full_path(rel_path):
11
+    try:
12
+        return os.path.join(settings.MYCREOLE_ROOT, *rel_path.split('/'))
13
+    except AttributeError:
14
+        raise AttributeError("You need to define a root directory for mycreole in settings.py: MYCREOLE_ROOT")
15
+
16
+
17
+def delete_attachment_target_path(attachment_target_path):
18
+    shutil.rmtree(get_full_path(attachment_target_path), True)
19
+
20
+
21
+def mycreole_help_pagecontent():
22
+    return creole.creole2html(MYCREOLE_HELP)
23
+
24
+
25
+def render_simple(text):
26
+    return creole.creole2html(text)
27
+
28
+
29
+def render(request, text, attachment_target_path, next_anchor=''):
30
+    def get_attachment_name(text, state):
31
+        end_idx = text.index(']]' if state == '[' else '}}')
32
+        try:
33
+            split_idx = text.index('|')
34
+        except ValueError:
35
+            split_idx = len(text)
36
+        return text[:min(end_idx, split_idx)]
37
+
38
+    # Call the additional filters before rendering
39
+    try:
40
+        ext_filters = settings.MYCREOLE_EXT_FILTERS
41
+    except AttributeError:
42
+        pass    # No filters defined
43
+    else:
44
+        for function_string in ext_filters:
45
+            m_name, f_name = function_string.rsplit('.', 1)
46
+            try:
47
+                mod = importlib.import_module(m_name)
48
+            except ModuleNotFoundError:
49
+                pass
50
+            else:
51
+                try:
52
+                    func = getattr(mod, f_name)
53
+                except AttributeError:
54
+                    pass
55
+                else:
56
+                    text = func(text)
57
+
58
+    if not attachment_target_path.endswith('/'):
59
+        attachment_target_path += '/'
60
+    try:
61
+        render_txt = ''
62
+        while len(text) > 0:
63
+            try:
64
+                link_pos = text.index('[[attachment:')
65
+            except ValueError:
66
+                link_pos = len(text)
67
+            try:
68
+                embed_pos = text.index('{{attachment:')
69
+            except ValueError:
70
+                embed_pos = len(text)
71
+            pos = min(link_pos, embed_pos)
72
+            render_txt += text[:pos]
73
+            text = text[pos + 13:]
74
+            if link_pos != embed_pos:
75
+                if link_pos < embed_pos:
76
+                    state = '['
77
+                else:
78
+                    state = '{'
79
+                attachment_name = get_attachment_name(text, state)
80
+                text = text[len(attachment_name):]
81
+                rel_path = attachment_target_path + attachment_name
82
+                if os.path.isfile(get_full_path(rel_path)):
83
+                    render_txt += 2 * state + url_attachment(rel_path)
84
+                else:
85
+                    render_txt += '[[%s|Upload]]' % url_upload(request, rel_path, next_anchor)
86
+                    text = text[text.index(']]' if state == '[' else '}}') + 2:]
87
+        return creole.creole2html(render_txt)
88
+    except:
89
+        return creole.creole2html(text)
90
+
91
+
92
+def url_upload(request, rel_path, next_anchor=''):
93
+    nxt = request.GET.get('next', request.get_full_path())
94
+    return reverse('mycreole-upload', kwargs={'rel_path': rel_path}) + '?next=%s' % nxt + ('' if not next_anchor else '#%s' % next_anchor)
95
+
96
+
97
+def url_attachment(rel_path):
98
+    return reverse('mycreole-attachment', kwargs={'rel_path': rel_path})
99
+
100
+
101
+def url_manage_uploads(request, rel_path):
102
+    nxt = request.GET.get('next', request.get_full_path())
103
+    return reverse('mycreole-manageuploads', kwargs={'rel_path': rel_path}) + '?next=%s' % nxt

+ 3
- 0
admin.py View File

@@ -0,0 +1,3 @@
1
+from django.contrib import admin
2
+
3
+# Register your models here.

+ 5
- 0
apps.py View File

@@ -0,0 +1,5 @@
1
+from django.apps import AppConfig
2
+
3
+
4
+class MycreoleConfig(AppConfig):
5
+    name = 'mycreole'

+ 11
- 0
context.py View File

@@ -0,0 +1,11 @@
1
+from users.context import menubar as user_menubar
2
+import themes
3
+
4
+
5
+def context_adaption(context, request, title, **kwargs):
6
+    context.set_additional_title(title)
7
+    user_menubar(context[context.MENUBAR], request)
8
+    context[context.NAVIGATIONBAR].append_entry(*themes.empty_entry_parameters(request))
9
+    context[context.ACTIONBAR].append_entry(*themes.empty_entry_parameters(request))
10
+    for key in kwargs:
11
+        context[key] = kwargs[key]

+ 118
- 0
help.py View File

@@ -0,0 +1,118 @@
1
+MYCREOLE_HELP = """\
2
+= Creole Markup
3
+
4
+The Fields //"Description"// and //"Name"// of Tasks and Projects are interpreted as creole. \
5
+See http://www.wikicreole.org for more details.
6
+
7
+== Text Markup
8
+| =Name | =Syntax | =Result |
9
+| Normal | {{{text}}} | text |
10
+| Bold | {{{**text**}}} | **text** |
11
+| Italics | {{{//text//}}} | //text// |
12
+| Underline | {{{__text__}}} | __text__ |
13
+| Raw Link | {{{https://python.org}}} | https://python.org |
14
+| Text Link | {{{[[https://python.org|Python]]}}} | [[https://python.org|Python]] |
15
+| Image | {{{ {{/media/theme/logo.png|logo}}     }}} | {{/media/theme/logo.png|logo}} |
16
+| Attachment Text Link | {{{[[attachment:file.ext|Python]]}}} |  |
17
+| Attachment Image | {{{ {{attachment:logo.png|logo}}     }}} |  |
18
+
19
+== Paragraph Markup
20
+=== Bullet List
21
+{{{
22
+* Entry One
23
+* Entry Two
24
+** Subentry Two.One
25
+}}}
26
+results in:
27
+* Entry One
28
+* Entry Two
29
+** Subentry Two.One
30
+
31
+=== Numbered List
32
+{{{
33
+# Entry One
34
+# Entry Two
35
+## Subentry Two.One
36
+}}}
37
+results in:
38
+# Entry One
39
+# Entry Two
40
+## Subentry Two.One
41
+
42
+=== Headings
43
+{{{ = Large Heading }}}
44
+results in:
45
+= Large Heading
46
+
47
+{{{ == Medium Heading }}}
48
+results in:
49
+== Medium Heading
50
+
51
+{{{ === Small Heading }}}
52
+results in:
53
+=== Small Heading
54
+
55
+=== Line Break
56
+{{{
57
+Multiple
58
+line text
59
+}}}
60
+results in:
61
+
62
+Multiple
63
+line text
64
+
65
+=== No Line Break
66
+{{{
67
+Single \\
68
+Line
69
+}}}
70
+results in:
71
+
72
+Single \
73
+Line
74
+
75
+=== Paragraph
76
+{{{
77
+Part 1
78
+
79
+Part 2
80
+}}}
81
+results in:
82
+
83
+Part 1
84
+
85
+Part 2
86
+
87
+=== Horizontal Line
88
+{{{
89
+----
90
+}}}
91
+results in:
92
+----
93
+
94
+=== Table
95
+{{{
96
+| =Header 1   | =Header 2   | =Header 3  |
97
+| body row 1 | column 2   | column 3  |
98
+| body row 2 | -  | -   |
99
+
100
+}}}
101
+results in:
102
+
103
+| =Header 1   | =Header 2   | =Header 3  |
104
+| body row 1 | column 2   | column 3  |
105
+| body row 2 | -  | -   |
106
+
107
+=== Unprocessed (raw) Text
108
+{{{
109
+{{{
110
+unprocessde data! <div>
111
+}}\0}
112
+}}}
113
+results in:
114
+
115
+{{{
116
+unprocessde data! <div>
117
+}}}
118
+"""

+ 0
- 0
migrations/__init__.py View File


+ 3
- 0
models.py View File

@@ -0,0 +1,3 @@
1
+from django.db import models
2
+
3
+# Create your models here.

+ 12
- 0
templates/mycreole/upload.html View File

@@ -0,0 +1,12 @@
1
+{% extends "themes/"|add:settings.page_theme|add:"/base.html" %}
2
+{% load i18n %}
3
+
4
+{% block content %}
5
+    <form class="form" method="post" enctype="multipart/form-data">
6
+      {% csrf_token %}
7
+      <input type="hidden" name="next" value="{{ next }}">
8
+      <label class="required" for="f_file">Upload</label>
9
+      <input  class="required" required="required" type="file" name="file" id="f_file">
10
+      <input type="submit" value="{% trans "Upload" %}" class="button" />
11
+    </form>
12
+{% endblock content %}

+ 0
- 0
templatetags/__init__.py View File


+ 16
- 0
templatetags/mycreole.py View File

@@ -0,0 +1,16 @@
1
+from django import template
2
+from django.utils.safestring import mark_safe
3
+import mycreole
4
+
5
+register = template.Library()
6
+
7
+
8
+@register.simple_tag(name='render_creole_simple')
9
+def render_creole_simple(text):
10
+    return mark_safe(mycreole.render_simple(text))
11
+
12
+
13
+@register.simple_tag(name='render_creole', takes_context=True)
14
+def render_creole(context, text, attachment_target_path, next_anchor=''):
15
+    request = context['request']
16
+    return mark_safe(mycreole.render(request, text, attachment_target_path, next_anchor))

+ 3
- 0
tests.py View File

@@ -0,0 +1,3 @@
1
+from django.test import TestCase
2
+
3
+# Create your tests here.

+ 9
- 0
urls.py View File

@@ -0,0 +1,9 @@
1
+from django.urls import path
2
+from . import views
3
+
4
+
5
+urlpatterns = [
6
+    path('attachment/<path:rel_path>', views.mycreole_attachment, name='mycreole-attachment'),
7
+    path('upload/<path:rel_path>', views.mycreole_upload, name='mycreole-upload'),
8
+    path('manageuploads/<path:rel_path>', views.mycreole_manageuploads, name='mycreole-manageuploads'),
9
+]

+ 84
- 0
views.py View File

@@ -0,0 +1,84 @@
1
+from .context import context_adaption
2
+from django.conf import settings
3
+from django.shortcuts import render, redirect, HttpResponse
4
+from django.http import HttpResponseNotFound, HttpResponseForbidden
5
+import importlib
6
+import mimetypes
7
+import logging
8
+import mycreole
9
+import os
10
+import themes
11
+from django.contrib import messages
12
+
13
+logger = logging.getLogger('APP')
14
+
15
+
16
+def get_method(import_string):
17
+    class_data = import_string.split(".")
18
+    module_path = ".".join(class_data[:-1])
19
+    class_str = class_data[-1]
20
+    #
21
+    module = importlib.import_module(module_path)
22
+    return getattr(module, class_str)
23
+
24
+
25
+def access(access_type, request, rel_path):
26
+    def log_warning(access_type, e):
27
+        logger.warning('Could not import %s_access method for checking access rights: %s - %s', access_type, type(e).__name__, e)
28
+
29
+    try:
30
+        method_name = settings.MYCREOLE_ATTACHMENT_ACCESS[access_type]
31
+    except (AttributeError, KeyError) as e:
32
+        log_warning(access_type, e)
33
+        return False
34
+    else:
35
+        if method_name in [True, None]:
36
+            return True
37
+        elif method_name is False:
38
+            return False
39
+        else:
40
+            try:
41
+                return get_method(method_name)(request, rel_path)
42
+            except AttributeError as e:
43
+                log_warning(access_type, e)
44
+                return False
45
+
46
+
47
+def mycreole_attachment(request, rel_path):
48
+    full_path = mycreole.get_full_path(rel_path)
49
+    if access('read', request, rel_path):
50
+        if os.path.isfile(full_path):
51
+            mimetypes.init()
52
+            mime_type = mimetypes.types_map.get(os.path.splitext(full_path)[1])
53
+            data = open(full_path, 'rb').read()
54
+            return HttpResponse(data, content_type=mime_type)
55
+        else:
56
+            return HttpResponseNotFound(rel_path)
57
+    else:
58
+        return HttpResponseForbidden(rel_path)
59
+
60
+
61
+def mycreole_upload(request, rel_path):
62
+    if access('modify', request, rel_path):
63
+        if not request.POST:
64
+            context = themes.Context(request)
65
+            context_adaption(context, request, 'Upload %s' % rel_path, next=request.GET.get('next', '/'))
66
+            return render(request, 'mycreole/upload.html', context=context)
67
+        else:
68
+            full_path = mycreole.get_full_path(rel_path)
69
+            try:
70
+                os.makedirs(os.path.dirname(full_path), exist_ok=True)
71
+            except PermissionError:
72
+                raise PermissionError("Ensure that we have access to MYCREOLE_ROOT=%s" % repr(settings.MYCREOLE_ROOT))
73
+            else:
74
+                with open(full_path, 'wb') as fh:
75
+                    fh.write(request.FILES['file'].read())
76
+            return redirect(request.POST.get('next', '/'))
77
+    else:
78
+        messages.error(request, "Upload: Access denied!")
79
+        return redirect(request.GET.get('next', '/'))
80
+
81
+
82
+def mycreole_manageuploads(request, rel_path):
83
+    messages.error(request, 'Manage Uploads: Not yet implemented!')
84
+    return redirect(request.GET.get('next', '/'))

Loading…
Cancel
Save