diff --git a/base_dav/__manifest__.py b/base_dav/__manifest__.py index 5c714e454..3bd76b108 100644 --- a/base_dav/__manifest__.py +++ b/base_dav/__manifest__.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). { "name": "Caldav and Carddav support", - "version": "12.0.1.0.0", + "version": "14.0.1.0.0", "author": "initOS GmbH,Therp BV,Odoo Community Association (OCA)", "license": "AGPL-3", "category": "Extra Tools", diff --git a/base_dav/models/dav_collection.py b/base_dav/models/dav_collection.py index 62d530f7d..5a6ad83a9 100644 --- a/base_dav/models/dav_collection.py +++ b/base_dav/models/dav_collection.py @@ -7,6 +7,7 @@ from operator import itemgetter from urllib.parse import quote_plus from odoo import api, fields, models, tools +from odoo.tools.safe_eval import safe_eval import logging import vobject @@ -19,8 +20,8 @@ logger = logging.getLogger(__name__) class DavCollection(models.Model): - _name = 'dav.collection' - _description = 'A collection accessible via WebDAV' + _name = "dav.collection" + _description = "A collection accessible via WebDAV" name = fields.Char(required=True) rights = fields.Selection( @@ -34,99 +35,94 @@ class DavCollection(models.Model): ) dav_type = fields.Selection( [ - ('calendar', 'Calendar'), - ('addressbook', 'Addressbook'), - ('files', 'Files'), + ("calendar", "Calendar"), + ("addressbook", "Addressbook"), + ("files", "Files"), ], - string='Type', + string="Type", required=True, - default='calendar', + default="calendar", ) - tag = fields.Char(compute='_compute_tag') + tag = fields.Char(compute="_compute_tag") model_id = fields.Many2one( - 'ir.model', - string='Model', + "ir.model", + string="Model", required=True, - domain=[('transient', '=', False)], + ondelete="cascade", + domain=[("transient", "=", False)], ) domain = fields.Char( required=True, - default='[]', + default="[]", ) - field_uuid = fields.Many2one('ir.model.fields') + field_uuid = fields.Many2one("ir.model.fields") field_mapping_ids = fields.One2many( - 'dav.collection.field_mapping', - 'collection_id', - string='Field mappings', + "dav.collection.field_mapping", + "collection_id", + string="Field mappings", ) - url = fields.Char(compute='_compute_url') + url = fields.Char(compute="_compute_url") - @api.multi def _compute_tag(self): for this in self: - if this.dav_type == 'calendar': - this.tag = 'VCALENDAR' - elif this.dav_type == 'addressbook': - this.tag = 'VADDRESSBOOK' + if this.dav_type == "calendar": + this.tag = "VCALENDAR" + elif this.dav_type == "addressbook": + this.tag = "VADDRESSBOOK" - @api.multi def _compute_url(self): - base_url = self.env['ir.config_parameter'].get_param('web.base.url') + base_url = self.env["ir.config_parameter"].get_param("web.base.url") for this in self: - this.url = '%s%s/%s/%s' % ( + this.url = "%s%s/%s/%s" % ( base_url, PREFIX, self.env.user.login, this.id, ) - @api.constrains('domain') + @api.constrains("domain") def _check_domain(self): self._eval_domain() @api.model def _eval_context(self): return { - 'user': self.env.user, + "user": self.env.user, } @api.model def get_logger(self): return logger - @api.multi def _eval_domain(self): self.ensure_one() - return list(tools.safe_eval(self.domain, self._eval_context())) + return list(safe_eval(self.domain, self._eval_context())) - @api.multi def eval(self): if not self: - return self.env['unknown'] + return self.env["unknown"] self.ensure_one() return self.env[self.model_id.model].search(self._eval_domain()) - @api.multi def get_record(self, components): self.ensure_one() collection_model = self.env[self.model_id.model] field_name = self.field_uuid.name or "id" - domain = [(field_name, '=', components[-1])] + self._eval_domain() + domain = [(field_name, "=", components[-1])] + self._eval_domain() return collection_model.search(domain, limit=1) - @api.multi def from_vobject(self, item): self.ensure_one() result = {} - if self.dav_type == 'calendar': - if item.name != 'VCALENDAR': + if self.dav_type == "calendar": + if item.name != "VCALENDAR": return None - if not hasattr(item, 'vevent'): + if not hasattr(item, "vevent"): return None item = item.vevent - elif self.dav_type == 'addressbook' and item.name != 'VCARD': + elif self.dav_type == "addressbook" and item.name != "VCARD": return None children = {c.name.lower(): c for c in item.getChildren()} @@ -142,15 +138,14 @@ class DavCollection(models.Model): return result - @api.multi def to_vobject(self, record): self.ensure_one() result = None vobj = None - if self.dav_type == 'calendar': + if self.dav_type == "calendar": result = vobject.iCalendar() - vobj = result.add('vevent') - if self.dav_type == 'addressbook': + vobj = result.add("vevent") + if self.dav_type == "addressbook": result = vobject.vCard() vobj = result for mapping in self.field_mapping_ids: @@ -158,50 +153,49 @@ class DavCollection(models.Model): if value: vobj.add(mapping.name).value = value - if 'uid' not in vobj.contents: - vobj.add('uid').value = '%s,%s' % (record._name, record.id) - if 'rev' not in vobj.contents and 'write_date' in record._fields: - vobj.add('rev').value = record.write_date.strftime('%Y-%m-%dT%H%M%SZ') + if "uid" not in vobj.contents: + vobj.add("uid").value = "%s,%s" % (record._name, record.id) + if "rev" not in vobj.contents and "write_date" in record._fields: + vobj.add("rev").value = record.write_date.strftime("%Y-%m-%dT%H%M%SZ") return result @api.model def _odoo_to_http_datetime(self, value): - return value.strftime('%a, %d %b %Y %H:%M:%S GMT') + return value.strftime("%a, %d %b %Y %H:%M:%S GMT") @api.model def _split_path(self, path): - return list(filter( - None, os.path.normpath(path or '').strip('/').split('/') - )) + return list(filter(None, os.path.normpath(path or "").strip("/").split("/"))) - @api.multi def dav_list(self, collection, path_components): self.ensure_one() - if self.dav_type == 'files': + if self.dav_type == "files": if len(path_components) == 3: collection_model = self.env[self.model_id.model] - record = collection_model.browse(map( - itemgetter(0), - collection_model.name_search( - path_components[2], operator='=', limit=1, + record = collection_model.browse( + map( + itemgetter(0), + collection_model.name_search( + path_components[2], + operator="=", + limit=1, + ), ) - )) + ) return [ - '/' + '/'.join( - path_components + [quote_plus(attachment.name)] + "/" + "/".join(path_components + [quote_plus(attachment.name)]) + for attachment in self.env["ir.attachment"].search( + [ + ("type", "=", "binary"), + ("res_model", "=", record._name), + ("res_id", "=", record.id), + ] ) - for attachment in self.env['ir.attachment'].search([ - ('type', '=', 'binary'), - ('res_model', '=', record._name), - ('res_id', '=', record.id), - ]) ] elif len(path_components) == 2: return [ - '/' + '/'.join( - path_components + [quote_plus(record.display_name)] - ) + "/" + "/".join(path_components + [quote_plus(record.display_name)]) for record in self.eval() ] @@ -214,11 +208,16 @@ class DavCollection(models.Model): uuid = record[self.field_uuid.name] else: uuid = str(record.id) - href = '/' + '/'.join(path_components + [uuid]) + try: + href = "/" + "/".join(path_components + [str(uuid)]) + except: + import ipdb + + ipdb.set_trace() + result.append(self.dav_get(collection, href)) return result - @api.multi def dav_delete(self, collection, components): self.ensure_one() @@ -228,13 +227,12 @@ class DavCollection(models.Model): else: self.get_record(components).unlink() - @api.multi def dav_upload(self, collection, href, item): self.ensure_one() components = self._split_path(href) collection_model = self.env[self.model_id.model] - if self.dav_type == 'files': + if self.dav_type == "files": # TODO: Handle upload of attachments return None data = self.from_vobject(item) @@ -257,37 +255,41 @@ class DavCollection(models.Model): last_modified=self._odoo_to_http_datetime(record.write_date), ) - @api.multi def dav_get(self, collection, href): self.ensure_one() components = self._split_path(href) collection_model = self.env[self.model_id.model] - if self.dav_type == 'files': + if self.dav_type == "files": if len(components) == 3: result = Collection(href) result.logger = self.logger return result if len(components) == 4: - record = collection_model.browse(map( - itemgetter(0), - collection_model.name_search( - components[2], operator='=', limit=1, + record = collection_model.browse( + map( + itemgetter(0), + collection_model.name_search( + components[2], + operator="=", + limit=1, + ), ) - )) - attachment = self.env['ir.attachment'].search([ - ('type', '=', 'binary'), - ('res_model', '=', record._name), - ('res_id', '=', record.id), - ('name', '=', components[3]), - ], limit=1) + ) + attachment = self.env["ir.attachment"].search( + [ + ("type", "=", "binary"), + ("res_model", "=", record._name), + ("res_id", "=", record.id), + ("name", "=", components[3]), + ], + limit=1, + ) return FileItem( collection, item=attachment, href=href, - last_modified=self._odoo_to_http_datetime( - record.write_date - ), + last_modified=self._odoo_to_http_datetime(record.write_date), ) record = self.get_record(components) diff --git a/base_dav/models/dav_collection_field_mapping.py b/base_dav/models/dav_collection_field_mapping.py index f516f5ed0..fd92b5b87 100644 --- a/base_dav/models/dav_collection_field_mapping.py +++ b/base_dav/models/dav_collection_field_mapping.py @@ -13,11 +13,13 @@ from dateutil import tz class DavCollectionFieldMapping(models.Model): - _name = 'dav.collection.field_mapping' - _description = 'A field mapping for a WebDAV collection' + _name = "dav.collection.field_mapping" + _description = "A field mapping for a WebDAV collection" collection_id = fields.Many2one( - 'dav.collection', required=True, ondelete='cascade', + "dav.collection", + required=True, + ondelete="cascade", ) name = fields.Char( required=True, @@ -25,59 +27,57 @@ class DavCollectionFieldMapping(models.Model): ) mapping_type = fields.Selection( [ - ('simple', 'Simple'), - ('code', 'Code'), + ("simple", "Simple"), + ("code", "Code"), ], - default='simple', + default="simple", required=True, ) field_id = fields.Many2one( - 'ir.model.fields', + "ir.model.fields", required=True, + ondelete="cascade", help="Field of the model the values are mapped to", ) model_id = fields.Many2one( - 'ir.model', - related='collection_id.model_id', + "ir.model", + related="collection_id.model_id", ) import_code = fields.Text( help="Code to import the value from a vobject. Use the variable " - "result for the output of the value and item as input" + "result for the output of the value and item as input" ) export_code = fields.Text( help="Code to export the value to a vobject. Use the variable " - "result for the output of the value and record as input" + "result for the output of the value and record as input" ) - @api.multi def from_vobject(self, child): self.ensure_one() - if self.mapping_type == 'code': + if self.mapping_type == "code": return self._from_vobject_code(child) return self._from_vobject_simple(child) - @api.multi def _from_vobject_code(self, child): self.ensure_one() context = { - 'datetime': datetime, - 'dateutil': dateutil, - 'item': child, - 'result': None, - 'tools': tools, - 'tz': tz, - 'vobject': vobject, + "datetime": datetime, + "dateutil": dateutil, + "item": child, + "result": None, + "tools": tools, + "tz": tz, + "vobject": vobject, } safe_eval(self.import_code, context, mode="exec", nocopy=True) - return context.get('result', {}) + return context.get("result", {}) - @api.multi def _from_vobject_simple(self, child): self.ensure_one() name = self.name.lower() conversion_funcs = [ - '_from_vobject_%s_%s' % (self.field_id.ttype, name), - '_from_vobject_%s' % self.field_id.ttype, + "_from_vobject_%s_%s" % (self.field_id.ttype, name), + "_from_vobject_%s" % self.field_id.ttype, ] for conversion_func in conversion_funcs: @@ -108,16 +108,15 @@ class DavCollectionFieldMapping(models.Model): @api.model def _from_vobject_binary(self, item): - return item.value.encode('ascii') + return item.value.encode("ascii") @api.model def _from_vobject_char_n(self, item): return item.family - @api.multi def to_vobject(self, record): self.ensure_one() - if self.mapping_type == 'code': + if self.mapping_type == "code": result = self._to_vobject_code(record) else: result = self._to_vobject_simple(record) @@ -126,29 +125,25 @@ class DavCollectionFieldMapping(models.Model): return result.replace(tzinfo=tz.UTC) return result - @api.multi def _to_vobject_code(self, record): self.ensure_one() context = { - 'datetime': datetime, - 'dateutil': dateutil, - 'record': record, - 'result': None, - 'tools': tools, - 'tz': tz, - 'vobject': vobject, + "datetime": datetime, + "dateutil": dateutil, + "record": record, + "result": None, + "tools": tools, + "tz": tz, + "vobject": vobject, } - safe_eval(self.export_code, context, mode="exec", nocopy=True) - return context.get('result', None) + safe_eval(self.export_code, context, mode="exec", nocopy=True) + return context.get("result", None) - @api.multi def _to_vobject_simple(self, record): self.ensure_one() conversion_funcs = [ - '_to_vobject_%s_%s' % ( - self.field_id.ttype, self.name.lower() - ), - '_to_vobject_%s' % self.field_id.ttype, + "_to_vobject_%s_%s" % (self.field_id.ttype, self.name.lower()), + "_to_vobject_%s" % self.field_id.ttype, ] value = record[self.field_id.name] for conversion_func in conversion_funcs: @@ -163,8 +158,7 @@ class DavCollectionFieldMapping(models.Model): @api.model def _to_vobject_datetime_rev(self, value): - return value and value\ - .replace('-', '').replace(' ', 'T').replace(':', '') + 'Z' + return value and value.replace("-", "").replace(" ", "T").replace(":", "") + "Z" @api.model def _to_vobject_date(self, value): @@ -172,7 +166,7 @@ class DavCollectionFieldMapping(models.Model): @api.model def _to_vobject_binary(self, value): - return value and value.decode('ascii') + return value and value.decode("ascii") @api.model def _to_vobject_char_n(self, value): diff --git a/base_dav/radicale/auth.py b/base_dav/radicale/auth.py index ba6c3b407..707a7422b 100644 --- a/base_dav/radicale/auth.py +++ b/base_dav/radicale/auth.py @@ -12,7 +12,7 @@ except ImportError: class Auth(BaseAuth): def login(self, user, password): env = request.env - uid = env['res.users']._login(env.cr.dbname, user, password) + uid = env['res.users']._login(env.cr.dbname, user, password, {}) login = request.env['res.users'].browse(uid).login if uid: request._env = env(user=uid) diff --git a/requirements.txt b/requirements.txt index 66d6d61ae..f9ef27417 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1 @@ -sqlalchemy -mysqlclient==2.0.1 -pymssql radicale==3.1.1