Mercurial > libervia-web
comparison 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 |
comparison
equal
deleted
inserted
replaced
448:14c35f7f1ef5 | 449:981ed669d3b3 |
---|---|
1 #!/usr/bin/python | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # Libervia: a Salut à Toi frontend | |
5 # Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org> | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 from sat.core.i18n import _ | |
21 from sat_frontends.tools.strings import addURLToText | |
22 | |
23 from twisted.internet import defer | |
24 from twisted.web import server | |
25 from twisted.web.resource import Resource | |
26 from twisted.words.protocols.jabber.jid import JID | |
27 from datetime import datetime | |
28 import uuid | |
29 import re | |
30 | |
31 from libervia.server.html_tools import sanitizeHtml | |
32 from libervia.server.constants import Const as C | |
33 | |
34 | |
35 class MicroBlog(Resource): | |
36 isLeaf = True | |
37 | |
38 ERROR_TEMPLATE = """ | |
39 <html> | |
40 <head profile="http://www.w3.org/2005/10/profile"> | |
41 <link rel="icon" type="image/png" href="%(root)ssat_logo_16.png"> | |
42 <title>MICROBLOG ERROR</title> | |
43 </head> | |
44 <body> | |
45 <h1 style='text-align: center; color: red;'>%(message)s</h1> | |
46 </body> | |
47 </html> | |
48 """ | |
49 | |
50 def __init__(self, host): | |
51 self.host = host | |
52 Resource.__init__(self) | |
53 | |
54 def render_GET(self, request): | |
55 if not request.postpath: | |
56 return MicroBlog.ERROR_TEMPLATE % {'root': '', | |
57 'message': "You must indicate a nickname"} | |
58 else: | |
59 prof_requested = request.postpath[0] | |
60 #TODO: char check: only use alphanumerical chars + some extra(_,-,...) here | |
61 prof_found = self.host.bridge.getProfileName(prof_requested) | |
62 if not prof_found or prof_found == 'libervia': | |
63 return MicroBlog.ERROR_TEMPLATE % {'root': '../' * len(request.postpath), | |
64 'message': "Invalid nickname"} | |
65 else: | |
66 def got_jid(pub_jid_s): | |
67 pub_jid = JID(pub_jid_s) | |
68 d2 = defer.Deferred() | |
69 item_id = None | |
70 if len(request.postpath) > 1: | |
71 if request.postpath[1] == 'atom.xml': # return the atom feed | |
72 d2.addCallbacks(self.render_atom_feed, self.render_error_blog, [request], None, [request, prof_found], None) | |
73 self.host.bridge.getLastGroupBlogsAtom(pub_jid.userhost(), 10, 'libervia', d2.callback, d2.errback) | |
74 return | |
75 try: # check if the given path is a valid UUID | |
76 uuid.UUID(request.postpath[1]) | |
77 item_id = request.postpath[1] | |
78 except ValueError: | |
79 pass | |
80 d2.addCallbacks(self.render_html_blog, self.render_error_blog, [request, prof_found], None, [request, prof_found], None) | |
81 if item_id: # display one message and its comments | |
82 self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [item_id], 'libervia', d2.callback, d2.errback) | |
83 else: # display the last messages without comment | |
84 self.host.bridge.getLastGroupBlogs(pub_jid.userhost(), 10, 'libervia', d2.callback, d2.errback) | |
85 | |
86 d1 = defer.Deferred() | |
87 JID(self.host.bridge.asyncGetParamA('JabberID', 'Connection', 'value', C.SERVER_SECURITY_LIMIT, prof_found, callback=d1.callback, errback=d1.errback)) | |
88 d1.addCallbacks(got_jid) | |
89 | |
90 return server.NOT_DONE_YET | |
91 | |
92 def render_html_blog(self, mblog_data, request, profile): | |
93 """Retrieve the user parameters before actually rendering the static blog | |
94 @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data) | |
95 @param request: HTTP request | |
96 @param profile | |
97 """ | |
98 d_list = [] | |
99 style = {} | |
100 | |
101 def getCallback(param_name): | |
102 d = defer.Deferred() | |
103 d.addCallback(lambda value: style.update({param_name: value})) | |
104 d_list.append(d) | |
105 return d.callback | |
106 | |
107 eb = lambda failure: self.render_error_blog(failure, request, profile) | |
108 | |
109 for param_name in (C.STATIC_BLOG_PARAM_TITLE, C.STATIC_BLOG_PARAM_BANNER, C.STATIC_BLOG_PARAM_KEYWORDS, C.STATIC_BLOG_PARAM_DESCRIPTION): | |
110 self.host.bridge.asyncGetParamA(param_name, C.STATIC_BLOG_KEY, 'value', C.SERVER_SECURITY_LIMIT, profile, callback=getCallback(param_name), errback=eb) | |
111 | |
112 cb = lambda dummy: self.__render_html_blog(mblog_data, style, request, profile) | |
113 defer.DeferredList(d_list).addCallback(cb) | |
114 | |
115 def __render_html_blog(self, mblog_data, style, request, profile): | |
116 """Actually render the static blog. If mblog_data is a list of dict, we are missing | |
117 the comments items so we just display the main items. If mblog_data is a list of couple, | |
118 each couple is associating a main item data with the list of its comments, so we render all. | |
119 @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data) | |
120 @param style: dict defining the blog's rendering parameters | |
121 @param request: the HTTP request | |
122 @profile | |
123 """ | |
124 if not isinstance(style, dict): | |
125 style = {} | |
126 user = sanitizeHtml(profile).encode('utf-8') | |
127 root_url = '../' * len(request.postpath) | |
128 base_url = root_url + 'blog/' + user | |
129 | |
130 def getFromData(key): | |
131 return sanitizeHtml(style[key]).encode('utf-8') if key in style else '' | |
132 | |
133 def getImageFromData(key, alt): | |
134 """regexp from http://answers.oreilly.com/topic/280-how-to-validate-urls-with-regular-expressions/""" | |
135 url = style[key].encode('utf-8') if key in style else '' | |
136 regexp = r"^(https?|ftp)://[a-z0-9-]+(\.[a-z0-9-]+)+(/[\w-]+)*/[\w-]+\.(gif|png|jpg)$" | |
137 return "<img src='%(url)s' alt='%(alt)s'/>" % {'alt': alt, 'url': url} if re.match(regexp, url) else alt | |
138 | |
139 request.write(""" | |
140 <html> | |
141 <head> | |
142 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> | |
143 <meta name="keywords" content="%(keywords)s"> | |
144 <meta name="description" content="%(description)s"> | |
145 <link rel="alternate" type="application/atom+xml" href="%(base)s/atom.xml"/> | |
146 <link rel="stylesheet" type="text/css" href="%(root)scss/blog.css" /> | |
147 <link rel="icon" type="image/png" href="%(root)ssat_logo_16.png"> | |
148 <title>%(title)s</title> | |
149 </head> | |
150 <body> | |
151 <div class="mblog_title"><a href="%(base)s">%(banner_elt)s</a></div> | |
152 """ % {'base': base_url, | |
153 'root': root_url, | |
154 'user': user, | |
155 'keywords': getFromData(C.STATIC_BLOG_PARAM_KEYWORDS), | |
156 'description': getFromData(C.STATIC_BLOG_PARAM_DESCRIPTION), | |
157 'title': getFromData(C.STATIC_BLOG_PARAM_TITLE) or "%s's microblog" % user, | |
158 'banner_elt': getImageFromData(C.STATIC_BLOG_PARAM_BANNER, user)}) | |
159 mblog_data = [(entry if isinstance(entry, tuple) else (entry, [])) for entry in mblog_data] | |
160 mblog_data = sorted(mblog_data, key=lambda entry: (-float(entry[0].get('published', 0)))) | |
161 for entry in mblog_data: | |
162 self.__render_html_entry(entry[0], base_url, request) | |
163 comments = sorted(entry[1], key=lambda entry: (float(entry.get('published', 0)))) | |
164 for comment in comments: | |
165 self.__render_html_entry(comment, base_url, request) | |
166 request.write('</body></html>') | |
167 request.finish() | |
168 | |
169 def __render_html_entry(self, entry, base_url, request): | |
170 """Render one microblog entry. | |
171 @param entry: the microblog entry | |
172 @param base_url: the base url of the blog | |
173 @param request: the HTTP request | |
174 """ | |
175 timestamp = float(entry.get('published', 0)) | |
176 datetime_ = datetime.fromtimestamp(timestamp) | |
177 is_comment = entry['type'] == 'comment' | |
178 if is_comment: | |
179 author = (_("comment from %s") % entry['author']).encode('utf-8') | |
180 item_link = '' | |
181 else: | |
182 author = ' ' | |
183 item_link = ("%(base)s/%(item_id)s" % {'base': base_url, 'item_id': entry['id']}).encode('utf-8') | |
184 | |
185 def getText(key): | |
186 if ('%s_xhtml' % key) in entry: | |
187 return entry['%s_xhtml' % key].encode('utf-8') | |
188 elif key in entry: | |
189 processor = addURLToText if key.startswith('content') else sanitizeHtml | |
190 return processor(entry[key]).encode('utf-8') | |
191 return '' | |
192 | |
193 def addMainItemLink(elem): | |
194 if not item_link or not elem: | |
195 return elem | |
196 return """<a href="%(link)s" class="item_link">%(elem)s</a>""" % {'link': item_link, 'elem': elem} | |
197 | |
198 header = addMainItemLink("""<div class="mblog_header"> | |
199 <div class="mblog_metadata"> | |
200 <div class="mblog_author">%(author)s</div> | |
201 <div class="mblog_timestamp">%(date)s</div> | |
202 </div> | |
203 </div>""" % {'author': author, 'date': datetime_}) | |
204 | |
205 title = addMainItemLink(getText('title')) | |
206 body = getText('content') | |
207 if title: # insert the title within the body | |
208 body = """<h1>%(title)s</h1>\n%(body)s""" % {'title': title, 'body': body} | |
209 | |
210 request.write("""<div class="mblog_entry %(extra_style)s"> | |
211 %(header)s | |
212 <span class="mblog_content">%(content)s</span> | |
213 </div>""" % | |
214 {'extra_style': 'mblog_comment' if entry['type'] == 'comment' else '', | |
215 'item_link': item_link, | |
216 'header': header, | |
217 'content': body}) | |
218 | |
219 def render_atom_feed(self, feed, request): | |
220 request.write(feed.encode('utf-8')) | |
221 request.finish() | |
222 | |
223 def render_error_blog(self, error, request, profile): | |
224 request.write(MicroBlog.ERROR_TEMPLATE % {'root': '../' * len(request.postpath), | |
225 'message': "Can't access requested data"}) | |
226 request.finish() |