Browse Source

WIP

Signed-off-by: Valentin Lab <valentin.lab@kalysto.org>
dav
Valentin Lab 2 years ago
parent
commit
fc1d007236
  1. 2
      base_dav/__manifest__.py
  2. 176
      base_dav/models/dav_collection.py
  3. 86
      base_dav/models/dav_collection_field_mapping.py
  4. 2
      base_dav/radicale/auth.py
  5. 3
      requirements.txt

2
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",

176
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)

86
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):

2
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)

3
requirements.txt

@ -1,4 +1 @@
sqlalchemy
mysqlclient==2.0.1
pymssql
radicale==3.1.1
Loading…
Cancel
Save