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