changeset 456:b1f899f5136d

wiki: Add support for builtin/readonly endpoints.
author Ludovic Chabant <ludovic@chabant.com>
date Wed, 10 Jan 2018 21:23:00 -0800
parents b478eb51c4d5
children 038b22935250
files wikked/endpoint.py wikked/fs.py wikked/resources/defaults.cfg wikked/resources/special/User.md wikked/views/__init__.py wikked/webimpl/__init__.py wikked/webimpl/read.py wikked/wiki.py
diffstat 8 files changed, 112 insertions(+), 53 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/endpoint.py	Wed Jan 10 21:23:00 2018 -0800
@@ -0,0 +1,29 @@
+import os.path
+
+
+class EndpointInfo(object):
+    def __init__(self, name):
+        self.name = name
+        self.query = True
+        self.readonly = False
+        self.builtin = False
+        self.default = None
+        self.root_dir = None
+
+
+_resources_root_dir = os.path.join(os.path.dirname(__file__), 'resources')
+
+
+def create_endpoint_infos(config):
+    endpoints = {}
+    sections = [s for s in config.sections() if s.startswith('endpoint:')]
+    for s in sections:
+        ep = EndpointInfo(s[9:])   # 9 = len('endpoint:')
+        ep.query = config.getboolean(s, 'query', fallback=True)
+        ep.readonly = config.getboolean(s, 'readonly', fallback=False)
+        ep.default = config.get(s, 'default', fallback=None)
+        if config.get(s, '__is_builtin', fallback=False):
+            ep.builtin = True
+            ep.root_dir = os.path.join(_resources_root_dir, ep.name)
+        endpoints[ep.name] = ep
+    return endpoints
--- a/wikked/fs.py	Wed Jan 10 21:21:25 2018 -0800
+++ b/wikked/fs.py	Wed Jan 10 21:23:00 2018 -0800
@@ -15,9 +15,6 @@
 
 valid_filename_pattern = re.compile('^[\w \.\-\(\)\[\]\\/]+$', re.UNICODE)
 
-builtin_namespace_help = os.path.join(os.path.dirname(__file__),
-                                      'resources', 'help')
-
 
 class PageInfo(object):
     def __init__(self, url, path):
