Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion caldav/davclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,12 @@ def _expand_simple_prop(
values = []
if proptag in props_found:
prop_xml = props_found[proptag]
error.assert_(not prop_xml.items())
if prop_xml.items():
from caldav.lib.debug import xmlstring

log.error(
f"If you see this, please add a report at https://github.com/python-caldav/caldav/issues/209 - in _expand_simple_prop, dealing with {proptag}, extra items found: {xmlstring(prop_xml)}."
)
if not xpath and len(prop_xml) == 0:
if prop_xml.text:
values.append(prop_xml.text)
Expand Down
21 changes: 19 additions & 2 deletions caldav/lib/vcal.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,12 @@ def __call__(self, line):

## sorry for being english-language-euro-centric ... fits rather perfectly as default language for me :-)
def create_ical(ical_fragment=None, objtype=None, language="en_DK", **props):
"""
I somehow feel this fits more into the icalendar library than here
"""Creates some icalendar based on properties given as parameters.
It basically creates an icalendar object with all the boilerplate,
some sensible defaults, the properties given and returns it as a
string.

TODO: timezones not supported so far
"""
ical_fragment = to_normal_str(ical_fragment)
if "class_" in props:
Expand Down Expand Up @@ -193,6 +197,7 @@ def create_ical(ical_fragment=None, objtype=None, language="en_DK", **props):
if not component.get("uid") and not props.get("uid"):
component.add("uid", uuid.uuid1())

alarm = {}
for prop in props:
if props[prop] is not None:
if isinstance(props[prop], datetime.datetime) and not props[prop].tzinfo:
Expand All @@ -206,8 +211,12 @@ def create_ical(ical_fragment=None, objtype=None, language="en_DK", **props):
parameters={"reltype": prop.upper()},
encode=True,
)
elif prop.startswith("alarm_"):
alarm[prop[6:]] = props[prop]
else:
component.add(prop, props[prop])
if alarm:
add_alarm(my_instance, alarm)
ret = to_normal_str(my_instance.to_ical())
if ical_fragment and ical_fragment.strip():
ret = re.sub(
Expand All @@ -218,3 +227,11 @@ def create_ical(ical_fragment=None, objtype=None, language="en_DK", **props):
count=1,
)
return ret


def add_alarm(ical, alarm):
ia = icalendar.Alarm()
for prop in alarm:
ia.add(prop, alarm[prop])
ical.subcomponents[0].add_component(ia)
return ical
16 changes: 12 additions & 4 deletions caldav/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -864,7 +864,8 @@ def save_object(
* ical - ical object (text)
* no_overwrite - existing calendar objects should not be overwritten
* no_create - don't create a new object, existing calendar objects should be updated
* ical_data - passed to lib.vcal.create_ical
* dt_start, dt_end, summary, etc - properties to be inserted into the icalendar object
* alarm_trigger, alarm_action, alarm_attach, etc - when given, one alarm will be added
"""
o = objclass(
self.client,
Expand Down Expand Up @@ -1345,6 +1346,8 @@ def build_search_xml_query(
start=None,
end=None,
props=None,
alarm_start=None,
alarm_end=None,
**kwargs,
):
"""This method will produce a caldav search query as an etree object.
Expand Down Expand Up @@ -1405,6 +1408,11 @@ def build_search_xml_query(
if start or end:
filters.append(cdav.TimeRange(start, end))

if alarm_start or alarm_end:
filters.append(
cdav.CompFilter("VALARM") + cdav.TimeRange(alarm_start, alarm_end)
)

if todo is not None:
if not todo:
raise NotImplementedError()
Expand Down Expand Up @@ -2426,7 +2434,7 @@ def copy(self, keep_uid: bool = False, new_parent: Optional[Any] = None) -> Self
id=self.id if keep_uid else str(uuid.uuid1()),
)
if new_parent or not keep_uid:
obj.url = obj.generate_url()
obj.url = obj._generate_url()
else:
obj.url = self.url
return obj
Expand Down Expand Up @@ -2516,7 +2524,7 @@ def _find_id_path(self, id=None, path=None) -> None:
error.assert_(x.get("UID", None) == self.id)

if path is None:
path = self.generate_url()
path = self._generate_url()
else:
path = self.parent.url.join(path)

Expand Down Expand Up @@ -2545,7 +2553,7 @@ def _create(self, id=None, path=None, retry_on_failure=True) -> None:
self._find_id_path(id=id, path=path)
self._put()

def generate_url(self):
def _generate_url(self):
## See https://github.com/python-caldav/caldav/issues/143 for the rationale behind double-quoting slashes
## TODO: should try to wrap my head around issues that arises when id contains weird characters. maybe it's
## better to generate a new uuid here, particularly if id is in some unexpected format.
Expand Down
9 changes: 9 additions & 0 deletions tests/compatibility_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
"""date searches covering recurrances may yield no results, """
"""and events/todos may not be expanded with recurrances""",

'no_alarmsearch':
"""Searching for alarms may yield too few or too many or even a 500 internal server error""",

'no_recurring_todo':
"""Recurring events are supported, but not recurring todos""",

Expand Down Expand Up @@ -271,6 +274,9 @@
## rather than date, just to have the test exercised ... but we
## should report this upstream
#'broken_expand_on_exceptions',

## No alarm search (500 internal server error)
"no_alarmsearch",
]

## This can soon be removed (relevant for running tests under python 3.7 and python 3.8)
Expand All @@ -286,11 +292,14 @@
except Exception:
pass

## TODO - there has been quite some development in radicale recently, so this list
## should probably be gone through
radicale = [
## calendar listings and calendar creation works a bit
## "weird" on radicale
"broken_expand",
"no_default_calendar",
"no_alarmsearch", ## This is fixed and will be released soon

## freebusy is not supported yet, but on the long-term road map
#"no_freebusy_rfc4791",
Expand Down
40 changes: 40 additions & 0 deletions tests/test_caldav.py
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,46 @@ def testCreateEvent(self):
assert len(events) == len(existing_events) + 2
ev2.delete()

def testAlarm(self):
## Ref https://github.com/python-caldav/caldav/issues/132
c = self._fixCalendar()
ev = c.save_event(
dtstart=datetime(2015, 10, 10, 8, 0, 0),
summary="This is a test event",
dtend=datetime(2016, 10, 10, 9, 0, 0),
alarm_trigger=timedelta(minutes=-15),
alarm_action="AUDIO",
)

self.skip_on_compatibility_flag("no_alarmsearch")

## So we have an alarm that goes off 07:45 for an event starting 08:00

## Search for alarms after 8 should find nothing
## (search for an alarm 07:55 - 08:05 should most likely find nothing).
assert (
len(
c.search(
event=True,
alarm_start=datetime(2015, 10, 10, 8, 1),
alarm_end=datetime(2015, 10, 10, 8, 7),
)
)
== 0
)

## Search for alarms from 07:40 to 07:55 should definitively find the alarm.
assert (
len(
c.search(
event=True,
alarm_start=datetime(2015, 10, 10, 7, 40),
alarm_end=datetime(2015, 10, 10, 7, 55),
)
)
== 1
)

def testCalendarByFullURL(self):
"""
ref private email, passing a full URL as cal_id works in 0.5.0 but
Expand Down