1 # -*- coding: utf-8 -*-
12 from sys import maxint
16 import werkzeug.exceptions
18 import werkzeug.wrappers
22 from slugify import slugify
24 def slugify(s, max_length=None):
25 spaceless = re.sub(r'\s+', '-', s)
26 specialless = re.sub(r'[^-_a-z0-9]', '', spaceless)
27 return specialless[:max_length]
30 from openerp.osv import fields
31 from openerp.addons.website.models import website
32 from openerp.addons.web import http
33 from openerp.addons.web.http import request
35 logger = logging.getLogger(__name__)
38 def auth_method_public():
39 registry = openerp.modules.registry.RegistryManager.get(request.db)
40 if not request.session.uid:
41 request.uid = registry['website'].get_public_user().id
43 request.uid = request.session.uid
44 http.auth_methods['public'] = auth_method_public
47 # Completely arbitrary limits
48 MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT = IMAGE_LIMITS = (1024, 768)
49 class Website(openerp.addons.web.controllers.main.Home):
50 @website.route('/', type='http', auth="public", multilang=True)
51 def index(self, **kw):
52 return self.page("website.homepage")
54 @website.route('/pagenew/<path:path>', type='http', auth="user")
55 def pagenew(self, path, noredirect=NOPE):
57 # completely arbitrary max_length
58 idname = slugify(path, max_length=50)
60 request.cr.execute('SAVEPOINT pagenew')
61 imd = request.registry['ir.model.data']
62 view = request.registry['ir.ui.view']
63 view_model, view_id = imd.get_object_reference(
64 request.cr, request.uid, 'website', 'default_page')
65 newview_id = view.copy(
66 request.cr, request.uid, view_id, context=request.context)
67 newview = view.browse(
68 request.cr, request.uid, newview_id, context=request.context)
70 'arch': newview.arch.replace("website.default_page",
71 "%s.%s" % (module, idname)),
75 # Fuck it, we're doing it live
77 imd.create(request.cr, request.uid, {
80 'model': 'ir.ui.view',
83 }, context=request.context)
84 except psycopg2.IntegrityError:
85 logger.exception('Unable to create ir_model_data for page %s', path)
86 request.cr.execute('ROLLBACK TO SAVEPOINT pagenew')
87 return werkzeug.exceptions.InternalServerError()
89 request.cr.execute('RELEASE SAVEPOINT pagenew')
91 url = "/page/%s" % idname
92 if noredirect is not NOPE:
93 return werkzeug.wrappers.Response(url, mimetype='text/plain')
94 return werkzeug.utils.redirect(url)
96 @website.route('/website/theme_change', type='http', auth="admin")
97 def theme_change(self, theme_id=False, **kwargs):
98 imd = request.registry['ir.model.data']
99 view = request.registry['ir.ui.view']
101 view_model, view_option_id = imd.get_object_reference(
102 request.cr, request.uid, 'website', 'theme')
104 request.cr, request.uid, [('inherit_id', '=', view_option_id)],
105 context=request.context)
106 view.write(request.cr, request.uid, views, {'inherit_id': False},
107 context=request.context)
110 module, xml_id = theme_id.split('.')
111 view_model, view_id = imd.get_object_reference(
112 request.cr, request.uid, module, xml_id)
113 view.write(request.cr, request.uid, [view_id],
114 {'inherit_id': view_option_id}, context=request.context)
116 return request.website.render('website.themes', {'theme_changed': True})
118 @website.route(['/website/snippets'], type='json', auth="public")
120 return request.website.render('website.snippets')
122 @website.route('/page/<path:path>', type='http', auth="public", multilang=True)
123 def page(self, path, **kwargs):
128 html = request.website.render(path, values)
130 html = request.website.render('website.404', values)
133 @website.route('/website/customize_template_toggle', type='json', auth='user')
134 def customize_template_set(self, view_id):
135 view_obj = request.registry.get("ir.ui.view")
136 view = view_obj.browse(request.cr, request.uid, int(view_id),
137 context=request.context)
141 value = view.inherit_option_id and view.inherit_option_id.id or False
142 view_obj.write(request.cr, request.uid, [view_id], {
144 }, context=request.context)
147 @website.route('/website/customize_template_get', type='json', auth='user')
148 def customize_template_get(self, xml_id, optional=True):
149 imd = request.registry['ir.model.data']
150 view_model, view_theme_id = imd.get_object_reference(
151 request.cr, request.uid, 'website', 'theme')
153 view = request.registry.get("ir.ui.view")
154 views = view._views_get(request.cr, request.uid, xml_id, request.context)
158 if v.inherit_option_id and v.inherit_option_id.id != view_theme_id or not optional:
159 if v.inherit_option_id.id not in done:
161 'name': v.inherit_option_id.name,
166 done[v.inherit_option_id.id] = True
171 'active': (v.inherit_id.id == v.inherit_option_id.id) or (not optional and v.inherit_id.id)
175 @website.route('/website/get_view_translations', type='json', auth='admin')
176 def get_view_translations(self, xml_id, lang=None):
177 lang = lang or request.context.get('lang')
178 views = self.customize_template_get(xml_id, optional=False)
179 views_ids = [view.get('id') for view in views if view.get('active')]
180 domain = [('type', '=', 'view'), ('res_id', 'in', views_ids), ('lang', '=', lang)]
181 irt = request.registry.get('ir.translation')
182 return irt.search_read(request.cr, request.uid, domain, ['id', 'res_id', 'value'], context=request.context)
184 @website.route('/website/set_translations', type='json', auth='admin')
185 def set_translations(self, data, lang):
186 irt = request.registry.get('ir.translation')
187 for view_id, trans in data.items():
188 view_id = int(view_id)
190 initial_content = t['initial_content'].strip()
191 new_content = t['new_content'].strip()
192 tid = t['translation_id']
194 old_trans = irt.search_read(
195 request.cr, request.uid,
197 ('type', '=', 'view'),
198 ('res_id', '=', view_id),
200 ('src', '=', initial_content),
203 tid = old_trans[0]['id']
205 vals = {'value': new_content}
206 irt.write(request.cr, request.uid, [tid], vals)
213 'source': initial_content,
214 'value': new_content,
216 irt.create(request.cr, request.uid, new_trans)
219 @website.route('/website/attach', type='http', auth='user')
220 def attach(self, func, upload):
221 req = request.httprequest
222 if req.method != 'POST':
223 return werkzeug.exceptions.MethodNotAllowed(valid_methods=['POST'])
227 attachment_id = request.registry['ir.attachment'].create(request.cr, request.uid, {
228 'name': upload.filename,
229 'datas': upload.read().encode('base64'),
230 'datas_fname': upload.filename,
231 'res_model': 'ir.ui.view',
234 url = website.urlplus('/website/image', {
235 'model': 'ir.attachment',
238 'max_height': MAX_IMAGE_HEIGHT,
239 'max_width': MAX_IMAGE_WIDTH,
242 logger.exception("Failed to upload image to attachment")
245 return """<script type='text/javascript'>
246 window.parent['%s'](%s, %s);
247 </script>""" % (func, json.dumps(url), json.dumps(message))
249 @website.route(['/website/publish'], type='json', auth="public")
250 def publish(self, id, object):
252 _object = request.registry[object]
253 obj = _object.browse(request.cr, request.uid, _id)
256 if 'website_published' in _object._all_columns:
257 values['website_published'] = not obj.website_published
258 if 'website_published_datetime' in _object._all_columns and values.get('website_published'):
259 values['website_published_datetime'] = fields.datetime.now()
260 _object.write(request.cr, request.uid, [_id],
261 values, context=request.context)
263 obj = _object.browse(request.cr, request.uid, _id)
264 return obj.website_published and True or False
266 @website.route(['/website/kanban/'], type='http', auth="public")
267 def kanban(self, **post):
268 return request.website.kanban_col(**post)
270 @website.route(['/robots.txt'], type='http', auth="public")
272 return request.website.render('website.robots', {'url_root': request.httprequest.url_root})
274 @website.route(['/sitemap.xml'], type='http', auth="public")
276 return request.website.render('website.sitemap', {'pages': request.website.list_pages()})
278 class Images(http.Controller):
279 def placeholder(self, response):
280 # file_open may return a StringIO. StringIO can be closed but are
281 # not context managers in Python 2 though that is fixed in 3
282 with contextlib.closing(openerp.tools.misc.file_open(
283 os.path.join('web', 'static', 'src', 'img', 'placeholder.png'),
285 response.set_data(f.read())
286 return response.make_conditional(request.httprequest)
288 @website.route('/website/image', auth="public")
289 def image(self, model, id, field, max_width=maxint, max_height=maxint):
290 Model = request.registry[model]
292 response = werkzeug.wrappers.Response()
296 ids = Model.search(request.cr, request.uid,
297 [('id', '=', id)], context=request.context) \
298 or Model.search(request.cr, openerp.SUPERUSER_ID,
299 [('id', '=', id), ('website_published', '=', True)], context=request.context)
302 return self.placeholder(response)
304 concurrency = '__last_update'
305 [record] = Model.read(request.cr, openerp.SUPERUSER_ID, [id],
306 [concurrency, field], context=request.context)
308 if concurrency in record:
309 server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
311 response.last_modified = datetime.datetime.strptime(
312 record[concurrency], server_format + '.%f')
314 # just in case we have a timestamp without microseconds
315 response.last_modified = datetime.datetime.strptime(
316 record[concurrency], server_format)
318 # Field does not exist on model or field set to False
319 if not record.get(field):
320 # FIXME: maybe a field which does not exist should be a 404?
321 return self.placeholder(response)
323 response.set_etag(hashlib.sha1(record[field]).hexdigest())
324 response.make_conditional(request.httprequest)
326 # conditional request match
327 if response.status_code == 304:
330 data = record[field].decode('base64')
331 fit = int(max_width), int(max_height)
333 buf = cStringIO.StringIO(data)
335 image = Image.open(buf)
337 response.mimetype = Image.MIME[image.format]
342 if w < max_w and h < max_h:
343 response.set_data(data)
345 image.thumbnail(fit, Image.ANTIALIAS)
346 image.save(response.stream, image.format)
347 # invalidate content-length computed by make_conditional as writing
348 # to response.stream does not do it (as of werkzeug 0.9.3)
349 del response.headers['Content-Length']
354 # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: