1302e2802e0f139ce090fc025a763ae0cc24ecb7
[odoo/odoo.git] / addons / website / 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 import math
11 import traceback
12 from openerp.tools.safe_eval import safe_eval
13 from openerp.exceptions import AccessError, AccessDenied
14
15 import logging
16 logger = logging.getLogger(__name__)
17
18 def route(*route_args, **route_kwargs):
19     def decorator(f):
20         @http.route(*route_args, **route_kwargs)
21         @functools.wraps(f, assigned=functools.WRAPPER_ASSIGNMENTS + ('func_name',))
22         def wrap(*args, **kwargs):
23             request.route_lang = None # WIP: decorator will support lang argument
24             if not hasattr(request, 'website'):
25                 request.website = request.registry['website'].get_current()
26                 request.website.preprocess_request(*args, **kwargs)
27             return f(*args, **kwargs)
28         return wrap
29     return decorator
30
31 def auth_method_public():
32     registry = openerp.modules.registry.RegistryManager.get(request.db)
33     if not request.session.uid:
34         request.uid = registry['website'].get_public_user().id
35     else:
36         request.uid = request.session.uid
37 http.auth_methods['public'] = auth_method_public
38
39
40 def urlplus(url, params):
41     if not params:
42         return url
43     url += "?"
44     for k,v in params.items():
45         url += "%s=%s&" % (k, urllib.quote_plus(str(v)))
46     return url
47
48 class website(osv.osv):
49     _name = "website" # Avoid website.website convention for conciseness (for new api). Got a special authorization from xmo and rco
50     _description = "Website"
51     _columns = {
52         'name': fields.char('Domain'),
53         'company_id': fields.many2one('res.company', string="Company"),
54         'language_ids': fields.many2many('res.lang', 'website_lang_rel', 'website_id', 'lang_id', 'Languages'),
55         'default_lang_id': fields.many2one('res.lang', string="Default language"),
56     }
57
58     public_user = None
59
60     def get_public_user(self):
61         if not self.public_user:
62             ref = request.registry['ir.model.data'].get_object_reference(request.cr, openerp.SUPERUSER_ID, 'website', 'public_user')
63             self.public_user = request.registry[ref[0]].browse(request.cr, openerp.SUPERUSER_ID, ref[1])
64         return self.public_user
65
66     def get_lang(self):
67         website = request.registry['website'].get_current()
68
69         if hasattr(request, 'route_lang'):
70             lang = request.route_lang
71         else:
72             lang = request.params.get('lang', None) or request.httprequest.cookies.get('lang', None)
73
74         if lang not in [lg.code for lg in website.language_ids]:
75             lang = website.default_lang_id.code
76
77         return lang
78
79     def preprocess_request(self, cr, uid, ids, *args, **kwargs):
80         is_public_user = request.uid == self.get_public_user().id
81         request.context.update({
82             'is_public_user': is_public_user,
83             'editable': not is_public_user, # TODO: check perms
84         })
85         request.context['lang'] = self.get_lang()
86
87     def get_current(self):
88         # WIP, currently hard coded
89         return self.browse(request.cr, request.uid, 1)
90
91     def render(self, cr, uid, ids, template, values=None):
92         view = request.registry.get("ir.ui.view")
93         IMD = request.registry.get("ir.model.data")
94
95         qweb_context = request.context.copy()
96
97         if values is None:
98             values = {}
99
100         values.update({
101             'request': request,
102             'registry': request.registry,
103             'json': simplejson,
104             'website': request.website,
105             'res_company': request.website.company_id,
106         })
107
108         qweb_context.update(values)
109         context = {
110             'inherit_branding': qweb_context.get('editable', False),
111         }
112
113         # check if xmlid of the template exists
114         try:
115             model, xmlid = template.split('.', 1)
116             model, id = IMD.get_object_reference(cr, uid, model, xmlid)
117         except ValueError:
118             logger.error("Website Rendering Error.\n\n%s" % traceback.format_exc())
119             return self.render('website.404', qweb_context)
120
121         # render template and catch error
122         try:
123             return view.render(cr, uid, template, qweb_context, context=context)
124         except (AccessError, AccessDenied), err:
125             logger.error(err)
126             qweb_context['error'] = err[1]
127             logger.warn("Website Rendering Error.\n\n%s" % traceback.format_exc())
128             return self.render('website.401', qweb_context)
129         except Exception:
130             qweb_context['traceback'] = traceback.format_exc()
131             logger.error("Website Rendering Error.\n\n%s" % qweb_context['traceback'])
132             if qweb_context['editable']:
133                 return view.render(cr, uid, 'website.500', qweb_context, context=context)
134             else:
135                 return view.render(cr, uid, 'website.404', qweb_context, context=context)
136
137     def pager(self, cr, uid, ids, url, total, page=1, step=30, scope=5, url_args=None):
138         # Compute Pager
139         page_count = int(math.ceil(float(total) / step))
140
141         page = max(1, min(int(page), page_count))
142         scope -= 1
143
144         pmin = max(page - int(math.floor(scope/2)), 1)
145         pmax = min(pmin + scope, page_count)
146
147         if pmax - pmin < scope:
148             pmin = pmax - scope if pmax - scope > 0 else 1
149
150         def get_url(page):
151             _url = "%spage/%s/" % (url, page)
152             if url_args:
153                 _url = "%s?%s" % (_url, urllib.urlencode(url_args))
154             return _url
155
156         return {
157             "page_count": page_count,
158             "offset": (page - 1) * step,
159             "page": {'url': get_url(page), 'num': page},
160             "page_start": {'url': get_url(pmin), 'num': pmin},
161             "page_end": {'url': get_url(min(pmax, page + 1)),
162                          'num': min(pmax, page + 1)},
163             "pages": [
164                 {'url': get_url(page), 'num': page}
165                 for page in xrange(pmin, pmax+1)
166             ]
167         }
168
169     def list_pages(self, cr, uid, context=None):
170         """ Available pages in the website/CMS. This is mostly used for links
171         generation and can be overridden by modules setting up new HTML
172         controllers for dynamic pages (e.g. blog).
173
174         By default, returns template views marked as pages.
175
176         :returns: a list of mappings with two keys: ``name`` is the displayable
177                   name of the resource (page), ``url`` is the absolute URL
178                   of the same.
179         :rtype: list({name: str, url: str})
180         """
181         View = self.pool['ir.ui.view']
182         views = View.search_read(cr, uid, [['page', '=', True]],
183                                  fields=['name'], order='name', context=context)
184         xids = View.get_external_id(cr, uid, [view['id'] for view in views], context=context)
185
186         return [
187             {'name': view['name'], 'url': '/page/' + xids[view['id']]}
188             for view in views
189             if xids[view['id']]
190         ]
191
192     def kanban(self, cr, uid, ids, model, domain, column, template, step=None, scope=None, orderby=None):
193         step = step and int(step) or 10
194         scope = scope and int(scope) or 5
195         orderby = orderby or "name"
196
197         get_args = dict(request.httprequest.args or {})
198         model_obj = request.registry[model]
199         relation = model_obj._columns.get(column)._obj
200         relation_obj = request.registry[relation]
201
202         get_args.setdefault('kanban', "")
203         kanban = get_args.pop('kanban')
204         kanban_url = "?%s&kanban=" % urllib.urlencode(get_args)
205
206         pages = {}
207         for col in kanban.split(","):
208             if col:
209                 col = col.split("-")
210                 pages[int(col[0])] = int(col[1])
211
212         objects = []
213         for group in model_obj.read_group(cr, uid, domain, ["id", column], groupby=column):
214             obj = {}
215
216             # browse column
217             relation_id = group[column][0]
218             obj['column_id'] = relation_obj.browse(cr, uid, relation_id)
219
220             obj['kanban_url'] = kanban_url
221             for k, v in pages.items():
222                 if k != relation_id:
223                     obj['kanban_url'] += "%s-%s" % (k, v)
224
225             # pager
226             number = model_obj.search(cr, uid, group['__domain'], count=True)
227             obj['page_count'] = int(math.ceil(float(number) / step))
228             obj['page'] = pages.get(relation_id) or 1
229             if obj['page'] > obj['page_count']:
230                 obj['page'] = obj['page_count']
231             offset = (obj['page']-1) * step
232             obj['page_start'] = max(obj['page'] - int(math.floor((scope-1)/2)), 1)
233             obj['page_end'] = min(obj['page_start'] + (scope-1), obj['page_count'])
234
235             # view data
236             obj['domain'] = group['__domain']
237             obj['model'] = model
238             obj['step'] = step
239             obj['orderby'] = orderby
240
241             # browse objects
242             object_ids = model_obj.search(cr, uid, group['__domain'], limit=step, offset=offset, order=orderby)
243             obj['object_ids'] = model_obj.browse(cr, uid, object_ids)
244
245             objects.append(obj)
246
247         values = {
248             'objects': objects,
249             'range': range,
250             'template': template,
251         }
252         return request.website.render("website.kanban_contain", values)
253
254     def kanban_col(self, cr, uid, ids, model, domain, page, template, step, orderby):
255         html = ""
256         model_obj = request.registry[model]
257         domain = safe_eval(domain)
258         step = int(step)
259         offset = (int(page)-1) * step
260         object_ids = model_obj.search(cr, uid, domain, limit=step, offset=offset, order=orderby)
261         object_ids = model_obj.browse(cr, uid, object_ids)
262         for object_id in object_ids:
263             html += request.website.render(template, {'object_id': object_id})
264         return html
265
266 class res_partner(osv.osv):
267     _inherit = "res.partner"
268
269     def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
270         partner = self.browse(cr, uid, ids[0], context=context)
271         params = {
272             'center': '%s, %s %s, %s' % (partner.street, partner.city, partner.zip, partner.country_id and partner.country_id.name_get()[0][1] or ''),
273             'size': "%sx%s" % (height, width),
274             'zoom': zoom,
275             'sensor': 'false',
276         }
277         return urlplus('http://maps.googleapis.com/maps/api/staticmap' , params)
278
279     def google_map_link(self, cr, uid, ids, zoom=8, context=None):
280         partner = self.browse(cr, uid, ids[0], context=context)
281         params = {
282             'q': '%s, %s %s, %s' % (partner.street, partner.city, partner.zip, partner.country_id and partner.country_id.name_get()[0][1] or ''),
283         }
284         return urlplus('https://maps.google.be/maps' , params)