# HG changeset patch # User souliane # Date 1433414367 -7200 # Node ID 5319110a862c87d340bf8e12d95458f945fcf09d # Parent 1a19ee7d8d8a4c772f850b6d463017294e9e5ee0 server_side: static blog uses the default template diff -r 1a19ee7d8d8a -r 5319110a862c src/server/blog.py --- a/src/server/blog.py Thu Jun 04 11:56:34 2015 +0200 +++ b/src/server/blog.py Thu Jun 04 12:39:27 2015 +0200 @@ -27,6 +27,8 @@ from twisted.web.resource import Resource from twisted.words.protocols.jabber.jid import JID from datetime import datetime +from sys import path +import importlib import uuid import re import os @@ -35,24 +37,39 @@ from libervia.server.constants import Const as C -class MicroBlog(Resource): - isLeaf = True +class TemplateProcessor(object): + + THEME = 'default' + + def __init__(self, host): + self.host = host + + # add Libervia's themes directory to the python path + path.append(os.path.dirname(self.host.themes_dir)) + + def useTemplate(self, request, tpl, data=None): + root_url = '../' * len(request.postpath) + theme_url = os.path.join(root_url, 'themes', self.THEME) - ERROR_TEMPLATE = """ - - - - MICROBLOG ERROR - - -

%(message)s

