comparison libervia_server/blog.py @ 389:2d782349b88a

server_side: display blog comments when you click on a main item header or title
author souliane <souliane@mailoo.org>
date Tue, 25 Feb 2014 17:50:47 +0100
parents 893451e35686
children 35a43d0dc032
comparison
equal deleted inserted replaced
388:893451e35686 389:2d782349b88a
15 # GNU Affero General Public License for more details. 15 # GNU Affero General Public License for more details.
16 16
17 # You should have received a copy of the GNU Affero General Public License 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/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 from sat.core.i18n import _
20 from sat_frontends.tools.strings import addURLToText 21 from sat_frontends.tools.strings import addURLToText
21 from libervia_server.html_tools import sanitizeHtml 22 from libervia_server.html_tools import sanitizeHtml
22 from twisted.internet import defer 23 from twisted.internet import defer
23 from twisted.web import server 24 from twisted.web import server
24 from twisted.web.resource import Resource 25 from twisted.web.resource import Resource
25 from twisted.words.protocols.jabber.jid import JID 26 from twisted.words.protocols.jabber.jid import JID
26 from datetime import datetime 27 from datetime import datetime
27 from constants import Const 28 from constants import Const
29 import uuid
28 import re 30 import re
29 31
30 32
31 class MicroBlog(Resource): 33 class MicroBlog(Resource):
32 isLeaf = True 34 isLeaf = True
59 return MicroBlog.ERROR_TEMPLATE % "Invalid nickname" 61 return MicroBlog.ERROR_TEMPLATE % "Invalid nickname"
60 else: 62 else:
61 def got_jid(pub_jid_s): 63 def got_jid(pub_jid_s):
62 pub_jid = JID(pub_jid_s) 64 pub_jid = JID(pub_jid_s)
63 d2 = defer.Deferred() 65 d2 = defer.Deferred()
64 if len(request.postpath) > 1 and request.postpath[1] == 'atom.xml': 66 item_id = None
65 d2.addCallbacks(self.render_atom_feed, self.render_error_blog, [request], None, [request, prof_found], None) 67 if len(request.postpath) > 1:
66 self.host.bridge.getLastGroupBlogsAtom(pub_jid.userhost(), 10, 'libervia', d2.callback, d2.errback) 68 if request.postpath[1] == 'atom.xml': # return the atom feed
67 else: 69 d2.addCallbacks(self.render_atom_feed, self.render_error_blog, [request], None, [request, prof_found], None)
68 d2.addCallbacks(self._render_html_blog, self.render_error_blog, [request, prof_found], None, [request, prof_found], None) 70 self.host.bridge.getLastGroupBlogsAtom(pub_jid.userhost(), 10, 'libervia', d2.callback, d2.errback)
71 return
72 try: # check if the given path is a valid UUID
73 uuid.UUID(request.postpath[1])
74 item_id = request.postpath[1]
75 except ValueError:
76 pass
77 d2.addCallbacks(self.render_html_blog, self.render_error_blog, [request, prof_found], None, [request, prof_found], None)
78 if item_id: # display one message and its comments
79 self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [item_id], 'libervia', d2.callback, d2.errback)
80 else: # display the last messages without comment
69 self.host.bridge.getLastGroupBlogs(pub_jid.userhost(), 10, 'libervia', d2.callback, d2.errback) 81 self.host.bridge.getLastGroupBlogs(pub_jid.userhost(), 10, 'libervia', d2.callback, d2.errback)
70 82
71 d1 = defer.Deferred() 83 d1 = defer.Deferred()
72 JID(self.host.bridge.asyncGetParamA('JabberID', 'Connection', 'value', Const.SERVER_SECURITY_LIMIT, prof_found, callback=d1.callback, errback=d1.errback)) 84 JID(self.host.bridge.asyncGetParamA('JabberID', 'Connection', 'value', Const.SERVER_SECURITY_LIMIT, prof_found, callback=d1.callback, errback=d1.errback))
73 d1.addCallbacks(got_jid) 85 d1.addCallbacks(got_jid)
74 86
75 return server.NOT_DONE_YET 87 return server.NOT_DONE_YET
76 88
77 def _render_html_blog(self, mblog_data, request, profile): 89 def render_html_blog(self, mblog_data, request, profile):
78 """Retrieve the blog banner or other user's specific stuff before actually rendering the static blog""" 90 """Retrieve the blog banner or other user's specific stuff before actually rendering the static blog
91 @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data)
92 @param request: HTTP request
93 @param profile
94 """
79 def check_banner(banner): 95 def check_banner(banner):
80 """Regexp from http://answers.oreilly.com/topic/280-how-to-validate-urls-with-regular-expressions/""" 96 """Regexp from http://answers.oreilly.com/topic/280-how-to-validate-urls-with-regular-expressions/"""
81 if re.match(r"^(https?|ftp)://[a-z0-9-]+(\.[a-z0-9-]+)+(/[\w-]+)*/[\w-]+\.(gif|png|jpg)$", banner): 97 if re.match(r"^(https?|ftp)://[a-z0-9-]+(\.[a-z0-9-]+)+(/[\w-]+)*/[\w-]+\.(gif|png|jpg)$", banner):
82 style = {'banner': banner} 98 style = {'banner': banner}
83 else: 99 else:
84 style = {} 100 style = {}
85 self.render_html_blog(mblog_data, style, request, profile) 101 self.__render_html_blog(mblog_data, style, request, profile)
86 eb = lambda failure: self.render_error_blog(failure, request, profile) 102 eb = lambda failure: self.render_error_blog(failure, request, profile)
87 self.host.bridge.asyncGetParamA('Blog banner', 'Misc', 'value', Const.SERVER_SECURITY_LIMIT, profile, callback=check_banner, errback=eb) 103 self.host.bridge.asyncGetParamA('Blog banner', 'Misc', 'value', Const.SERVER_SECURITY_LIMIT, profile, callback=check_banner, errback=eb)
88 104
89 def render_html_blog(self, mblog_data, style, request, profile): 105 def __render_html_blog(self, mblog_data, style, request, profile):
90 """Actually rendering the static blog 106 """Actually render the static blog. If mblog_data is a list of dict, we are missing
91 @param mblog_data: list of microblog data 107 the comments items so we just display the main items. If mblog_data is a list of couple,
108 each couple is associating a main item data with the list of its comments, so we render all.
109 @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data)
92 @param style: dict defining the blog's rendering parameters 110 @param style: dict defining the blog's rendering parameters
93 @param request: the HTTP request 111 @param request: the HTTP request
94 @profile 112 @profile
95 """ 113 """
96 if not isinstance(style, dict): 114 if not isinstance(style, dict):
97 style = {} 115 style = {}
98 user = sanitizeHtml(profile).encode('utf-8') 116 user = sanitizeHtml(profile).encode('utf-8')
117 root_url = '../' * len(request.postpath)
118 base_url = root_url + 'blog/' + user
99 banner = style['banner'].encode('utf-8') if 'banner' in style else '' 119 banner = style['banner'].encode('utf-8') if 'banner' in style else ''
100 banner_elt = "<img src='%(banner)s' alt='%(user)s'/>" % {'user': user, 'banner': banner} if banner else user 120 banner_elt = "<img src='%(banner)s' alt='%(user)s'/>" % {'user': user, 'banner': banner} if banner else user
101 request.write(""" 121 request.write("""
102 <html> 122 <html>
103 <head> 123 <head>
104 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 124 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
105 <link rel="alternate" type="application/atom+xml" href="%(user)s/atom.xml"/> 125 <link rel="alternate" type="application/atom+xml" href="%(base)s/atom.xml"/>
106 <link rel="stylesheet" type="text/css" href="../css/blog.css" /> 126 <link rel="stylesheet" type="text/css" href="%(root)scss/blog.css" />
107 <title>%(user)s's microblog</title> 127 <title>%(user)s's microblog</title>
108 </head> 128 </head>
109 <body> 129 <body>
110 <div class='mblog_title'>%(banner_elt)s</div> 130 <div class="mblog_title"><a href="%(base)s">%(banner_elt)s</a></div>
111 """ % {'user': user, 'banner_elt': banner_elt}) 131 """ % {'base': base_url,
112 mblog_data = sorted(mblog_data, key=lambda entry: (-float(entry.get('published', 0)))) 132 'root': root_url,
133 'user': user,
134 'banner_elt': banner_elt})
135 mblog_data = [(entry if isinstance(entry, tuple) else (entry, [])) for entry in mblog_data]
136 mblog_data = sorted(mblog_data, key=lambda entry: (-float(entry[0].get('published', 0))))
113 for entry in mblog_data: 137 for entry in mblog_data:
114 timestamp = float(entry.get('published', 0)) 138 self.__render_html_entry(entry[0], base_url, request)
115 _datetime = datetime.fromtimestamp(timestamp) 139 comments = sorted(entry[1], key=lambda entry: (float(entry.get('published', 0))))
116 140 for comment in comments:
117 def getText(key): 141 self.__render_html_entry(comment, base_url, request)
118 if ('%s_xhtml' % key) in entry:
119 return entry['%s_xhtml' % key].encode('utf-8')
120 elif key in entry:
121 processor = addURLToText if key.startswith('content') else sanitizeHtml
122 return processor(entry[key]).encode('utf-8')
123 return ''
124
125 body = getText('content')
126 title = getText('title')
127 if title:
128 body = "<h1>%s</h1>\n%s" % (title, body)
129 request.write("""<div class='mblog_entry'><span class='mblog_timestamp'>%(date)s</span>
130 <span class='mblog_content'>%(content)s</span></div>""" % {
131 'date': _datetime,
132 'content': body})
133 request.write('</body></html>') 142 request.write('</body></html>')
134 request.finish() 143 request.finish()
144
145 def __render_html_entry(self, entry, base_url, request):
146 """Render one microblog entry.
147 @param entry: the microblog entry
148 @param base_url: the base url of the blog
149 @param request: the HTTP request
150 """
151 timestamp = float(entry.get('published', 0))
152 datetime_ = datetime.fromtimestamp(timestamp)
153 is_comment = entry['type'] == 'comment'
154 if is_comment:
155 author = (_("comment from %s") % entry['author']).encode('utf-8')
156 item_link = ''
157 else:
158 author = '&nbsp;'
159 item_link = ("%(base)s/%(item_id)s" % {'base': base_url, 'item_id': entry['id']}).encode('utf-8')
160
161 def getText(key):
162 if ('%s_xhtml' % key) in entry:
163 return entry['%s_xhtml' % key].encode('utf-8')
164 elif key in entry:
165 processor = addURLToText if key.startswith('content') else sanitizeHtml
166 return processor(entry[key]).encode('utf-8')
167 return ''
168
169 def addMainItemLink(elem):
170 if not item_link or not elem:
171 return elem
172 return """<a href="%(link)s" class="item_link">%(elem)s</a>""" % {'link': item_link, 'elem': elem}
173
174 header = addMainItemLink("""<div class="mblog_header">
175 <div class="mblog_metadata">
176 <div class="mblog_author">%(author)s</div>
177 <div class="mblog_timestamp">%(date)s</div>
178 </div>
179 </div>""" % {'author': author, 'date': datetime_})
180
181 title = addMainItemLink(getText('title'))
182 body = getText('content')
183 if title: # insert the title within the body
184 body = """<h1>%(title)s</h1>\n%(body)s""" % {'title': title, 'body': body}
185
186 request.write("""<div class="mblog_entry %(extra_style)s">
187 %(header)s
188 <span class="mblog_content">%(content)s</span>
189 </div>""" %
190 {'extra_style': 'mblog_comment' if entry['type'] == 'comment' else '',
191 'item_link': item_link,
192 'header': header,
193 'content': body})
135 194
136 def render_atom_feed(self, feed, request): 195 def render_atom_feed(self, feed, request):
137 request.write(feed.encode('utf-8')) 196 request.write(feed.encode('utf-8'))
138 request.finish() 197 request.finish()
139 198