Mercurial > libervia-web
diff src/server/blog.py @ 449:981ed669d3b3
/!\ reorganize all the file hierarchy, move the code and launching script to src:
- browser_side --> src/browser
- public --> src/browser_side/public
- libervia.py --> src/browser/libervia_main.py
- libervia_server --> src/server
- libervia_server/libervia.sh --> src/libervia.sh
- twisted --> src/twisted
- new module src/common
- split constants.py in 3 files:
- src/common/constants.py
- src/browser/constants.py
- src/server/constants.py
- output --> html (generated by pyjsbuild during the installation)
- new option/parameter "data_dir" (-d) to indicates the directory containing html and server_css
- setup.py installs libervia to the following paths:
- src/common --> <LIB>/libervia/common
- src/server --> <LIB>/libervia/server
- src/twisted --> <LIB>/twisted
- html --> <SHARE>/libervia/html
- server_side --> <SHARE>libervia/server_side
- LIBERVIA_INSTALL environment variable takes 2 new options with prompt confirmation:
- clean: remove previous installation directories
- purge: remove building and previous installation directories
You may need to update your sat.conf and/or launching script to update the following options/parameters:
- ssl_certificate
- data_dir
author | souliane <souliane@mailoo.org> |
---|---|
date | Tue, 20 May 2014 06:41:16 +0200 |
parents | libervia_server/blog.py@c406e46fe9c0 |
children | 3ef6ce200c27 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/blog.py Tue May 20 06:41:16 2014 +0200 @@ -0,0 +1,226 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Libervia: a Salut à Toi frontend +# Copyright (C) 2011, 2012, 2013, 2014 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/>. + +from sat.core.i18n import _ +from sat_frontends.tools.strings import addURLToText + +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 datetime import datetime +import uuid +import re + +from libervia.server.html_tools import sanitizeHtml +from libervia.server.constants import Const as C + + +class MicroBlog(Resource): + isLeaf = True + + ERROR_TEMPLATE = """ + <html> + <head profile="http://www.w3.org/2005/10/profile"> + <link rel="icon" type="image/png" href="%(root)ssat_logo_16.png"> + <title>MICROBLOG ERROR</title> + </head> + <body> + <h1 style='text-align: center; color: red;'>%(message)s</h1> + </body> + </html> + """ + + def __init__(self, host): + self.host = host + Resource.__init__(self) + + 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 == 'libervia': + return MicroBlog.ERROR_TEMPLATE % {'root': '../' * len(request.postpath), + 'message': "Invalid nickname"} + else: + def got_jid(pub_jid_s): + pub_jid = JID(pub_jid_s) + d2 = defer.Deferred() + item_id = None + if len(request.postpath) > 1: + if request.postpath[1] == 'atom.xml': # return the atom feed + d2.addCallbacks(self.render_atom_feed, self.render_error_blog, [request], None, [request, prof_found], None) + self.host.bridge.getLastGroupBlogsAtom(pub_jid.userhost(), 10, 'libervia', d2.callback, d2.errback) + return + try: # check if the given path is a valid UUID + uuid.UUID(request.postpath[1]) + item_id = request.postpath[1] + except ValueError: + pass + d2.addCallbacks(self.render_html_blog, self.render_error_blog, [request, prof_found], None, [request, prof_found], None) + if item_id: # display one message and its comments + self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [item_id], 'libervia', d2.callback, d2.errback) + else: # display the last messages without comment + self.host.bridge.getLastGroupBlogs(pub_jid.userhost(), 10, 'libervia', 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 render_html_blog(self, mblog_data, request, profile): + """Retrieve the user parameters before actually rendering the static blog + @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data) + @param request: HTTP request + @param profile + """ + d_list = [] + style = {} + + def getCallback(param_name): + d = defer.Deferred() + d.addCallback(lambda value: style.update({param_name: value})) + d_list.append(d) + return d.callback + + eb = lambda failure: self.render_error_blog(failure, request, profile) + + for param_name in (C.STATIC_BLOG_PARAM_TITLE, C.STATIC_BLOG_PARAM_BANNER, C.STATIC_BLOG_PARAM_KEYWORDS, C.STATIC_BLOG_PARAM_DESCRIPTION): + self.host.bridge.asyncGetParamA(param_name, C.STATIC_BLOG_KEY, 'value', C.SERVER_SECURITY_LIMIT, profile, callback=getCallback(param_name), errback=eb) + + cb = lambda dummy: self.__render_html_blog(mblog_data, style, request, profile) + defer.DeferredList(d_list).addCallback(cb) + + def __render_html_blog(self, mblog_data, style, request, profile): + """Actually render the static blog. If mblog_data is a list of dict, we are missing + the comments items so we just display the main items. If mblog_data is a list of couple, + each couple is associating a main item data with the list of its comments, so we render all. + @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data) + @param style: dict defining the blog's rendering parameters + @param request: the HTTP request + @profile + """ + if not isinstance(style, dict): + style = {} + user = sanitizeHtml(profile).encode('utf-8') + root_url = '../' * len(request.postpath) + base_url = root_url + 'blog/' + user + + def getFromData(key): + return sanitizeHtml(style[key]).encode('utf-8') if key in style else '' + + def getImageFromData(key, alt): + """regexp from http://answers.oreilly.com/topic/280-how-to-validate-urls-with-regular-expressions/""" + url = style[key].encode('utf-8') if key in style else '' + regexp = r"^(https?|ftp)://[a-z0-9-]+(\.[a-z0-9-]+)+(/[\w-]+)*/[\w-]+\.(gif|png|jpg)$" + return "<img src='%(url)s' alt='%(alt)s'/>" % {'alt': alt, 'url': url} if re.match(regexp, url) else alt + + request.write(""" + <html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <meta name="keywords" content="%(keywords)s"> + <meta name="description" content="%(description)s"> + <link rel="alternate" type="application/atom+xml" href="%(base)s/atom.xml"/> + <link rel="stylesheet" type="text/css" href="%(root)scss/blog.css" /> + <link rel="icon" type="image/png" href="%(root)ssat_logo_16.png"> + <title>%(title)s</title> + </head> + <body> + <div class="mblog_title"><a href="%(base)s">%(banner_elt)s</a></div> + """ % {'base': base_url, + 'root': root_url, + 'user': user, + 'keywords': getFromData(C.STATIC_BLOG_PARAM_KEYWORDS), + 'description': getFromData(C.STATIC_BLOG_PARAM_DESCRIPTION), + 'title': getFromData(C.STATIC_BLOG_PARAM_TITLE) or "%s's microblog" % user, + 'banner_elt': getImageFromData(C.STATIC_BLOG_PARAM_BANNER, user)}) + 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('published', 0)))) + for entry in mblog_data: + self.__render_html_entry(entry[0], base_url, request) + comments = sorted(entry[1], key=lambda entry: (float(entry.get('published', 0)))) + for comment in comments: + self.__render_html_entry(comment, base_url, request) + request.write('</body></html>') + request.finish() + + def __render_html_entry(self, entry, base_url, request): + """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' + if is_comment: + author = (_("comment from %s") % entry['author']).encode('utf-8') + item_link = '' + else: + author = ' ' + item_link = ("%(base)s/%(item_id)s" % {'base': base_url, 'item_id': entry['id']}).encode('utf-8') + + 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 processor(entry[key]).encode('utf-8') + return '' + + def addMainItemLink(elem): + if not item_link or not elem: + return elem + return """<a href="%(link)s" class="item_link">%(elem)s</a>""" % {'link': item_link, 'elem': elem} + + header = addMainItemLink("""<div class="mblog_header"> + <div class="mblog_metadata"> + <div class="mblog_author">%(author)s</div> + <div class="mblog_timestamp">%(date)s</div> + </div> + </div>""" % {'author': author, 'date': datetime_}) + + title = addMainItemLink(getText('title')) + body = getText('content') + if title: # insert the title within the body + body = """<h1>%(title)s</h1>\n%(body)s""" % {'title': title, 'body': body} + + request.write("""<div class="mblog_entry %(extra_style)s"> + %(header)s + <span class="mblog_content">%(content)s</span> + </div>""" % + {'extra_style': 'mblog_comment' if entry['type'] == 'comment' else '', + 'item_link': item_link, + 'header': header, + 'content': body}) + + 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(MicroBlog.ERROR_TEMPLATE % {'root': '../' * len(request.postpath), + 'message': "Can't access requested data"}) + request.finish()