[MERGE] OPW 51079: purchase: if invoicing control is 'from picking', line with produc...
[odoo/odoo.git] / addons / document_webdav / nodes.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22
23 from document import nodes
24 from tools.safe_eval import safe_eval as eval
25 import time
26 import urllib
27 import uuid
28 try:
29     from tools.dict_tools import dict_filter
30 except ImportError:
31     from document.dict_tools import dict_filter
32
33 class node_acl_mixin(object):
34     def _get_dav_owner(self, cr):
35         return self.uuser
36
37     def _get_dav_group(self, cr):
38         return self.ugroup
39         
40     def _get_dav_supported_privilege_set(self, cr):
41         return '' # TODO
42     
43     def _get_dav_current_user_privilege_set(self, cr):
44         return '' # TODO
45
46     def _get_dav_props_hlpr(self, cr, par_class, prop_model, 
47                             prop_ref_field, res_id):
48         """ Helper for dav properties, usable in subclasses
49         
50         @param par_class The parent class
51         @param prop_model The name of the orm model holding the properties
52         @param prop_ref_field The name of the field at prop_model pointing to us
53         @param res_id the id of self in the corresponing orm table, that should
54                         match prop_model.prop_ref_field
55         """
56         ret = par_class.get_dav_props(self, cr)
57         if prop_model:
58             propobj = self.context._dirobj.pool.get(prop_model)
59             uid = self.context.uid
60             ctx = self.context.context.copy()
61             ctx.update(self.dctx)
62             # Not really needed because we don't do eval here:
63             # ctx.update({'uid': uid, 'dbname': self.context.dbname })
64             # dict_filter(self.context.extra_ctx, ['username', 'groupname', 'webdav_path'], ctx)
65             sdomain = [(prop_ref_field, '=', False),]
66             if res_id:
67                 sdomain = ['|', (prop_ref_field, '=', res_id)] + sdomain
68             prop_ids = propobj.search(cr, uid, sdomain, context=ctx)
69             if prop_ids:
70                 ret = ret.copy()
71                 for pbro in propobj.browse(cr, uid, prop_ids, context=ctx):
72                     ret[pbro.namespace] = ret.get(pbro.namespace, ()) + \
73                         (pbro.name,)
74                     # Note that we cannot have properties to conditionally appear
75                     # on the context, yet.
76                 
77         return ret
78
79     def _get_dav_eprop_hlpr(self, cr, ns, prop,
80                             par_class, prop_model, 
81                             prop_ref_field, res_id):
82         """ Helper for get dav eprop, usable in subclasses
83         
84         @param namespace the one to search for
85         @param name Name to search for
86         @param par_class The parent class
87         @param prop_model The name of the orm model holding the properties
88         @param prop_ref_field The name of the field at prop_model pointing to us
89         @param res_id the id of self in the corresponing orm table, that should
90                         match prop_model.prop_ref_field
91         """
92         ret = par_class.get_dav_eprop(self, cr, ns, prop)
93         if ret is not None:
94             return ret
95         if prop_model:
96             propobj = self.context._dirobj.pool.get(prop_model)
97             uid = self.context.uid
98             ctx = self.context.context.copy()
99             ctx.update(self.dctx)
100             ctx.update({'uid': uid, 'dbname': self.context.dbname })
101             ctx['node_classname'] = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
102             dict_filter(self.context.extra_ctx, ['username', 'groupname', 'webdav_path'], ctx)
103             sdomain = [(prop_ref_field, '=', False),('namespace', '=', ns), ('name','=', prop)]
104             if res_id:
105                 sdomain = ['|', (prop_ref_field, '=', res_id)] + sdomain
106             prop_ids = propobj.search(cr, uid, sdomain, context=ctx)
107             if prop_ids:
108                 pbro = propobj.browse(cr, uid, prop_ids[0], context=ctx)
109                 val = pbro.value
110                 if pbro.do_subst:
111                     if val.startswith("('") and val.endswith(")"):
112                         glbls = { 'urlquote': urllib.quote, }
113                         val = eval(val, glbls, ctx)
114                     else:
115                         val = val % ctx
116                 return val
117         return None
118
119     def _dav_lock_hlpr(self, cr, lock_data, par_class, prop_model,
120                             prop_ref_field, res_id):
121         """ Helper, which uses the dav properties table for placing locks
122         
123         @param lock_data a dictionary of input to this function.
124         @return list of tuples, DAV:activelock _contents_ structure.
125                 See webdav.py:class Prop2Xml() for semantics
126         
127         Note: although the DAV response shall be an <activelock/>, this
128         function will only return the elements inside the activelock,
129         because the calling function needs to append the <lockroot/> in
130         it. See webdav.py:mk_lock_response()
131         
132         In order to reuse code, this function can be called with 
133         lock_data['unlock_mode']=True, in order to unlock.
134         
135         @return bool in unlock mode, (davstruct, prop_id, token) in lock/refresh,
136                     or (False, prop_id, token) if already locked,
137                     or (False, False, False) if lock not found to refresh
138         """
139         assert prop_model
140         assert res_id
141         assert isinstance(lock_data, dict), '%r' % lock_data
142         propobj = self.context._dirobj.pool.get(prop_model)
143         uid = self.context.uid
144         ctx = self.context.context.copy()
145         ctx.update(self.dctx)
146         ctx.update({'uid': uid, 'dbname': self.context.dbname })
147         ctx['node_classname'] = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
148         dict_filter(self.context.extra_ctx, ['username', 'groupname', 'webdav_path'], ctx)
149         sdomain = [(prop_ref_field, '=', res_id), ('namespace', '=', 'DAV:'),
150                     ('name','=', 'lockdiscovery')]
151         props_to_delete = []
152         lock_found = False
153         lock_val = None
154         tmout2 = int(lock_data.get('timeout', 3*3600))
155         
156         prop_ids = propobj.search(cr, uid, sdomain, context=ctx)
157         if prop_ids:
158             for pbro in propobj.browse(cr, uid, prop_ids, context=ctx):
159                 val = pbro.value
160                 if pbro.do_subst:
161                     if val.startswith("('") and val.endswith(")"):
162                         glbls = { 'urlquote': urllib.quote, }
163                         val = eval(val, glbls, ctx)
164                     else:
165                         # all locks should be at "subst" format
166                         continue
167                 if not (val and isinstance(val, tuple) 
168                         and val[0:2] == ( 'activelock','DAV:')):
169                     # print "Value is not activelock:", val
170                     continue
171                 
172                 old_token = False
173                 old_owner = False
174                 try:
175                     # discover the timeout. If anything goes wrong, delete
176                     # the lock (cleanup)
177                     tmout = False
178                     for parm in val[2]:
179                         if parm[1] != 'DAV:':
180                             continue
181                         if parm[0] == 'timeout':
182                             if isinstance(parm[2], basestring) \
183                                     and parm[2].startswith('Second-'):
184                                 tmout = int(parm[2][7:])
185                         elif parm[0] == 'locktoken':
186                             if isinstance(parm[2], basestring):
187                                 old_token = parm[2]
188                             elif isinstance(parm[2], tuple) and \
189                                 parm[2][0:2] == ('href','DAV:'):
190                                     old_token = parm[2][2]
191                             else:
192                                 # print "Mangled token in DAV property: %r" % parm[2]
193                                 props_to_delete.append(pbro.id)
194                                 continue
195                         elif parm[0] == 'owner':
196                             old_owner = parm[2] # not used yet
197                     if tmout:
198                         mdate = pbro.write_date or pbro.create_date
199                         mdate = time.mktime(time.strptime(mdate,'%Y-%m-%d %H:%M:%S'))
200                         if mdate + tmout < time.time():
201                             props_to_delete.append(pbro.id)
202                             continue
203                     else:
204                         props_to_delete.append(pbro.id)
205                         continue
206                 except ValueError:
207                     props_to_delete.append(pbro.id)
208                     continue
209                 
210                 # A valid lock is found here
211                 if lock_data.get('refresh', False):
212                     if old_token != lock_data.get('token'):
213                         continue
214                     # refresh mode. Just touch anything and the ORM will update
215                     # the write uid+date, won't it?
216                     # Note: we don't update the owner, because incoming refresh
217                     # wouldn't have a body, anyway.
218                     propobj.write(cr, uid, [pbro.id,], { 'name': 'lockdiscovery'})
219                 elif lock_data.get('unlock_mode', False):
220                     if old_token != lock_data.get('token'):
221                         continue
222                     props_to_delete.append(pbro.id)
223                 
224                 lock_found = pbro.id
225                 lock_val = val
226
227         if tmout2 > 3*3600: # 3 hours maximum
228             tmout2 = 3*3600
229         elif tmout2 < 300:
230             # 5 minutes minimum, but an unlock request can always
231             # break it at any time. Ensures no negative values, either.
232             tmout2 = 300
233         
234         if props_to_delete:
235             # explicitly delete, as admin, any of the ids we have identified.
236             propobj.unlink(cr, 1, props_to_delete)
237         
238         if lock_data.get('unlock_mode', False):
239             return lock_found and True
240         elif (not lock_found) and not (lock_data.get('refresh', False)):
241             # Create a new lock, attach and return it.
242             new_token = uuid.uuid4().urn
243             lock_val = ('activelock', 'DAV:', 
244                     [ ('locktype', 'DAV:', (lock_data.get('locktype',False) or 'write','DAV:')),
245                       ('lockscope', 'DAV:', (lock_data.get('lockscope',False) or 'exclusive','DAV:')),
246                       # ? ('depth', 'DAV:', lock_data.get('depth','0') ),
247                       ('timeout','DAV:', 'Second-%d' % tmout2),
248                       ('locktoken', 'DAV:', ('href', 'DAV:', new_token)),
249                       # ('lockroot', 'DAV: ..., we don't store that, appended by caller
250                     ])
251             new_owner = lock_data.get('lockowner',False) or ctx.get('username', False)
252             if new_owner:
253                 lock_val[2].append( ('owner', 'DAV:',  new_owner) )
254             prop_id = propobj.create(cr, uid, { prop_ref_field: res_id,
255                     'namespace': 'DAV:', 'name': 'lockdiscovery',
256                     'do_subst': True, 'value': repr(lock_val) })
257             return (lock_val[2], prop_id, new_token )
258         elif not lock_found: # and refresh
259             return (False, False, False)
260         elif lock_found and not lock_data.get('refresh', False):
261             # already locked
262             return (False, lock_found, old_token)
263         else:
264             return (lock_val[2], lock_found, old_token )
265
266 class node_dir(node_acl_mixin, nodes.node_dir):
267     """ override node_dir and add DAV functionality
268     """
269     DAV_PROPS = { "DAV:": ('owner', 'group', 
270                             'supported-privilege-set', 
271                             'current-user-privilege-set'), 
272                 }
273     DAV_M_NS = { "DAV:" : '_get_dav',}
274     http_options = { 'DAV': ['access-control',] }
275
276     def get_dav_resourcetype(self, cr):
277         return ('collection', 'DAV:')
278
279     def get_dav_props(self, cr):
280         return self._get_dav_props_hlpr(cr, nodes.node_dir, 
281                 'document.webdav.dir.property', 'dir_id', self.dir_id)
282
283     def get_dav_eprop(self, cr, ns, prop):
284         return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_dir,
285                 'document.webdav.dir.property', 'dir_id', self.dir_id)
286
287
288 class node_file(node_acl_mixin, nodes.node_file):
289     DAV_PROPS = { "DAV:": ('owner', 'group', 
290                             'supported-privilege-set', 
291                             'current-user-privilege-set',
292                             ), 
293                 }
294     DAV_M_NS = { "DAV:" : '_get_dav',}
295     http_options = { 'DAV': ['access-control', ] }
296     pass
297
298     def get_dav_resourcetype(self, cr):
299         return ''
300
301     def get_dav_props(self, cr):
302         return self._get_dav_props_hlpr(cr, nodes.node_file, 
303                 'document.webdav.file.property', 'file_id', self.file_id)
304
305     def dav_lock(self, cr, lock_data):
306         """ Locks or unlocks the node, using DAV semantics.
307         
308         Unlocking will be done when lock_data['unlock_mode'] == True
309         
310         See _dav_lock_hlpr() for calling details.
311         
312         It is fundamentally OK to use this function from non-DAV endpoints,
313         but they will all have to emulate the tuple-in-list structure of
314         the DAV lock data. RFC if this translation should be done inside
315         the _dav_lock_hlpr (to ease other protocols).
316         """
317         return self._dav_lock_hlpr(cr, lock_data, nodes.node_file, 
318                 'document.webdav.file.property', 'file_id', self.file_id)
319
320     def dav_unlock(self, cr, token):
321         """Releases the token lock held for the node
322         
323         This is a utility complement of dav_lock()
324         """
325         lock_data = { 'token': token, 'unlock_mode': True }
326         return self._dav_lock_hlpr(cr, lock_data, nodes.node_file, 
327                 'document.webdav.file.property', 'file_id', self.file_id)
328
329     def get_dav_eprop(self, cr, ns, prop):
330         if ns == 'DAV:' and prop == 'supportedlock':
331             return [ ('lockentry', 'DAV:', 
332                         [ ('lockscope','DAV:', ('shared', 'DAV:')),
333                           ('locktype','DAV:', ('write', 'DAV:')),
334                         ]),
335                    ('lockentry', 'DAV:', 
336                         [ ('lockscope','DAV:', ('exclusive', 'DAV:')),
337                           ('locktype','DAV:', ('write', 'DAV:')),
338                         ] )
339                    ]
340         return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_file,
341                 'document.webdav.file.property', 'file_id', self.file_id)
342
343 class node_database(nodes.node_database):
344     def get_dav_resourcetype(self, cr):
345         return ('collection', 'DAV:')
346
347     def get_dav_props(self, cr):
348         return self._get_dav_props_hlpr(cr, nodes.node_database,
349                 'document.webdav.dir.property', 'dir_id', False)
350
351     def get_dav_eprop(self, cr, ns, prop):
352         return self._get_dav_eprop_hlpr(cr, nodes.node_database, ns, prop,
353                 'document.webdav.dir.property', 'dir_id', False)
354
355 class node_res_obj(node_acl_mixin, nodes.node_res_obj):
356     DAV_PROPS = { "DAV:": ('owner', 'group', 
357                             'supported-privilege-set', 
358                             'current-user-privilege-set'), 
359                 }
360     DAV_M_NS = { "DAV:" : '_get_dav',}
361     http_options = { 'DAV': ['access-control',] }
362
363     def get_dav_resourcetype(self, cr):
364         return ('collection', 'DAV:')
365
366     def get_dav_props(self, cr):
367         return self._get_dav_props_hlpr(cr, nodes.node_res_obj, 
368                 'document.webdav.dir.property', 'dir_id', self.dir_id)
369
370     def get_dav_eprop(self, cr, ns, prop):
371         return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_res_obj,
372                 'document.webdav.dir.property', 'dir_id', self.dir_id)
373
374
375 class node_res_dir(node_acl_mixin, nodes.node_res_dir):
376     DAV_PROPS = { "DAV:": ('owner', 'group', 
377                             'supported-privilege-set', 
378                             'current-user-privilege-set'), 
379                 }
380     DAV_M_NS = { "DAV:" : '_get_dav',}
381     http_options = { 'DAV': ['access-control',] }
382     res_obj_class = node_res_obj
383
384     def get_dav_resourcetype(self, cr):
385         return ('collection', 'DAV:')
386
387     def get_dav_props(self, cr):
388         return self._get_dav_props_hlpr(cr, nodes.node_res_dir, 
389                 'document.webdav.dir.property', 'dir_id', self.dir_id)
390
391     def get_dav_eprop(self, cr, ns, prop):
392         return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_res_dir,
393                 'document.webdav.dir.property', 'dir_id', self.dir_id)
394
395 # Some copies, so that this module can replace 'from document import nodes'
396 get_node_context = nodes.get_node_context
397 node_context = nodes.node_context
398 node_class = nodes.node_class
399 node_descriptor = nodes.node_descriptor
400
401
402 #eof