@@ -40,12 +37,14 @@
     """
     def __init__(self, root, config):
         self.root = root
+        self.wiki = None
         self.excluded = None
         self.page_extensions = None
         self.default_extension = config.get('wiki', 'default_extension')
-        self.include_builtin_namespaces = True
+        self.include_builtin_endpoints = True
 
     def start(self, wiki):
+        self.wiki = wiki
         self.page_extensions = list(set(
             itertools.chain(*wiki.formatters.values())))
 
@@ -67,8 +66,9 @@
 
         yield from self._getPageInfos(basepath)
 
-        if subdir is None and self.include_builtin_namespaces:
-            yield from self._getPageInfos(builtin_namespace_help)
+        if subdir is None and self.include_builtin_endpoints:
+            for ep in self.wiki.getBuiltinEndpoints():
+                yield from self._getPageInfos(ep.root_dir)
 
     def getPageInfo(self, path):
         logger.debug("Reading page info from: %s" % path)
@@ -131,9 +131,12 @@
         if rel_path.startswith(META_ENDPOINT + os.sep):
             rel_path = rel_path[len(META_ENDPOINT) + 1:]
             meta, rel_path = rel_path.split(os.sep, 1)
-        elif abs_path.startswith(builtin_namespace_help):
-            meta = 'help'
-            rel_path = abs_path[len(builtin_namespace_help) + 1:]
+        elif self.include_builtin_endpoints:
+            for ep in self.wiki.getBuiltinEndpoints():
+                if abs_path.startswith(ep.root_dir):
+                    meta = ep.name
+                    rel_path = abs_path[len(ep.root_dir) + 1:]
+                    break
         rel_path_split = os.path.splitext(rel_path)
         ext = rel_path_split[1].lstrip('.')
         name = rel_path_split[0].replace(os.sep, '/')
@@ -159,7 +162,11 @@
         # page file.
         root = self.root
         if endpoint:
-            root = os.path.join(self.root, META_ENDPOINT, endpoint)
+            ep_info = self.wiki.getEndpoint(endpoint)
+            if ep_info is None or not ep_info.builtin:
+                root = os.path.join(self.root, META_ENDPOINT, endpoint)
+            else:
+                root = ep_info.root_dir
 
         # Make the URL into a relative file-system path.
         url_path = url[1:].replace('/', os.sep)
--- a/wikked/resources/defaults.cfg	Wed Jan 10 21:21:25 2018 -0800
+++ b/wikked/resources/defaults.cfg	Wed Jan 10 21:23:00 2018 -0800
@@ -8,13 +8,19 @@
 database=sql
 database_url=sqlite:///%(root)s/.wiki/wiki.db
 
-[endpoint:special]
-query=False
-
 [endpoint:templates]
 query=False
 
 [endpoint:user]
 query=False
-default=special:User
+default=special:/User
+
+[endpoint:special]
+query=False
+__is_builtin=True
 
+[endpoint:help]
+query=False
+readonly=True
+__is_builtin=True
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/resources/special/User.md	Wed Jan 10 21:23:00 2018 -0800
@@ -0,0 +1,5 @@
+
+This is your personal wiki page. You can write whatever you want here,
+although it's customary to start with a short presentation and some contact
+information.
+
--- a/wikked/views/__init__.py	Wed Jan 10 21:21:25 2018 -0800
+++ b/wikked/views/__init__.py	Wed Jan 10 21:23:00 2018 -0800
@@ -1,7 +1,8 @@
 import urllib.parse
 from flask import request, url_for
 from flask.ext.login import current_user
-from wikked.utils import get_url_folder
+from wikked.utils import get_url_folder, split_page_url
+from wikked.web import get_wiki
 
 
 def add_auth_data(data):
@@ -26,8 +27,13 @@
         home=True, new_page=True,
         read=False, edit=False, history=False, inlinks=False, upload=False,
         raw_url=None, extras=None, footers=None):
+    is_readonly_endpoint = False
     if url is not None:
         url = url.lstrip('/')
+        endpoint, _ = split_page_url(url)
+        if endpoint:
+            epinfo = get_wiki().getEndpoint(endpoint)
+            is_readonly_endpoint = (epinfo is not None and epinfo.readonly)
     elif read or edit or history or inlinks:
         raise Exception("Default navigation entries require a valid URL.")
 
@@ -38,14 +44,14 @@
 
     if home:
         nav['url_home'] = '/'
-    if new_page:
+    if new_page and not is_readonly_endpoint:
         url_folder = get_url_folder(url).lstrip('/')
         nav['url_new'] = url_for('create_page', url_folder=url_folder)
     if read:
         nav['url_read'] = url_for('read', url=url)
-    if edit:
+    if edit and not is_readonly_endpoint:
         nav['url_edit'] = url_for('edit_page', url=url)
-    if history:
+    if history and not is_readonly_endpoint:
         nav['url_hist'] = url_for('page_history', url=url)
 
     if inlinks:
@@ -55,7 +61,7 @@
             'icon': 'link'
             })
 
-    if upload:
+    if upload and not is_readonly_endpoint:
         nav['extras'].append({
             'title': "Upload File",
             'url': url_for('upload_file', p=url),
--- a/wikked/webimpl/__init__.py	Wed Jan 10 21:21:25 2018 -0800
+++ b/wikked/webimpl/__init__.py	Wed Jan 10 21:23:00 2018 -0800
@@ -214,14 +214,14 @@
     return builder
 
 
-def make_page_title(url):
+def make_page_title(url, include_endpoint=False):
     endpoint, path = split_page_url(url)
     last_slash = path.rstrip('/').rfind('/')
     if last_slash < 0 or last_slash == 0:
         title = path.lstrip('/')
     else:
         title = path[last_slash + 1:]
-    if endpoint:
+    if include_endpoint and endpoint:
         return '%s: %s' % (endpoint, title)
     return title
 
--- a/wikked/webimpl/read.py	Wed Jan 10 21:21:25 2018 -0800
+++ b/wikked/webimpl/read.py	Wed Jan 10 21:23:00 2018 -0800
@@ -1,7 +1,8 @@
 import os.path
 import urllib.parse
 from wikked.webimpl import (
-        get_redirect_target, get_page_meta, get_page_or_raise)
+    get_redirect_target, get_page_meta, get_page_or_raise,
+    make_page_title)
 from wikked.utils import split_page_url, PageNotFoundError
 
 
@@ -40,15 +41,17 @@
         # found that's OK.
         info_page = None
 
+    info_page_is_default = False
     endpoint_info = wiki.endpoints.get(endpoint)
-    if endpoint_info is not None:
-        # We have some information about this endpoint...
-        if endpoint_info.default and info_page is None:
-            # Default page text.
-            info_page = get_page_or_raise(
-                    wiki, endpoint_info.default,
-                    fields=['url', 'path', 'title', 'text', 'meta'],
-                    check_perms=(user, 'read'))
+    if (endpoint_info is not None and endpoint_info.default and
+            info_page is None):
+        # We have no actual page to show, but we have a default one
+        # that we can use for this endpoint.
+        info_page = get_page_or_raise(
+            wiki, endpoint_info.default,
+            fields=['url', 'path', 'title', 'text', 'meta'],
+            check_perms=(user, 'read'))
+        info_page_is_default = True
 
     ext = None
     if info_page is not None:
@@ -64,6 +67,20 @@
                 'text': info_page.text,
                 'page_title': info_page.title,
                 'format': ext}
+
+            if info_page_is_default:
+                # If our page is actually the endpoint's default page
+                # because the real page didn't exist, we need to change
+                # the title to match the page that we wanted originally.
+                # We also fix the URL so navigation links to edit/create
+                # the page acts on the wanted page -- not the default info
+                # page.
+                wanted_page_title = make_page_title(meta_page_url)
+                result['page_title'] = wanted_page_title
+                result['meta']['title'] = wanted_page_title
+                result['meta']['url'] = urllib.parse.quote(
+                    meta_page_url.encode('utf-8'))
+
             result.update(additional_info)
             return result
         raise PageNotFoundError(url)
--- a/wikked/wiki.py	Wed Jan 10 21:21:25 2018 -0800
+++ b/wikked/wiki.py	Wed Jan 10 21:23:00 2018 -0800
@@ -6,6 +6,7 @@
 import multiprocessing
 from configparser import SafeConfigParser, NoOptionError
 from wikked.db.base import DatabaseUpgradeRequired
+from wikked.endpoint import create_endpoint_infos
 from wikked.fs import FileSystem
 from wikked.auth import UserManager
 from wikked.scheduler import ResolveScheduler
@@ -204,13 +205,6 @@
                     "No such source control: " + scm_type)
 
 
-class EndpointInfo(object):
-    def __init__(self, name):
-        self.name = name
-        self.query = True
-        self.default = None
-
-
 class Wiki(object):
     """ The wiki class! This is where the magic happens.
     """
@@ -233,7 +227,7 @@
         self.templates_url = (
             parameters.config.get('wiki', 'templates_endpoint') +
             ':/')
-        self.endpoints = self._createEndpointInfos(parameters.config)
+        self.endpoints = create_endpoint_infos(parameters.config)
 
         self.fs = parameters.fs_factory()
         self.index = parameters.index_factory()
@@ -436,21 +430,16 @@
     def getSpecialFilenames(self):
         return self.special_filenames
 
-    def _createEndpointInfos(self, config):
-        endpoints = {}
-        sections = [s for s in config.sections() if s.startswith('endpoint:')]
-        for s in sections:
-            ep = EndpointInfo(s[9:])   # 9 = len('endpoint:')
-            if config.has_option(s, 'query'):
-                ep.query = config.getboolean(s, 'query')
-            if config.has_option(s, 'default'):
-                ep.default = config.get(s, 'default')
-            endpoints[ep.name] = ep
-        # The 'help' endpoint is built-in and is always the same.
-        help_ep = EndpointInfo('help')
-        help_ep.query = False
-        endpoints['help'] = help_ep
-        return endpoints
+    def getEndpoints(self):
+        return self.endpoints.values()
+
+    def getEndpoint(self, name):
+        return self.endpoints.get(name)
+
+    def getBuiltinEndpoints(self):
+        for ep in self.endpoints.values():
+            if ep.builtin:
+                yield ep
 
 
 def reloader_stat_loop(wiki, interval=1):