Initial mycreole implementation
This commit is contained in:
parent
b5f611468f
commit
dd0edc2d56
103
__init__.py
Normal file
103
__init__.py
Normal 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
3
admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
5
apps.py
Normal file
5
apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class MycreoleConfig(AppConfig):
|
||||||
|
name = 'mycreole'
|
11
context.py
Normal file
11
context.py
Normal 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
118
help.py
Normal 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
0
migrations/__init__.py
Normal file
3
models.py
Normal file
3
models.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
12
templates/mycreole/upload.html
Normal file
12
templates/mycreole/upload.html
Normal 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
0
templatetags/__init__.py
Normal file
16
templatetags/mycreole.py
Normal file
16
templatetags/mycreole.py
Normal 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
3
tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
9
urls.py
Normal file
9
urls.py
Normal 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
84
views.py
Normal 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', '/'))
|
Loading…
x
Reference in New Issue
Block a user