1 # -*- coding: utf-8 -*-
17 from sys import maxint
20 # optional python-slugify import (https://github.com/un33k/python-slugify)
22 import slugify as slugify_lib
27 from openerp.osv import orm, osv, fields
28 from openerp.tools import html_escape as escape
29 from openerp.tools import ustr as ustr
30 from openerp.tools.safe_eval import safe_eval
31 from openerp.addons.web.http import request
32 from werkzeug.exceptions import NotFound
34 logger = logging.getLogger(__name__)
36 def url_for(path_or_uri, lang=None):
37 if isinstance(path_or_uri, unicode):
38 path_or_uri = path_or_uri.encode('utf-8')
39 current_path = request.httprequest.path
40 if isinstance(current_path, unicode):
41 current_path = current_path.encode('utf-8')
42 location = path_or_uri.strip()
43 force_lang = lang is not None
44 url = urlparse.urlparse(location)
46 if request and not url.netloc and not url.scheme and (url.path or force_lang):
47 location = urlparse.urljoin(current_path, location)
49 lang = lang or request.context.get('lang')
50 langs = [lg[0] for lg in request.website.get_languages()]
52 if (len(langs) > 1 or force_lang) and is_multilang_url(location, langs):
53 ps = location.split('/')
55 # Replace the language only if we explicitly provide a language to url_for
58 # Remove the default language unless it's explicitly provided
59 elif ps[1] == request.website.default_lang_code:
61 # Insert the context language or the provided language
62 elif lang != request.website.default_lang_code or force_lang:
64 location = '/'.join(ps)
66 return location.decode('utf-8')
68 def is_multilang_url(local_url, langs=None):
70 langs = [lg[0] for lg in request.website.get_languages()]
71 spath = local_url.split('/')
72 # if a language is already in the path, remove it
75 local_url = '/'.join(spath)
77 # Try to match an endpoint in werkzeug's routing table
78 url = local_url.split('?')
80 query_string = url[1] if len(url) > 1 else None
81 router = request.httprequest.app.get_db_router(request.db).bind('')
82 func = router.match(path, query_args=query_string)[0]
83 return func.routing.get('website', False) and func.routing.get('multilang', True)
87 def slugify(s, max_length=None):
88 """ Transform a string to a slug that can be used in a url path.
90 This method will first try to do the job with python-slugify if present.
91 Otherwise it will process string by stripping leading and ending spaces,
92 converting unicode chars to ascii, lowering all chars and replacing spaces
93 and underscore with hyphen "-".
96 :param max_length: int
101 # There are 2 different libraries only python-slugify is supported
103 return slugify_lib.slugify(s, max_length=max_length)
106 uni = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore').decode('ascii')
107 slug = re.sub('[\W_]', ' ', uni).strip().lower()
108 slug = re.sub('[-\s]+', '-', slug)
110 return slug[:max_length]
113 if isinstance(value, orm.browse_record):
114 # [(id, name)] = value.name_get()
115 id, name = value.id, value.display_name
117 # assume name_search result tuple
119 slugname = slugify(name or '').strip().strip('-')
122 return "%s-%d" % (slugname, id)
125 # NOTE: as the pattern is used as it for the ModelConverter (ir_http.py), do not use any flags
126 _UNSLUG_RE = re.compile(r'(?:(\w{1,2}|\w[A-Za-z0-9-_]+?\w)-)?(-?\d+)(?=$|/)')
129 """Extract slug and id from a string.
130 Always return un 2-tuple (str|None, int|None)
132 m = _UNSLUG_RE.match(s)
135 return m.group(1), int(m.group(2))
137 def urlplus(url, params):
138 return werkzeug.Href(url)(params or None)
140 class website(osv.osv):
141 def _get_menu(self, cr, uid, ids, name, arg, context=None):
143 menu_obj = self.pool.get('website.menu')
145 menu_ids = menu_obj.search(cr, uid, [('parent_id', '=', False), ('website_id', '=', id)], order='id', context=context)
146 res[id] = menu_ids and menu_ids[0] or False
149 _name = "website" # Avoid website.website convention for conciseness (for new api). Got a special authorization from xmo and rco
150 _description = "Website"
152 'name': fields.char('Website Name'),
153 'domain': fields.char('Website Domain'),
154 'company_id': fields.many2one('res.company', string="Company"),
155 'language_ids': fields.many2many('res.lang', 'website_lang_rel', 'website_id', 'lang_id', 'Languages'),
156 'default_lang_id': fields.many2one('res.lang', string="Default language"),
157 'default_lang_code': fields.related('default_lang_id', 'code', type="char", string="Default language code", store=True),
158 'social_twitter': fields.char('Twitter Account'),
159 'social_facebook': fields.char('Facebook Account'),
160 'social_github': fields.char('GitHub Account'),
161 'social_linkedin': fields.char('LinkedIn Account'),
162 'social_youtube': fields.char('Youtube Account'),
163 'social_googleplus': fields.char('Google+ Account'),
164 'google_analytics_key': fields.char('Google Analytics Key'),
165 'user_id': fields.many2one('res.users', string='Public User'),
166 'compress_html': fields.boolean('Compress HTML'),
167 'partner_id': fields.related('user_id','partner_id', type='many2one', relation='res.partner', string='Public Partner'),
168 'menu_id': fields.function(_get_menu, relation='website.menu', type='many2one', string='Main Menu')
171 'user_id': lambda self,cr,uid,c: self.pool['ir.model.data'].xmlid_to_res_id(cr, openerp.SUPERUSER_ID, 'base.public_user'),
172 'company_id': lambda self,cr,uid,c: self.pool['ir.model.data'].xmlid_to_res_id(cr, openerp.SUPERUSER_ID,'base.main_company'),
173 'compress_html': False,
176 # cf. Wizard hack in website_views.xml
177 def noop(self, *args, **kwargs):
180 def write(self, cr, uid, ids, vals, context=None):
181 self._get_languages.clear_cache(self)
182 return super(website, self).write(cr, uid, ids, vals, context)
184 def new_page(self, cr, uid, name, template='website.default_page', ispage=True, context=None):
185 context = context or {}
186 imd = self.pool.get('ir.model.data')
187 view = self.pool.get('ir.ui.view')
188 template_module, template_name = template.split('.')
190 # completely arbitrary max_length
191 page_name = slugify(name, max_length=50)
192 page_xmlid = "%s.%s" % (template_module, page_name)
196 imd.get_object_reference(cr, uid, template_module, page_name)
199 _, template_id = imd.get_object_reference(cr, uid, template_module, template_name)
200 website_id = context.get('website_id')
201 key = template_module+'.'+page_name
202 page_id = view.copy(cr, uid, template_id, {'website_id': website_id, 'key': key}, context=context)
203 page = view.browse(cr, uid, page_id, context=context)
205 'arch': page.arch.replace(template, page_xmlid),
211 def page_for_name(self, cr, uid, ids, name, module='website', context=None):
213 return '%s.%s' % (module, slugify(name, max_length=50))
215 def page_exists(self, cr, uid, ids, name, module='website', context=None):
217 name = (name or "").replace("/page/website.", "").replace("/page/", "")
220 return self.pool["ir.model.data"].get_object_reference(cr, uid, module, name)
224 @openerp.tools.ormcache(skiparg=3)
225 def _get_languages(self, cr, uid, id, context=None):
226 website = self.browse(cr, uid, id)
227 return [(lg.code, lg.name) for lg in website.language_ids]
229 def get_languages(self, cr, uid, ids, context=None):
230 return self._get_languages(cr, uid, ids[0], context=context)
232 def get_alternate_languages(self, cr, uid, ids, req=None, context=None):
235 req = request.httprequest
236 default = self.get_current_website(cr, uid, context=context).default_lang_code
239 uri += '?' + req.query_string
241 for code, name in self.get_languages(cr, uid, ids, context=context):
242 lg_path = ('/' + code) if code != default else ''
246 'hreflang': ('-'.join(lg)).lower(),
248 'href': req.url_root[0:-1] + lg_path + uri,
252 if shorts.count(lang['short']) == 1:
253 lang['hreflang'] = lang['short']
256 @openerp.tools.ormcache(skiparg=4)
257 def _get_current_website_id(self, cr, uid, domain_name, context=None):
260 ids = self.search(cr, uid, [('name', '=', domain_name)], context=context)
265 def get_current_website(self, cr, uid, context=None):
266 domain_name = request.httprequest.environ.get('HTTP_HOST', '').split(':')[0]
267 website_id = self._get_current_website_id(cr, uid, domain_name, context=context)
268 return self.browse(cr, uid, website_id, context=context)
270 def is_publisher(self, cr, uid, ids, context=None):
271 Access = self.pool['ir.model.access']
272 is_website_publisher = Access.check(cr, uid, 'ir.ui.view', 'write', False, context=context)
273 return is_website_publisher
275 def is_user(self, cr, uid, ids, context=None):
276 Access = self.pool['ir.model.access']
277 return Access.check(cr, uid, 'ir.ui.menu', 'read', False, context=context)
279 def get_template(self, cr, uid, ids, template, context=None):
280 if not isinstance(template, (int, long)) and '.' not in template:
281 template = 'website.%s' % template
282 View = self.pool['ir.ui.view']
283 view_id = View.get_view_id(cr, uid, template, context=context)
286 return View.browse(cr, uid, view_id, context=context)
288 def _render(self, cr, uid, ids, template, values=None, context=None):
289 # TODO: remove this. (just kept for backward api compatibility for saas-3)
290 return self.pool['ir.ui.view'].render(cr, uid, template, values=values, context=context)
292 def render(self, cr, uid, ids, template, values=None, status_code=None, context=None):
293 # TODO: remove this. (just kept for backward api compatibility for saas-3)
294 return request.render(template, values, uid=uid)
296 def pager(self, cr, uid, ids, url, total, page=1, step=30, scope=5, url_args=None, context=None):
298 page_count = int(math.ceil(float(total) / step))
300 page = max(1, min(int(page if str(page).isdigit() else 1), page_count))
303 pmin = max(page - int(math.floor(scope/2)), 1)
304 pmax = min(pmin + scope, page_count)
306 if pmax - pmin < scope:
307 pmin = pmax - scope if pmax - scope > 0 else 1
310 _url = "%s/page/%s" % (url, page) if page > 1 else url
312 _url = "%s?%s" % (_url, werkzeug.url_encode(url_args))
316 "page_count": page_count,
317 "offset": (page - 1) * step,
319 'url': get_url(page),
323 'url': get_url(pmin),
327 'url': get_url(max(pmin, page - 1)),
328 'num': max(pmin, page - 1)
331 'url': get_url(min(pmax, page + 1)),
332 'num': min(pmax, page + 1)
335 'url': get_url(pmax),
339 {'url': get_url(page), 'num': page}
340 for page in xrange(pmin, pmax+1)
344 def rule_is_enumerable(self, rule):
345 """ Checks that it is possible to generate sensible GET queries for
346 a given rule (if the endpoint matches its own requirements)
348 :type rule: werkzeug.routing.Rule
351 endpoint = rule.endpoint
352 methods = rule.methods or ['GET']
353 converters = rule._converters.values()
354 if not ('GET' in methods
355 and endpoint.routing['type'] == 'http'
356 and endpoint.routing['auth'] in ('none', 'public')
357 and endpoint.routing.get('website', False)
358 and all(hasattr(converter, 'generate') for converter in converters)
359 and endpoint.routing.get('website')):
362 # dont't list routes without argument having no default value or converter
363 spec = inspect.getargspec(endpoint.method.original_func)
365 # remove self and arguments having a default value
366 defaults_count = len(spec.defaults or [])
367 args = spec.args[1:(-defaults_count or None)]
369 # check that all args have a converter
370 return all( (arg in rule._converters) for arg in args)
372 def enumerate_pages(self, cr, uid, ids, query_string=None, context=None):
373 """ Available pages in the website/CMS. This is mostly used for links
374 generation and can be overridden by modules setting up new HTML
375 controllers for dynamic pages (e.g. blog).
377 By default, returns template views marked as pages.
379 :param str query_string: a (user-provided) string, fetches pages
381 :returns: a list of mappings with two keys: ``name`` is the displayable
382 name of the resource (page), ``url`` is the absolute URL
384 :rtype: list({name: str, url: str})
386 router = request.httprequest.app.get_db_router(request.db)
387 # Force enumeration to be performed as public user
389 for rule in router.iter_rules():
390 if not self.rule_is_enumerable(rule):
393 converters = rule._converters or {}
394 if query_string and not converters and (query_string not in rule.build([{}], append_unknown=False)[1]):
397 convitems = converters.items()
398 # converters with a domain are processed after the other ones
399 gd = lambda x: hasattr(x[1], 'domain') and (x[1].domain <> '[]')
400 convitems.sort(lambda x, y: cmp(gd(x), gd(y)))
401 for (i,(name, converter)) in enumerate(convitems):
404 query = i==(len(convitems)-1) and query_string
405 for v in converter.generate(request.cr, uid, query=query, args=val, context=context):
406 newval.append( val.copy() )
413 domain_part, url = rule.build(value, append_unknown=False)
415 for key,val in value.items():
416 if key.startswith('__'):
418 if url in ('/sitemap.xml',):
426 def search_pages(self, cr, uid, ids, needle=None, limit=None, context=None):
427 name = (needle or "").replace("/page/website.", "").replace("/page/", "")
429 for page in self.enumerate_pages(cr, uid, ids, query_string=name, context=context):
430 if needle in page['loc']:
432 if len(res) == limit:
436 def kanban(self, cr, uid, ids, model, domain, column, template, step=None, scope=None, orderby=None, context=None):
437 step = step and int(step) or 10
438 scope = scope and int(scope) or 5
439 orderby = orderby or "name"
441 get_args = dict(request.httprequest.args or {})
442 model_obj = self.pool[model]
443 relation = model_obj._columns.get(column)._obj
444 relation_obj = self.pool[relation]
446 get_args.setdefault('kanban', "")
447 kanban = get_args.pop('kanban')
448 kanban_url = "?%s&kanban=" % werkzeug.url_encode(get_args)
451 for col in kanban.split(","):
454 pages[int(col[0])] = int(col[1])
457 for group in model_obj.read_group(cr, uid, domain, ["id", column], groupby=column):
461 relation_id = group[column][0]
462 obj['column_id'] = relation_obj.browse(cr, uid, relation_id)
464 obj['kanban_url'] = kanban_url
465 for k, v in pages.items():
467 obj['kanban_url'] += "%s-%s" % (k, v)
470 number = model_obj.search(cr, uid, group['__domain'], count=True)
471 obj['page_count'] = int(math.ceil(float(number) / step))
472 obj['page'] = pages.get(relation_id) or 1
473 if obj['page'] > obj['page_count']:
474 obj['page'] = obj['page_count']
475 offset = (obj['page']-1) * step
476 obj['page_start'] = max(obj['page'] - int(math.floor((scope-1)/2)), 1)
477 obj['page_end'] = min(obj['page_start'] + (scope-1), obj['page_count'])
480 obj['domain'] = group['__domain']
483 obj['orderby'] = orderby
486 object_ids = model_obj.search(cr, uid, group['__domain'], limit=step, offset=offset, order=orderby)
487 obj['object_ids'] = model_obj.browse(cr, uid, object_ids)
494 'template': template,
496 return request.website._render("website.kanban_contain", values)
498 def kanban_col(self, cr, uid, ids, model, domain, page, template, step, orderby, context=None):
500 model_obj = self.pool[model]
501 domain = safe_eval(domain)
503 offset = (int(page)-1) * step
504 object_ids = model_obj.search(cr, uid, domain, limit=step, offset=offset, order=orderby)
505 object_ids = model_obj.browse(cr, uid, object_ids)
506 for object_id in object_ids:
507 html += request.website._render(template, {'object_id': object_id})
510 def _image_placeholder(self, response):
511 # file_open may return a StringIO. StringIO can be closed but are
512 # not context managers in Python 2 though that is fixed in 3
513 with contextlib.closing(openerp.tools.misc.file_open(
514 os.path.join('web', 'static', 'src', 'img', 'placeholder.png'),
516 response.data = f.read()
517 return response.make_conditional(request.httprequest)
519 def _image(self, cr, uid, model, id, field, response, max_width=maxint, max_height=maxint, context=None):
520 """ Fetches the requested field and ensures it does not go above
521 (max_width, max_height), resizing it if necessary.
523 Resizing is bypassed if the object provides a $field_big, which will
524 be interpreted as a pre-resized version of the base field.
526 If the record is not found or does not have the requested field,
527 returns a placeholder image via :meth:`~._image_placeholder`.
529 Sets and checks conditional response parameters:
530 * :mailheader:`ETag` is always set (and checked)
531 * :mailheader:`Last-Modified is set iif the record has a concurrency
532 field (``__last_update``)
534 The requested field is assumed to be base64-encoded image data in
537 Model = self.pool[model]
540 ids = Model.search(cr, uid,
541 [('id', '=', id)], context=context)
542 if not ids and 'website_published' in Model._all_columns:
543 ids = Model.search(cr, openerp.SUPERUSER_ID,
544 [('id', '=', id), ('website_published', '=', True)], context=context)
546 return self._image_placeholder(response)
548 concurrency = '__last_update'
549 [record] = Model.read(cr, openerp.SUPERUSER_ID, [id],
550 [concurrency, field],
553 if concurrency in record:
554 server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
556 response.last_modified = datetime.datetime.strptime(
557 record[concurrency], server_format + '.%f')
559 # just in case we have a timestamp without microseconds
560 response.last_modified = datetime.datetime.strptime(
561 record[concurrency], server_format)
563 # Field does not exist on model or field set to False
564 if not record.get(field):
565 # FIXME: maybe a field which does not exist should be a 404?
566 return self._image_placeholder(response)
568 response.set_etag(hashlib.sha1(record[field]).hexdigest())
569 response.make_conditional(request.httprequest)
571 # conditional request match
572 if response.status_code == 304:
575 data = record[field].decode('base64')
577 if (not max_width) and (not max_height):
581 image = Image.open(cStringIO.StringIO(data))
582 response.mimetype = Image.MIME[image.format]
585 max_w = int(max_width) if max_width else maxint
586 max_h = int(max_height) if max_height else maxint
588 if w < max_w and h < max_h:
591 image.thumbnail((max_w, max_h), Image.ANTIALIAS)
592 image.save(response.stream, image.format)
593 # invalidate content-length computed by make_conditional as
594 # writing to response.stream does not do it (as of werkzeug 0.9.3)
595 del response.headers['Content-Length']
600 class website_menu(osv.osv):
601 _name = "website.menu"
602 _description = "Website Menu"
604 'name': fields.char('Menu', required=True, translate=True),
605 'url': fields.char('Url'),
606 'new_window': fields.boolean('New Window'),
607 'sequence': fields.integer('Sequence'),
608 # TODO: support multiwebsite once done for ir.ui.views
609 'website_id': fields.many2one('website', 'Website'),
610 'parent_id': fields.many2one('website.menu', 'Parent Menu', select=True, ondelete="cascade"),
611 'child_id': fields.one2many('website.menu', 'parent_id', string='Child Menus'),
612 'parent_left': fields.integer('Parent Left', select=True),
613 'parent_right': fields.integer('Parent Right', select=True),
616 def __defaults_sequence(self, cr, uid, context):
617 menu = self.search_read(cr, uid, [(1,"=",1)], ["sequence"], limit=1, order="sequence DESC", context=context)
618 return menu and menu[0]["sequence"] or 0
622 'sequence': __defaults_sequence,
626 _parent_order = 'sequence'
629 # would be better to take a menu_id as argument
630 def get_tree(self, cr, uid, website_id, context=None):
636 new_window=node.new_window,
637 sequence=node.sequence,
638 parent_id=node.parent_id.id,
641 for child in node.child_id:
642 menu_node['children'].append(make_tree(child))
644 menu = self.pool.get('website').browse(cr, uid, website_id, context=context).menu_id
645 return make_tree(menu)
647 def save(self, cr, uid, website_id, data, context=None):
648 def replace_id(old_id, new_id):
649 for menu in data['data']:
650 if menu['id'] == old_id:
652 if menu['parent_id'] == old_id:
653 menu['parent_id'] = new_id
654 to_delete = data['to_delete']
656 self.unlink(cr, uid, to_delete, context=context)
657 for menu in data['data']:
659 if isinstance(mid, str):
660 new_id = self.create(cr, uid, {'name': menu['name']}, context=context)
661 replace_id(mid, new_id)
662 for menu in data['data']:
663 self.write(cr, uid, [menu['id']], menu, context=context)
666 class ir_attachment(osv.osv):
667 _inherit = "ir.attachment"
668 def _website_url_get(self, cr, uid, ids, name, arg, context=None):
670 for attach in self.browse(cr, uid, ids, context=context):
672 result[attach.id] = attach.url
674 result[attach.id] = urlplus('/website/image', {
675 'model': 'ir.attachment',
680 def _datas_checksum(self, cr, uid, ids, name, arg, context=None):
682 (attach['id'], self._compute_checksum(attach))
683 for attach in self.read(
684 cr, uid, ids, ['res_model', 'res_id', 'type', 'datas'],
688 def _compute_checksum(self, attachment_dict):
689 if attachment_dict.get('res_model') == 'ir.ui.view'\
690 and not attachment_dict.get('res_id') and not attachment_dict.get('url')\
691 and attachment_dict.get('type', 'binary') == 'binary'\
692 and attachment_dict.get('datas'):
693 return hashlib.new('sha1', attachment_dict['datas']).hexdigest()
696 def _datas_big(self, cr, uid, ids, name, arg, context=None):
697 result = dict.fromkeys(ids, False)
698 if context and context.get('bin_size'):
701 for record in self.browse(cr, uid, ids, context=context):
702 if not record.datas: continue
704 result[record.id] = openerp.tools.image_resize_image_big(record.datas)
705 except IOError: # apparently the error PIL.Image.open raises
711 'datas_checksum': fields.function(_datas_checksum, size=40,
712 string="Datas checksum", type='char', store=True, select=True),
713 'website_url': fields.function(_website_url_get, string="Attachment URL", type='char'),
714 'datas_big': fields.function (_datas_big, type='binary', store=True,
715 string="Resized file content"),
716 'mimetype': fields.char('Mime Type', readonly=True),
719 def _add_mimetype_if_needed(self, values):
720 if values.get('datas_fname'):
721 values['mimetype'] = mimetypes.guess_type(values.get('datas_fname'))[0] or 'application/octet-stream'
723 def create(self, cr, uid, values, context=None):
724 chk = self._compute_checksum(values)
726 match = self.search(cr, uid, [('datas_checksum', '=', chk)], context=context)
729 self._add_mimetype_if_needed(values)
730 return super(ir_attachment, self).create(
731 cr, uid, values, context=context)
733 def write(self, cr, uid, ids, values, context=None):
734 self._add_mimetype_if_needed(values)
735 return super(ir_attachment, self).write(cr, uid, ids, values, context=context)
737 def try_remove(self, cr, uid, ids, context=None):
738 """ Removes a web-based image attachment if it is used by no view
741 Returns a dict mapping attachments which would not be removed (if any)
742 mapped to the views preventing their removal
744 Views = self.pool['ir.ui.view']
745 attachments_to_remove = []
746 # views blocking removal of the attachment
747 removal_blocked_by = {}
749 for attachment in self.browse(cr, uid, ids, context=context):
750 # in-document URLs are html-escaped, a straight search will not
752 url = escape(attachment.website_url)
753 ids = Views.search(cr, uid, ["|", ('arch', 'like', '"%s"' % url), ('arch', 'like', "'%s'" % url)], context=context)
756 removal_blocked_by[attachment.id] = Views.read(
757 cr, uid, ids, ['name'], context=context)
759 attachments_to_remove.append(attachment.id)
760 if attachments_to_remove:
761 self.unlink(cr, uid, attachments_to_remove, context=context)
762 return removal_blocked_by
764 class res_partner(osv.osv):
765 _inherit = "res.partner"
767 def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
768 partner = self.browse(cr, uid, ids[0], context=context)
770 '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 ''),
771 'size': "%sx%s" % (height, width),
775 return urlplus('http://maps.googleapis.com/maps/api/staticmap' , params)
777 def google_map_link(self, cr, uid, ids, zoom=8, context=None):
778 partner = self.browse(cr, uid, ids[0], context=context)
780 '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 ''),
783 return urlplus('https://maps.google.com/maps' , params)
785 class res_company(osv.osv):
786 _inherit = "res.company"
787 def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
788 partner = self.browse(cr, openerp.SUPERUSER_ID, ids[0], context=context).partner_id
789 return partner and partner.google_map_img(zoom, width, height, context=context) or None
790 def google_map_link(self, cr, uid, ids, zoom=8, context=None):
791 partner = self.browse(cr, openerp.SUPERUSER_ID, ids[0], context=context).partner_id
792 return partner and partner.google_map_link(zoom, context=context) or None
794 class base_language_install(osv.osv_memory):
795 _inherit = "base.language.install"
797 'website_ids': fields.many2many('website', string='Websites to translate'),
800 def default_get(self, cr, uid, fields, context=None):
803 defaults = super(base_language_install, self).default_get(cr, uid, fields, context)
804 website_id = context.get('params', {}).get('website_id')
806 if 'website_ids' not in defaults:
807 defaults['website_ids'] = []
808 defaults['website_ids'].append(website_id)
811 def lang_install(self, cr, uid, ids, context=None):
814 action = super(base_language_install, self).lang_install(cr, uid, ids, context)
815 language_obj = self.browse(cr, uid, ids)[0]
816 website_ids = [website.id for website in language_obj['website_ids']]
817 lang_id = self.pool['res.lang'].search(cr, uid, [('code', '=', language_obj['lang'])])
818 if website_ids and lang_id:
819 data = {'language_ids': [(4, lang_id[0])]}
820 self.pool['website'].write(cr, uid, website_ids, data)
821 params = context.get('params', {})
822 if 'url_return' in params:
824 'url': params['url_return'].replace('[lang]', language_obj['lang']),
825 'type': 'ir.actions.act_url',
830 class website_seo_metadata(osv.Model):
831 _name = 'website.seo.metadata'
832 _description = 'SEO metadata'
835 'website_meta_title': fields.char("Website meta title", translate=True),
836 'website_meta_description': fields.text("Website meta description", translate=True),
837 'website_meta_keywords': fields.char("Website meta keywords", translate=True),