Initial mycreole implementation

This commit is contained in:
Dirk Alders 2020-01-26 20:43:19 +01:00
parent b5f611468f
commit dd0edc2d56
13 changed files with 367 additions and 0 deletions

103
__init__.py Normal file
View File

@ -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

3
admin.py Normal file
View File

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

5
apps.py Normal file
View File

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

11
context.py Normal file
View File

@ -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]

118
help.py Normal file
View File

@ -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! <div>
}}\0}
}}}
results in:
{{{
unprocessde data! <div>
}}}
"""

0
migrations/__init__.py Normal file
View File

3
models.py Normal file
View File

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

View File

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

0
templatetags/__init__.py Normal file
View File

16
templatetags/mycreole.py Normal file
View File

@ -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))

3
tests.py Normal file
View File

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

9
urls.py Normal file
View File

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

84
views.py Normal file
View File

@ -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', '/'))