Mercurial > libervia-web
comparison src/pages/blog/view/page_meta.py @ 1077:880ea673aaff
blog: moved blog page from /common to /blog:
- removed common pages (it was only used for blog so far, it may come back in the future if needed)
- /blog now prepares a discover page by default
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 26 Mar 2018 08:20:41 +0200 |
parents | |
children | ff503f23ac37 |
comparison
equal
deleted
inserted
replaced
1076:5bf288f84862 | 1077:880ea673aaff |
---|---|
1 #!/usr/bin/env python2.7 | |
2 # -*- coding: utf-8 -*- | |
3 from libervia.server.constants import Const as C | |
4 from twisted.words.protocols.jabber import jid | |
5 from twisted.internet import defer | |
6 from sat.tools.common import data_objects | |
7 from libervia.server import session_iface | |
8 from sat.core.i18n import _ | |
9 from sat.tools.common.template import safe | |
10 from sat.tools.common import uri | |
11 from libervia.server import utils | |
12 import unicodedata | |
13 import re | |
14 import cgi | |
15 from sat.core.log import getLogger | |
16 log = getLogger('pages/common/blog') | |
17 | |
18 """generic blog (with service/node provided)""" | |
19 name = u'blog_view' | |
20 template = u"blog/articles.html" | |
21 uri_handlers = {(u'pubsub', u'microblog'): 'microblog_uri'} | |
22 | |
23 RE_TEXT_URL = re.compile(ur'[^a-zA-Z,_]+') | |
24 TEXT_MAX_LEN = 60 | |
25 TEXT_WORD_MIN_LENGHT = 4 | |
26 URL_LIMIT_MARK = 90 # if canonical URL is longer than that, text will not be appended | |
27 | |
28 | |
29 def microblog_uri(self, uri_data): | |
30 args = [uri_data[u'path'], uri_data[u'node']] | |
31 if u'item' in uri_data: | |
32 args.extend([u'id', uri_data[u'item']]) | |
33 return self.getURL(*args) | |
34 | |
35 def parse_url(self, request): | |
36 """URL is /[service]/[node]/[filter_keyword]/[item]|[other] | |
37 | |
38 if [node] is '@', default namespace is used | |
39 if a value is unset, default one will be used | |
40 keyword can be one of: | |
41 id: next value is a item id | |
42 tag: next value is a blog tag | |
43 """ | |
44 data = self.getRData(request) | |
45 | |
46 try: | |
47 service = self.nextPath(request) | |
48 except IndexError: | |
49 data['service'] = u'' | |
50 else: | |
51 try: | |
52 data[u"service"] = jid.JID(service) | |
53 except Exception: | |
54 log.warning(_(u"bad service entered: {}").format(service)) | |
55 self.pageError(request, C.HTTP_BAD_REQUEST) | |
56 | |
57 try: | |
58 data['node'] = self.nextPath(request) | |
59 except IndexError: | |
60 data['node'] = u'' | |
61 else: | |
62 if data['node'] == u'@': | |
63 data['node'] = u'' | |
64 | |
65 try: | |
66 filter_kw = data['filter_keyword'] = self.nextPath(request) | |
67 except IndexError: | |
68 pass | |
69 else: | |
70 if filter_kw == u'id': | |
71 try: | |
72 data[u'item'] = self.nextPath(request) | |
73 except IndexError: | |
74 self.pageError(request, C.HTTP_BAD_REQUEST) | |
75 # we get one more argument in case text has been added to have a nice URL | |
76 try: | |
77 self.nextPath(request) | |
78 except IndexError: | |
79 pass | |
80 elif filter_kw == u'tag': | |
81 try: | |
82 data[u'tag'] = self.nextPath(request) | |
83 except IndexError: | |
84 self.pageError(request, C.HTTP_BAD_REQUEST) | |
85 else: | |
86 # invalid filter keyword | |
87 log.warning(_(u"invalid filter keyword: {filter_kw}").format(filter_kw=filter_kw)) | |
88 self.pageError(request, C.HTTP_BAD_REQUEST) | |
89 | |
90 | |
91 @defer.inlineCallbacks | |
92 def appendComments(self, blog_items, identities, profile): | |
93 for blog_item in blog_items: | |
94 if identities is not None: | |
95 author = blog_item.author_jid | |
96 if not author: | |
97 log.warning(_(u"no author found for item {item_id}").format(item_id=blog_item.id)) | |
98 else: | |
99 if author not in identities: | |
100 identities[author] = yield self.host.bridgeCall(u'identityGet', author, profile) | |
101 for comment_data in blog_item.comments: | |
102 service = comment_data[u'service'] | |
103 node = comment_data[u'node'] | |
104 try: | |
105 comments_data = yield self.host.bridgeCall(u'mbGet', | |
106 service, | |
107 node, | |
108 C.NO_LIMIT, | |
109 [], | |
110 {}, | |
111 profile) | |
112 except Exception as e: | |
113 log.warning(_(u"Can't get comments at {node} (service: {service}): {msg}").format( | |
114 service=service, | |
115 node=node, | |
116 msg=e)) | |
117 continue | |
118 | |
119 comments = data_objects.BlogItems(comments_data) | |
120 blog_item.appendCommentsItems(comments) | |
121 yield appendComments(self, comments, identities, profile) | |
122 | |
123 @defer.inlineCallbacks | |
124 def getBlogData(self, request, service, node, item_id, extra, profile): | |
125 try: | |
126 if item_id: | |
127 items_id = [item_id] | |
128 else: | |
129 items_id = [] | |
130 blog_data = yield self.host.bridgeCall(u'mbGet', | |
131 service.userhost(), | |
132 node, | |
133 C.NO_LIMIT, | |
134 items_id, | |
135 extra, | |
136 profile) | |
137 except Exception as e: | |
138 # FIXME: need a better way to test errors in bridge errback | |
139 if u"forbidden" in unicode(e): | |
140 self.pageError(request, 401) | |
141 else: | |
142 log.warning(_(u"can't retrieve blog for [{service}]: {msg}".format( | |
143 service = service.userhost(), msg=e))) | |
144 blog_data = ([], {}) | |
145 | |
146 items = data_objects.BlogItems(blog_data) | |
147 defer.returnValue((blog_data, items)) | |
148 | |
149 @defer.inlineCallbacks | |
150 def prepare_render(self, request): | |
151 data = self.getRData(request) | |
152 # if the comments are not explicitly hidden, we show them | |
153 service, node, item_id, show_comments = data.get(u'service', u''), data.get(u'node', u''), data.get(u'item'), data.get(u'show_comments', True) | |
154 profile = self.getProfile(request) | |
155 if profile is None: | |
156 profile = C.SERVICE_PROFILE | |
157 | |
158 ## pagination/filtering parameters | |
159 params = self.getAllPostedData(request, multiple=False) | |
160 if item_id: | |
161 extra = {} | |
162 else: | |
163 extra = {u'rsm_max': u'10'} | |
164 if u'after' in params: | |
165 extra[u'rsm_after'] = params[u'after'] | |
166 elif u'before' in params: | |
167 extra[u'rsm_before'] = params[u'before'] | |
168 tag = data.get('tag') | |
169 if tag: | |
170 extra[u'mam_filter_{}'.format(C.MAM_FILTER_CATEGORY)] = tag | |
171 | |
172 ## main data ## | |
173 # we get data from backend/XMPP here | |
174 blog_data, items = yield getBlogData(self, request, service, node, item_id, extra, profile) | |
175 | |
176 ## navigation ## | |
177 # no let's fill service, node and pagination URLs | |
178 template_data = request.template_data | |
179 if u'service' not in template_data: | |
180 template_data[u'service'] = service | |
181 if u'node' not in template_data: | |
182 template_data[u'node'] = node | |
183 target_profile = template_data.get(u'target_profile') | |
184 | |
185 if items: | |
186 if not item_id: | |
187 last_id = items[-1].id | |
188 template_data['older_url'] = self.getParamURL(request, after=last_id) | |
189 if u'before' in params or u'after' in params: | |
190 first_id = items[0].id | |
191 template_data['newer_url'] = self.getParamURL(request, before=first_id) | |
192 else: | |
193 if item_id: | |
194 # if item id has been specified in URL and it's not found, | |
195 # we must return an error | |
196 self.pageError(request, C.HTTP_NOT_FOUND) | |
197 | |
198 # no items, we have requested items before last post, or blog is empty | |
199 extra = {u'rsm_max': u'10'} | |
200 blog_data, items = yield getBlogData(self, request, service, node, None, extra, profile) | |
201 if items: | |
202 last_id = items[-1].id | |
203 template_data['older_url'] = self.getParamURL(request, after=last_id) | |
204 | |
205 ## identities ## | |
206 # identities are use to show nice nickname or avatars | |
207 identities = template_data[u'identities'] = self.host.getSessionData(request, session_iface.ISATSession).identities | |
208 | |
209 ## Comments ## | |
210 # if comments are requested, we need to take them | |
211 if show_comments: | |
212 yield appendComments(self, items, identities, profile) | |
213 | |
214 ## URLs ## | |
215 # We will fill items_http_uri and tags_http_uri in template_data with suitable urls | |
216 # if we know the profile, we use it instead of service + blog (nicer url) | |
217 if target_profile is None: | |
218 blog_base_url_item = self.getPageByName(u'blog_view').getURL(service.full(), node or u'@', u'id') | |
219 blog_base_url_tag = self.getPageByName(u'blog_view').getURL(service.full(), node or u'@', u'tag') | |
220 else: | |
221 blog_base_url_item = self.getURLByNames([(u'user', [target_profile]), (u'user_blog', [u'id'])]) | |
222 blog_base_url_tag = self.getURLByNames([(u'user', [target_profile]), (u'user_blog', [u'tag'])]) | |
223 # we also set the background image if specified by user | |
224 bg_img = yield self.host.bridgeCall(u'asyncGetParamA', u'Background', u'Blog page', u'value', -1, template_data[u'target_profile']) | |
225 if bg_img: | |
226 template_data['dynamic_style'] = safe(u""" | |
227 :root { | |
228 --bg-img: url("%s"); | |
229 } | |
230 """ % cgi.escape(bg_img, True)) | |
231 | |
232 template_data[u'items'] = data[u'items'] = items | |
233 if request.args.get('reverse') == ['1']: | |
234 template_data[u'items'].items.reverse() | |
235 template_data[u'items_http_uri'] = items_http_uri = {} | |
236 template_data[u'tags_http_uri'] = tags_http_uri = {} | |
237 | |
238 | |
239 for item in items: | |
240 blog_canonical_url = u'/'.join([blog_base_url_item, utils.quote(item.id)]) | |
241 if len(blog_canonical_url) > URL_LIMIT_MARK: | |
242 blog_url = blog_canonical_url | |
243 else: | |
244 # we add text from title or body at the end of URL | |
245 # to make it more human readable | |
246 text = item.title or item.content | |
247 # we change special chars to ascii one, trick found at https://stackoverflow.com/a/3194567 | |
248 text = unicodedata.normalize('NFD', text).encode('ascii', 'ignore') | |
249 text = RE_TEXT_URL.sub(u' ', text).lower() | |
250 text = u'-'.join([t for t in text.split() if t and len(t)>=TEXT_WORD_MIN_LENGHT]) | |
251 while len(text) > TEXT_MAX_LEN: | |
252 if u'-' in text: | |
253 text = text.rsplit(u'-', 1)[0] | |
254 else: | |
255 text = text[:TEXT_MAX_LEN] | |
256 if text: | |
257 blog_url = blog_canonical_url + u'/' + text | |
258 else: | |
259 blog_url = blog_canonical_url | |
260 | |
261 items_http_uri[item.id] = self.host.getExtBaseURL(request, blog_url) | |
262 for tag in item.tags: | |
263 if tag not in tags_http_uri: | |
264 tag_url = u'/'.join([blog_base_url_tag, utils.quote(tag)]) | |
265 tags_http_uri[tag] = self.host.getExtBaseURL(request, tag_url) | |
266 | |
267 # if True, page should display a comment box | |
268 template_data[u'allow_commenting'] = data.get(u'allow_commenting', False) | |
269 | |
270 # last but not least, we add a xmpp: link to the node | |
271 uri_args = {u'path': service.full()} | |
272 if node: | |
273 uri_args[u'node'] = node | |
274 if item_id: | |
275 uri_args[u'item'] = item_id | |
276 template_data[u'xmpp_uri'] = uri.buildXMPPUri(u'pubsub', subtype='microblog', **uri_args) | |
277 | |
278 | |
279 @defer.inlineCallbacks | |
280 def on_data_post(self, request): | |
281 profile = self.getProfile(request) | |
282 if profile is None: | |
283 self.pageError(request, C.HTTP_UNAUTHORIZED) | |
284 type_ = self.getPostedData(request, u'type') | |
285 if type_ == u'comment': | |
286 service, node, body = self.getPostedData(request, (u'service', u'node', u'body')) | |
287 | |
288 if not body: | |
289 self.pageError(request, C.HTTP_BAD_REQUEST) | |
290 comment_data = {u"content": body} | |
291 try: | |
292 yield self.host.bridgeCall(u'mbSend', service, node, comment_data, profile) | |
293 except Exception as e: | |
294 if u"forbidden" in unicode(e): | |
295 self.pageError(request, 401) | |
296 else: | |
297 raise e | |
298 else: | |
299 log.warning(_(u"Unhandled data type: {}").format(type_)) |