[MERGE] sitemap backport, merge trunk from 9438 til 9440
[odoo/odoo.git] / addons / website / models / website.py
1 # -*- coding: utf-8 -*-
2 import hashlib
3 import inspect
4 import itertools
5 import logging
6 import math
7 import mimetypes
8 import re
9 import urlparse
10
11 import werkzeug
12 import werkzeug.exceptions
13 import werkzeug.utils
14 import werkzeug.wrappers
15 # optional python-slugify import (https://github.com/un33k/python-slugify)
16 try:
17     import slugify as slugify_lib
18 except ImportError:
19     slugify_lib = None
20
21 import openerp
22 from openerp.osv import orm, osv, fields
23 from openerp.tools.safe_eval import safe_eval
24 from openerp.addons.web.http import request
25
26 logger = logging.getLogger(__name__)
27
28 def url_for(path_or_uri, lang=None):
29     if isinstance(path_or_uri, unicode):
30         path_or_uri = path_or_uri.encode('utf-8')
31     current_path = request.httprequest.path
32     if isinstance(current_path, unicode):
33         current_path = current_path.encode('utf-8')
34     location = path_or_uri.strip()
35     force_lang = lang is not None
36     url = urlparse.urlparse(location)
37
38     if request and not url.netloc and not url.scheme and (url.path or force_lang):
39         location = urlparse.urljoin(current_path, location)
40
41         lang = lang or request.context.get('lang')
42         langs = [lg[0] for lg in request.website.get_languages()]
43
44         if (len(langs) > 1 or force_lang) and is_multilang_url(location, langs):
45             ps = location.split('/')
46             if ps[1] in langs:
47                 # Replace the language only if we explicitly provide a language to url_for
48                 if force_lang:
49                     ps[1] = lang
50                 # Remove the default language unless it's explicitly provided
51                 elif ps[1] == request.website.default_lang_code:
52                     ps.pop(1)
53             # Insert the context language or the provided language
54             elif lang != request.website.default_lang_code or force_lang:
55                 ps.insert(1, lang)
56             location = '/'.join(ps)
57
58     return location.decode('utf-8')
59
60 def is_multilang_url(path, langs=None):
61     if not langs:
62         langs = [lg[0] for lg in request.website.get_languages()]
63     spath = path.split('/')
64     # if a language is already in the path, remove it
65     if spath[1] in langs:
66         spath.pop(1)
67         path = '/'.join(spath)
68     try:
69         router = request.httprequest.app.get_db_router(request.db).bind('')
70         func = router.match(path)[0]
71         return func.routing.get('multilang', False)
72     except Exception:
73         return False
74
75 def slugify(s, max_length=None):
76     if slugify_lib:
77         # There are 2 different libraries only python-slugify is supported
78         try:
79             return slugify_lib.slugify(s, max_length=max_length)
80         except TypeError:
81             pass
82     spaceless = re.sub(r'\s+', '-', s)
83     specialless = re.sub(r'[^-_A-Za-z0-9]', '', spaceless)
84     return specialless[:max_length]
85
86 def slug(value):
87     if isinstance(value, orm.browse_record):
88         # [(id, name)] = value.name_get()
89         id, name = value.id, value[value._rec_name]
90     else:
91         # assume name_search result tuple
92         id, name = value
93     slugname = slugify(name or '')
94     if not slugname:
95         return str(id)
96     return "%s-%d" % (slugname, id)
97
98 def urlplus(url, params):
99     return werkzeug.Href(url)(params or None)
100
101 class website(osv.osv):
102     def _get_menu_website(self, cr, uid, ids, context=None):
103         # IF a menu is changed, update all websites
104         return self.search(cr, uid, [], context=context)
105
106     def _get_menu(self, cr, uid, ids, name, arg, context=None):
107         root_domain = [('parent_id', '=', False)]
108         menus = self.pool.get('website.menu').search(cr, uid, root_domain, order='id', context=context)
109         menu = menus and menus[0] or False
110         return dict( map(lambda x: (x, menu), ids) )
111
112     def _get_public_user(self, cr, uid, ids, name='public_user', arg=(), context=None):
113         ref = self.get_public_user(cr, uid, context=context)
114         return dict( map(lambda x: (x, ref), ids) )
115
116     _name = "website" # Avoid website.website convention for conciseness (for new api). Got a special authorization from xmo and rco
117     _description = "Website"
118     _columns = {
119         'name': fields.char('Domain'),
120         'company_id': fields.many2one('res.company', string="Company"),
121         'language_ids': fields.many2many('res.lang', 'website_lang_rel', 'website_id', 'lang_id', 'Languages'),
122         'default_lang_id': fields.many2one('res.lang', string="Default language"),
123         'default_lang_code': fields.related('default_lang_id', 'code', type="char", string="Default language code", store=True),
124         'social_twitter': fields.char('Twitter Account'),
125         'social_facebook': fields.char('Facebook Account'),
126         'social_github': fields.char('GitHub Account'),
127         'social_linkedin': fields.char('LinkedIn Account'),
128         'social_youtube': fields.char('Youtube Account'),
129         'social_googleplus': fields.char('Google+ Account'),
130         'google_analytics_key': fields.char('Google Analytics Key'),
131         'user_id': fields.many2one('res.users', string='Public User'),
132         'public_user': fields.function(_get_public_user, relation='res.users', type='many2one', string='Public User'),
133         'menu_id': fields.function(_get_menu, relation='website.menu', type='many2one', string='Main Menu',
134             store= {
135                 'website.menu': (_get_menu_website, ['sequence','parent_id','website_id'], 10)
136             })
137     }
138
139     # cf. Wizard hack in website_views.xml
140     def noop(self, *args, **kwargs):
141         pass
142
143     def write(self, cr, uid, ids, vals, context=None):
144         self._get_languages.clear_cache(self)
145         return super(website, self).write(cr, uid, ids, vals, context)
146
147     def new_page(self, cr, uid, name, template='website.default_page', ispage=True, context=None):
148         context = context or {}
149         imd = self.pool.get('ir.model.data')
150         view = self.pool.get('ir.ui.view')
151         template_module, template_name = template.split('.')
152
153         # completely arbitrary max_length
154         page_name = slugify(name, max_length=50)
155         page_xmlid = "%s.%s" % (template_module, page_name)
156
157         try:
158             # existing page
159             imd.get_object_reference(cr, uid, template_module, page_name)
160         except ValueError:
161             # new page
162             _, template_id = imd.get_object_reference(cr, uid, template_module, template_name)
163             page_id = view.copy(cr, uid, template_id, context=context)
164             page = view.browse(cr, uid, page_id, context=context)
165             page.write({
166                 'arch': page.arch.replace(template, page_xmlid),
167                 'name': page_name,
168                 'page': ispage,
169             })
170             imd.create(cr, uid, {
171                 'name': page_name,
172                 'module': template_module,
173                 'model': 'ir.ui.view',
174                 'res_id': page_id,
175                 'noupdate': True
176             }, context=context)
177         return page_xmlid
178
179     def page_for_name(self, cr, uid, ids, name, module='website', context=None):
180         # whatever
181         return '%s.%s' % (module, slugify(name, max_length=50))
182
183     def page_exists(self, cr, uid, ids, name, module='website', context=None):
184         try:
185            return self.pool["ir.model.data"].get_object_reference(cr, uid, module, name)
186         except:
187             return False
188
189     def get_public_user(self, cr, uid, context=None):
190         uid = openerp.SUPERUSER_ID
191         res = self.pool['ir.model.data'].get_object_reference(cr, uid, 'base', 'public_user')
192         return res and res[1] or False
193
194     @openerp.tools.ormcache(skiparg=3)
195     def _get_languages(self, cr, uid, id, context=None):
196         website = self.browse(cr, uid, id)
197         return [(lg.code, lg.name) for lg in website.language_ids]
198
199     def get_languages(self, cr, uid, ids, context=None):
200         return self._get_languages(cr, uid, ids[0])
201
202     def get_current_website(self, cr, uid, context=None):
203         # TODO: Select website, currently hard coded
204         return self.pool['website'].browse(cr, uid, 1, context=context)
205
206     def preprocess_request(self, cr, uid, ids, request, context=None):
207         # TODO FP: is_website_publisher and editable in context should be removed
208         # for performance reasons (1 query per image to load) but also to be cleaner
209         # I propose to replace this by a group 'base.group_website_publisher' on the
210         # view that requires it.
211         Access = request.registry['ir.model.access']
212         is_website_publisher = Access.check(cr, uid, 'ir.ui.view', 'write', False, context)
213
214         lang = request.context['lang']
215         is_master_lang = lang == request.website.default_lang_code
216
217         request.redirect = lambda url: werkzeug.utils.redirect(url_for(url))
218         request.context.update(
219             editable=is_website_publisher,
220             translatable=not is_master_lang,
221         )
222
223     def get_template(self, cr, uid, ids, template, context=None):
224         if isinstance(template, (int, long)):
225             view_id = template
226         else:
227             if '.' not in template:
228                 template = 'website.%s' % template
229             module, xmlid = template.split('.', 1)
230             model, view_id = request.registry["ir.model.data"].get_object_reference(cr, uid, module, xmlid)
231         return self.pool["ir.ui.view"].browse(cr, uid, view_id, context=context)
232
233     def _render(self, cr, uid, ids, template, values=None, context=None):
234         # TODO: remove this. (just kept for backward api compatibility for saas-3)
235         return self.pool['ir.ui.view'].render(cr, uid, template, values=values, context=context)
236
237     def render(self, cr, uid, ids, template, values=None, status_code=None, context=None):
238         # TODO: remove this. (just kept for backward api compatibility for saas-3)
239         return request.render(template, values, uid=uid)
240
241     def pager(self, cr, uid, ids, url, total, page=1, step=30, scope=5, url_args=None, context=None):
242         # Compute Pager
243         page_count = int(math.ceil(float(total) / step))
244
245         page = max(1, min(int(page), page_count))
246         scope -= 1
247
248         pmin = max(page - int(math.floor(scope/2)), 1)
249         pmax = min(pmin + scope, page_count)
250
251         if pmax - pmin < scope:
252             pmin = pmax - scope if pmax - scope > 0 else 1
253
254         def get_url(page):
255             _url = "%s/page/%s" % (url, page) if page > 1 else url
256             if url_args:
257                 _url = "%s?%s" % (_url, werkzeug.url_encode(url_args))
258             return _url
259
260         return {
261             "page_count": page_count,
262             "offset": (page - 1) * step,
263             "page": {
264                 'url': get_url(page),
265                 'num': page
266             },
267             "page_start": {
268                 'url': get_url(pmin),
269                 'num': pmin
270             },
271             "page_previous": {
272                 'url': get_url(max(pmin, page - 1)),
273                 'num': max(pmin, page - 1)
274             },
275             "page_next": {
276                 'url': get_url(min(pmax, page + 1)),
277                 'num': min(pmax, page + 1)
278             },
279             "page_end": {
280                 'url': get_url(pmax),
281                 'num': pmax
282             },
283             "pages": [
284                 {'url': get_url(page), 'num': page}
285                 for page in xrange(pmin, pmax+1)
286             ]
287         }
288
289     def rule_is_enumerable(self, rule):
290         """ Checks that it is possible to generate sensible GET queries for
291         a given rule (if the endpoint matches its own requirements)
292
293         :type rule: werkzeug.routing.Rule
294         :rtype: bool
295         """
296         endpoint = rule.endpoint
297         methods = rule.methods or ['GET']
298         converters = rule._converters.values()
299         if not ('GET' in methods
300             and endpoint.routing['type'] == 'http'
301             and endpoint.routing['auth'] in ('none', 'public')
302             and endpoint.routing.get('website', False)
303             and all(hasattr(converter, 'generate') for converter in converters)
304             and endpoint.routing.get('website')):
305             return False
306
307         # dont't list routes without argument having no default value or converter
308         spec = inspect.getargspec(endpoint.method.original_func)
309
310         # remove self and arguments having a default value
311         defaults_count = len(spec.defaults or [])
312         args = spec.args[1:(-defaults_count or None)]
313
314         # check that all args have a converter
315         return all( (arg in rule._converters) for arg in args)
316
317     def enumerate_pages(self, cr, uid, ids, query_string=None, context=None):
318         """ Available pages in the website/CMS. This is mostly used for links
319         generation and can be overridden by modules setting up new HTML
320         controllers for dynamic pages (e.g. blog).
321
322         By default, returns template views marked as pages.
323
324         :param str query_string: a (user-provided) string, fetches pages
325                                  matching the string
326         :returns: a list of mappings with two keys: ``name`` is the displayable
327                   name of the resource (page), ``url`` is the absolute URL
328                   of the same.
329         :rtype: list({name: str, url: str})
330         """
331         router = request.httprequest.app.get_db_router(request.db)
332         # Force enumeration to be performed as public user
333         uid = self.get_public_user(cr, uid, context=context)
334         url_list = []
335         for rule in router.iter_rules():
336             if not self.rule_is_enumerable(rule):
337                 continue
338
339             converters = rule._converters or {}
340             values = [{}]
341             convitems = converters.items()
342             # converters with a domain are processed after the other ones
343             gd = lambda x: hasattr(x[1], 'domain') and (x[1].domain <> '[]')
344             convitems.sort(lambda x, y: cmp(gd(x), gd(y)))
345             for (name, converter) in convitems:
346                 newval = []
347                 for val in values:
348                     for v in converter.generate(request.cr, uid, query=query_string, args=val, context=context):
349                         newval.append( val.copy() )
350                         v[name] = v['loc']
351                         del v['loc']
352                         newval[-1].update(v)
353                 values = newval
354
355             for value in values:
356                 domain_part, url = rule.build(value, append_unknown=False)
357                 page = {'loc': url}
358                 for key,val in value.items():
359                     if key.startswith('__'):
360                         page[key[2:]] = val
361                 if url in ('/sitemap.xml',):
362                     continue
363                 if url in url_list:
364                     continue
365                 url_list.append(url)
366                 if query_string and not self.page_matches(cr, uid, page, query_string, context=context):
367                     continue
368                 yield page
369
370     def search_pages(self, cr, uid, ids, needle=None, limit=None, context=None):
371         return list(itertools.islice(
372             self.enumerate_pages(cr, uid, ids, query_string=needle, context=context),
373             limit))
374
375     def page_matches(self, cr, uid, page, needle, context=None):
376         """ Checks that a "page" matches a user-provide search string.
377
378         The default implementation attempts to perform a non-contiguous
379         substring match of the page's name.
380
381         :param page: {'name': str, 'url': str}
382         :param needle: str
383         :rtype: bool
384         """
385         haystack = page['name'].lower()
386
387         needle = iter(needle.lower())
388         n = next(needle)
389         end = object()
390
391         for char in haystack:
392             if char != n: continue
393
394             n = next(needle, end)
395             # found all characters of needle in haystack in order
396             if n is end:
397                 return True
398
399         return False
400
401     def kanban(self, cr, uid, ids, model, domain, column, template, step=None, scope=None, orderby=None, context=None):
402         step = step and int(step) or 10
403         scope = scope and int(scope) or 5
404         orderby = orderby or "name"
405
406         get_args = dict(request.httprequest.args or {})
407         model_obj = self.pool[model]
408         relation = model_obj._columns.get(column)._obj
409         relation_obj = self.pool[relation]
410
411         get_args.setdefault('kanban', "")
412         kanban = get_args.pop('kanban')
413         kanban_url = "?%s&kanban=" % werkzeug.url_encode(get_args)
414
415         pages = {}
416         for col in kanban.split(","):
417             if col:
418                 col = col.split("-")
419                 pages[int(col[0])] = int(col[1])
420
421         objects = []
422         for group in model_obj.read_group(cr, uid, domain, ["id", column], groupby=column):
423             obj = {}
424
425             # browse column
426             relation_id = group[column][0]
427             obj['column_id'] = relation_obj.browse(cr, uid, relation_id)
428
429             obj['kanban_url'] = kanban_url
430             for k, v in pages.items():
431                 if k != relation_id:
432                     obj['kanban_url'] += "%s-%s" % (k, v)
433
434             # pager
435             number = model_obj.search(cr, uid, group['__domain'], count=True)
436             obj['page_count'] = int(math.ceil(float(number) / step))
437             obj['page'] = pages.get(relation_id) or 1
438             if obj['page'] > obj['page_count']:
439                 obj['page'] = obj['page_count']
440             offset = (obj['page']-1) * step
441             obj['page_start'] = max(obj['page'] - int(math.floor((scope-1)/2)), 1)
442             obj['page_end'] = min(obj['page_start'] + (scope-1), obj['page_count'])
443
444             # view data
445             obj['domain'] = group['__domain']
446             obj['model'] = model
447             obj['step'] = step
448             obj['orderby'] = orderby
449
450             # browse objects
451             object_ids = model_obj.search(cr, uid, group['__domain'], limit=step, offset=offset, order=orderby)
452             obj['object_ids'] = model_obj.browse(cr, uid, object_ids)
453
454             objects.append(obj)
455
456         values = {
457             'objects': objects,
458             'range': range,
459             'template': template,
460         }
461         return request.website._render("website.kanban_contain", values)
462
463     def kanban_col(self, cr, uid, ids, model, domain, page, template, step, orderby, context=None):
464         html = ""
465         model_obj = self.pool[model]
466         domain = safe_eval(domain)
467         step = int(step)
468         offset = (int(page)-1) * step
469         object_ids = model_obj.search(cr, uid, domain, limit=step, offset=offset, order=orderby)
470         object_ids = model_obj.browse(cr, uid, object_ids)
471         for object_id in object_ids:
472             html += request.website._render(template, {'object_id': object_id})
473         return html
474
475 class website_menu(osv.osv):
476     _name = "website.menu"
477     _description = "Website Menu"
478     _columns = {
479         'name': fields.char('Menu', size=64, required=True, translate=True),
480         'url': fields.char('Url', translate=True),
481         'new_window': fields.boolean('New Window'),
482         'sequence': fields.integer('Sequence'),
483         # TODO: support multiwebsite once done for ir.ui.views
484         'website_id': fields.many2one('website', 'Website'),
485         'parent_id': fields.many2one('website.menu', 'Parent Menu', select=True, ondelete="cascade"),
486         'child_id': fields.one2many('website.menu', 'parent_id', string='Child Menus'),
487         'parent_left': fields.integer('Parent Left', select=True),
488         'parent_right': fields.integer('Parent Right', select=True),
489     }
490
491     def __defaults_sequence(self, cr, uid, context):
492         menu = self.search_read(cr, uid, [(1,"=",1)], ["sequence"], limit=1, order="sequence DESC", context=context)
493         return menu and menu[0]["sequence"] or 0
494
495     _defaults = {
496         'url': '',
497         'sequence': __defaults_sequence,
498         'new_window': False,
499     }
500     _parent_store = True
501     _parent_order = 'sequence'
502     _order = "sequence"
503
504     # would be better to take a menu_id as argument
505     def get_tree(self, cr, uid, website_id, context=None):
506         def make_tree(node):
507             menu_node = dict(
508                 id=node.id,
509                 name=node.name,
510                 url=node.url,
511                 new_window=node.new_window,
512                 sequence=node.sequence,
513                 parent_id=node.parent_id.id,
514                 children=[],
515             )
516             for child in node.child_id:
517                 menu_node['children'].append(make_tree(child))
518             return menu_node
519         menu = self.pool.get('website').browse(cr, uid, website_id, context=context).menu_id
520         return make_tree(menu)
521
522     def save(self, cr, uid, website_id, data, context=None):
523         def replace_id(old_id, new_id):
524             for menu in data['data']:
525                 if menu['id'] == old_id:
526                     menu['id'] = new_id
527                 if menu['parent_id'] == old_id:
528                     menu['parent_id'] = new_id
529         to_delete = data['to_delete']
530         if to_delete:
531             self.unlink(cr, uid, to_delete, context=context)
532         for menu in data['data']:
533             mid = menu['id']
534             if isinstance(mid, str):
535                 new_id = self.create(cr, uid, {'name': menu['name']}, context=context)
536                 replace_id(mid, new_id)
537         for menu in data['data']:
538             self.write(cr, uid, [menu['id']], menu, context=context)
539         return True
540
541 class ir_attachment(osv.osv):
542     _inherit = "ir.attachment"
543     def _website_url_get(self, cr, uid, ids, name, arg, context=None):
544         result = {}
545         for attach in self.browse(cr, uid, ids, context=context):
546             if attach.url:
547                 result[attach.id] = attach.url
548             else:
549                 result[attach.id] = urlplus('/website/image', {
550                     'model': 'ir.attachment',
551                     'field': 'datas',
552                     'id': attach.id,
553                     'max_width': 1024,
554                     'max_height': 768,
555                 })
556         return result
557     def _datas_checksum(self, cr, uid, ids, name, arg, context=None):
558         return dict(
559             (attach['id'], self._compute_checksum(attach))
560             for attach in self.read(
561                 cr, uid, ids, ['res_model', 'res_id', 'type', 'datas'],
562                 context=context)
563         )
564
565     def _compute_checksum(self, attachment_dict):
566         if attachment_dict.get('res_model') == 'ir.ui.view'\
567                 and not attachment_dict.get('res_id') and not attachment_dict.get('url')\
568                 and attachment_dict.get('type', 'binary') == 'binary'\
569                 and attachment_dict.get('datas'):
570             return hashlib.new('sha1', attachment_dict['datas']).hexdigest()
571         return None
572
573     def _datas_big(self, cr, uid, ids, name, arg, context=None):
574         result = dict.fromkeys(ids, False)
575         if context and context.get('bin_size'):
576             return result
577
578         for record in self.browse(cr, uid, ids, context=context):
579             if not record.datas: continue
580             try:
581                 result[record.id] = openerp.tools.image_resize_image_big(record.datas)
582             except IOError: # apparently the error PIL.Image.open raises
583                 pass
584
585         return result
586
587     _columns = {
588         'datas_checksum': fields.function(_datas_checksum, size=40,
589               string="Datas checksum", type='char', store=True, select=True),
590         'website_url': fields.function(_website_url_get, string="Attachment URL", type='char'),
591         'datas_big': fields.function (_datas_big, type='binary', store=True,
592                                       string="Resized file content"),
593         'mimetype': fields.char('Mime Type', readonly=True),
594     }
595
596     def _add_mimetype_if_needed(self, values):
597         if values.get('datas_fname'):
598             values['mimetype'] = mimetypes.guess_type(values.get('datas_fname'))[0] or 'application/octet-stream'
599
600     def create(self, cr, uid, values, context=None):
601         chk = self._compute_checksum(values)
602         if chk:
603             match = self.search(cr, uid, [('datas_checksum', '=', chk)], context=context)
604             if match:
605                 return match[0]
606         self._add_mimetype_if_needed(values)
607         return super(ir_attachment, self).create(
608             cr, uid, values, context=context)
609
610     def write(self, cr, uid, ids, values, context=None):
611         self._add_mimetype_if_needed(values)
612         return super(ir_attachment, self).write(cr, uid, ids, values, context=context)
613
614     def try_remove(self, cr, uid, ids, context=None):
615         """ Removes a web-based image attachment if it is used by no view
616         (template)
617
618         Returns a dict mapping attachments which would not be removed (if any)
619         mapped to the views preventing their removal
620         """
621         Views = self.pool['ir.ui.view']
622         attachments_to_remove = []
623         # views blocking removal of the attachment
624         removal_blocked_by = {}
625
626         for attachment in self.browse(cr, uid, ids, context=context):
627             # in-document URLs are html-escaped, a straight search will not
628             # find them
629             url = werkzeug.utils.escape(attachment.website_url)
630             ids = Views.search(cr, uid, ["|", ('arch', 'like', '"%s"' % url), ('arch', 'like', "'%s'" % url)], context=context)
631
632             if ids:
633                 removal_blocked_by[attachment.id] = Views.read(
634                     cr, uid, ids, ['name'], context=context)
635             else:
636                 attachments_to_remove.append(attachment.id)
637         if attachments_to_remove:
638             self.unlink(cr, uid, attachments_to_remove, context=context)
639         return removal_blocked_by
640
641 class res_partner(osv.osv):
642     _inherit = "res.partner"
643
644     def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
645         partner = self.browse(cr, uid, ids[0], context=context)
646         params = {
647             'center': '%s, %s %s, %s' % (partner.street or '', partner.city or '', partner.zip or '', partner.country_id and partner.country_id.name_get()[0][1] or ''),
648             'size': "%sx%s" % (height, width),
649             'zoom': zoom,
650             'sensor': 'false',
651         }
652         return urlplus('http://maps.googleapis.com/maps/api/staticmap' , params)
653
654     def google_map_link(self, cr, uid, ids, zoom=8, context=None):
655         partner = self.browse(cr, uid, ids[0], context=context)
656         params = {
657             'q': '%s, %s %s, %s' % (partner.street or '', partner.city  or '', partner.zip or '', partner.country_id and partner.country_id.name_get()[0][1] or ''),
658             'z': 10
659         }
660         return urlplus('https://maps.google.com/maps' , params)
661
662 class res_company(osv.osv):
663     _inherit = "res.company"
664     def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
665         partner = self.browse(cr, openerp.SUPERUSER_ID, ids[0], context=context).partner_id
666         return partner and partner.google_map_img(zoom, width, height, context=context) or None
667     def google_map_link(self, cr, uid, ids, zoom=8, context=None):
668         partner = self.browse(cr, openerp.SUPERUSER_ID, ids[0], context=context).partner_id
669         return partner and partner.google_map_link(zoom, context=context) or None
670
671 class base_language_install(osv.osv_memory):
672     _inherit = "base.language.install"
673     _columns = {
674         'website_ids': fields.many2many('website', string='Websites to translate'),
675     }
676
677     def default_get(self, cr, uid, fields, context=None):
678         if context is None:
679             context = {}
680         defaults = super(base_language_install, self).default_get(cr, uid, fields, context)
681         website_id = context.get('params', {}).get('website_id')
682         if website_id:
683             if 'website_ids' not in defaults:
684                 defaults['website_ids'] = []
685             defaults['website_ids'].append(website_id)
686         return defaults
687
688     def lang_install(self, cr, uid, ids, context=None):
689         if context is None:
690             context = {}
691         action = super(base_language_install, self).lang_install(cr, uid, ids, context)
692         language_obj = self.browse(cr, uid, ids)[0]
693         website_ids = [website.id for website in language_obj['website_ids']]
694         lang_id = self.pool['res.lang'].search(cr, uid, [('code', '=', language_obj['lang'])])
695         if website_ids and lang_id:
696             data = {'language_ids': [(4, lang_id[0])]}
697             self.pool['website'].write(cr, uid, website_ids, data)
698         params = context.get('params', {})
699         if 'url_return' in params:
700             return {
701                 'url': params['url_return'].replace('[lang]', language_obj['lang']),
702                 'type': 'ir.actions.act_url',
703                 'target': 'self'
704             }
705         return action
706
707 class website_seo_metadata(osv.Model):
708     _name = 'website.seo.metadata'
709     _description = 'SEO metadata'
710
711     _columns = {
712         'website_meta_title': fields.char("Website meta title", translate=True),
713         'website_meta_description': fields.text("Website meta description", translate=True),
714         'website_meta_keywords': fields.char("Website meta keywords", translate=True),
715     }
716
717 # vim:et: