Mercurial > libervia-web
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)))) |