2013-06-24 13:36:54 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2013-07-31 12:13:43 +00:00
|
|
|
import base64
|
2013-09-03 14:32:37 +00:00
|
|
|
import cStringIO
|
2013-10-02 09:59:17 +00:00
|
|
|
import contextlib
|
2013-09-03 14:32:37 +00:00
|
|
|
import hashlib
|
2013-07-31 12:13:43 +00:00
|
|
|
import json
|
|
|
|
import logging
|
2013-09-03 14:32:37 +00:00
|
|
|
import os
|
2013-10-02 09:59:17 +00:00
|
|
|
import datetime
|
|
|
|
|
|
|
|
from sys import maxint
|
2013-07-31 12:13:43 +00:00
|
|
|
|
2013-09-03 14:32:37 +00:00
|
|
|
import psycopg2
|
|
|
|
import werkzeug
|
|
|
|
import werkzeug.exceptions
|
|
|
|
import werkzeug.utils
|
|
|
|
import werkzeug.wrappers
|
2013-08-14 08:45:05 +00:00
|
|
|
from PIL import Image
|
2013-07-24 08:59:21 +00:00
|
|
|
|
2013-06-24 13:36:54 +00:00
|
|
|
import openerp
|
2013-10-01 08:57:39 +00:00
|
|
|
from openerp.addons.website.models import website
|
2013-06-24 13:36:54 +00:00
|
|
|
from openerp.addons.web import http
|
|
|
|
from openerp.addons.web.http import request
|
|
|
|
|
2013-07-31 12:13:43 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
2013-07-26 12:33:17 +00:00
|
|
|
|
2013-08-14 08:45:05 +00:00
|
|
|
|
|
|
|
def auth_method_public():
|
|
|
|
registry = openerp.modules.registry.RegistryManager.get(request.db)
|
|
|
|
if not request.session.uid:
|
|
|
|
request.uid = registry['website'].get_public_user().id
|
|
|
|
else:
|
|
|
|
request.uid = request.session.uid
|
|
|
|
http.auth_methods['public'] = auth_method_public
|
|
|
|
|
2013-09-03 14:32:37 +00:00
|
|
|
NOPE = object()
|
2013-08-21 09:45:50 +00:00
|
|
|
# Completely arbitrary limits
|
|
|
|
MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT = IMAGE_LIMITS = (1024, 768)
|
2013-06-24 13:36:54 +00:00
|
|
|
class Website(openerp.addons.web.controllers.main.Home):
|
2013-09-26 13:31:55 +00:00
|
|
|
@website.route('/', type='http', auth="public", multilang=True)
|
2013-06-24 13:36:54 +00:00
|
|
|
def index(self, **kw):
|
2013-07-01 00:20:54 +00:00
|
|
|
return self.page("website.homepage")
|
2013-06-24 13:36:54 +00:00
|
|
|
|
|
|
|
@http.route('/admin', type='http', auth="none")
|
|
|
|
def admin(self, *args, **kw):
|
|
|
|
return super(Website, self).index(*args, **kw)
|
|
|
|
|
2013-09-04 11:32:22 +00:00
|
|
|
# FIXME: auth, if /pagenew known anybody can create new empty page
|
2013-09-04 15:32:36 +00:00
|
|
|
@website.route('/pagenew/<path:path>', type='http', auth="admin")
|
2013-09-03 14:32:37 +00:00
|
|
|
def pagenew(self, path, noredirect=NOPE):
|
|
|
|
if '.' in path:
|
|
|
|
module, idname = path.split('.', 1)
|
|
|
|
else:
|
|
|
|
module = 'website'
|
|
|
|
idname = path
|
2013-09-20 14:20:52 +00:00
|
|
|
xid = "%s.%s" % (module, idname)
|
2013-09-03 14:32:37 +00:00
|
|
|
|
|
|
|
request.cr.execute('SAVEPOINT pagenew')
|
2013-07-02 19:11:30 +00:00
|
|
|
imd = request.registry['ir.model.data']
|
2013-07-31 12:40:27 +00:00
|
|
|
view = request.registry['ir.ui.view']
|
2013-09-05 15:18:03 +00:00
|
|
|
view_model, view_id = imd.get_object_reference(
|
2013-09-09 10:00:59 +00:00
|
|
|
request.cr, request.uid, 'website', 'default_page')
|
2013-09-05 15:18:03 +00:00
|
|
|
newview_id = view.copy(
|
|
|
|
request.cr, request.uid, view_id, context=request.context)
|
|
|
|
newview = view.browse(
|
|
|
|
request.cr, request.uid, newview_id, context=request.context)
|
2013-07-31 12:58:17 +00:00
|
|
|
newview.write({
|
2013-09-20 14:20:52 +00:00
|
|
|
'arch': newview.arch.replace("website.default_page", xid),
|
2013-08-21 12:13:58 +00:00
|
|
|
'name': "page/%s" % path,
|
|
|
|
'page': True,
|
2013-07-31 12:58:17 +00:00
|
|
|
})
|
2013-09-03 14:32:37 +00:00
|
|
|
# Fuck it, we're doing it live
|
|
|
|
try:
|
|
|
|
imd.create(request.cr, request.uid, {
|
|
|
|
'name': idname,
|
|
|
|
'module': module,
|
|
|
|
'model': 'ir.ui.view',
|
|
|
|
'res_id': newview_id,
|
|
|
|
'noupdate': True
|
2013-09-05 15:18:03 +00:00
|
|
|
}, context=request.context)
|
2013-09-03 14:32:37 +00:00
|
|
|
except psycopg2.IntegrityError:
|
|
|
|
request.cr.execute('ROLLBACK TO SAVEPOINT pagenew')
|
2013-07-02 19:11:30 +00:00
|
|
|
else:
|
2013-09-03 14:32:37 +00:00
|
|
|
request.cr.execute('RELEASE SAVEPOINT pagenew')
|
|
|
|
url = "/page/%s" % path
|
|
|
|
if noredirect is not NOPE:
|
|
|
|
return werkzeug.wrappers.Response(url, mimetype='text/plain')
|
|
|
|
return werkzeug.utils.redirect(url)
|
2013-07-02 19:11:30 +00:00
|
|
|
|
2013-09-04 15:32:36 +00:00
|
|
|
@website.route('/website/theme_change', type='http', auth="admin")
|
2013-08-27 09:56:58 +00:00
|
|
|
def theme_change(self, theme_id=False, **kwargs):
|
|
|
|
imd = request.registry['ir.model.data']
|
|
|
|
view = request.registry['ir.ui.view']
|
|
|
|
|
2013-09-05 15:18:03 +00:00
|
|
|
view_model, view_option_id = imd.get_object_reference(
|
2013-09-09 10:00:59 +00:00
|
|
|
request.cr, request.uid, 'website', 'theme')
|
2013-09-05 15:18:03 +00:00
|
|
|
views = view.search(
|
|
|
|
request.cr, request.uid, [('inherit_id', '=', view_option_id)],
|
|
|
|
context=request.context)
|
|
|
|
view.write(request.cr, request.uid, views, {'inherit_id': False},
|
|
|
|
context=request.context)
|
2013-08-27 09:56:58 +00:00
|
|
|
|
|
|
|
if theme_id:
|
|
|
|
module, xml_id = theme_id.split('.')
|
2013-09-05 15:18:03 +00:00
|
|
|
view_model, view_id = imd.get_object_reference(
|
2013-09-09 10:00:59 +00:00
|
|
|
request.cr, request.uid, module, xml_id)
|
2013-09-05 15:18:03 +00:00
|
|
|
view.write(request.cr, request.uid, [view_id],
|
|
|
|
{'inherit_id': view_option_id}, context=request.context)
|
2013-08-27 09:56:58 +00:00
|
|
|
|
2013-09-10 13:41:33 +00:00
|
|
|
return request.website.render('website.themes', {'theme_changed': True})
|
2013-08-27 09:56:58 +00:00
|
|
|
|
2013-10-04 10:41:33 +00:00
|
|
|
@website.route(['/website/snippets'], type='json', auth="public")
|
|
|
|
def snippets(self):
|
|
|
|
return request.website.render('website.snippets')
|
|
|
|
|
2013-09-26 13:31:55 +00:00
|
|
|
@website.route('/page/<path:path>', type='http', auth="public", multilang=True)
|
2013-08-12 10:48:25 +00:00
|
|
|
def page(self, path, **kwargs):
|
2013-09-10 13:41:33 +00:00
|
|
|
values = {
|
|
|
|
'path': path,
|
|
|
|
}
|
2013-07-02 19:04:49 +00:00
|
|
|
try:
|
2013-09-10 13:41:33 +00:00
|
|
|
html = request.website.render(path, values)
|
2013-08-05 16:16:00 +00:00
|
|
|
except ValueError:
|
2013-09-10 13:41:33 +00:00
|
|
|
html = request.website.render('website.404', values)
|
2013-06-27 09:14:09 +00:00
|
|
|
return html
|
|
|
|
|
2013-09-04 15:32:36 +00:00
|
|
|
@website.route('/website/customize_template_toggle', type='json', auth='admin') # FIXME: auth
|
2013-08-11 14:06:19 +00:00
|
|
|
def customize_template_set(self, view_id):
|
|
|
|
view_obj = request.registry.get("ir.ui.view")
|
2013-09-05 15:18:03 +00:00
|
|
|
view = view_obj.browse(request.cr, request.uid, int(view_id),
|
|
|
|
context=request.context)
|
2013-08-11 14:06:19 +00:00
|
|
|
if view.inherit_id:
|
|
|
|
value = False
|
|
|
|
else:
|
|
|
|
value = view.inherit_option_id and view.inherit_option_id.id or False
|
|
|
|
view_obj.write(request.cr, request.uid, [view_id], {
|
|
|
|
'inherit_id': value
|
|
|
|
}, context=request.context)
|
|
|
|
return True
|
|
|
|
|
2013-09-04 15:32:36 +00:00
|
|
|
@website.route('/website/customize_template_get', type='json', auth='admin') # FIXME: auth
|
2013-09-09 21:46:26 +00:00
|
|
|
def customize_template_get(self, xml_id, optional=True):
|
2013-08-27 09:56:58 +00:00
|
|
|
imd = request.registry['ir.model.data']
|
2013-09-05 15:18:03 +00:00
|
|
|
view_model, view_theme_id = imd.get_object_reference(
|
2013-09-09 10:00:59 +00:00
|
|
|
request.cr, request.uid, 'website', 'theme')
|
2013-08-27 09:56:58 +00:00
|
|
|
|
2013-08-11 14:06:19 +00:00
|
|
|
view = request.registry.get("ir.ui.view")
|
|
|
|
views = view._views_get(request.cr, request.uid, xml_id, request.context)
|
|
|
|
done = {}
|
|
|
|
result = []
|
|
|
|
for v in views:
|
2013-09-09 21:46:26 +00:00
|
|
|
if v.inherit_option_id and v.inherit_option_id.id != view_theme_id or not optional:
|
2013-08-11 14:06:19 +00:00
|
|
|
if v.inherit_option_id.id not in done:
|
|
|
|
result.append({
|
|
|
|
'name': v.inherit_option_id.name,
|
2013-09-09 21:46:26 +00:00
|
|
|
'id': v.id,
|
2013-08-11 14:06:19 +00:00
|
|
|
'header': True,
|
|
|
|
'active': False
|
|
|
|
})
|
|
|
|
done[v.inherit_option_id.id] = True
|
|
|
|
result.append({
|
|
|
|
'name': v.name,
|
|
|
|
'id': v.id,
|
|
|
|
'header': False,
|
2013-09-19 14:53:39 +00:00
|
|
|
'active': (v.inherit_id.id == v.inherit_option_id.id) or (not optional and v.inherit_id.id)
|
2013-08-11 14:06:19 +00:00
|
|
|
})
|
|
|
|
return result
|
|
|
|
|
2013-09-23 12:00:58 +00:00
|
|
|
@website.route('/website/get_view_translations', type='json', auth='admin')
|
2013-09-24 19:06:37 +00:00
|
|
|
def get_view_translations(self, xml_id, lang=None):
|
|
|
|
lang = lang or request.context.get('lang')
|
|
|
|
views = self.customize_template_get(xml_id, optional=False)
|
|
|
|
views_ids = [view.get('id') for view in views if view.get('active')]
|
|
|
|
domain = [('type', '=', 'view'), ('res_id', 'in', views_ids), ('lang', '=', lang)]
|
|
|
|
irt = request.registry.get('ir.translation')
|
|
|
|
return irt.search_read(request.cr, request.uid, domain, ['id', 'res_id', 'value'], context=request.context)
|
2013-09-23 12:00:58 +00:00
|
|
|
|
|
|
|
@website.route('/website/set_translations', type='json', auth='admin')
|
|
|
|
def set_translations(self, data, lang):
|
|
|
|
irt = request.registry.get('ir.translation')
|
|
|
|
for view_id, trans in data.items():
|
|
|
|
view_id = int(view_id)
|
|
|
|
for t in trans:
|
|
|
|
initial_content = t['initial_content'].strip()
|
|
|
|
new_content = t['new_content'].strip()
|
2013-09-24 19:06:37 +00:00
|
|
|
tid = t['translation_id']
|
|
|
|
if not tid:
|
|
|
|
old_trans = irt.search_read(
|
|
|
|
request.cr, request.uid,
|
|
|
|
[
|
|
|
|
('type', '=', 'view'),
|
|
|
|
('res_id', '=', view_id),
|
|
|
|
('lang', '=', lang),
|
|
|
|
('src', '=', initial_content),
|
|
|
|
])
|
|
|
|
if old_trans:
|
|
|
|
tid = old_trans[0]['id']
|
|
|
|
if tid:
|
2013-09-23 12:00:58 +00:00
|
|
|
vals = {'value': new_content}
|
2013-09-24 19:06:37 +00:00
|
|
|
irt.write(request.cr, request.uid, [tid], vals)
|
2013-09-23 12:00:58 +00:00
|
|
|
else:
|
|
|
|
new_trans = {
|
|
|
|
'name': 'website',
|
|
|
|
'res_id': view_id,
|
|
|
|
'lang': lang,
|
|
|
|
'type': 'view',
|
|
|
|
'source': initial_content,
|
|
|
|
'value': new_content,
|
|
|
|
}
|
|
|
|
irt.create(request.cr, request.uid, new_trans)
|
|
|
|
irt._get_source.clear_cache(irt) # FIXME: find why ir.translation does not invalidate
|
|
|
|
return True
|
|
|
|
|
2013-09-04 11:32:22 +00:00
|
|
|
# # FIXME: auth, anybody can upload an attachment if URL known/found
|
2013-09-04 15:32:36 +00:00
|
|
|
@website.route('/website/attach', type='http', auth='admin')
|
2013-09-02 07:52:16 +00:00
|
|
|
def attach(self, func, upload):
|
2013-07-31 12:13:43 +00:00
|
|
|
req = request.httprequest
|
|
|
|
if req.method != 'POST':
|
|
|
|
return werkzeug.exceptions.MethodNotAllowed(valid_methods=['POST'])
|
|
|
|
|
|
|
|
url = message = None
|
|
|
|
try:
|
|
|
|
attachment_id = request.registry['ir.attachment'].create(request.cr, request.uid, {
|
|
|
|
'name': upload.filename,
|
|
|
|
'datas': base64.encodestring(upload.read()),
|
|
|
|
'datas_fname': upload.filename,
|
|
|
|
'res_model': 'ir.ui.view',
|
|
|
|
}, request.context)
|
|
|
|
# FIXME: auth=user... no good.
|
|
|
|
url = '/website/attachment/%d' % attachment_id
|
|
|
|
except Exception, e:
|
|
|
|
logger.exception("Failed to upload image to attachment")
|
|
|
|
message = str(e)
|
|
|
|
|
|
|
|
return """<script type='text/javascript'>
|
2013-09-02 07:52:16 +00:00
|
|
|
window.parent['%s'](%s, %s);
|
|
|
|
</script>""" % (func, json.dumps(url), json.dumps(message))
|
2013-07-31 12:13:43 +00:00
|
|
|
|
2013-09-26 11:18:54 +00:00
|
|
|
@website.route(['/website/publish'], type='json', auth="public")
|
|
|
|
def publish(self, id, object):
|
|
|
|
_id = int(id)
|
|
|
|
_object = request.registry[object]
|
2013-08-14 08:45:05 +00:00
|
|
|
|
|
|
|
obj = _object.browse(request.cr, request.uid, _id)
|
2013-09-05 15:18:03 +00:00
|
|
|
_object.write(request.cr, request.uid, [_id],
|
|
|
|
{'website_published': not obj.website_published},
|
|
|
|
context=request.context)
|
2013-08-14 08:45:05 +00:00
|
|
|
obj = _object.browse(request.cr, request.uid, _id)
|
2013-09-26 13:09:54 +00:00
|
|
|
return obj.website_published and True or False
|
2013-08-14 08:45:05 +00:00
|
|
|
|
2013-09-04 15:32:36 +00:00
|
|
|
@website.route(['/website/kanban/'], type='http', auth="public")
|
2013-08-21 16:06:32 +00:00
|
|
|
def kanban(self, **post):
|
2013-09-10 13:41:33 +00:00
|
|
|
return request.website.kanban_col(**post)
|
2013-08-21 16:06:32 +00:00
|
|
|
|
2013-09-23 13:42:40 +00:00
|
|
|
@website.route(['/robots.txt'], type='http', auth="public")
|
|
|
|
def robots(self):
|
|
|
|
return request.website.render('website.robots', {'url_root': request.httprequest.url_root})
|
|
|
|
|
|
|
|
@website.route(['/sitemap.xml'], type='http', auth="public")
|
|
|
|
def sitemap(self):
|
|
|
|
return request.website.render('website.sitemap', {'pages': request.website.list_pages()})
|
|
|
|
|
2013-10-02 09:59:17 +00:00
|
|
|
class Images(http.Controller):
|
|
|
|
@website.route('/website/image', auth="public")
|
|
|
|
def image(self, model, id, field):
|
|
|
|
Model = request.registry[model]
|
|
|
|
|
|
|
|
response = werkzeug.wrappers.Response()
|
|
|
|
|
|
|
|
id = int(id)
|
|
|
|
|
|
|
|
ids = Model.search(request.cr, request.uid,
|
|
|
|
[('id', '=', id)], context=request.context)\
|
|
|
|
or Model.search(request.cr, openerp.SUPERUSER_ID,
|
|
|
|
[('id', '=', id), ('website_published', '=', True)], context=request.context)
|
|
|
|
|
|
|
|
if not ids:
|
|
|
|
# file_open may return a StringIO. StringIO can be closed but are
|
|
|
|
# not context managers in Python 2 though that is fixed in 3
|
|
|
|
with contextlib.closing(openerp.tools.misc.file_open(
|
|
|
|
os.path.join('web', 'static', 'src', 'img', 'placeholder.png'),
|
|
|
|
mode='rb')) as f:
|
|
|
|
response.set_data(f.read())
|
|
|
|
return response
|
|
|
|
|
|
|
|
concurrency = '__last_update'
|
|
|
|
[record] = Model.read(request.cr, openerp.SUPERUSER_ID, [id],
|
|
|
|
[concurrency, field], context=request.context)
|
|
|
|
|
|
|
|
if concurrency in record:
|
|
|
|
server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
|
|
|
|
try:
|
|
|
|
response.last_modified = datetime.datetime.strptime(
|
|
|
|
record[concurrency], server_format + '.%f')
|
|
|
|
except ValueError:
|
|
|
|
# just in case we have a timestamp without microseconds
|
|
|
|
response.last_modified = datetime.datetime.strptime(
|
|
|
|
record[concurrency], server_format)
|
|
|
|
# FIXME: no field in record?
|
|
|
|
response.set_etag(hashlib.sha1(record[field]).hexdigest())
|
|
|
|
response.make_conditional(request.httprequest)
|
|
|
|
|
|
|
|
# conditional request match
|
|
|
|
if response.status_code == 304:
|
|
|
|
return response
|
|
|
|
|
|
|
|
return self.set_image_data(response, record[field].decode('base64'))
|
|
|
|
|
|
|
|
# FIXME: auth
|
|
|
|
# FIXME: delegate to image?
|
|
|
|
@website.route('/website/attachment/<int:id>', auth='admin')
|
|
|
|
def attachment(self, id):
|
|
|
|
attachment = request.registry['ir.attachment'].browse(
|
|
|
|
request.cr, request.uid, id, request.context)
|
|
|
|
|
|
|
|
return self.set_image_data(
|
|
|
|
werkzeug.wrappers.Response(),
|
|
|
|
attachment.datas.decode('base64'),
|
|
|
|
fit=IMAGE_LIMITS,)
|
|
|
|
|
|
|
|
def set_image_data(self, response, data, fit=(maxint, maxint)):
|
|
|
|
""" Sets an inferred mime type on the response object, and puts the
|
|
|
|
provided image's data in it, possibly after resizing if requested
|
|
|
|
|
|
|
|
Returns the response object after setting its mime and content, so
|
|
|
|
the result of ``get_final_image`` can be returned directly.
|
|
|
|
"""
|
|
|
|
buf = cStringIO.StringIO(data)
|
|
|
|
|
|
|
|
# FIXME: unknown format or not an image
|
|
|
|
image = Image.open(buf)
|
2013-10-04 09:17:31 +00:00
|
|
|
response.mimetype = Image.MIME[image.format]
|
2013-10-02 09:59:17 +00:00
|
|
|
|
|
|
|
w, h = image.size
|
|
|
|
max_w, max_h = fit
|
|
|
|
|
|
|
|
if w < max_w and h < max_h:
|
|
|
|
response.set_data(data)
|
|
|
|
return response
|
|
|
|
|
|
|
|
image.thumbnail(fit, Image.ANTIALIAS)
|
|
|
|
image.save(response.stream, image.format)
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
2013-06-24 13:36:54 +00:00
|
|
|
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
|