Просмотр исходного кода

Reorganize filters

Related to #33 and #372.
Guillaume Ayoub 9 лет назад
Родитель
Сommit
a28df4dd4b
2 измененных файлов с 235 добавлено и 13 удалено
  1. 112 13
      radicale/xmlutils.py
  2. 123 0
      tests/test_base.py

+ 112 - 13
radicale/xmlutils.py

@@ -117,6 +117,108 @@ def _href(collection, href):
         href.lstrip("/"))
 
 
+def _comp_match(item, filter_, scope="collection"):
+    """Check whether the ``item`` matches the comp ``filter_``.
+
+    If ``scope`` is ``"collection"``, the filter is applied on the
+    item's collection. Otherwise, it's applied on the item.
+
+    See rfc4791-9.7.1.
+
+    """
+    filter_length = len(filter_)
+    if scope == "collection":
+        tag = item.collection.get_meta("tag")
+    else:
+        for component in item.components():
+            if component.name in ("VTODO", "VEVENT", "VJOURNAL"):
+                tag = component.name
+    if filter_length == 0:
+        # Point #1 of rfc4791-9.7.1
+        return filter_.get("name") == tag
+    else:
+        if filter_length == 1:
+            if filter_[0].tag == _tag("C", "is-not-defined"):
+                # Point #2 of rfc4791-9.7.1
+                return filter_.get("name") != tag
+        if filter_[0].tag == _tag("C", "time-range"):
+            # Point #3 of rfc4791-9.7.1
+            if not _time_range_match(item, filter_[0]):
+                return False
+            filter_.remove(filter_[0])
+        # Point #4 of rfc4791-9.7.1
+        return all(
+            _prop_match(item, child) if child.tag == _tag("C", "prop-filter")
+            else _comp_match(item, child, scope="component")
+            for child in filter_)
+
+
+def _prop_match(item, filter_):
+    """Check whether the ``item`` matches the prop ``filter_``.
+
+    See rfc4791-9.7.2 and rfc6352-10.5.1.
+
+    """
+    filter_length = len(filter_)
+    if item.collection.get_meta("tag") == "VCALENDAR":
+        for component in item.components():
+            if component.name in ("VTODO", "VEVENT", "VJOURNAL"):
+                vobject_item = component
+    else:
+        vobject_item = item.item
+    if filter_length == 0:
+        # Point #1 of rfc4791-9.7.2
+        return filter_.get("name").lower() in vobject_item.contents
+    else:
+        if filter_length == 1:
+            if filter_[0].tag == _tag("C", "is-not-defined"):
+                # Point #2 of rfc4791-9.7.2
+                return filter_.get("name").lower() not in vobject_item.contents
+        if filter_[0].tag == _tag("C", "time-range"):
+            # Point #3 of rfc4791-9.7.2
+            if not _time_range_match(item, filter_[0]):
+                return False
+            filter_.remove(filter_[0])
+        elif filter_[0].tag == _tag("C", "text-match"):
+            # Point #4 of rfc4791-9.7.2
+            if not _text_match(item, filter_[0]):
+                return False
+            filter_.remove(filter_[0])
+        return all(
+            _param_filter_match(item, param_filter)
+            for param_filter in filter_)
+
+
+def _time_range_match(item, _filter):
+    """Check whether the ``item`` matches the time-range ``filter_``.
+
+    See rfc4791-9.9.
+
+    """
+    # TODO: implement this
+    return True
+
+
+def _text_match(item, _filter):
+    """Check whether the ``item`` matches the text-match ``filter_``.
+
+    See rfc4791-9.7.3.
+
+    """
+    # TODO: implement this
+    return True
+
+
+def _param_filter_match(item, _filter):
+    """Check whether the ``item`` matches the param-filter ``filter_``.
+
+    See rfc4791-9.7.3.
+
+    """
+    # TODO: implement this
+    return True
+
+
 def name_from_path(path, collection):
     """Return Radicale item name from ``path``."""
     collection_parts = collection.path.strip("/").split("/")
@@ -491,16 +593,11 @@ def report(path, xml_request, collection):
                     hreferences.add(href_path[len(base_prefix) - 1:])
         else:
             hreferences = (path,)
-        # TODO: handle other filters
-        # TODO: handle the nested comp-filters correctly
-        # Read rfc4791-9.7.1 for info
-        tag_filters = set(
-            element.get("name").upper() for element
-            in root.findall(".//%s" % _tag("C", "comp-filter")))
-        tag_filters.discard("VCALENDAR")
+        filters = (
+            root.findall(".//%s" % _tag("C", "filter")) +
+            root.findall(".//%s" % _tag("CR", "filter")))
     else:
-        hreferences = ()
-        tag_filters = set()
+        hreferences = filters = ()
 
     # Writing answer
     multistatus = ET.Element(_tag("D", "multistatus"))
@@ -526,10 +623,12 @@ def report(path, xml_request, collection):
             items = [collection.get(href) for href, etag in collection.list()]
 
         for item in items:
-            if (tag_filters and
-                    item.name not in tag_filters and
-                    not {tag.upper() for tag in item.contents} & tag_filters):
-                continue
+            if filters:
+                match = (
+                    _comp_match if collection.get_meta("tag") == "VCALENDAR"
+                    else _prop_match)
+                if not all(match(item, filter_[0]) for filter_ in filters):
+                    continue
 
             found_props = []
             not_found_props = []

+ 123 - 0
tests/test_base.py

@@ -93,6 +93,129 @@ class BaseRequests:
         status, headers, answer = self.request("GET", "/calendar.ics/")
         assert "VEVENT" not in answer
 
+    def _test_filter(self, filters):
+        filters_text = "".join(
+            "<C:filter>%s</C:filter>" % filter_ for filter_ in filters)
+        self.request(
+            "PUT", "/calendar.ics/", "BEGIN:VCALENDAR\r\nEND:VCALENDAR")
+        event = get_file_content("event.ics")
+        self.request("PUT", "/calendar.ics/event.ics", event)
+        status, headers, answer = self.request(
+            "REPORT", "/calendar.ics",
+            """<?xml version="1.0" encoding="utf-8" ?>
+               <C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
+                 <D:prop xmlns:D="DAV:">
+                   <D:getetag/>
+                 </D:prop>
+                 %s
+               </C:calendar-query>""" % filters_text)
+        return answer
+
+    def test_calendar_tag_filter(self):
+        """Report request with tag-based filter on calendar."""
+        assert "href>/calendar.ics/event.ics</" in self._test_filter(["""
+            <C:comp-filter name="VCALENDAR"></C:comp-filter>"""])
+
+    def test_item_tag_filter(self):
+        """Report request with tag-based filter on an item."""
+        assert "href>/calendar.ics/event.ics</" in self._test_filter(["""
+            <C:comp-filter name="VCALENDAR">
+              <C:comp-filter name="VEVENT"></C:comp-filter>
+            </C:comp-filter>"""])
+        assert "href>/calendar.ics/event.ics</" not in self._test_filter(["""
+            <C:comp-filter name="VCALENDAR">
+              <C:comp-filter name="VTODO"></C:comp-filter>
+            </C:comp-filter>"""])
+
+    def test_item_not_tag_filter(self):
+        """Report request with tag-based is-not filter on an item."""
+        assert "href>/calendar.ics/event.ics</" not in self._test_filter(["""
+            <C:comp-filter name="VCALENDAR">
+              <C:comp-filter name="VEVENT">
+                <C:is-not-defined />
+              </C:comp-filter>
+            </C:comp-filter>"""])
+        assert "href>/calendar.ics/event.ics</" in self._test_filter(["""
+            <C:comp-filter name="VCALENDAR">
+              <C:comp-filter name="VTODO">
+                <C:is-not-defined />
+              </C:comp-filter>
+            </C:comp-filter>"""])
+
+    def test_item_prop_filter(self):
+        """Report request with prop-based filter on an item."""
+        assert "href>/calendar.ics/event.ics</" in self._test_filter(["""
+            <C:comp-filter name="VCALENDAR">
+              <C:comp-filter name="VEVENT">
+                <C:prop-filter name="SUMMARY"></C:prop-filter>
+              </C:comp-filter>
+            </C:comp-filter>"""])
+        assert "href>/calendar.ics/event.ics</" not in self._test_filter(["""
+            <C:comp-filter name="VCALENDAR">
+              <C:comp-filter name="VEVENT">
+                <C:prop-filter name="UNKNOWN"></C:prop-filter>
+              </C:comp-filter>
+            </C:comp-filter>"""])
+
+    def test_item_not_prop_filter(self):
+        """Report request with prop-based is-not filter on an item."""
+        assert "href>/calendar.ics/event.ics</" not in self._test_filter(["""
+            <C:comp-filter name="VCALENDAR">
+              <C:comp-filter name="VEVENT">
+                <C:prop-filter name="SUMMARY">
+                  <C:is-not-defined />
+                </C:prop-filter>
+              </C:comp-filter>
+            </C:comp-filter>"""])
+        assert "href>/calendar.ics/event.ics</" in self._test_filter(["""
+            <C:comp-filter name="VCALENDAR">
+              <C:comp-filter name="VEVENT">
+                <C:prop-filter name="UNKNOWN">
+                  <C:is-not-defined />
+                </C:prop-filter>
+              </C:comp-filter>
+            </C:comp-filter>"""])
+
+    def test_mutiple_filters(self):
+        """Report request with multiple filters on an item."""
+        assert "href>/calendar.ics/event.ics</" not in self._test_filter(["""
+            <C:comp-filter name="VCALENDAR">
+              <C:comp-filter name="VEVENT">
+                <C:prop-filter name="SUMMARY">
+                  <C:is-not-defined />
+                </C:prop-filter>
+              </C:comp-filter>
+            </C:comp-filter>""", """
+            <C:comp-filter name="VCALENDAR">
+              <C:comp-filter name="VEVENT">
+                <C:prop-filter name="UNKNOWN">
+                  <C:is-not-defined />
+                </C:prop-filter>
+              </C:comp-filter>
+            </C:comp-filter>"""])
+        assert "href>/calendar.ics/event.ics</" in self._test_filter(["""
+            <C:comp-filter name="VCALENDAR">
+              <C:comp-filter name="VEVENT">
+                <C:prop-filter name="SUMMARY"></C:prop-filter>
+              </C:comp-filter>
+            </C:comp-filter>""", """
+            <C:comp-filter name="VCALENDAR">
+              <C:comp-filter name="VEVENT">
+                <C:prop-filter name="UNKNOWN">
+                  <C:is-not-defined />
+                </C:prop-filter>
+              </C:comp-filter>
+            </C:comp-filter>"""])
+        assert "href>/calendar.ics/event.ics</" in self._test_filter(["""
+            <C:comp-filter name="VCALENDAR">
+              <C:comp-filter name="VEVENT">
+                <C:prop-filter name="SUMMARY"></C:prop-filter>
+                <C:prop-filter name="UNKNOWN">
+                  <C:is-not-defined />
+                </C:prop-filter>
+              </C:comp-filter>
+            </C:comp-filter>"""])
+
 
 class TestMultiFileSystem(BaseRequests, BaseTest):
     """Base class for filesystem tests."""