diff --git a/pages/__init__.py b/pages/__init__.py index cd5e624..02caeef 100644 --- a/pages/__init__.py +++ b/pages/__init__.py @@ -18,10 +18,18 @@ def url_helpview(page): return reverse('page-helpview', kwargs={'page': page}) +def url_delete(rel_path, **kwargs): + return reverse('page-delete', kwargs={'rel_path': rel_path}) + params(**kwargs) + + def url_edit(rel_path, **kwargs): return reverse('page-edit', kwargs={'rel_path': rel_path}) + params(**kwargs) +def url_rename(rel_path, **kwargs): + return reverse('page-rename', kwargs={'rel_path': rel_path}) + params(**kwargs) + + def get_search_query(request): return request.GET.get('q') diff --git a/pages/context.py b/pages/context.py index 55a56b7..3855e9e 100644 --- a/pages/context.py +++ b/pages/context.py @@ -73,13 +73,13 @@ def menubar(context, request, caller_name, **kwargs): def actionbar(context, request, caller_name, **kwargs): bar = context[context.ACTIONBAR] if not cms_mode_active(request): - if caller_name == 'page': + if caller_name in ['page', 'edit', 'delete', 'rename']: if access.write_page(request, kwargs["rel_path"]): - add_edit_menu(request, bar, kwargs["rel_path"]) + add_page_menu(request, bar, kwargs["rel_path"], kwargs.get('is_available', False)) if access.modify_attachment(request, kwargs["rel_path"]): - add_manageupload_menu(request, bar, kwargs['upload_path']) + add_manageupload_menu(request, bar, kwargs['upload_path'], kwargs.get('is_available', False)) if access.read_page(request, kwargs["rel_path"]): - add_meta_menu(request, bar, kwargs["rel_path"]) + add_meta_menu(request, bar, kwargs["rel_path"], kwargs.get('is_available', False)) elif caller_name == 'helpview': actionbar_add_help(context, request, **kwargs) finalise_bar(request, bar) @@ -137,49 +137,68 @@ def add_nav_links(request, bar, rel_path): ) -def add_edit_menu(request, bar, rel_path): +def add_page_menu(request, bar, rel_path, is_available): bar.append_entry( EDIT_UID, # uid _('Edit'), # name color_icon_url(request, 'edit2.png'), # icon pages.url_edit(rel_path), # url True, # left - False # active + request.path == pages.url_edit(rel_path) # active ) - - -def add_manageupload_menu(request, bar, upload_path): - bar.append_entry( - ATTACHMENT_UID, # uid - _("Attachments"), # name - color_icon_url(request, 'upload.png'), # icon - mycreole.url_manage_uploads(request, upload_path), # url - True, # left - False, # active - ) - - -def add_meta_menu(request, bar, rel_path): - if "meta" in request.GET: + if is_available: bar.append_entry( - EDIT_UID, # uid - _('Page'), # name - color_icon_url(request, 'display.png'), # icon - pages.url_page(rel_path), # url - True, # left - False # active + EDIT_UID, # uid + _('Rename'), # name + color_icon_url(request, 'shuffle.png'), # icon + pages.url_rename(rel_path), # url + True, # left + request.path == pages.url_rename(rel_path) # active ) - else: bar.append_entry( - EDIT_UID, # uid - _('Meta'), # name - color_icon_url(request, 'info.png'), # icon - pages.url_page(rel_path, meta=None), # url - True, # left - False # active + EDIT_UID, # uid + _('Delete'), # name + color_icon_url(request, 'delete.png'), # icon + pages.url_delete(rel_path), # url + True, # left + request.path == pages.url_delete(rel_path) # active ) +def add_manageupload_menu(request, bar, upload_path, is_available): + if is_available: + bar.append_entry( + ATTACHMENT_UID, # uid + _("Attachments"), # name + color_icon_url(request, 'upload.png'), # icon + mycreole.url_manage_uploads(request, upload_path), # url + True, # left + False, # active + ) + + +def add_meta_menu(request, bar, rel_path, is_available): + if is_available: + if "meta" in request.GET: + bar.append_entry( + EDIT_UID, # uid + _('Page'), # name + color_icon_url(request, 'display.png'), # icon + pages.url_page(rel_path), # url + True, # left + False # active + ) + else: + bar.append_entry( + EDIT_UID, # uid + _('Meta'), # name + color_icon_url(request, 'info.png'), # icon + pages.url_page(rel_path, meta=None), # url + True, # left + False # active + ) + + def finalise_bar(request, bar): if len(bar) == 0: bar.append_entry(*empty_entry_parameters(request)) diff --git a/pages/forms.py b/pages/forms.py index 4d2410f..44233b1 100644 --- a/pages/forms.py +++ b/pages/forms.py @@ -6,7 +6,7 @@ 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"})) - page_tags = forms.CharField(max_length=20000, label="Tags (words separated by spaces)", required=False) + page_tags = forms.CharField(max_length=500, label="Tags (words separated by spaces)", required=False) def __init__(self, *args, **kwargs) -> None: page_data = kwargs.pop("page_data") @@ -14,3 +14,12 @@ class EditForm(forms.Form): # Note that it is not inheriting from forms.ModelFo super().__init__(*args, **kwargs) self.fields['page_txt'].initial = page_data self.fields['page_tags'].initial = page_tags + + +class RenameForm(forms.Form): # Note that it is not inheriting from forms.ModelForm + page_name = forms.CharField(max_length=500, label="Change the page name:", required=True) + + def __init__(self, *args, **kwargs) -> None: + page_name = kwargs.pop("page_name") + super().__init__(*args, **kwargs) + self.fields['page_name'].initial = page_name diff --git a/pages/messages.py b/pages/messages.py index a197978..a5ee91d 100644 --- a/pages/messages.py +++ b/pages/messages.py @@ -18,11 +18,26 @@ def edit_success(request): messages.success(request, _('Thanks for editing, page stored.')) -def edit_no_change(request): +def no_change(request): # TODO: Add translation for this message messages.info(request, _("Nothing changed, no storage needed.")) +def operation_canceled(request): + # TODO: Add translation for this message + messages.info(request, _('Operation caneled, no change to the content.')) + + +def page_deleted(request, title): + # TODO: Add translation for this message + messages.info(request, _('The page "%s" has been deleted.') % title) + + +def page_renamed(request): + # TODO: Add translation for this message + messages.info(request, _('The page has been renamed.')) + + def history_version_display(request, rel_path, history_version): # TODO: Add translation for this message messages.warning(request, _("You see an old version of the page (Version = %d). Click here to recover this Version.") % ( diff --git a/pages/page.py b/pages/page.py index 9c04a1c..eb48d79 100644 --- a/pages/page.py +++ b/pages/page.py @@ -22,8 +22,24 @@ def full_path_all_pages(expression="*"): system_pages = fstools.dirlist(settings.SYSTEM_PAGES_ROOT, expression=expression, rekursive=False) system_pages = [os.path.join(settings.PAGES_ROOT, os.path.basename(path)) for path in system_pages] pages = fstools.dirlist(settings.PAGES_ROOT, expression=expression, rekursive=False) - # TODO: strip path, if page or meta.json is missing - return list(set(system_pages + pages)) + rv = [] + for path in set(system_pages + pages): + p = page_wrapped(None, path) + if p.is_available(): + rv.append(path) + return rv + + +def full_path_deleted_pages(expression="*"): + system_pages = fstools.dirlist(settings.SYSTEM_PAGES_ROOT, expression=expression, rekursive=False) + system_pages = [os.path.join(settings.PAGES_ROOT, os.path.basename(path)) for path in system_pages] + pages = fstools.dirlist(settings.PAGES_ROOT, expression=expression, rekursive=False) + rv = [] + for path in set(system_pages + pages): + p = page_wrapped(None, path) + if not p.is_available(): + rv.append(path) + return rv class meta_data(dict): @@ -45,6 +61,9 @@ class meta_data(dict): except (FileNotFoundError, json.decoder.JSONDecodeError) as e: super().__init__() + def delete(self): + os.remove(self.filename) + @property def filename(self): if not self._history_version: @@ -106,6 +125,20 @@ class page_data(object): except FileNotFoundError: self._raw_page_src = "" + def delete(self): + os.remove(self.filename) + + def rename(self, page_name): + # Change backslash to slash and remove double slashes + page_name = page_name.replace("\\", "/") + while "//" in page_name: + page_name = page_name.replace("//", "/") + # move path + target_path = os.path.join(settings.PAGES_ROOT, page_name.replace("/", 2*SPLITCHAR)) + shutil.move(self._path, target_path) + # set my path + self._path = target_path + def update_required(self, page_txt): return page_txt.replace("\r\n", "\n") != self.raw_page_src @@ -143,7 +176,7 @@ class page_data(object): @property def title(self): - return os.path.basename(self._path).split("::")[-1] + return os.path.basename(self._path).split(2*SPLITCHAR)[-1] @property def raw_page_src(self): @@ -421,6 +454,11 @@ class page_wrapped(object): rv = meta.get(meta.KEY_CREATION_TIME) return rv + def delete(self): + self.__store_history__() + self._page.delete() + self._page_meta.delete() + @property def modified_time(self): meta = self.__meta_choose__() @@ -433,6 +471,9 @@ class page_wrapped(object): rv = meta.get(meta.KEY_MODIFIED_USER) return rv + def rename(self, page_name): + self._page.rename(page_name) + @property def tags(self): meta = self.__meta_choose__() @@ -451,6 +492,9 @@ class page_wrapped(object): def is_available(self): return self._page.is_available() or self._system_page.is_available() + def userpage_is_available(self): + return self._page.is_available() + @property def raw_page_src(self): page = self.__page_choose__() diff --git a/pages/search.py b/pages/search.py index d7e4a34..80bcce4 100644 --- a/pages/search.py +++ b/pages/search.py @@ -98,7 +98,7 @@ def whoosh_search(search_txt): def delete_item(ix, pw: page_wrapped): with ix.writer() as w: logger.info('Removing document with id=%s from the search index.', pw.rel_path) - w.delete_by_term("task_id", pw.rel_path) + w.delete_by_term("id", pw.rel_path) def update_item(pw: page_wrapped): diff --git a/pages/templates/pages/page_delete.html b/pages/templates/pages/page_delete.html new file mode 100644 index 0000000..8f2b428 --- /dev/null +++ b/pages/templates/pages/page_delete.html @@ -0,0 +1,16 @@ +{% extends "themes/"|add:settings.page_theme|add:"/base.html" %} +{% load static %} +{% load i18n %} + +{% block content %} +
+ + {{ page_content|safe }} + +{% endblock content %} diff --git a/pages/templates/pages/page_form.html b/pages/templates/pages/page_edit.html similarity index 100% rename from pages/templates/pages/page_form.html rename to pages/templates/pages/page_edit.html diff --git a/pages/templates/pages/page_rename.html b/pages/templates/pages/page_rename.html new file mode 100644 index 0000000..ebfb938 --- /dev/null +++ b/pages/templates/pages/page_rename.html @@ -0,0 +1,15 @@ +{% extends "themes/"|add:settings.page_theme|add:"/base.html" %} +{% load static %} +{% load i18n %} + +{% block content %} + + + {{ page_content|safe }} + +{% endblock content %} diff --git a/pages/views.py b/pages/views.py index 287f7a8..9631542 100644 --- a/pages/views.py +++ b/pages/views.py @@ -12,11 +12,11 @@ from . import url_page from . import get_search_query import config from .context import context_adaption -from .forms import EditForm +from .forms import EditForm, RenameForm from .help import help_pages import mycreole from .page import page_wrapped, page_list -from .search import whoosh_search +from .search import whoosh_search, load_index, delete_item, add_item from themes import Context logger = logging.getLogger(settings.ROOT_LOGGER_NAME).getChild(__name__) @@ -52,7 +52,8 @@ def page(request, rel_path): rel_path=rel_path, title=p.title, upload_path=p.attachment_path, - page_content=page_content + page_content=page_content, + is_available=p.userpage_is_available() ) return render(request, 'pages/page.html', context=context) @@ -73,12 +74,14 @@ def edit(request, rel_path): context_adaption( context, request, + rel_path=rel_path, + is_available=p.userpage_is_available(), 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) + return render(request, 'pages/page_edit.html', context=context) else: p = page_wrapped(request, rel_path) # @@ -91,7 +94,7 @@ def edit(request, rel_path): if p.update_page(page_txt, tags): messages.edit_success(request) else: - messages.edit_no_change(request) + messages.no_change(request) return HttpResponseRedirect(url_page(rel_path)) elif preview is not None: form = EditForm(page_data=page_txt, page_tags=tags) @@ -99,13 +102,15 @@ def edit(request, rel_path): context_adaption( context, request, + rel_path=rel_path, + is_available=p.userpage_is_available(), form=form, # TODO: Add translation title=_("Edit page %s") % repr(p.title), upload_path=p.attachment_path, page_content=p.render_text(request, page_txt) ) - return render(request, 'pages/page_form.html', context=context) + return render(request, 'pages/page_edit.html', context=context) else: return HttpResponseRedirect(url_page(rel_path)) else: @@ -113,6 +118,96 @@ def edit(request, rel_path): return HttpResponseRedirect(url_page(rel_path)) +def delete(request, rel_path): + if access.write_page(request, rel_path): + context = Context(request) # needs to be executed first because of time mesurement + # + if not request.POST: + p = page_wrapped(request, rel_path) + # + # form = DeleteForm(page_data=p.raw_page_src, page_tags=p.tags) + # + context_adaption( + context, + request, + rel_path=rel_path, + is_available=p.userpage_is_available(), + # form=form, + # TODO: Add translation + title=_("Delete page %s") % repr(p.title), + upload_path=p.attachment_path, + page_content=p.render_to_html(), + ) + else: + p = page_wrapped(request, rel_path) + # + delete = request.POST.get("delete") + # + if delete: + # delete page from search index + ix = load_index() + delete_item(ix, p) + # delete move files to history + p.delete() + # add delete message + messages.page_deleted(request, p.title) + return HttpResponseRedirect("/") + else: + messages.operation_canceled(request) + return HttpResponseRedirect(url_page(rel_path)) + return render(request, 'pages/page_delete.html', context=context) + else: + messages.permission_denied_msg_page(request, rel_path) + return HttpResponseRedirect(url_page(rel_path)) + + +def rename(request, rel_path): + if access.write_page(request, rel_path): + context = Context(request) # needs to be executed first because of time mesurement + # + if not request.POST: + p = page_wrapped(request, rel_path) + # + form = RenameForm(page_name=p.rel_path) + # + context_adaption( + context, + request, + rel_path=rel_path, + is_available=p.userpage_is_available(), + form=form, + # TODO: Add translation + title=_("Delete page %s") % repr(p.title), + upload_path=p.attachment_path, + page_content=p.render_to_html(), + ) + else: + p = page_wrapped(request, rel_path) + # + rename = request.POST.get("rename") + page_name = request.POST.get("page_name") + if rename: + if page_name == p.rel_path: + messages.no_change(request) + else: + # delete page from search index + ix = load_index() + delete_item(ix, p) + # rename the storage folder + p.rename(page_name) + # add the renamed page to the search index + add_item(ix, p) + # add rename message + messages.page_renamed(request) + else: + messages.operation_canceled(request) + return HttpResponseRedirect(url_page(p.rel_path)) + return render(request, 'pages/page_rename.html', context=context) + else: + messages.permission_denied_msg_page(request, rel_path) + return HttpResponseRedirect(url_page(rel_path)) + + def search(request): context = Context(request) # needs to be executed first because of time mesurement # diff --git a/piki/urls.py b/piki/urls.py index c5d2c02..26abb48 100644 --- a/piki/urls.py +++ b/piki/urls.py @@ -30,6 +30,8 @@ urlpatterns = [ path('page/', pages.views.root, name='page-root'), path('page/