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 @http.route('/admin', type='http', auth="none")
55 def admin(self, *args, **kw):
56 return super(Website, self).index(*args, **kw)
58 # FIXME: auth, if /pagenew known anybody can create new empty page
59 @website.route('/pagenew/<path:path>', type='http', auth="admin")
60 def pagenew(self, path, noredirect=NOPE):
62 # completely arbitrary max_length
63 idname = slugify(path, max_length=50)
65 request.cr.execute('SAVEPOINT pagenew')
66 imd = request.registry['ir.model.data']
67 view = request.registry['ir.ui.view']
68 view_model, view_id = imd.get_object_reference(
69 request.cr, request.uid, 'website', 'default_page')
70 newview_id = view.copy(
71 request.cr, request.uid, view_id, context=request.context)
72 newview = view.browse(
73 request.cr, request.uid, newview_id, context=request.context)
75 'arch': newview.arch.replace("website.default_page",
76 "%s.%s" % (module, idname)),
80 # Fuck it, we're doing it live
82 imd.create(request.cr, request.uid, {
85 'model': 'ir.ui.view',
88 }, context=request.context)
89 except psycopg2.IntegrityError:
90 logger.exception('Unable to create ir_model_data for page %s', path)
91 request.cr.execute('ROLLBACK TO SAVEPOINT pagenew')
92 return werkzeug.exceptions.InternalServerError()
94 request.cr.execute('RELEASE SAVEPOINT pagenew')
96 url = "/page/%s" % idname
97 if noredirect is not NOPE:
98 return werkzeug.wrappers.Response(url, mimetype='text/plain')
99 return werkzeug.utils.redirect(url)
101 @website.route('/website/theme_change', type='http', auth="admin")
102 def theme_change(self, theme_id=False, **kwargs):
103 imd = request.registry['ir.model.data']
104 view = request.registry['ir.ui.view']
106 view_model, view_option_id = imd.get_object_reference(
107 request.cr, request.uid, 'website', 'theme')
109 request.cr, request.uid, [('inherit_id', '=', view_option_id)],
110 context=request.context)
111 view.write(request.cr, request.uid, views, {'inherit_id': False},
112 context=request.context)
115 module, xml_id = theme_id.split('.')
116 view_model, view_id = imd.get_object_reference(
117 request.cr, request.uid, module, xml_id)
118 view.write(request.cr, request.uid, [view_id],
119 {'inherit_id': view_option_id}, context=request.context)
121 return request.website.render('website.themes', {'theme_changed': True})
123 @website.route(['/website/snippets'], type='json', auth="public")
125 return request.website.render('website.snippets')
127 @website.route('/page/<path:path>', type='http', auth="public", multilang=True)
128 def page(self, path, **kwargs):
133 html = request.website.render(path, values)
135 html = request.website.render('website.404', values)
138 @website.route('/website/customize_template_toggle', type='json', auth='admin') # FIXME: auth
139 def customize_template_set(self, view_id):
140 view_obj = request.registry.get("ir.ui.view")
141 view = view_obj.browse(request.cr, request.uid, int(view_id),
142 context=request.context)
146 value = view.inherit_option_id and view.inherit_option_id.id or False
147 view_obj.write(request.cr, request.uid, [view_id], {
149 }, context=request.context)
152 @website.route('/website/customize_template_get', type='json', auth='admin') # FIXME: auth
153 def customize_template_get(self, xml_id, optional=True):
154 imd = request.registry['ir.model.data']
155 view_model, view_theme_id = imd.get_object_reference(
156 request.cr, request.uid, 'website', 'theme')
158 view = request.registry.get("ir.ui.view")
159 views = view._views_get(request.cr, request.uid, xml_id, request.context)
163 if v.inherit_option_id and v.inherit_option_id.id != view_theme_id or not optional:
164 if v.inherit_option_id.id not in done:
166 'name': v.inherit_option_id.name,
171 done[v.inherit_option_id.id] = True
176 'active': (v.inherit_id.id == v.inherit_option_id.id) or (not optional and v.inherit_id.id)
180 @website.route('/website/get_view_translations', type='json', auth='admin')
181 def get_view_translations(self, xml_id, lang=None):
182 lang = lang or request.context.get('lang')
183 views = self.customize_template_get(xml_id, optional=False)
184 views_ids = [view.get('id') for view in views if view.get('active')]
185 domain = [('type', '=', 'view'), ('res_id', 'in', views_ids), ('lang', '=', lang)]
186 irt = request.registry.get('ir.translation')
187 return irt.search_read(request.cr, request.uid, domain, ['id', 'res_id', 'value'], context=request.context)
189 @website.route('/website/set_translations', type='json', auth='admin')
190 def set_translations(self, data, lang):
191 irt = request.registry.get('ir.translation')
192 for view_id, trans in data.items():
193 view_id = int(view_id)
195 initial_content = t['initial_content'].strip()
196 new_content = t['new_content'].strip()
197 tid = t['translation_id']
199 old_trans = irt.search_read(
200 request.cr, request.uid,
202 ('type', '=', 'view'),
203 ('res_id', '=', view_id),
205 ('src', '=', initial_content),
208 tid = old_trans[0]['id']
210 vals = {'value': new_content}
211 irt.write(request.cr, request.uid, [tid], vals)
218 'source': initial_content,
219 'value': new_content,
221 irt.create(request.cr, request.uid, new_trans)
222 irt._get_source.clear_cache(irt) # FIXME: find why ir.translation does not invalidate
225 # # FIXME: auth, anybody can upload an attachment if URL known/found
226 @website.route('/website/attach', type='http', auth='admin')
227 def attach(self, func, upload):
228 req = request.httprequest
229 if req.method != 'POST':
230 return werkzeug.exceptions.MethodNotAllowed(valid_methods=['POST'])
234 attachment_id = request.registry['ir.attachment'].create(request.cr, request.uid, {
235 'name': upload.filename,
236 'datas': base64.encodestring(upload.read()),
237 'datas_fname': upload.filename,
238 'res_model': 'ir.ui.view',
240 # FIXME: auth=user... no good.
241 url = '/website/attachment/%d' % attachment_id
243 logger.exception("Failed to upload image to attachment")
246 return """<script type='text/javascript'>
247 window.parent['%s'](%s, %s);
248 </script>""" % (func, json.dumps(url), json.dumps(message))
250 @website.route(['/website/publish'], type='json', auth="public")
251 def publish(self, id, object):
253 _object = request.registry[object]
254 obj = _object.browse(request.cr, request.uid, _id)
257 if 'website_published' in _object._all_columns:
258 values['website_published'] = not obj.website_published
259 if 'website_published_datetime' in _object._all_columns and values.get('website_published'):
260 values['website_published_datetime'] = fields.datetime.now()
261 _object.write(request.cr, request.uid, [_id],
262 values, context=request.context)
264 obj = _object.browse(request.cr, request.uid, _id)
265 return obj.website_published and True or False
267 @website.route(['/website/kanban/'], type='http', auth="public")
268 def kanban(self, **post):
269 return request.website.kanban_col(**post)
271 @website.route(['/robots.txt'], type='http', auth="public")
273 return request.website.render('website.robots', {'url_root': request.httprequest.url_root})
275 @website.route(['/sitemap.xml'], type='http', auth="public")
277 return request.website.render('website.sitemap', {'pages': request.website.list_pages()})
279 class Images(http.Controller):
280 def placeholder(self, response):
281 # file_open may return a StringIO. StringIO can be closed but are
282 # not context managers in Python 2 though that is fixed in 3
283 with contextlib.closing(openerp.tools.misc.file_open(
284 os.path.join('web', 'static', 'src', 'img', 'placeholder.png'),
286 response.set_data(f.read())
287 return response.make_conditional(request.httprequest)
289 @website.route('/website/image', auth="public")
290 def image(self, model, id, field):
291 Model = request.registry[model]
293 response = werkzeug.wrappers.Response()
297 ids = Model.search(request.cr, request.uid,
298 [('id', '=', id)], context=request.context)\
299 or Model.search(request.cr, openerp.SUPERUSER_ID,
300 [('id', '=', id), ('website_published', '=', True)], context=request.context)
303 return self.placeholder(response)
305 concurrency = '__last_update'
306 [record] = Model.read(request.cr, openerp.SUPERUSER_ID, [id],
307 [concurrency, field], context=request.context)
309 if concurrency in record:
310 server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
312 response.last_modified = datetime.datetime.strptime(
313 record[concurrency], server_format + '.%f')
315 # just in case we have a timestamp without microseconds
316 response.last_modified = datetime.datetime.strptime(
317 record[concurrency], server_format)
319 if not record.get(field):
320 # Field does not exist on model or field set to False
321 # FIXME: maybe a field which does not exist should be a 404?
322 return self.placeholder(response)
324 response.set_etag(hashlib.sha1(record[field]).hexdigest())
325 response.make_conditional(request.httprequest)
327 # conditional request match
328 if response.status_code == 304:
331 return self.set_image_data(response, record[field].decode('base64'))
334 # FIXME: delegate to image?
335 @website.route('/website/attachment/<int:id>', auth='admin')
336 def attachment(self, id):
337 attachment = request.registry['ir.attachment'].browse(
338 request.cr, request.uid, id, request.context)
340 return self.set_image_data(
341 werkzeug.wrappers.Response(),
342 attachment.datas.decode('base64'),
345 def set_image_data(self, response, data, fit=(maxint, maxint)):
346 """ Sets an inferred mime type on the response object, and puts the
347 provided image's data in it, possibly after resizing if requested
349 Returns the response object after setting its mime and content, so
350 the result of ``get_final_image`` can be returned directly.
352 buf = cStringIO.StringIO(data)
354 # FIXME: unknown format or not an image
355 image = Image.open(buf)
356 response.mimetype = Image.MIME[image.format]
361 if w < max_w and h < max_h:
362 response.set_data(data)
365 image.thumbnail(fit, Image.ANTIALIAS)
366 image.save(response.stream, image.format)
370 # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: