diff --git a/mycreole b/mycreole index 057388e..8a2da2b 160000 --- a/mycreole +++ b/mycreole @@ -1 +1 @@ -Subproject commit 057388e3b44b5fe42b133fe030c41aa6a254de58 +Subproject commit 8a2da2b84379df346d4f24c8fc5c1e6fe62c75e9 diff --git a/pages/__init__.py b/pages/__init__.py index 8270097..4b7ce5d 100644 --- a/pages/__init__.py +++ b/pages/__init__.py @@ -2,8 +2,12 @@ from django.urls.base import reverse def url_page(request, rel_path): - return reverse('pages-pages', kwargs={'rel_path': rel_path}) + return reverse('page-page', kwargs={'rel_path': rel_path}) def url_helpview(request, page): - return reverse('pages-helpview', kwargs={'page': page}) + return reverse('page-helpview', kwargs={'page': page}) + + +def url_edit(request, rel_path): + return reverse('page-edit', kwargs={'rel_path': rel_path}) diff --git a/pages/access.py b/pages/access.py index 401730a..5d9b887 100644 --- a/pages/access.py +++ b/pages/access.py @@ -1,8 +1,16 @@ # TODO: Implement access control for pages -def read_attachment(request, rel_path): +def read_page(request, rel_path): return True +def write_page(request, rel_path): + return request.user.is_authenticated and request.user.username in ['root', 'dirk'] + + +def read_attachment(request, rel_path): + return read_page(request, rel_path) + + def modify_attachment(request, rel_path): - return True + return write_page(request, rel_path) diff --git a/pages/context.py b/pages/context.py index 83dcf91..7b85bd6 100644 --- a/pages/context.py +++ b/pages/context.py @@ -4,6 +4,7 @@ import logging from django.utils.translation import gettext as _ +from pages import access from .help import actionbar as actionbar_add_help import mycreole import pages @@ -18,6 +19,7 @@ logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__) ATTACHMENT_UID = 'attachment' BACK_UID = 'back' +EDIT_UID = 'edit' HELP_UID = 'help' @@ -56,12 +58,7 @@ def add_back_menu(request, bar): def menubar(context, request, caller_name, **kwargs): bar = context[context.MENUBAR] - # replace_profile(request, bar) add_help_menu(request, bar) - # add_tasklist_menu(request, bar) - # add_filter_submenu(request, bar, VIEW_TASKLIST_UID) - # add_projectlist_menu(request, bar) - # add_printview_menu(request, bar) finalise_bar(request, bar) @@ -78,18 +75,27 @@ def add_help_menu(request, bar): def actionbar(context, request, caller_name, **kwargs): bar = context[context.ACTIONBAR] - if caller_name == 'pages': - # acc = acc_task(kwargs['task'], request.user) - # if acc.modify or acc.modify_limited: - # add_edittask_menu(request, bar, kwargs['task'].id) - # if acc.add_comments: - # add_newcomment_menu(request, bar, kwargs['task'].id) - add_manageupload_menu(request, bar, kwargs['upload_path']) + if caller_name == 'page': + if access.write_page(request, kwargs["rel_path"]): + add_edit_menu(request, bar, kwargs["rel_path"]) + if access.modify_attachment(request, kwargs["rel_path"]): + add_manageupload_menu(request, bar, kwargs['upload_path']) elif caller_name == 'helpview': actionbar_add_help(context, request, **kwargs) finalise_bar(request, bar) +def add_edit_menu(request, bar, rel_path): + bar.append_entry( + EDIT_UID, # uid + _('Edit'), # name + color_icon_url(request, 'edit.png'), # icon + pages.url_edit(request, rel_path), # url + True, # left + False # active + ) + + def add_manageupload_menu(request, bar, upload_path): bar.append_entry( ATTACHMENT_UID, # uid @@ -105,185 +111,3 @@ def finalise_bar(request, bar): if len(bar) == 0: bar.append_entry(*empty_entry_parameters(request)) - -"""from .access import create_project_possible, create_task_possible, acc_task -from django.db.models.functions import Lower -import patt -from .search import common_searches -from themes import empty_entry_parameters, color_icon_url, gray_icon_url -from users.context import PROFILE_ENTRY_UID - - -COMMENTNEW_UID = 'commentnew' -CREATE_PROJECT_UID = 'create-project' -CREATE_TASK_UID = 'create-task' -PRINTVIEW_UID = 'printview' -TASKEDIT_UID = 'taskedit' -VIEW_PROJECTLIST_UID = 'view-projectlist' -VIEW_TASKLIST_UID = 'view-tasklist' - - - - - - - - -def replace_profile(request, bar): - try: - bar.replace_entry( - PROFILE_ENTRY_UID, - PROFILE_ENTRY_UID, # uid - request.user.username, # name - color_icon_url(request, 'user.png'), # icon - patt.url_profile(request), # url - False, # left - False # active - ) - except ValueError: - pass # Profile entry does not exist, so exchange is not needed (e.g. no user is logged in) - - - - -def add_tasklist_menu(request, bar): - bar.append_entry( - VIEW_TASKLIST_UID, # uid - _('Tasklist'), # name - color_icon_url(request, 'task.png'), # icon - patt.url_tasklist(request), # url - True, # left - patt.is_tasklistview(request) # active - ) - - -def add_projectlist_menu(request, bar): - bar.append_entry( - VIEW_PROJECTLIST_UID, # uid - _('Projectlist'), # name - color_icon_url(request, 'folder.png'), # icon - patt.url_projectlist(request), # url - True, # left - patt.is_projectlistview(request) # active - ) - - -def add_printview_menu(request, bar): - bar.append_entry( - PRINTVIEW_UID, # uid - _('Printview'), # name - color_icon_url(request, 'print.png'), # icon - patt.url_printview(request), # url - True, # left - patt.is_printview(request) # active - ) - - -def add_newtask_menu(request, bar, project_id): - bar.append_entry( - CREATE_TASK_UID, # uid - _('New Task'), # name - color_icon_url(request, 'plus.png'), # icon - patt.url_tasknew(request, project_id), # url - True, # left - False # active - ) - - -def add_edittask_menu(request, bar, task_id): - bar.append_entry( - TASKEDIT_UID, # uid - _('Edit'), # name - color_icon_url(request, 'edit.png'), # icon - patt.url_taskedit(request, task_id), # url - True, # left - False # active - ) - - -def add_newcomment_menu(request, bar, task_id): - bar.append_entry( - COMMENTNEW_UID, # uid - _('Add Comment'), # name - color_icon_url(request, 'edit2.png'), # icon - patt.url_commentnew(request, task_id), # url - True, # left - False # active - ) - - -def add_newproject_menu(request, bar): - bar.append_entry( - CREATE_PROJECT_UID, # uid - _('New Project'), # name - color_icon_url(request, 'plus.png'), # icon - patt.url_projectnew(request), # url - True, # left - False # active - ) - - - - - - -def add_filter_submenu(request, bar, menu_uid): - bar.append_entry_to_entry( - menu_uid, - menu_uid + '-easysearch', # uid - _('Easysearch'), # name - gray_icon_url(request, 'search.png'), # icon - patt.url_easysearch(request), # url - True, # left - False # active - ) - if patt.get_search_query(request) is not None: - bar.append_entry_to_entry( - menu_uid, - menu_uid + '-save', # uid - _('Save Search as Filter'), # name - gray_icon_url(request, 'save.png'), # icon - patt.url_filteredit(request), # url - True, # left - False # active - ) - bar.append_entry_to_entry( - menu_uid, - menu_uid + '-all', # uid - _('All Tasks'), # name - gray_icon_url(request, 'task.png'), # icon - patt.url_tasklist(request), # url - True, # left - False # active - ) - cs = common_searches(request) - for common_filter_id in cs: - bar.append_entry_to_entry( - menu_uid, - menu_uid + '-common', # uid - _(cs[common_filter_id][0]), # name - gray_icon_url(request, 'filter.png'), # icon - patt.url_tasklist(request, common_filter_id=common_filter_id), # url - True, # left - False # active - ) - for s in request.user.search_set.order_by(Lower('name')): - active = patt.is_tasklistview(request, s.id) - if active is True: - url = patt.url_filteredit(request, s.id) - else: - url = patt.url_tasklist(request, user_filter_id=s.id) - if active: - icon = 'settings.png' - else: - icon = 'favourite.png' - bar.append_entry_to_entry( - menu_uid, - menu_uid + '-sub', # uid - s.name, # name - gray_icon_url(request, icon), # icon - url, # url - True, # left - active # active - ) -""" diff --git a/pages/forms.py b/pages/forms.py new file mode 100644 index 0000000..5b5a036 --- /dev/null +++ b/pages/forms.py @@ -0,0 +1,14 @@ +from typing import Any, Mapping +from django import forms +from django.forms.renderers import BaseRenderer +from django.forms.utils import ErrorList + + +class EditForm(forms.Form): # Note that it is not inheriting from forms.ModelForm + page_txt = forms.CharField(max_length=20000, label="Page source text", widget=forms.Textarea(attrs={"rows": "20"})) + + def __init__(self, *args, **kwargs) -> None: + page_data = kwargs.pop("page_data") + super().__init__(*args, **kwargs) + page_txt = self.fields['page_txt'] + page_txt.initial = page_data diff --git a/pages/messages.py b/pages/messages.py new file mode 100644 index 0000000..be26f79 --- /dev/null +++ b/pages/messages.py @@ -0,0 +1,12 @@ +from django.contrib import messages +from django.utils.translation import gettext as _ + + +def permission_denied_msg_page(request, rel_path): + # TODO: Add translation for this message + messages.error(request, _("Permission denied: You don't have sufficient acces to the Page '%s'. Please contact the adminstrator.") % rel_path) + + +def unavailable_msg_page(request, rel_path): + # TODO: Add translation for this message + messages.info(request, _("Unavailable: The Page '%s' is not available. Create it or follow a valid link, please.") % rel_path) diff --git a/pages/page.py b/pages/page.py index 27354a4..b33adba 100644 --- a/pages/page.py +++ b/pages/page.py @@ -1,10 +1,12 @@ from django.conf import settings +import fstools +from pages import messages import mycreole import os -class page(object): +class creol_page(object): SPLITCHAR = ":" FOLDER_ATTACHMENTS = "attachments" FOLDER_CONTENT = 'content' @@ -35,13 +37,24 @@ class page(object): def content_file_name(self): return os.path.join(settings.PAGES_ROOT, self.content_folder_name, self.FOLDER_CONTENT, self.FILE_NAME) - def __read_content__(self): - if self.is_available(): + @property + def raw_page_src(self): + try: with open(self.content_file_name, 'r') as fh: return fh.read() - else: - # TODO: Create message for creation or no content dependent of user has write access - return "Page not available. Create it." + except FileNotFoundError: + return "" + + def update_page(self, page_txt): + folder = os.path.dirname(self.content_file_name) + if not os.path.exists(folder): + fstools.mkdir(folder) + with open(self.content_file_name, 'w') as fh: + fh.write(page_txt) def render_to_html(self, request): - return mycreole.render(request, self.__read_content__(), self.attachment_path, "next_anchor") + if self.is_available(): + return mycreole.render(request, self.raw_page_src, self.attachment_path, "next_anchor") + else: + messages.unavailable_msg_page(request, self._rel_path) + return "" diff --git a/pages/templates/pages/page_form.html b/pages/templates/pages/page_form.html new file mode 100644 index 0000000..1c5e325 --- /dev/null +++ b/pages/templates/pages/page_form.html @@ -0,0 +1,20 @@ +{% extends "themes/"|add:settings.page_theme|add:"/base.html" %} +{% load static %} +{% load i18n %} + +{% block content %} +
+ {% csrf_token %} + {{ form.as_p }} + + {% if not disable_preview %}{% endif %} + {{ form_comment.as_p }} +
+ + {% if page_content %} +
+
+ {{ page_content|safe }} +
+ {% endif %} +{% endblock content %} diff --git a/pages/views.py b/pages/views.py index 6650bab..c13f921 100644 --- a/pages/views.py +++ b/pages/views.py @@ -4,11 +4,15 @@ from django.utils.translation import gettext as _ import logging -import config +from . import access +from . import messages from . import url_page +import config from .context import context_adaption +from .forms import EditForm from .help import help_pages -from .page import page +import mycreole +from .page import creol_page from themes import Context logger = logging.getLogger(__name__) @@ -18,21 +22,73 @@ def root(request): return HttpResponseRedirect(url_page(request, config.STARTPAGE)) -def pages(request, rel_path=''): +def page(request, rel_path): context = Context(request) # needs to be executed first because of time mesurement # - p = page(rel_path) + p = creol_page(rel_path) + if access.read_page(request, rel_path): + page_content = p.render_to_html(request) + else: + messages.permission_denied_msg_page(request, rel_path) + page_content = "" # context_adaption( context, request, + rel_path=rel_path, title=p.title, upload_path=p.attachment_path, - page_content=p.render_to_html(request) + page_content=page_content ) return render(request, 'pages/page.html', context=context) +def edit(request, rel_path): + if access.write_page(request, rel_path): + context = Context(request) # needs to be executed first because of time mesurement + # + p = creol_page(rel_path) + # + if not request.POST: + form = EditForm(page_data=p.raw_page_src) + # + context_adaption( + context, + request, + form=form, + # TODO: Add translation + title=_("Edit page %s") % repr(p.title), + upload_path=p.attachment_path, + ) + return render(request, 'pages/page_form.html', context=context) + else: + save = request.POST.get("save") + page_txt = request.POST.get("page_txt") + preview = request.POST.get("preview") + # + if save is not None: + p.update_page(page_txt) + return HttpResponseRedirect(url_page(request, rel_path)) + elif preview is not None: + form = EditForm(page_data=page_txt) + # + context_adaption( + context, + request, + form=form, + # TODO: Add translation + title=_("Edit page %s") % repr(p.title), + upload_path=p.attachment_path, + page_content=mycreole.render(request, page_txt, p.attachment_path, 'next-anchor') + ) + return render(request, 'pages/page_form.html', context=context) + else: + return HttpResponseRedirect(url_page(request, rel_path)) + else: + messages.permission_denied_msg_page(request, rel_path) + return HttpResponseRedirect(url_page(request, rel_path)) + + def search(request): context = Context(request) # needs to be executed first because of time mesurement context_adaption( diff --git a/piki/urls.py b/piki/urls.py index 9a2cb10..7f5447a 100644 --- a/piki/urls.py +++ b/piki/urls.py @@ -24,11 +24,12 @@ import pages.views urlpatterns = [ path('admin/', admin.site.urls), # - path('', pages.views.root, name='pages-root'), - path('pages/', pages.views.root, name='pages-root'), - path('pages/', pages.views.pages, name='pages-pages'), - path('helpview/', pages.views.helpview, name='pages-helpview'), - path('helpview/', pages.views.helpview, name='pages-helpview'), + path('', pages.views.root, name='page-root'), + path('page/', pages.views.root, name='page-root'), + path('page/', pages.views.page, name='page-page'), + path('pageedit//', pages.views.edit, name='page-edit'), + path('helpview/', pages.views.helpview, name='page-helpview'), + path('helpview/', pages.views.helpview, name='page-helpview'), path('search/', pages.views.search, name='search'), path('mycreole/', include('mycreole.urls')), path('users/', include('users.urls')),