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 = '&nbsp;'
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()