Mercurial > libervia-web
changeset 712:bf562fb9c273
server_side: use Jinja2 template engine for static blog
author | souliane <souliane@mailoo.org> |
---|---|
date | Mon, 13 Jul 2015 18:11:38 +0200 |
parents | 052d1d19016d |
children | d75935e2b279 |
files | setup.py src/server/blog.py src/twisted/plugins/libervia_server.py themes/default/__init__.py themes/default/static_blog.html themes/default/static_blog_error.html themes/default/styles/blog.css themes/default/templates.py |
diffstat | 7 files changed, 235 insertions(+), 210 deletions(-) [+] |
line wrap: on
line diff
--- a/setup.py Mon Jul 13 13:33:01 2015 +0200 +++ b/setup.py Mon Jul 13 18:11:38 2015 +0200 @@ -297,6 +297,6 @@ scripts=[], zip_safe=False, dependency_links=['http://www.blarg.net/%7Esteveha/pyfeed-0.7.4.tar.gz', 'http://www.blarg.net/%7Esteveha/xe-0.7.4.tar.gz'], - install_requires=['sat', 'twisted', 'pyfeed', 'xe', 'txJSON-RPC', 'zope.interface', 'pyopenssl'], + install_requires=['sat', 'twisted', 'pyfeed', 'xe', 'txJSON-RPC', 'zope.interface', 'pyopenssl', 'jinja2'], cmdclass={'install': CustomInstall}, )
--- a/src/server/blog.py Mon Jul 13 13:33:01 2015 +0200 +++ b/src/server/blog.py Mon Jul 13 18:11:38 2015 +0200 @@ -3,6 +3,7 @@ # Libervia: a Salut à Toi frontend # Copyright (C) 2011, 2012, 2013, 2014, 2015 Jérôme Poisson <goffi@goffi.org> +# Copyright (C) 2013, 2014, 2015 Adrien Cossa <souliane@mailoo.org> # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -20,15 +21,16 @@ from sat.core.i18n import _, D_ from sat_frontends.tools.strings import addURLToText from sat.core.log import getLogger +from django.conf.urls import url log = getLogger(__name__) from twisted.internet import defer from twisted.web import server from twisted.web.resource import Resource from twisted.words.protocols.jabber.jid import JID +from jinja2 import Environment, PackageLoader from datetime import datetime from sys import path -import importlib import uuid import re import os @@ -45,22 +47,22 @@ self.host = host # add Libervia's themes directory to the python path - path.append(os.path.dirname(self.host.themes_dir)) + path.append(os.path.dirname(os.path.normpath(self.host.themes_dir))) + themes = os.path.basename(os.path.normpath(self.host.themes_dir)) + self.env = Environment(loader=PackageLoader(themes, self.THEME)) def useTemplate(self, request, tpl, data=None): root_url = '../' * len(request.postpath) theme_url = os.path.join(root_url, 'themes', self.THEME) - # import the theme module - themes = os.path.basename(os.path.dirname(os.path.dirname(self.host.themes_dir))) - theme = importlib.import_module("%s.templates" % self.THEME, themes) - data_ = {'theme': theme_url, - 'images': os.path.join(theme_url, 'images'), + data_ = {'images': os.path.join(theme_url, 'images'), 'styles': os.path.join(theme_url, 'styles'), } if data: data_.update(data) - return getattr(theme, tpl.upper()).encode('utf-8').format(**data_) + + template = self.env.get_template('%s.html' % tpl) + return template.render(**data_).encode('utf-8') class MicroBlog(Resource, TemplateProcessor): @@ -126,20 +128,20 @@ jid_s = (profile + '@' + self.host.bridge.getNewAccountDomain()).lower() if jid_s in self.avatars_cache: return defer.succeed(self.avatars_cache[jid_s]) - # FIXME: request_id is no more need when actionResult is removed + # FIXME: request_id is no more needed when actionResult is removed request_id = self.host.bridge.getCard(jid_s, C.SERVICE_PROFILE) self.waiting_deferreds[jid_s] = (request_id, defer.Deferred()) return self.waiting_deferreds[jid_s][1] def render_GET(self, request): if not request.postpath: - return self.useTemplate(request, "error", {'message': "You must indicate a nickname"}) + return self.useTemplate(request, "static_blog_error", {'message': "You must indicate a nickname"}) prof_requested = request.postpath[0] - #TODO: char check: only use alphanumerical chars + some extra(_,-,...) here + #TODO : char check: only use alphanumeric chars + some extra(_,-,...) here prof_found = self.host.bridge.getProfileName(prof_requested) if not prof_found or prof_found == C.SERVICE_PROFILE: - return self.useTemplate(request, "error", {'message': "Invalid nickname"}) + return self.useTemplate(request, "static_blog_error", {'message': "Invalid nickname"}) d = defer.Deferred() JID(self.host.bridge.asyncGetParamA('JabberID', 'Connection', 'value', C.SERVER_SECURITY_LIMIT, prof_found, callback=d.callback, errback=d.errback)) @@ -266,43 +268,48 @@ def getOption(key): return sanitizeHtml(options[key]).encode('utf-8') if key in options else '' - def getImageOption(key, default, alt): + def getImageParams(key, default, alt): """regexp from http://answers.oreilly.com/topic/280-how-to-validate-urls-with-regular-expressions/""" url = options[key].encode('utf-8') if key in options else '' regexp = r"^(https?|ftp)://[a-z0-9-]+(\.[a-z0-9-]+)+(/[\w-]+)*/[\w-]+\.(gif|png|jpg)$" if re.match(regexp, url): url = url - suffix = "<br/>" else: url = default - suffix = "" - return self.useTemplate(request, "banner", {'alt': alt, 'url': url, 'suffix': suffix}) + return BlogImage(url, alt) avatar = os.path.normpath(root_url + getOption('avatar')) title = getOption(C.STATIC_BLOG_PARAM_TITLE) or user data = {'base_url': base_url, - 'user': user, 'keywords': getOption(C.STATIC_BLOG_PARAM_KEYWORDS), 'description': getOption(C.STATIC_BLOG_PARAM_DESCRIPTION), - 'title': getOption(C.STATIC_BLOG_PARAM_TITLE) or "%s's microblog" % user, + 'title': title, 'favicon': avatar, - 'banner_elt': getImageOption(C.STATIC_BLOG_PARAM_BANNER, avatar, title), - 'title_elt': title, + 'banner_img': getImageParams(C.STATIC_BLOG_PARAM_BANNER, avatar, title) } mblog_data, main_rsm = mblog_data mblog_data = [(entry if isinstance(entry, tuple) else (entry, ([], {}))) for entry in mblog_data] - mblog_data = sorted(mblog_data, key=lambda entry: (-float(entry[0].get('updated', 0)))) + mblog_data.sort(key=lambda entry: (-float(entry[0].get('updated', 0)))) - data.update(self.getNavigationLinks(request, mblog_data, main_rsm, base_url)) - request.write(self.useTemplate(request, 'header', data)) + data['navlinks'] = NavigationLinks(request, mblog_data, main_rsm, base_url) + data['messages'] = [BlogMessage(request, base_url, entry, comments[0]) for entry, comments in mblog_data] - BlogMessages(self.host, request, base_url, mblog_data).render() - - request.write(self.useTemplate(request, "footer", data)) + request.write(self.useTemplate(request, 'static_blog', data)) request.finish() - def getNavigationLinks(self, request, mblog_data, rsm_data, base_url): + def render_atom_feed(self, feed, request): + request.write(feed.encode('utf-8')) + request.finish() + + def render_error_blog(self, error, request, profile): + request.write(self.useTemplate(request, "static_blog_error", {'message': "Can't access requested data"})) + request.finish() + + +class NavigationLinks(object): + + def __init__(self, request, mblog_data, rsm_data, base_url): """Build the navigation links. @param mblog_data (dict): the microblogs that are displayed on the page @@ -310,10 +317,9 @@ @param base_url (unicode): the base URL for this user's blog @return: dict """ - data = {} for key in ('later_message', 'later_messages', 'older_message', 'older_messages'): count = int(rsm_data.get('count', 0)) - data[key] = '' # key must exist when using the template + setattr(self, key, '') # key must exist when using the template if count <= 0 or (request.display_single == key.endswith('s')): continue @@ -337,33 +343,64 @@ link = "%(base_url)s?%(post_arg)s=%(item_id)s%(suffix)s" % link_data - link_data = {'link': link, 'class': key, 'text': key.replace('_', ' ')} - data[key] = (self.useTemplate(request, 'nav_link', link_data)).encode('utf-8') + setattr(self, key, BlogLink(link, key, key.replace('_', ' '))) - return data + +class BlogImage(object): - def render_atom_feed(self, feed, request): - request.write(feed.encode('utf-8')) - request.finish() + def __init__(self, url_, alt): + self.url = url_ + self.alt = alt + - def render_error_blog(self, error, request, profile): - request.write(self.useTemplate(request, "error", {'message': "Can't access requested data"})) - request.finish() +class BlogLink(object): + + def __init__(self, url_, style, text): + self.url = url_ + self.style = style + self.text = text -class BlogMessages(TemplateProcessor): +class BlogMessage(object): + + def __init__(self, request, base_url, entry, comments=None): + """ + + @param request: HTTP request + @param base_url (unicode): the base URL + @param entry (dict{unicode:unicode]): microblog entry received from the backend + @param comments (list[dict]): comments + """ + timestamp = float(entry.get('published', 0)) + is_comment = entry['type'] == 'comment' + + self.date = datetime.fromtimestamp(timestamp) + self.type = entry['type'] + self.style = 'mblog_comment' if entry['type'] == 'comment' else '' + self.content = self.getText(entry, 'content') - def __init__(self, host, request, base_url, mblog_data): - TemplateProcessor.__init__(self, host) - self.request = request - self.base_url = base_url - self.mblog_data = mblog_data + if is_comment: + self.author = (_("from %s") % entry['author']).encode('utf-8') + else: + self.author = ' ' + self.url = (u"%s/%s" % (base_url, entry['id'])).encode('utf-8') + self.title = self.getText(entry, 'title') + + comments_count = int(entry['comments_count']) + count_text = lambda count: D_('comments') if count > 1 else D_('comment') - def render(self): - for entry, comments_data in self.mblog_data: - comments, comments_rsm = comments_data - comments = sorted(comments, key=lambda entry: (float(entry.get('published', 0)))) - self.render_html(entry, comments) + self.comments_text = "%s %s" % (comments_count, count_text(comments_count)) + + delta = comments_count - len(comments) + if request.display_single and delta > 0: + prev_url = "%s?max=%s" % (self.url, entry['comments_count']) + prev_text = D_("show %(count)d previous %(comments)s") % \ + {'count': delta, 'comments': count_text(delta)} + self.all_comments_link = BlogLink(prev_url, "comments_link", prev_text) + + if comments: + comments.sort(key=lambda entry: float(entry.get('published', 0))) + self.comments = [BlogMessage(request, base_url, comment) for comment in comments] def getText(self, entry, key): if ('%s_xhtml' % key) in entry: @@ -372,51 +409,3 @@ processor = addURLToText if key.startswith('content') else sanitizeHtml return convertNewLinesToXHTML(processor(entry[key])).encode('utf-8') return None - - def render_html(self, entry, comments=None): - """Render one microblog entry. - @param entry: the microblog entry - @param base_url: the base url of the blog - @param request: the HTTP request - """ - timestamp = float(entry.get('published', 0)) - is_comment = entry['type'] == 'comment' - - data = {'date': datetime.fromtimestamp(timestamp), - 'comments_link': '', - 'previous_comments': '', - } - - if is_comment: - author = (_("from %s") % entry['author']).encode('utf-8') - else: - author = ' ' - message_link = (u"%s/%s" % (self.base_url, entry['id'])).encode('utf-8') - - count_text = lambda count: D_('comments') if count > 1 else D_('comment') - - comments_count = int(entry['comments_count']) - delta = comments_count - len(comments) - if self.request.display_single and delta > 0: - data['comments_link'] = ("%s?max=%s" % (message_link, entry['comments_count'])) - data['previous_comments'] = D_("Show %(count)d previous %(comments)s") % \ - {'count': delta, 'comments': count_text(delta)} - - data.update({'comments_count': comments_count, - 'comments_text': count_text(comments_count), - 'message_link': message_link, - 'message_title': self.getText(entry, 'title'), - }) - - data.update({'author': author, - 'extra_style': 'mblog_comment' if entry['type'] == 'comment' else '', - 'content': self.getText(entry, 'content'), - }) - - tpl = "%s%s" % ("" if data.get('message_title', None) else "micro_", "comment" if is_comment else "message") - self.request.write(self.useTemplate(self.request, tpl, data)) - - if comments: - for comment in comments: - self.render_html(comment) -
--- a/src/twisted/plugins/libervia_server.py Mon Jul 13 13:33:01 2015 +0200 +++ b/src/twisted/plugins/libervia_server.py Mon Jul 13 18:11:38 2015 +0200 @@ -55,7 +55,7 @@ if not os.path.isfile(os.path.join(html, C.LIBERVIA_MAIN_PAGE)): raise ValueError("%s is not a Libervia's browser HTML directory" % os.path.realpath(html)) themes_dir = os.path.join(value, C.THEMES_DIR) - if not os.path.isfile(os.path.join(themes_dir, 'default/index.html')): + if not os.path.isfile(os.path.join(themes_dir, 'default/styles/blog.css')): raise ValueError("%s is not a Libervia's server data directory" % os.path.realpath(themes_dir)) return value
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/themes/default/static_blog.html Mon Jul 13 18:11:38 2015 +0200 @@ -0,0 +1,111 @@ +{# +Libervia: a Salut à Toi frontend +Copyright (C) 2011, 2012, 2013, 2014, 2015 Jérôme Poisson <goffi@goffi.org> +Copyright (C) 2013, 2014, 2015 Adrien Cossa <souliane@mailoo.org> + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% macro message(entry) -%} + + <div class="mblog_entry {{ entry.style }}"> + {% if entry.type == "comment" %} + <div class="mblog_header"> + <div class="mblog_metadata"> + <div class="mblog_author">{{entry.author}}</div> + <div class="mblog_timestamp">{{entry.date}}</div> + </div> + </div> + {% else %} + <a href="{{entry.url}}" class="item_link"> + <div class="mblog_header mblog_header_main"> + <div class="mblog_metadata"> + <div class="mblog_author">{{entry.author}}</div> + <div class="mblog_timestamp">{{entry.date}}</div> + </div> + </div> + </a> + {% endif %} + <span class="mblog_content"> + {% if entry.message_title %} + <h1><a href="{{entry.url}}" class="item_link">{{entry.title}}</a></h1> + {% endif %} + {{entry.content}} + </span> + {% if entry.type == "main_item" %} + <a href="{{entry.url}}" class="item_link"> + <div class="mblog_footer mblog_footer_main"> + <div class="mblog_metadata"> + <div class="mblog_comments">{{ entry.comments_text }}</div> + </div> + </div> + </a> + {% endif %} + </div> + {% if entry.all_comments_link %} + {{ link(entry.all_comments_link) }} + {% endif %} + + {% for comment in entry.comments %} + {{ message(comment) }} + {% endfor %} + +{%- endmacro %} + +{% macro link(entry) -%} + <a href="{{entry.url}}" class="{{entry.style}}">{{entry.text}}</a> +{%- endmacro %} + +{% macro image(entry) -%} + <img src="{{entry.url}}" alt="{{entry.alt}}"/> +{%- endmacro %} + +<html> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <meta name="keywords" content="{{keywords}}"> + <meta name="description" content="{{description}}"> + <link rel="alternate" type="application/atom+xml" href="{{base_url}}/atom.xml"/> + <link rel='stylesheet' type="text/css" href='{{styles}}/blog.css'> + <link rel="icon" type="image/png" href="{{favicon}}"> + + <head profile="http://www.w3.org/2005/10/profile"> + <title>{{title}}</title> + </head> + + <body> + <div class="mblog_title"><a href="{{base_url}}"> + {{ image(banner_img) }} + {{ title }} + </a></div> + + <div class="header"> + <div class="header_content"> + {{ link(navlinks.later_message) }} + {{ link(navlinks.later_messages) }} + {{ link(navlinks.older_message) }} + </div> + </div> + + {% for entry in messages %} + {{ message(entry) }} + {% endfor %} + + <div class="footer"> + <div class="footer_content"> + {{ link(navlinks.older_messages) }} + </div> + </div> + </body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/themes/default/static_blog_error.html Mon Jul 13 18:11:38 2015 +0200 @@ -0,0 +1,32 @@ +{# +Libervia: a Salut à Toi frontend +Copyright (C) 2011, 2012, 2013, 2014, 2015 Jérôme Poisson <goffi@goffi.org> +Copyright (C) 2013, 2014, 2015 Adrien Cossa <souliane@mailoo.org> + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +<html> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <link rel='stylesheet' href='{{styles}}/blog.css'> + <link rel="icon" type="image/png" href="{{images}}/sat_logo_16.png"> + + <head profile="http://www.w3.org/2005/10/profile"> + <title>Blog error</title> + </head> + + <body> + <h1 class="error">{{message}}</h1> + </body> +</html>
--- a/themes/default/styles/blog.css Mon Jul 13 13:33:01 2015 +0200 +++ b/themes/default/styles/blog.css Mon Jul 13 18:11:38 2015 +0200 @@ -128,7 +128,11 @@ .comments_link { text-decoration: none; text-align: center; + color: #2B73B7; + font-size: smaller; display: block; + left: 2%; + position: relative; } .header, .footer {
--- a/themes/default/templates.py Mon Jul 13 13:33:01 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Libervia: a Salut à Toi frontend -# Copyright (C) 2011, 2012, 2013, 2014, 2015 Jérôme Poisson <goffi@goffi.org> - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. - -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -ERROR = u""" -<html> - <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> - <link rel='stylesheet' href='{styles}/blog.css'> - <link rel="icon" type="image/png" href="{images}/sat_logo_16.png"> - - <head profile="http://www.w3.org/2005/10/profile"> - <title>MICROBLOG ERROR</title> - </head> - - <body> - <h1 class="error">{message}</h1> - </body> -</html> -""" - -HEADER = u""" -<html> - <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> - <meta name="keywords" content="{keywords}"> - <meta name="description" content="{description}"> - <link rel="alternate" type="application/atom+xml" href="{base_url}/atom.xml"/> - <link rel='stylesheet' href='{styles}/blog.css'> - <link rel="icon" type="image/png" href="{favicon}"> - - <head profile="http://www.w3.org/2005/10/profile"> - <title>{title}</title> - </head> - - <body> - <div class="mblog_title"><a href="{base_url}">{banner_elt}{title_elt}</a></div> - - <div class="header"> - <div class="header_content"> - {later_message} - {later_messages} - {older_message} - </div> - </div> -""" - -BANNER = u"""<img src="{url}" alt="{alt}"/>{suffix}""" - -NAV_LINK = u"""<a href="{link}" class="{class}">{text}</a>""" - -MICRO_MESSAGE = u""" -<div class="mblog_entry {extra_style}"> - <a href="{message_link}" class="item_link"> - <div class="mblog_header mblog_header_main"> - <div class="mblog_metadata"> - <div class="mblog_author">{author}</div> - <div class="mblog_timestamp">{date}</div> - </div> - </div> - </a> - <span class="mblog_content">{content}</span> - <a href="{message_link}" class="item_link"> - <div class="mblog_footer mblog_footer_main"> - <div class="mblog_metadata"> - <div class="mblog_comments">{comments_count} {comments_text}</div> - </div> - </div> - </a> -</div> -<a href="{comments_link}" class="comments_link">{previous_comments}</a> -""" - -MICRO_COMMENT = u""" -<div class="mblog_entry {extra_style}"> - <div class="mblog_header"> - <div class="mblog_metadata"> - <div class="mblog_author">{author}</div> - <div class="mblog_timestamp">{date}</div> - </div> - </div> - <span class="mblog_content">{content}</span> -</div> -""" - -message_title = u"""<h1><a href="{message_link}" class="item_link">{message_title}</a></h1>{content}""" -MESSAGE = MICRO_MESSAGE.replace('{content}', message_title) -COMMENT = MICRO_COMMENT.replace('{content}', message_title) - -FOOTER = u""" - <div class="footer"> - <div class="footer_content"> - {older_messages} - </div> - </div> - </body> -</html> -"""