# HG changeset patch # User Tero Marttila # Date 1412418779 -10800 # Node ID f5227f26231b85ce34f1fbc7d92d0c5e12d41cbb # Parent 5a16a53e9800b0ba93f1d0cfec909a4a50ea628a move qmsk_www_pages to qmsk.pages diff -r 5a16a53e9800 -r f5227f26231b qmsk/pages/pages.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/qmsk/pages/pages.py Sat Oct 04 13:32:59 2014 +0300 @@ -0,0 +1,379 @@ +from django.conf import settings + +import codecs +import datetime +import logging; log = logging.getLogger('qmsk.pages.pages') +import os, os.path + +import markdown +import mako.template + +class NotFound (Exception): + pass + +class RenderError (Exception): + pass + +class Site (object): + @classmethod + def lookup (cls): + return cls( + root = settings.QMSK_PAGES_DIR, + name = settings.QMSK_PAGES_SITE, + ) + + def __init__ (self, root, name): + self.root = root + self.name = name + + def tree (self): + return Tree(self.root, None, (), self, + title = self.name, + ) + +class Tree (object): + INDEX = 'index' + + @classmethod + def lookup (cls, site, parts): + """ + Returns Tree + + Raises NotFound + """ + + parents = ( ) + tree = site.tree() + + for name in parts: + if name.startswith('.'): + # evil + raise NotFound() + + if not name: + continue + + path = os.path.join(tree.path, name) + + if not os.path.exists(path): + raise NotFound() + + if not os.path.isdir(path): + raise NotFound() + + # title + title = tree.item_title(name) + + parents += (tree, ) + tree = cls(path, name, parents, site, + title = title, + ) + + return tree + + def __init__ (self, path, name, parents, site, + title = None, + ): + """ + path: filesystem path + name: subtree name, or None for root + parents: (Tree) + site: Site + """ + + self.path = path + self.name = name + self.parents = parents + self.site = site + + self.title = title or name + + def hierarchy (self): + """ + Yield Tree. + """ + + for tree in self.parents: + yield tree + + yield self + + def url (self, tree=None, page=None): + path = '/'.join(tree.name for tree in self.hierarchy() if tree.name is not None) + + if path: + path += '/' + + if tree: + path = tree + '/' + + if page: + path += page + + return path + + def scan (self): + """ + Scan for files in tree. + """ + + for filename in os.listdir(self.path): + if filename.startswith('.'): + continue + + if '.' in filename: + file_name, file_type = filename.rsplit('.', 1) + else: + file_name = filename + file_type = None + + if not file_name: + continue + + path = os.path.join(self.path, filename) + + yield path, file_name, file_type + + def item_title (self, name): + """ + Lookup item title if exists. + """ + + title_path = os.path.join(self.path, name + '.title') + + log.info("%s: %s title_path=%s", self, name, title_path) + + if os.path.exists(title_path): + return open(title_path).read().strip() + else: + return None + + def list (self): + """ + Lists all Trees and Pages for this Tree. + + Yields (name, url, page_type or None, title) + """ + + for path, name, file_type in self.scan(): + title = self.item_title(name) or name + + # trees + if os.path.isdir(path): + yield name, self.url(tree=name), None, title + + if name == self.INDEX: + continue + + # pages + if not file_type: + continue + + if file_type not in TYPES: + continue + + yield name, self.url(page=name), file_type, title + + def list_sorted (self): + return sorted(list(self.list())) + + def page (self, name): + """ + Scans through tree looking for a matching page. + + Returns Page or None. + """ + + if not name: + name = self.INDEX + title_default = self.title + else: + title_default = None + + parents = self.parents + (self, ) + + for path, file_name, file_type in self.scan(): + # match on name + if file_name == name: + pass + elif file_type and (file_name + '.' + file_type == name): + pass + else: + continue + + # redirects? + if os.path.islink(path): + target = os.readlink(path) + + # XXX: this should be some kind of common code + if '.' in target: + target, target_type = target.rsplit('.', 1) + + log.info("%s: %s -> %s", self, name, target) + + return RedirectPage(path, name, self, parents, + target = target, + ) + + # match on type + if not file_type: + continue + + page_type = TYPES.get(file_type) + + if not page_type: + continue + + # out + title = self.item_title(file_name) or title_default + + return page_type(path, name, self, parents, + title = title, + ) + +class Page (object): + ENCODING = 'utf-8' + + @classmethod + def lookup (cls, site, page): + """ + Returns Page. + + Raises NotFound + """ + + log.info("page=%r", page) + + if page: + parts = page.split('/') + else: + parts = [ ] + + if parts: + page_name = parts.pop(-1) + tree_parts = parts + else: + page_name = '' + tree_parts = [] + + # scan dir + tree = Tree.lookup(site, tree_parts) + + # scan page + page = tree.page(page_name) + + if not page: + raise NotFound() + + return page + + def __init__ (self, path, name, tree, parents=(), encoding=ENCODING, title=None): + self.path = path + self.name = name + self.tree = tree + self.parents = parents + + self.encoding = encoding + self.title = title or name + + def hierarchy (self): + """ + Yield (Tree, name) pairs + """ + + parent = None + + for tree in self.parents: + if parent: + yield parent, tree.name + + parent = tree + + yield parent, self.name + + def url (self): + return self.tree.url(page=self.name) + + def open (self): + return codecs.open(self.path, encoding=self.encoding) + + def stat (self): + return os.stat(self.path) + + def breadcrumb (self): + for tree in self.tree.hierarchy(): + yield tree.url(), tree.title + + if self.name != self.tree.INDEX: + yield self.url(), self.title + + def modified (self): + return datetime.datetime.utcfromtimestamp(self.stat().st_mtime) + + def redirect_page (self, request): + return None + + def render_html (self, request): + raise NotImplementedError() + +# TODO: tree redirects +class RedirectPage (Page): + def __init__ (self, path, name, tree, parents, + target, + **opts + ) : + super(RedirectPage, self).__init__(path, name, tree, parents, **opts) + + self.target = target + + def redirect_page (self, request): + return os.path.normpath(self.tree.url() + '/' + self.target) + +class HTML_Page (Page): + def render_html (self, request): + return self.open().read() + +class MarkdownPage (Page): + FORMAT = 'html5' + + def __init__ (self, path, name, tree, parents, + format=FORMAT, + **opts + ) : + super(MarkdownPage, self).__init__(path, name, tree, parents, **opts) + + self.format = format + + def render_html (self, request): + return markdown.markdown(self.open().read(), + output_format = self.format, + ) + +class TemplatePage (Page): + def render_html (self, request): + """ + Raises RenderError if !DEBUG, arbitrary error with stack trace otherwise. + """ + + try: + return mako.template.Template(filename=self.path).render( + request = request, + ) + except Exception as error: + if settings.DEBUG: + raise + else: + raise RenderError(error) + +SITE = Site.lookup() + +TYPES = { + 'html': HTML_Page, + 'md': MarkdownPage, + 'markdown': MarkdownPage, + 'tmpl': TemplatePage, +} + +def page (page): + return Page.lookup(SITE, page) + diff -r 5a16a53e9800 -r f5227f26231b qmsk/pages/static/qmsk.pages/pages.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/qmsk/pages/static/qmsk.pages/pages.css Sat Oct 04 13:32:59 2014 +0300 @@ -0,0 +1,3 @@ +li.page-tree-item i.glyphicon { + float: right; +} diff -r 5a16a53e9800 -r f5227f26231b qmsk/pages/templates/qmsk.pages/error.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/qmsk/pages/templates/qmsk.pages/error.html Sat Oct 04 13:32:59 2014 +0300 @@ -0,0 +1,41 @@ +{% extends "site.html" %} + +{% block title %}{{ site_name }} :: {{ error_title }}{%endblock %} + +{% block header %} +