- - - """ + # 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'), + 'styles': os.path.join(theme_url, 'styles'), + } + if data: + data_.update(data) + return (getattr(theme, tpl.upper()).format(**data_)).encode('utf-8') + + +class MicroBlog(Resource, TemplateProcessor): + isLeaf = True def __init__(self, host): self.host = host Resource.__init__(self) + TemplateProcessor.__init__(self, host) self.host.bridge.register('entityDataUpdated', self.entityDataUpdatedCb) self.host.bridge.register('actionResult', self.actionResultCb) # FIXME: actionResult is to be removed self.avatars_cache = {} @@ -116,72 +133,73 @@ def render_GET(self, request): if not request.postpath: - return MicroBlog.ERROR_TEMPLATE % {'root': '', - 'message': "You must indicate a nickname"} - else: - prof_requested = request.postpath[0] - #TODO: char check: only use alphanumerical chars + some extra(_,-,...) here - prof_found = self.host.bridge.getProfileName(prof_requested) - if not prof_found or prof_found == C.SERVICE_PROFILE: - return MicroBlog.ERROR_TEMPLATE % {'root': '../' * len(request.postpath), - 'message': "Invalid nickname"} + return self.useTemplate(request, "error", {'message': "You must indicate a nickname"}) + + prof_requested = request.postpath[0] + #TODO: char check: only use alphanumerical 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"}) + + d = defer.Deferred() + JID(self.host.bridge.asyncGetParamA('JabberID', 'Connection', 'value', C.SERVER_SECURITY_LIMIT, prof_found, callback=d.callback, errback=d.errback)) + d.addCallbacks(lambda pub_jid_s: self.gotJID(pub_jid_s, request, prof_found)) + return server.NOT_DONE_YET + + def gotJID(self, pub_jid_s, request, profile): + pub_jid = JID(pub_jid_s) + d = defer.Deferred() + item_id = None + atom = None + + if len(request.postpath) > 1: + if request.postpath[1] == 'atom.xml': # return the atom feed + atom = True else: - def got_jid(pub_jid_s): - pub_jid = JID(pub_jid_s) - d2 = defer.Deferred() - item_id = None - atom = None - rsm_ = {} + try: # check if the given path is a valid UUID + uuid.UUID(request.postpath[1]) + item_id = request.postpath[1] + except ValueError: + pass - if len(request.postpath) > 1: - if request.postpath[1] == 'atom.xml': # return the atom feed - atom = True - else: - try: # check if the given path is a valid UUID - uuid.UUID(request.postpath[1]) - item_id = request.postpath[1] - except ValueError: - pass + rsm_ = self.parseURLParams(request, item_id) + max_items = int(rsm_['max']) - # retrieve RSM request data from URL parameters - try: - max_items = int(request.args['max'][0]) - except (ValueError, KeyError): - max_items = C.RSM_MAX_ITEMS if item_id else C.RSM_MAX_COMMENTS - rsm_['max'] = unicode(max_items) - try: - rsm_['index'] = request.args['index'][0] - except (ValueError, KeyError): - try: - rsm_['before'] = request.args['before'][0] - except KeyError: - try: - rsm_['after'] = request.args['after'][0] - except KeyError: - pass + if atom is not None: + d.addCallbacks(self.render_atom_feed, self.render_error_blog, [request], None, [request, profile], None) + self.host.bridge.getGroupBlogsAtom(pub_jid.userhost(), rsm_, C.SERVICE_PROFILE, d.callback, d.errback) + return + + d.addCallbacks(self.render_html_blog, self.render_error_blog, [request, profile], None, [request, profile], None) + if item_id: + if max_items > 0: # display one message and its comments + self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [item_id], {}, max_items, C.SERVICE_PROFILE, d.callback, d.errback) + else: # display one message, count its comments + self.host.bridge.getGroupBlogs(pub_jid.userhost(), [item_id], {}, True, C.SERVICE_PROFILE, d.callback, d.errback) + else: + if max_items == 1: # display one message and its comments + self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [], rsm_, C.RSM_MAX_COMMENTS, C.SERVICE_PROFILE, d.callback, d.errback) + else: # display the last messages, count their comments + self.host.bridge.getGroupBlogs(pub_jid.userhost(), [], rsm_, True, C.SERVICE_PROFILE, d.callback, d.errback) - if atom is not None: - d2.addCallbacks(self.render_atom_feed, self.render_error_blog, [request], None, [request, prof_found], None) - self.host.bridge.getGroupBlogsAtom(pub_jid.userhost(), rsm_, C.SERVICE_PROFILE, d2.callback, d2.errback) - return - - d2.addCallbacks(self.render_html_blog, self.render_error_blog, [request, prof_found], None, [request, prof_found], None) - if item_id: - if max_items > 0: # display one message and its comments - self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [item_id], {}, max_items, C.SERVICE_PROFILE, d2.callback, d2.errback) - else: # display one message, count its comments - self.host.bridge.getGroupBlogs(pub_jid.userhost(), [item_id], {}, True, C.SERVICE_PROFILE, d2.callback, d2.errback) - else: - if max_items == 1: # display one message and its comments - self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [], rsm_, C.RSM_MAX_COMMENTS, C.SERVICE_PROFILE, d2.callback, d2.errback) - else: # display the last messages, count their comments - self.host.bridge.getGroupBlogs(pub_jid.userhost(), [], rsm_, True, C.SERVICE_PROFILE, d2.callback, d2.errback) - - d1 = defer.Deferred() - JID(self.host.bridge.asyncGetParamA('JabberID', 'Connection', 'value', C.SERVER_SECURITY_LIMIT, prof_found, callback=d1.callback, errback=d1.errback)) - d1.addCallbacks(got_jid) - - return server.NOT_DONE_YET + def parseURLParams(self, request, item_id): + # retrieve RSM request data from URL parameters + rsm_ = {} + try: + rsm_['max'] = request.args['max'][0] + except (ValueError, KeyError): + rsm_['max'] = unicode(C.RSM_MAX_ITEMS if item_id else C.RSM_MAX_COMMENTS) + try: + rsm_['index'] = request.args['index'][0] + except (ValueError, KeyError): + try: + rsm_['before'] = request.args['before'][0] + except KeyError: + try: + rsm_['after'] = request.args['after'][0] + except KeyError: + pass + return rsm_ def render_html_blog(self, mblog_data, request, profile): """Retrieve the user parameters before actually rendering the static blog @@ -245,174 +263,148 @@ else: url = default suffix = "" - return "%(alt)s%(suffix)s" % {'alt': alt, 'url': url, 'suffix': suffix} + return self.useTemplate(request, "banner", {'alt': alt, 'url': url, 'suffix': suffix}) avatar = os.path.normpath(root_url + getOption('avatar')) title = getOption(C.STATIC_BLOG_PARAM_TITLE) or user - request.write(""" - - - - - - - - - %(title)s - - -
%(banner_elt)s%(title_elt)s
- """ % {'base': base_url, - 'root': root_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, - 'favicon': avatar, - 'banner_elt': getImageOption(C.STATIC_BLOG_PARAM_BANNER, avatar, title), - 'title_elt': title, - }) - mblog_data, main_rsm = mblog_data - display_single = len(mblog_data) == 1 + 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, + 'favicon': avatar, + 'banner_elt': getImageOption(C.STATIC_BLOG_PARAM_BANNER, avatar, title), + 'title_elt': title, + } - # build the navigation links - count = int(main_rsm['count']) if 'count' in main_rsm else 0 - if count > 0: - index = int(main_rsm['index']) - if index > 0: - before_link = ("%(base)s?before=%(item_id)s" % {'base': base_url, 'item_id': main_rsm['first']}).encode('utf-8') - if display_single: - before_link += '&max=1' - tmp_text = D_("later message") - class_ = 'later_message' - else: - tmp_text = D_("later messages") - class_ = 'later_messages' - before_tag = """%(text)s""" % {'link': before_link, 'class': class_, 'text': tmp_text} - else: - before_tag = None - if index + len(mblog_data) < count: - after_link = ("%(base)s?after=%(item_id)s" % {'base': base_url, 'item_id': main_rsm['last']}).encode('utf-8') - if display_single: - after_link += '&max=1' - text = D_("older message") - class_ = 'older_message' - else: - text = D_("older messages") - class_ = 'older_messages' - after_tag = """%(text)s""" % {'link': after_link, 'class': class_, 'text': text} - else: - after_tag = None - - # display navigation header - request.write("""
""") - request.write("""
""") - if before_tag: - request.write(before_tag) - if display_single and after_tag: - request.write(after_tag) - request.write("""
""") - + 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)))) - for main_data, comments_data in mblog_data: - self.__render_html_entry(main_data, base_url, request) - comments, comments_rsm = comments_data + + data.update(self.getNavigationLinks(request, mblog_data, main_rsm, base_url)) + request.write(self.useTemplate(request, 'header', data)) - # eventually display the link to show all comments - comments_count = int(main_data['comments_count']) - delta = comments_count - len(comments) - if display_single and delta > 0: - link = ("%(base)s/%(item_id)s?max=%(max)s" % {'base': base_url, - 'item_id': main_data['id'], - 'max': main_data['comments_count']}).encode('utf-8') - text = D_("Show %(count)d previous %(comments)s") % {'count': delta, - 'comments': D_('comments') if delta > 1 else D_('comment')} - request.write("""%(text)s""" % {'link': link, 'text': text}) + BlogMessages(self.host, request, base_url, mblog_data).render() - comments = sorted(comments, key=lambda entry: (float(entry.get('published', 0)))) - for comment in comments: - self.__render_html_entry(comment, base_url, request) - - # display navigation footer - request.write("""""") - - request.write('') + request.write(self.useTemplate(request, "footer", data)) request.finish() - def __render_html_entry(self, entry, base_url, request): + def getNavigationLinks(self, request, mblog_data, rsm_data, base_url): + """Build the navigation links. + + @param mblog_data (dict): the microblogs that are displayed on the page + @param rsm_data (dict): rsm data + @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)) + display_single = len(mblog_data) == 1 + data[key] = '' # key must exist when using the template + if count <= 0 or (display_single == key.endswith('s')): + continue + + index = int(rsm_data['index']) + + link_data = {'base_url': base_url, 'suffix': ''} + + if key.startswith('later_message'): + if index <= 0: + continue + link_data['item_id'] = rsm_data['first'] + link_data['post_arg'] = 'before' + else: + if index + len(mblog_data) >= count: + continue + link_data['item_id'] = rsm_data['last'] + link_data['post_arg'] = 'after' + + if display_single: + link_data['suffix'] = '&max=1' + + 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') + + return data + + 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, "error", {'message': "Can't access requested data"})) + request.finish() + + +class BlogMessages(TemplateProcessor): + + 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 + + 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) + + def getText(self, entry, key): + if ('%s_xhtml' % key) in entry: + return entry['%s_xhtml' % key].encode('utf-8') + elif key in entry: + 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)) - datetime_ = datetime.fromtimestamp(timestamp) is_comment = entry['type'] == 'comment' - def getText(key): - if ('%s_xhtml' % key) in entry: - return entry['%s_xhtml' % key].encode('utf-8') - elif key in entry: - processor = addURLToText if key.startswith('content') else sanitizeHtml - return convertNewLinesToXHTML(processor(entry[key])).encode('utf-8') - return '' - - def addMainItemLink(elem): - if not item_link or not elem: - return elem - return """%(elem)s""" % {'link': item_link, 'elem': elem} + data = {'date': datetime.fromtimestamp(timestamp), + 'comments_link': '', + 'previous_comments': '', + } if is_comment: author = (_("from %s") % entry['author']).encode('utf-8') - item_link = '' - footer = '' else: author = ' ' - item_link = ("%(base)s/%(item_id)s" % {'base': base_url, 'item_id': entry['id']}).encode('utf-8') - comments_count = int(entry['comments_count']) - comments_text = (D_('comments') if comments_count > 1 else D_('comment')).encode('utf-8') - footer = addMainItemLink("""""" % {'count': comments_count, - 'comments': comments_text}) + message_link = ("%s/%s" % (self.base_url, entry['id'])).encode('utf-8') + + count_text = lambda count: D_('comments') if count > 1 else D_('comment') - header = """
- -
""" % {'author': author, 'date': datetime_, - 'class': '' if is_comment else 'mblog_header_main'} - if not is_comment: - header = addMainItemLink(header) + comments_count = int(entry['comments_count']) + delta = comments_count - len(comments) + if len(self.mblog_data) == 1 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)} - title = addMainItemLink(getText('title')) - body = getText('content') - if title: # insert the title within the body - body = """

%(title)s

\n%(body)s""" % {'title': title, 'body': body} + data.update({'comments_count': comments_count, + 'comments_text': count_text(comments_count), + 'message_link': message_link, + 'message_title': self.getText(entry, 'title'), + }) - request.write("""
- %(header)s - %(content)s - %(footer)s -
""" % {'extra_style': 'mblog_comment' if entry['type'] == 'comment' else '', - 'item_link': item_link, - 'header': header, - 'content': body, - 'footer': footer}) + data.update({'author': author, + 'extra_style': 'mblog_comment' if entry['type'] == 'comment' else '', + 'content': self.getText(entry, 'content'), + }) - def render_atom_feed(self, feed, request): - request.write(feed.encode('utf-8')) - request.finish() + 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)) - def render_error_blog(self, error, request, profile): - request.write(MicroBlog.ERROR_TEMPLATE % {'root': '../' * len(request.postpath), - 'message': "Can't access requested data"}) - request.finish() + if comments: + for comment in comments: + self.render_html(comment) + diff -r 1a19ee7d8d8a -r 5319110a862c src/server/server.py --- a/src/server/server.py Thu Jun 04 11:56:34 2015 +0200 +++ b/src/server/server.py Thu Jun 04 12:39:27 2015 +0200 @@ -1057,7 +1057,7 @@ coerceDataDir(self.data_dir) # this is not done when using the default value self.html_dir = os.path.join(self.data_dir, C.HTML_DIR) - self.server_css_dir = os.path.join(self.data_dir, C.SERVER_CSS_DIR) + self.themes_dir = os.path.join(self.data_dir, C.THEMES_DIR) self._cleanup = [] @@ -1107,7 +1107,7 @@ putChild('upload_radiocol', _upload_radiocol) putChild('upload_avatar', _upload_avatar) putChild('blog', MicroBlog(self)) - putChild('css', ProtectedFile(self.server_css_dir)) + putChild('themes', ProtectedFile(self.themes_dir)) putChild(os.path.dirname(C.MEDIA_DIR), ProtectedFile(self.media_dir)) putChild(os.path.dirname(C.AVATARS_DIR), ProtectedFile(os.path.join(self.local_dir, C.AVATARS_DIR))) putChild('radiocol', ProtectedFile(_upload_radiocol.getTmpDir(), defaultType="audio/ogg")) # We cheat for PoC because we know we are on the same host, so we use directly upload dir diff -r 1a19ee7d8d8a -r 5319110a862c src/twisted/plugins/libervia_server.py --- a/src/twisted/plugins/libervia_server.py Thu Jun 04 11:56:34 2015 +0200 +++ b/src/twisted/plugins/libervia_server.py Thu Jun 04 12:39:27 2015 +0200 @@ -52,11 +52,11 @@ def coerceDataDir(value): # called from Libervia.OPT_PARAMETERS html = os.path.join(value, C.HTML_DIR) - if not os.path.isfile(os.path.join(html, 'libervia.html')): + 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)) - server_css = os.path.join(value, C.SERVER_CSS_DIR) - if not os.path.isfile(os.path.join(server_css, 'blog.css')): - raise ValueError("%s is not a Libervia's server data directory" % os.path.realpath(server_css)) + themes_dir = os.path.join(value, C.THEMES_DIR) + if not os.path.isfile(os.path.join(themes_dir, 'default/index.html')): + raise ValueError("%s is not a Libervia's server data directory" % os.path.realpath(themes_dir)) return value DATA_DIR_DEFAULT = ''