[FIX] website: change view (lang_selected) for new qweb security access
[odoo/odoo.git] / addons / website / models / website.py
1 # -*- coding: utf-8 -*-
2 import functools
3 import simplejson
4
5 import openerp
6 from openerp.osv import osv, fields
7 from openerp.addons.web import http
8 from openerp.addons.web.http import request
9 import urllib
10 from urlparse import urljoin
11 import math
12 import traceback
13 from openerp.tools.safe_eval import safe_eval
14 from openerp.exceptions import AccessError, AccessDenied
15 import werkzeug
16
17 import logging
18 logger = logging.getLogger(__name__)
19
20 def route(routes, *route_args, **route_kwargs):
21     def decorator(f):
22         new_routes = routes if isinstance(routes, list) else [routes]
23         f.multilang = route_kwargs.get('multilang', False)
24         if f.multilang:
25             route_kwargs.pop('multilang')
26             for r in list(new_routes):
27                 new_routes.append('/<string(length=5):lang_code>' + r)
28         @http.route(new_routes, *route_args, **route_kwargs)
29         @functools.wraps(f, assigned=functools.WRAPPER_ASSIGNMENTS + ('func_name',))
30         def wrap(*args, **kwargs):
31             request.route_lang = kwargs.get('lang_code', None)
32             if not hasattr(request, 'website'):
33                 request.multilang = f.multilang
34                 request.website = request.registry['website'].get_current()
35                 if request.route_lang:
36                     lang_ok = [lg.code for lg in request.website.language_ids if lg.code == request.route_lang]
37                     if not lang_ok:
38                         return request.not_found()
39                 request.website.preprocess_request(*args, **kwargs)
40             return f(*args, **kwargs)
41         return wrap
42     return decorator
43
44 def auth_method_public():
45     registry = openerp.modules.registry.RegistryManager.get(request.db)
46     if not request.session.uid:
47         request.uid = registry['website'].get_public_user().id
48     else:
49         request.uid = request.session.uid
50 http.auth_methods['public'] = auth_method_public
51
52 def url_for(path, lang=None):
53     if request:
54         path = urljoin(request.httprequest.path, path)
55         langs = request.context.get('langs')
56         if path[0] == '/' and len(langs) > 1:
57             ps = path.split('/')
58             lang = lang or request.context.get('lang')
59             if ps[1] in langs:
60                 ps[1] = lang
61             else:
62                 ps.insert(1, lang)
63             path = '/'.join(ps)
64     return path
65
66 def urlplus(url, params):
67     if not params:
68         return url
69     url += "?"
70     for k,v in params.items():
71         url += "%s=%s&" % (k, urllib.quote_plus(str(v)))
72     return url
73
74 class website(osv.osv):
75     _name = "website" # Avoid website.website convention for conciseness (for new api). Got a special authorization from xmo and rco
76     _description = "Website"
77     _columns = {
78         'name': fields.char('Domain'),
79         'company_id': fields.many2one('res.company', string="Company"),
80         'language_ids': fields.many2many('res.lang', 'website_lang_rel', 'website_id', 'lang_id', 'Languages'),
81         'default_lang_id': fields.many2one('res.lang', string="Default language"),
82         'social_twitter': fields.char('Twitter Account'),
83         'social_facebook': fields.char('Facebook Account'),
84         'social_github': fields.char('GitHub Account'),
85         'social_linkedin': fields.char('LinkedIn Account'),
86         'social_youtube': fields.char('Youtube Account'),
87         'social_googleplus': fields.char('Google+ Account'),
88     }
89
90     public_user = None
91
92     def get_public_user(self):
93         if not self.public_user:
94             ref = request.registry['ir.model.data'].get_object_reference(request.cr, openerp.SUPERUSER_ID, 'website', 'public_user')
95             self.public_user = request.registry[ref[0]].browse(request.cr, openerp.SUPERUSER_ID, ref[1])
96         return self.public_user
97
98     def get_lang(self):
99         website = request.registry['website'].get_current()
100
101         if hasattr(request, 'route_lang'):
102             lang = request.route_lang
103         else:
104             lang = request.params.get('lang', None) or request.httprequest.cookies.get('lang', None)
105
106         if lang not in [lg.code for lg in website.language_ids]:
107             lang = website.default_lang_id.code
108
109         return lang
110
111     def preprocess_request(self, cr, uid, ids, *args, **kwargs):
112         def redirect(url):
113             return werkzeug.utils.redirect(url_for(url))
114         request.redirect = redirect
115
116         is_public_user = request.uid == self.get_public_user().id
117         lang = self.get_lang()
118         is_master_lang = lang == request.website.default_lang_id.code
119         request.context.update({
120             'lang': lang,
121             'lang_selected': [lg for lg in request.website.language_ids if lg.code == lang],
122             'langs': [lg.code for lg in request.website.language_ids],
123             'multilang': request.multilang,
124             'is_public_user': is_public_user,
125             'is_master_lang': is_master_lang,
126             'editable': not is_public_user,
127             'translatable': not is_public_user and not is_master_lang and request.multilang,
128         })
129
130     def get_current(self):
131         # WIP, currently hard coded
132         return self.browse(request.cr, request.uid, 1)
133
134     def render(self, cr, uid, ids, template, values=None):
135         view = request.registry.get("ir.ui.view")
136         IMD = request.registry.get("ir.model.data")
137         user = request.registry.get("res.users")
138
139         qweb_context = request.context.copy()
140
141         if values:
142             qweb_context.update(values)
143
144         qweb_context.update(
145             request=request,
146             registry=request.registry,
147             json=simplejson,
148             website=request.website,
149             url_for=url_for,
150             res_company=request.website.company_id,
151             user_id=user.browse(cr, uid, uid),
152         )
153
154         context = request.context.copy()
155         context.update(
156             inherit_branding=qweb_context.setdefault('editable', False),
157         )
158
159         # check if xmlid of the template exists
160         try:
161             module, xmlid = template.split('.', 1)
162             IMD.get_object_reference(cr, uid, module, xmlid)
163         except ValueError: # catches both unpack errors and gor errors
164             module, xmlid = 'website', template
165             try:
166                 IMD.get_object_reference(cr, uid, module, xmlid)
167             except ValueError:
168                 logger.error("Website Rendering Error.\n\n%s" % traceback.format_exc())
169                 return self.render(cr, uid, ids, 'website.404', qweb_context)
170
171         try:
172             return view.render(cr, uid, "%s.%s" % (module, xmlid),
173                                qweb_context, context=context)
174         except (AccessError, AccessDenied), err:
175             logger.error(err)
176             qweb_context['error'] = err[1]
177             logger.warn("Website Rendering Error.\n\n%s" % traceback.format_exc())
178             return self.render(cr, uid, ids, 'website.401', qweb_context)
179         except Exception:
180             logger.exception("Website Rendering Error.")
181             qweb_context['traceback'] = traceback.format_exc()
182             return view.render(
183                 cr, uid,
184                 'website.500' if qweb_context['editable'] else 'website.404',
185                 qweb_context, context=context)
186
187     def pager(self, cr, uid, ids, url, total, page=1, step=30, scope=5, url_args=None):
188         # Compute Pager
189         page_count = int(math.ceil(float(total) / step))
190
191         page = max(1, min(int(page), page_count))
192         scope -= 1
193
194         pmin = max(page - int(math.floor(scope/2)), 1)
195         pmax = min(pmin + scope, page_count)
196
197         if pmax - pmin < scope:
198             pmin = pmax - scope if pmax - scope > 0 else 1
199
200         def get_url(page):
201             _url = "%spage/%s/" % (url, page)
202             if url_args:
203                 _url = "%s?%s" % (_url, urllib.urlencode(url_args))
204             return _url
205
206         return {
207             "page_count": page_count,
208             "offset": (page - 1) * step,
209             "page": {'url': get_url(page), 'num': page},
210             "page_start": {'url': get_url(pmin), 'num': pmin},
211             "page_end": {'url': get_url(min(pmax, page + 1)),
212                          'num': min(pmax, page + 1)},
213             "pages": [
214                 {'url': get_url(page), 'num': page}
215                 for page in xrange(pmin, pmax+1)
216             ]
217         }
218
219     def list_pages(self, cr, uid, ids, context=None):
220         """ Available pages in the website/CMS. This is mostly used for links
221         generation and can be overridden by modules setting up new HTML
222         controllers for dynamic pages (e.g. blog).
223
224         By default, returns template views marked as pages.
225
226         :returns: a list of mappings with two keys: ``name`` is the displayable
227                   name of the resource (page), ``url`` is the absolute URL
228                   of the same.
229         :rtype: list({name: str, url: str})
230         """
231         View = self.pool['ir.ui.view']
232         views = View.search_read(cr, uid, [['page', '=', True]],
233                                  fields=['name'], order='name', context=context)
234         xids = View.get_external_id(cr, uid, [view['id'] for view in views], context=context)
235
236         return [
237             {'name': view['name'], 'url': '/page/' + xids[view['id']]}
238             for view in views
239             if xids[view['id']]
240         ]
241
242     def kanban(self, cr, uid, ids, model, domain, column, template, step=None, scope=None, orderby=None):
243         step = step and int(step) or 10
244         scope = scope and int(scope) or 5
245         orderby = orderby or "name"
246
247         get_args = dict(request.httprequest.args or {})
248         model_obj = request.registry[model]
249         relation = model_obj._columns.get(column)._obj
250         relation_obj = request.registry[relation]
251
252         get_args.setdefault('kanban', "")
253         kanban = get_args.pop('kanban')
254         kanban_url = "?%s&kanban=" % urllib.urlencode(get_args)
255
256         pages = {}
257         for col in kanban.split(","):
258             if col:
259                 col = col.split("-")
260                 pages[int(col[0])] = int(col[1])
261
262         objects = []
263         for group in model_obj.read_group(cr, uid, domain, ["id", column], groupby=column):
264             obj = {}
265
266             # browse column
267             relation_id = group[column][0]
268             obj['column_id'] = relation_obj.browse(cr, uid, relation_id)
269
270             obj['kanban_url'] = kanban_url
271             for k, v in pages.items():
272                 if k != relation_id:
273                     obj['kanban_url'] += "%s-%s" % (k, v)
274
275             # pager
276             number = model_obj.search(cr, uid, group['__domain'], count=True)
277             obj['page_count'] = int(math.ceil(float(number) / step))
278             obj['page'] = pages.get(relation_id) or 1
279             if obj['page'] > obj['page_count']:
280                 obj['page'] = obj['page_count']
281             offset = (obj['page']-1) * step
282             obj['page_start'] = max(obj['page'] - int(math.floor((scope-1)/2)), 1)
283             obj['page_end'] = min(obj['page_start'] + (scope-1), obj['page_count'])
284
285             # view data
286             obj['domain'] = group['__domain']
287             obj['model'] = model
288             obj['step'] = step
289             obj['orderby'] = orderby
290
291             # browse objects
292             object_ids = model_obj.search(cr, uid, group['__domain'], limit=step, offset=offset, order=orderby)
293             obj['object_ids'] = model_obj.browse(cr, uid, object_ids)
294
295             objects.append(obj)
296
297         values = {
298             'objects': objects,
299             'range': range,
300             'template': template,
301         }
302         return request.website.render("website.kanban_contain", values)
303
304     def kanban_col(self, cr, uid, ids, model, domain, page, template, step, orderby):
305         html = ""
306         model_obj = request.registry[model]
307         domain = safe_eval(domain)
308         step = int(step)
309         offset = (int(page)-1) * step
310         object_ids = model_obj.search(cr, uid, domain, limit=step, offset=offset, order=orderby)
311         object_ids = model_obj.browse(cr, uid, object_ids)
312         for object_id in object_ids:
313             html += request.website.render(template, {'object_id': object_id})
314         return html
315
316 class ir_attachment(osv.osv):
317     _inherit = "ir.attachment"
318     def _website_url_get(self, cr, uid, ids, name, arg, context=None):
319         context = context or {}
320         result = {}
321         for attach in self.browse(cr, uid, ids, context=context):
322             if attach.type=='url':
323                 result[attach.id] = attach.url
324             else:
325                 result[attach.id] = "/website/attachment/"+str(attach.id)
326         return result
327     _columns = {
328         'website_url': fields.function(_website_url_get, string="Attachment URL", type='char')
329     }
330
331 class res_partner(osv.osv):
332     _inherit = "res.partner"
333
334     def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
335         partner = self.browse(cr, uid, ids[0], context=context)
336         params = {
337             'center': '%s, %s %s, %s' % (partner.street, partner.city, partner.zip, partner.country_id and partner.country_id.name_get()[0][1] or ''),
338             'size': "%sx%s" % (height, width),
339             'zoom': zoom,
340             'sensor': 'false',
341         }
342         return urlplus('http://maps.googleapis.com/maps/api/staticmap' , params)
343
344     def google_map_link(self, cr, uid, ids, zoom=8, context=None):
345         partner = self.browse(cr, uid, ids[0], context=context)
346         params = {
347             'q': '%s, %s %s, %s' % (partner.street, partner.city, partner.zip, partner.country_id and partner.country_id.name_get()[0][1] or ''),
348         }
349         return urlplus('https://maps.google.be/maps' , params)
350
351 class base_language_install(osv.osv):
352     _inherit = "base.language.install"
353     _columns = {
354         'website_ids': fields.many2many('website', string='Websites to translate'),
355     }
356
357     def lang_install(self, cr, uid, ids, context=None):
358         if context is None:
359             context = {}
360         action = super(base_language_install, self).lang_install(cr, uid, ids, context)
361         language_obj = self.browse(cr, uid, ids)[0]
362         website_ids = [website.id for website in language_obj['website_ids']]
363         lang_id = self.pool['res.lang'].search(cr, uid, [('code', '=', language_obj['lang'])])
364         if website_ids and lang_id:
365             data = {'language_ids': [(4, lang_id[0])]}
366             self.pool['website'].write(cr, uid, website_ids, data)
367         params = context.get('params', {})
368         if 'url_return' in params:
369             return {
370                 'url': params['url_return'].replace('[lang]', language_obj['lang']),
371                 'type': 'ir.actions.act_url',
372                 'target': 'self'
373             }
374         return action