+ {{ site_name }} +

+{% endblock %} + +{% block nav %} +{% for tree, tree_name in page_hierarchy %} + {% if not forloop.first %} +
+ {% endif %} + +{% endfor %} +{% endblock %} + +{% block content %} +

{{ error_title }}

+ +{% endblock %} diff -r 5a16a53e9800 -r f5227f26231b qmsk/pages/templates/qmsk.pages/page.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/qmsk/pages/templates/qmsk.pages/page.html Sat Oct 04 13:32:59 2014 +0300 @@ -0,0 +1,50 @@ +{% extends "site.html" %} + +{% block title %}{{ site_name }} :: {{ page_title }}{% endblock %} + +{% block header %} +

+ {{ site_name }} +

+{% endblock %} + +{% block breadcrumb %} + +{% endblock %} + +{% block nav %} +{% for tree, tree_name in page_hierarchy %} + {% if not forloop.first %} +
+ {% endif %} + +{% endfor %} +{% endblock %} + +{% block content %} +

{{ page_title }}

+ + {{ page_html|safe }} +{% endblock %} + +{% block footer %} + +{% endblock %} + diff -r 5a16a53e9800 -r f5227f26231b qmsk/pages/urls.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/qmsk/pages/urls.py Sat Oct 04 13:32:59 2014 +0300 @@ -0,0 +1,7 @@ +from django.conf.urls import patterns, include, url + +from qmsk.pages import views + +urlpatterns = patterns('', + url(r'^(?P.*)$', views.page, name='page'), +) diff -r 5a16a53e9800 -r f5227f26231b qmsk/pages/views.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/qmsk/pages/views.py Sat Oct 04 13:32:59 2014 +0300 @@ -0,0 +1,55 @@ +from django import http +from django.shortcuts import render, redirect + +from qmsk.pages import pages + +def page_error (request, page, status, title, error_message=None, error_exception=None): + site = pages.SITE + + if page: + page_hierarchy = list(page.hierarchy()) + else: + tree = site.tree() + page_hierarchy = [(tree, None)] + + return render(request, 'qmsk.pages/error.html', dict( + site_name = site.name, + page_hierarchy = page_hierarchy, + error_page = page, + error_title = title, + error_message = error_message, + error_output = str(error_exception) if error_exception else None, + ), status=status) + +# Create your views here. +def page (request, page): + try: + page = pages.page(page) + except pages.NotFound as error: + return page_error(request, None, + status = 404, + title = u"Not Found", + error_message = page, + ) + + redirect_page = page.redirect_page(request) + + if redirect_page: + return redirect('page', redirect_page) + + try: + return render(request, 'qmsk.pages/page.html', dict( + site_name = page.tree.site.name, + page_name = page.name, + page_title = page.title, + page_breadcrumb = page.breadcrumb(), + page_hierarchy = list(page.hierarchy()), + page_html = page.render_html(request), + page_modified = page.modified(), + )) + except pages.RenderError as error: + return page_error(request, page, + status = 500, + title = u"Server Error: {page}".format(page=page.url()), + error_exception = error, + ) diff -r 5a16a53e9800 -r f5227f26231b qmsk_www/settings/pages.py --- a/qmsk_www/settings/pages.py Sat Oct 04 13:32:35 2014 +0300 +++ b/qmsk_www/settings/pages.py Sat Oct 04 13:32:59 2014 +0300 @@ -1,2 +1,2 @@ -QMSK_WWW_PAGES_DIR = './pages' -QMSK_WWW_PAGES_SITE = "qmsk.net" +QMSK_PAGES_DIR = './pages' +QMSK_PAGES_SITE = "qmsk.net" diff -r 5a16a53e9800 -r f5227f26231b qmsk_www/settings/site.py --- a/qmsk_www/settings/site.py Sat Oct 04 13:32:35 2014 +0300 +++ b/qmsk_www/settings/site.py Sat Oct 04 13:32:59 2014 +0300 @@ -2,7 +2,7 @@ INSTALLED_APPS = ( 'django.contrib.staticfiles', - 'qmsk_www_pages', + 'qmsk.pages', ) MIDDLEWARE_CLASSES = ( diff -r 5a16a53e9800 -r f5227f26231b qmsk_www/templates/site.html --- a/qmsk_www/templates/site.html Sat Oct 04 13:32:35 2014 +0300 +++ b/qmsk_www/templates/site.html Sat Oct 04 13:32:59 2014 +0300 @@ -11,7 +11,7 @@ - + {% block head %} diff -r 5a16a53e9800 -r f5227f26231b qmsk_www/urls.py --- a/qmsk_www/urls.py Sat Oct 04 13:32:35 2014 +0300 +++ b/qmsk_www/urls.py Sat Oct 04 13:32:59 2014 +0300 @@ -2,9 +2,7 @@ urlpatterns = patterns('', # Examples: - # url(r'^$', 'qmsk_www.views.home', name='home'), - # url(r'^blog/', include('blog.urls')), - url(r'^', include('qmsk_www_pages.urls')), + url(r'^', include('qmsk.pages.urls')), ) if False: diff -r 5a16a53e9800 -r f5227f26231b qmsk_www_pages/pages.py --- a/qmsk_www_pages/pages.py Sat Oct 04 13:32:35 2014 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,379 +0,0 @@ -from django.conf import settings - -import codecs -import datetime -import logging; log = logging.getLogger('qmsk_www_pages.pages') -import os, os.path - -import markdown -import mako.template - -class NotFound (Exception): - pass - -class RenderError (Exception): - pass - -class Site (object): - @classmethod - def lookup (cls): - return cls( - root = settings.QMSK_WWW_PAGES_DIR, - name = settings.QMSK_WWW_PAGES_SITE, - ) - - def __init__ (self, root, name): - self.root = root - self.name = name - - def tree (self): - return Tree(self.root, None, (), self, - title = self.name, - ) - -class Tree (object): - INDEX = 'index' - - @classmethod - def lookup (cls, site, parts): - """ - Returns Tree - - Raises NotFound - """ - - parents = ( ) - tree = site.tree() - - for name in parts: - if name.startswith('.'): - # evil - raise NotFound() - - if not name: - continue - - path = os.path.join(tree.path, name) - - if not os.path.exists(path): - raise NotFound() - - if not os.path.isdir(path): - raise NotFound() - - # title - title = tree.item_title(name) - - parents += (tree, ) - tree = cls(path, name, parents, site, - title = title, - ) - - return tree - - def __init__ (self, path, name, parents, site, - title = None, - ): - """ - path: filesystem path - name: subtree name, or None for root - parents: (Tree) - site: Site - """ - - self.path = path - self.name = name - self.parents = parents - self.site = site - - self.title = title or name - - def hierarchy (self): - """ - Yield Tree. - """ - - for tree in self.parents: - yield tree - - yield self - - def url (self, tree=None, page=None): - path = '/'.join(tree.name for tree in self.hierarchy() if tree.name is not None) - - if path: - path += '/' - - if tree: - path = tree + '/' - - if page: - path += page - - return path - - def scan (self): - """ - Scan for files in tree. - """ - - for filename in os.listdir(self.path): - if filename.startswith('.'): - continue - - if '.' in filename: - file_name, file_type = filename.rsplit('.', 1) - else: - file_name = filename - file_type = None - - if not file_name: - continue - - path = os.path.join(self.path, filename) - - yield path, file_name, file_type - - def item_title (self, name): - """ - Lookup item title if exists. - """ - - title_path = os.path.join(self.path, name + '.title') - - log.info("%s: %s title_path=%s", self, name, title_path) - - if os.path.exists(title_path): - return open(title_path).read().strip() - else: - return None - - def list (self): - """ - Lists all Trees and Pages for this Tree. - - Yields (name, url, page_type or None, title) - """ - - for path, name, file_type in self.scan(): - title = self.item_title(name) or name - - # trees - if os.path.isdir(path): - yield name, self.url(tree=name), None, title - - if name == self.INDEX: - continue - - # pages - if not file_type: - continue - - if file_type not in TYPES: - continue - - yield name, self.url(page=name), file_type, title - - def list_sorted (self): - return sorted(list(self.list())) - - def page (self, name): - """ - Scans through tree looking for a matching page. - - Returns Page or None. - """ - - if not name: - name = self.INDEX - title_default = self.title - else: - title_default = None - - parents = self.parents + (self, ) - - for path, file_name, file_type in self.scan(): - # match on name - if file_name == name: - pass - elif file_type and (file_name + '.' + file_type == name): - pass - else: - continue - - # redirects? - if os.path.islink(path): - target = os.readlink(path) - - # XXX: this should be some kind of common code - if '.' in target: - target, target_type = target.rsplit('.', 1) - - log.info("%s: %s -> %s", self, name, target) - - return RedirectPage(path, name, self, parents, - target = target, - ) - - # match on type - if not file_type: - continue - - page_type = TYPES.get(file_type) - - if not page_type: - continue - - # out - title = self.item_title(file_name) or title_default - - return page_type(path, name, self, parents, - title = title, - ) - -class Page (object): - ENCODING = 'utf-8' - - @classmethod - def lookup (cls, site, page): - """ - Returns Page. - - Raises NotFound - """ - - log.info("page=%r", page) - - if page: - parts = page.split('/') - else: - parts = [ ] - - if parts: - page_name = parts.pop(-1) - tree_parts = parts - else: - page_name = '' - tree_parts = [] - - # scan dir - tree = Tree.lookup(site, tree_parts) - - # scan page - page = tree.page(page_name) - - if not page: - raise NotFound() - - return page - - def __init__ (self, path, name, tree, parents=(), encoding=ENCODING, title=None): - self.path = path - self.name = name - self.tree = tree - self.parents = parents - - self.encoding = encoding - self.title = title or name - - def hierarchy (self): - """ - Yield (Tree, name) pairs - """ - - parent = None - - for tree in self.parents: - if parent: - yield parent, tree.name - - parent = tree - - yield parent, self.name - - def url (self): - return self.tree.url(page=self.name) - - def open (self): - return codecs.open(self.path, encoding=self.encoding) - - def stat (self): - return os.stat(self.path) - - def breadcrumb (self): - for tree in self.tree.hierarchy(): - yield tree.url(), tree.title - - if self.name != self.tree.INDEX: - yield self.url(), self.title - - def modified (self): - return datetime.datetime.utcfromtimestamp(self.stat().st_mtime) - - def redirect_page (self, request): - return None - - def render_html (self, request): - raise NotImplementedError() - -# TODO: tree redirects -class RedirectPage (Page): - def __init__ (self, path, name, tree, parents, - target, - **opts - ) : - super(RedirectPage, self).__init__(path, name, tree, parents, **opts) - - self.target = target - - def redirect_page (self, request): - return os.path.normpath(self.tree.url() + '/' + self.target) - -class HTML_Page (Page): - def render_html (self, request): - return self.open().read() - -class MarkdownPage (Page): - FORMAT = 'html5' - - def __init__ (self, path, name, tree, parents, - format=FORMAT, - **opts - ) : - super(MarkdownPage, self).__init__(path, name, tree, parents, **opts) - - self.format = format - - def render_html (self, request): - return markdown.markdown(self.open().read(), - output_format = self.format, - ) - -class TemplatePage (Page): - def render_html (self, request): - """ - Raises RenderError if !DEBUG, arbitrary error with stack trace otherwise. - """ - - try: - return mako.template.Template(filename=self.path).render( - request = request, - ) - except Exception as error: - if settings.DEBUG: - raise - else: - raise RenderError(error) - -SITE = Site.lookup() - -TYPES = { - 'html': HTML_Page, - 'md': MarkdownPage, - 'markdown': MarkdownPage, - 'tmpl': TemplatePage, -} - -def page (page): - return Page.lookup(SITE, page) - diff -r 5a16a53e9800 -r f5227f26231b qmsk_www_pages/static/pages/pages.css --- a/qmsk_www_pages/static/pages/pages.css Sat Oct 04 13:32:35 2014 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -li.page-tree-item i.glyphicon { - float: right; -} diff -r 5a16a53e9800 -r f5227f26231b qmsk_www_pages/templates/pages/error.html --- a/qmsk_www_pages/templates/pages/error.html Sat Oct 04 13:32:35 2014 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -{% extends "site.html" %} - -{% block title %}{{ site_name }} :: {{ error_title }}{%endblock %} - -{% block header %} -

