comparison src/server/blog.py @ 483:0bbbef1d53a8

server side (blog): use user's avatar a the blog's favicon + small refactorization
author souliane <souliane@mailoo.org>
date Tue, 17 Jun 2014 16:21:42 +0200
parents bbdc5357dc00
children ae86b32b959c
comparison
equal deleted inserted replaced
482:437eefa53a01 483:0bbbef1d53a8
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.core.i18n import _
21 from sat_frontends.tools.strings import addURLToText 21 from sat_frontends.tools.strings import addURLToText
22 from sat.core.log import getLogger
23 log = getLogger(__name__)
22 24
23 from twisted.internet import defer 25 from twisted.internet import defer
24 from twisted.web import server 26 from twisted.web import server
25 from twisted.web.resource import Resource 27 from twisted.web.resource import Resource
26 from twisted.words.protocols.jabber.jid import JID 28 from twisted.words.protocols.jabber.jid import JID
27 from datetime import datetime 29 from datetime import datetime
28 import uuid 30 import uuid
29 import re 31 import re
32 import os
30 33
31 from libervia.server.html_tools import sanitizeHtml 34 from libervia.server.html_tools import sanitizeHtml
32 from libervia.server.constants import Const as C 35 from libervia.server.constants import Const as C
33 36
34 37
48 """ 51 """
49 52
50 def __init__(self, host): 53 def __init__(self, host):
51 self.host = host 54 self.host = host
52 Resource.__init__(self) 55 Resource.__init__(self)
56 self.host.bridge.register('entityDataUpdated', self.entityDataUpdatedCb)
57 self.host.bridge.register('actionResult', self.actionResultCb) # FIXME: actionResult is to be removed
58 self.waiting_deferreds = {}
59
60 def entityDataUpdatedCb(self, entity_jid_s, key, value, dummy):
61 """Retrieve the avatar we've been waiting for and fires the callback
62 for self.getAvatar to return.
63
64 @param entity_jid_s (str): JID of the contact
65 @param key (str): entity data key
66 @param value (str): entity data value
67 @param dummy (str): that would be C.SERVICE_PROFILE
68 """
69 if key != 'avatar':
70 return
71 try:
72 avatar = (C.AVATARS_DIR + value)
73 self.waiting_deferreds[entity_jid_s][1].callback(avatar)
74 del self.waiting_deferreds[entity_jid_s]
75 except KeyError:
76 log.error(_("Avatar retrieved but key not found in the waiting list for entity %s" % entity_jid_s))
77
78 def actionResultCb(self, answer_type, action_id, data, dummy):
79 """Fires the callback for self.getAvatar to return
80
81 @param answer_type (str): 'SUPPRESS' or another value that we would ignore
82 @param action_id (str): the request ID
83 @param data (dict): ignored
84 @param dummy (str): that would be C.SERVICE_PROFILE
85 """
86 # FIXME: actionResult is to be removed. For now we use it to get notified
87 # when the requested vCard hasn't been found. Replace with the new system.
88 if answer_type != 'SUPPRESS':
89 return
90 try:
91 entity_jid_s = [key for (key, value) in self.waiting_deferreds.items() if value[0] == action_id][0]
92 except IndexError:
93 log.error(_("Key not found in the waiting list for request ID %s" % action_id))
94 return
95 self.waiting_deferreds[entity_jid_s][1].callback(C.DEFAULT_AVATAR)
96 del self.waiting_deferreds[entity_jid_s]
97
98 def getAvatar(self, profile):
99 """Get the avatar of the given profile
100
101 @param profile (str):
102 @return: deferred avatar path, relative to the server's root
103 """
104 jid_s = profile + '@' + self.host.bridge.getNewAccountDomain()
105 data = self.host.bridge.getEntityData(jid_s, ['avatar'], C.SERVICE_PROFILE)
106 if 'avatar' in data:
107 return defer.succeed(C.AVATARS_DIR + data['avatar'])
108 # FIXME: request_id is no more need when actionResult is removed
109 request_id = self.host.bridge.getCard(jid_s, C.SERVICE_PROFILE)
110 self.waiting_deferreds[jid_s] = (request_id, defer.Deferred())
111 return self.waiting_deferreds[jid_s][1]
53 112
54 def render_GET(self, request): 113 def render_GET(self, request):
55 if not request.postpath: 114 if not request.postpath:
56 return MicroBlog.ERROR_TEMPLATE % {'root': '', 115 return MicroBlog.ERROR_TEMPLATE % {'root': '',
57 'message': "You must indicate a nickname"} 116 'message': "You must indicate a nickname"}
98 @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data) 157 @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data)
99 @param request: HTTP request 158 @param request: HTTP request
100 @param profile 159 @param profile
101 """ 160 """
102 d_list = [] 161 d_list = []
103 style = {} 162 options = {}
104 163
105 def getCallback(param_name): 164 def getCallback(param_name):
106 d = defer.Deferred() 165 d = defer.Deferred()
107 d.addCallback(lambda value: style.update({param_name: value})) 166 d.addCallback(lambda value: options.update({param_name: value}))
108 d_list.append(d) 167 d_list.append(d)
109 return d.callback 168 return d.callback
110 169
111 eb = lambda failure: self.render_error_blog(failure, request, profile) 170 eb = lambda failure: self.render_error_blog(failure, request, profile)
112 171
172 self.getAvatar(profile).addCallbacks(getCallback('avatar'), eb)
113 for param_name in (C.STATIC_BLOG_PARAM_TITLE, C.STATIC_BLOG_PARAM_BANNER, C.STATIC_BLOG_PARAM_KEYWORDS, C.STATIC_BLOG_PARAM_DESCRIPTION): 173 for param_name in (C.STATIC_BLOG_PARAM_TITLE, C.STATIC_BLOG_PARAM_BANNER, C.STATIC_BLOG_PARAM_KEYWORDS, C.STATIC_BLOG_PARAM_DESCRIPTION):
114 self.host.bridge.asyncGetParamA(param_name, C.STATIC_BLOG_KEY, 'value', C.SERVER_SECURITY_LIMIT, profile, callback=getCallback(param_name), errback=eb) 174 self.host.bridge.asyncGetParamA(param_name, C.STATIC_BLOG_KEY, 'value', C.SERVER_SECURITY_LIMIT, profile, callback=getCallback(param_name), errback=eb)
115 175
116 cb = lambda dummy: self.__render_html_blog(mblog_data, style, request, profile) 176 cb = lambda dummy: self.__render_html_blog(mblog_data, options, request, profile)
117 defer.DeferredList(d_list).addCallback(cb) 177 defer.DeferredList(d_list).addCallback(cb)
118 178
119 def __render_html_blog(self, mblog_data, style, request, profile): 179 def __render_html_blog(self, mblog_data, options, request, profile):
120 """Actually render the static blog. If mblog_data is a list of dict, we are missing 180 """Actually render the static blog. If mblog_data is a list of dict, we are missing
121 the comments items so we just display the main items. If mblog_data is a list of couple, 181 the comments items so we just display the main items. If mblog_data is a list of couple,
122 each couple is associating a main item data with the list of its comments, so we render all. 182 each couple is associating a main item data with the list of its comments, so we render all.
123 @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data) 183 @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data)
124 @param style: dict defining the blog's rendering parameters 184 @param options: dict defining the blog's parameters
125 @param request: the HTTP request 185 @param request: the HTTP request
126 @profile 186 @profile
127 """ 187 """
128 if not isinstance(style, dict): 188 if not isinstance(options, dict):
129 style = {} 189 options = {}
130 user = sanitizeHtml(profile).encode('utf-8') 190 user = sanitizeHtml(profile).encode('utf-8')
131 root_url = '../' * len(request.postpath) 191 root_url = '../' * len(request.postpath)
132 base_url = root_url + 'blog/' + user 192 base_url = root_url + 'blog/' + user
133 193
134 def getFromData(key): 194 def getOption(key):
135 return sanitizeHtml(style[key]).encode('utf-8') if key in style else '' 195 return sanitizeHtml(options[key]).encode('utf-8') if key in options else ''
136 196
137 def getImageFromData(key, alt): 197 def getImageOption(key, alt):
138 """regexp from http://answers.oreilly.com/topic/280-how-to-validate-urls-with-regular-expressions/""" 198 """regexp from http://answers.oreilly.com/topic/280-how-to-validate-urls-with-regular-expressions/"""
139 url = style[key].encode('utf-8') if key in style else '' 199 url = options[key].encode('utf-8') if key in options else ''
140 regexp = r"^(https?|ftp)://[a-z0-9-]+(\.[a-z0-9-]+)+(/[\w-]+)*/[\w-]+\.(gif|png|jpg)$" 200 regexp = r"^(https?|ftp)://[a-z0-9-]+(\.[a-z0-9-]+)+(/[\w-]+)*/[\w-]+\.(gif|png|jpg)$"
141 return "<img src='%(url)s' alt='%(alt)s'/>" % {'alt': alt, 'url': url} if re.match(regexp, url) else alt 201 return "<img src='%(url)s' alt='%(alt)s'/>" % {'alt': alt, 'url': url} if re.match(regexp, url) else alt
142 202
143 request.write(""" 203 request.write("""
144 <html> 204 <html>
146 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 206 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
147 <meta name="keywords" content="%(keywords)s"> 207 <meta name="keywords" content="%(keywords)s">
148 <meta name="description" content="%(description)s"> 208 <meta name="description" content="%(description)s">
149 <link rel="alternate" type="application/atom+xml" href="%(base)s/atom.xml"/> 209 <link rel="alternate" type="application/atom+xml" href="%(base)s/atom.xml"/>
150 <link rel="stylesheet" type="text/css" href="%(root)scss/blog.css" /> 210 <link rel="stylesheet" type="text/css" href="%(root)scss/blog.css" />
151 <link rel="icon" type="image/png" href="%(root)ssat_logo_16.png"> 211 <link rel="icon" type="image/png" href="%(favicon)s">
152 <title>%(title)s</title> 212 <title>%(title)s</title>
153 </head> 213 </head>
154 <body> 214 <body>
155 <div class="mblog_title"><a href="%(base)s">%(banner_elt)s</a></div> 215 <div class="mblog_title"><a href="%(base)s">%(banner_elt)s</a></div>
156 """ % {'base': base_url, 216 """ % {'base': base_url,
157 'root': root_url, 217 'root': root_url,
158 'user': user, 218 'user': user,
159 'keywords': getFromData(C.STATIC_BLOG_PARAM_KEYWORDS), 219 'keywords': getOption(C.STATIC_BLOG_PARAM_KEYWORDS),
160 'description': getFromData(C.STATIC_BLOG_PARAM_DESCRIPTION), 220 'description': getOption(C.STATIC_BLOG_PARAM_DESCRIPTION),
161 'title': getFromData(C.STATIC_BLOG_PARAM_TITLE) or "%s's microblog" % user, 221 'title': getOption(C.STATIC_BLOG_PARAM_TITLE) or "%s's microblog" % user,
162 'banner_elt': getImageFromData(C.STATIC_BLOG_PARAM_BANNER, user)}) 222 'favicon': os.path.normpath(root_url + getOption('avatar')),
223 'banner_elt': getImageOption(C.STATIC_BLOG_PARAM_BANNER, getOption(C.STATIC_BLOG_PARAM_TITLE) or user)})
163 mblog_data = [(entry if isinstance(entry, tuple) else (entry, [])) for entry in mblog_data] 224 mblog_data = [(entry if isinstance(entry, tuple) else (entry, [])) for entry in mblog_data]
164 mblog_data = sorted(mblog_data, key=lambda entry: (-float(entry[0].get('published', 0)))) 225 mblog_data = sorted(mblog_data, key=lambda entry: (-float(entry[0].get('published', 0))))
165 for entry in mblog_data: 226 for entry in mblog_data:
166 self.__render_html_entry(entry[0], base_url, request) 227 self.__render_html_entry(entry[0], base_url, request)
167 comments = sorted(entry[1], key=lambda entry: (float(entry.get('published', 0)))) 228 comments = sorted(entry[1], key=lambda entry: (float(entry.get('published', 0))))