Forráskód Böngészése

Merge pull request #1822 from metallerok/empty_calendars_after_filters

Fix: Exclude empty calendars from the result if no matching events were found after applying filters
Peter Bieringer 7 hónapja
szülő
commit
c208ad4374
2 módosított fájl, 142 hozzáadás és 6 törlés
  1. 22 6
      radicale/app/report.py
  2. 120 0
      radicale/tests/test_expand.py

+ 22 - 6
radicale/app/report.py

@@ -233,10 +233,17 @@ def xml_report(base_prefix: str, path: str, xml_request: Optional[ET.Element],
     for filter_ in filters:
         # extract time-range filter for processing after main filters
         # for expand request
-        time_range_element = filter_.find(".//" + xmlutils.make_clark("C:time-range"))
+        filter_copy = copy.deepcopy(filter_)
 
-        if expand is None or time_range_element is None:
-            main_filters.append(filter_)
+        if expand is not None:
+            for comp_filter in filter_copy.findall(".//" + xmlutils.make_clark("C:comp-filter")):
+                if comp_filter.get("name", "").upper() == "VCALENDAR":
+                    continue
+                time_range_element = comp_filter.find(xmlutils.make_clark("C:time-range"))
+                if time_range_element is not None:
+                    comp_filter.remove(time_range_element)
+
+        main_filters.append(filter_copy)
 
     # Retrieve everything required for finishing the request.
     retrieved_items = list(retrieve_items(
@@ -306,6 +313,11 @@ def xml_report(base_prefix: str, path: str, xml_request: Optional[ET.Element],
                         time_range_start=time_range_start, time_range_end=time_range_end,
                         max_occurrence=max_occurrence,
                     )
+
+                    if n_vev == 0:
+                        logger.debug("No VEVENTs found after expansion for %r, skipping", item.href)
+                        continue
+
                     n_vevents += n_vev
                     found_props.append(expanded_element)
                 else:
@@ -322,9 +334,11 @@ def xml_report(base_prefix: str, path: str, xml_request: Optional[ET.Element],
         assert item.href
         uri = pathutils.unstrip_path(
             posixpath.join(collection.path, item.href))
-        multistatus.append(xml_item_response(
-            base_prefix, uri, found_props=found_props,
-            not_found_props=not_found_props, found_item=True))
+
+        if found_props or not_found_props:
+            multistatus.append(xml_item_response(
+                base_prefix, uri, found_props=found_props,
+                not_found_props=not_found_props, found_item=True))
 
     return client.MULTI_STATUS, multistatus
 
@@ -340,6 +354,8 @@ def _expand(
 ) -> Tuple[ET.Element, int]:
     vevent_component: vobject.base.Component = copy.copy(item.vobject_item)
     logger.info("Expanding event %s", item.href)
+    logger.debug(f"Expand range: {start} to {end}")
+    logger.debug(f"Time range: {time_range_start} to {time_range_end}")
 
     # Split the vevents included in the component into one that contains the
     # recurrence information and others that contain a recurrence id to

+ 120 - 0
radicale/tests/test_expand.py

@@ -347,3 +347,123 @@ permissions: RrWw""")
                                         check=check)
         assert len(responses) == 0
         assert status == check
+
+    def test_report_vcalendar_all_components(self) -> None:
+        """Test calendar-query with comp-filter VCALENDAR, returns all components."""
+        self.mkcalendar("/test/")
+        self.put("/test/calendar.ics", get_file_content("event_daily_rrule.ics"))
+        self.put("/test/todo.ics", get_file_content("todo1.ics"))
+
+        request = """
+        <C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
+            <D:prop>
+                <C:calendar-data/>
+            </D:prop>
+            <C:filter>
+                <C:comp-filter name="VCALENDAR"/>
+            </C:filter>
+        </C:calendar-query>
+        """
+        status, responses = self.report("/test", request)
+        assert status == 207
+        assert len(responses) == 2
+        assert "/test/calendar.ics" in responses
+        assert "/test/todo.ics" in responses
+
+    def test_report_vevent_only(self) -> None:
+        """Test calendar-query with comp-filter VEVENT, returns only VEVENT."""
+        self.mkcalendar("/test/")
+        self.put("/test/calendar.ics", get_file_content("event_daily_rrule.ics"))
+        self.put("/test/todo.ics", get_file_content("todo1.ics"))
+
+        start = "20060101T000000Z"
+        end = "20060104T000000Z"
+
+        request = f"""
+        <C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
+            <D:prop>
+                <C:calendar-data>
+                    <C:expand start="{start}" end="{end}"/>
+                </C:calendar-data>
+            </D:prop>
+            <C:filter>
+                <C:comp-filter name="VCALENDAR">
+                    <C:comp-filter name="VEVENT">
+                        <C:time-range start="{start}" end="{end}"/>
+                    </C:comp-filter>
+                </C:comp-filter>
+            </C:filter>
+        </C:calendar-query>
+        """
+        status, responses = self.report("/test", request)
+        assert status == 207
+        assert len(responses) == 1
+        assert "/test/calendar.ics" in responses
+        vevent_response = responses["/test/calendar.ics"]
+        assert type(vevent_response) is dict
+        status, element = vevent_response["C:calendar-data"]
+        assert status == 200 and element.text
+        assert "BEGIN:VEVENT" in element.text
+        assert "UID:" in element.text
+        assert "BEGIN:VTODO" not in element.text
+
+    def test_report_time_range_no_vevent(self) -> None:
+        """Test calendar-query with time-range filter, no matching VEVENT."""
+        self.mkcalendar("/test/")
+        self.put("/test/calendar.ics/", get_file_content("event_daily_rrule.ics"))
+
+        request = """
+        <C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
+            <D:prop>
+                <C:calendar-data>
+                    <C:expand start="20000101T000000Z" end="20000105T000000Z"/>
+                </C:calendar-data>
+            </D:prop>
+            <C:filter>
+                <C:comp-filter name="VCALENDAR">
+                    <C:comp-filter name="VEVENT">
+                        <C:time-range start="20000101T000000Z" end="20000105T000000Z"/>
+                    </C:comp-filter>
+                </C:comp-filter>
+            </C:filter>
+        </C:calendar-query>
+        """
+        status, responses = self.report("/test", request)
+        assert status == 207
+        assert len(responses) == 0
+
+    def test_report_time_range_one_vevent(self) -> None:
+        """Test calendar-query with time-range filter, matches one VEVENT."""
+        self.mkcalendar("/test/")
+        self.put("/test/calendar1.ics/", get_file_content("event_daily_rrule.ics"))
+        self.put("/test/calendar2.ics/", get_file_content("event1.ics"))
+
+        start = "20060101T000000Z"
+        end = "20060104T000000Z"
+
+        request = f"""
+        <C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
+            <D:prop>
+                <C:calendar-data>
+                    <C:expand start="{start}" end="{end}"/>
+                </C:calendar-data>
+            </D:prop>
+            <C:filter>
+                <C:comp-filter name="VCALENDAR">
+                    <C:comp-filter name="VEVENT">
+                        <C:time-range start="{start}" end="{end}"/>
+                    </C:comp-filter>
+                </C:comp-filter>
+            </C:filter>
+        </C:calendar-query>
+        """
+        status, responses = self.report("/test", request)
+        assert status == 207
+        assert len(responses) == 1
+        response = responses["/test/calendar1.ics"]
+        assert type(response) is dict
+        status, element = response["C:calendar-data"]
+        assert status == 200 and element.text
+        assert "BEGIN:VEVENT" in element.text
+        assert "RECURRENCE-ID:20060103T170000Z" in element.text
+        assert "DTSTART:20060103T170000Z" in element.text