- {{ site_name }} -

-{% endblock %} - -{% block nav %} -{% for tree, tree_name in page_hierarchy %} - {% if not forloop.first %} -
- {% endif %} - -{% endfor %} -{% endblock %} - -{% block content %} -

{{ error_title }}

- -{% endblock %} diff -r 5a16a53e9800 -r f5227f26231b qmsk_www_pages/templates/pages/page.html --- a/qmsk_www_pages/templates/pages/page.html Sat Oct 04 13:32:35 2014 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -{% extends "site.html" %} - -{% block title %}{{ site_name }} :: {{ page_title }}{% endblock %} - -{% block header %} -

- {{ site_name }} -

-{% endblock %} - -{% block breadcrumb %} - -{% endblock %} - -{% block nav %} -{% for tree, tree_name in page_hierarchy %} - {% if not forloop.first %} -
- {% endif %} - -{% endfor %} -{% endblock %} - -{% block content %} -

{{ page_title }}

- - {{ page_html|safe }} -{% endblock %} - -{% block footer %} - -{% endblock %} - diff -r 5a16a53e9800 -r f5227f26231b qmsk_www_pages/urls.py --- a/qmsk_www_pages/urls.py Sat Oct 04 13:32:35 2014 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -from django.conf.urls import patterns, include, url - -from qmsk_www_pages import views - -urlpatterns = patterns('', - url(r'^(?P.*)$', views.page, name='page'), -) diff -r 5a16a53e9800 -r f5227f26231b qmsk_www_pages/views.py --- a/qmsk_www_pages/views.py Sat Oct 04 13:32:35 2014 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -from django import http -from django.shortcuts import render, redirect - -from qmsk_www_pages import pages - -def page_error (request, page, status, title, error_message=None, error_exception=None): - site = pages.SITE - - if page: - page_hierarchy = list(page.hierarchy()) - else: - tree = site.tree() - page_hierarchy = [(tree, None)] - - return render(request, 'pages/error.html', dict( - site_name = site.name, - page_hierarchy = page_hierarchy, - error_page = page, - error_title = title, - error_message = error_message, - error_output = str(error_exception) if error_exception else None, - ), status=status) - -# Create your views here. -def page (request, page): - try: - page = pages.page(page) - except pages.NotFound as error: - return page_error(request, None, - status = 404, - title = u"Not Found", - error_message = page, - ) - - redirect_page = page.redirect_page(request) - - if redirect_page: - return redirect('page', redirect_page) - - try: - return render(request, 'pages/page.html', dict( - site_name = page.tree.site.name, - page_name = page.name, - page_title = page.title, - page_breadcrumb = page.breadcrumb(), - page_hierarchy = list(page.hierarchy()), - page_html = page.render_html(request), - page_modified = page.modified(), - )) - except pages.RenderError as error: - return page_error(request, page, - status = 500, - title = u"Server Error: {page}".format(page=page.url()), - error_exception = error, - )