[CMF-checkins] SVN: CMF/branches/jens-collector-339-branch/C - CMFTopic Friendly Date Criteria: Fixed the strange and broken search

Jens Vagelpohl jens at dataflake.org
Thu Aug 11 12:02:38 EDT 2005


Log message for revision 37868:
  - CMFTopic Friendly Date Criteria: Fixed the strange and broken search
    filter interpretation which ran counter to what people might expect
    from a search (http://www.zope.org/Collectors/CMF/339)
  

Changed:
  U   CMF/branches/jens-collector-339-branch/CHANGES.txt
  U   CMF/branches/jens-collector-339-branch/CMFTopic/DateCriteria.py
  U   CMF/branches/jens-collector-339-branch/CMFTopic/skins/zpt_topic/friendlydatec_editform.pt
  U   CMF/branches/jens-collector-339-branch/CMFTopic/tests/test_DateC.py

-=-
Modified: CMF/branches/jens-collector-339-branch/CHANGES.txt
===================================================================
--- CMF/branches/jens-collector-339-branch/CHANGES.txt	2005-08-11 15:50:16 UTC (rev 37867)
+++ CMF/branches/jens-collector-339-branch/CHANGES.txt	2005-08-11 16:02:38 UTC (rev 37868)
@@ -32,6 +32,10 @@
 
   Bug Fixes
 
+    - CMFTopic Friendly Date Criteria: Fixed the strange and broken search
+      filter interpretation which ran counter to what people might expect
+      from a search (http://www.zope.org/Collectors/CMF/339)
+
     - Got rid of the "CMF Site" and "Configured CMF Site" duality in the ZMI
       add list by removing the "CMF Site" class registration in CMFDefault
       and moving the "Configured CMF Site" registration from CMFSetup into

Modified: CMF/branches/jens-collector-339-branch/CMFTopic/DateCriteria.py
===================================================================
--- CMF/branches/jens-collector-339-branch/CMFTopic/DateCriteria.py	2005-08-11 15:50:16 UTC (rev 37867)
+++ CMF/branches/jens-collector-339-branch/CMFTopic/DateCriteria.py	2005-08-11 16:02:38 UTC (rev 37868)
@@ -110,19 +110,57 @@
         if self.value is not None:
             field = self.Field()
             value = self.value
+            operation = self.operation
 
             # Negate the value for 'old' days
-            if self.daterange == 'old':
+            if self.daterange == 'old' and value != 0:
                 value = -value
 
+                # Also reverse the operator to match what a user would expect.
+                # Queries such as "More than 2 days ago" should match dates
+                # *earlier* than "today minus 2", and "Less than 2 days ago"
+                # would be expected to return dates *later* then "today minus
+                # two".
+                if operation == 'max':
+                    operation = 'min'
+                elif operation == 'min':
+                    operation = 'max'
+
             date = DateTime() + value
 
-            operation = self.operation
             if operation == 'within_day':
+                # When items within a day are requested, the range is between
+                # the earliest and latest time of that particular day
                 range = ( date.earliestTime(), date.latestTime() )
                 return ( ( field, {'query': range, 'range': 'min:max'} ), )
-            else:
-                return ( ( field, {'query': date, 'range': operation} ), )
+
+            elif operation == 'min':
+                if value != 0:
+                    if self.daterange == 'old':
+                        date_range = (date, DateTime())
+                        return ( ( field, { 'query': date_range
+                                          , 'range': 'min:max'
+                                          } ), )
+                    else:
+                        return ( ( field, { 'query': date.earliestTime()
+                                          , 'range': operation 
+                                          } ), )
+                else:
+                    # Value 0 means "Now", so get everything from now on
+                    return ( ( field, {'query': date,'range': operation } ), )
+
+            elif operation == 'max':
+                if value != 0:
+                    if self.daterange == 'old':
+                        return ((field, {'query': date, 'range': operation}),)
+                    else:
+                        date_range = (DateTime(), date.latestTime())
+                        return ( ( field, { 'query': date_range
+                                          , 'range': 'min:max'
+                                          } ), )
+                else:
+                    # Value is 0, meaning "Now", get everything before "Now"
+                    return ( ( field, {'query': date, 'range': operation} ), )
         else:
             return ()
 

Modified: CMF/branches/jens-collector-339-branch/CMFTopic/skins/zpt_topic/friendlydatec_editform.pt
===================================================================
--- CMF/branches/jens-collector-339-branch/CMFTopic/skins/zpt_topic/friendlydatec_editform.pt	2005-08-11 15:50:16 UTC (rev 37867)
+++ CMF/branches/jens-collector-339-branch/CMFTopic/skins/zpt_topic/friendlydatec_editform.pt	2005-08-11 16:02:38 UTC (rev 37868)
@@ -20,13 +20,13 @@
     <select name="criteria.operation:records">
      <option value="min"
              tal:attributes="selected python:here.operation=='min'"
-     >At the least:</option>
+     >More than</option>
      <option value="max"
              tal:attributes="selected python:here.operation=='max'"
-     >At the most:</option>
+     >Less than</option>
      <option value="within_day"
              tal:attributes="selected python:here.operation=='within_day'"
-     >Within the day:</option>
+     >On the day</option>
     </select>
 
    <select name="criteria.value:records"
@@ -43,7 +43,7 @@
     <select name="criteria.daterange:records">
      <option value="old"
              tal:attributes="selected python:here.daterange == 'old'"
-     >old</option>
+     >ago</option>
      <option value="ahead"
              tal:attributes="selected python:here.daterange == 'ahead'"
      >ahead</option>

Modified: CMF/branches/jens-collector-339-branch/CMFTopic/tests/test_DateC.py
===================================================================
--- CMF/branches/jens-collector-339-branch/CMFTopic/tests/test_DateC.py	2005-08-11 15:50:16 UTC (rev 37867)
+++ CMF/branches/jens-collector-339-branch/CMFTopic/tests/test_DateC.py	2005-08-11 16:02:38 UTC (rev 37868)
@@ -22,17 +22,20 @@
 
 from DateTime.DateTime import DateTime
 
+from Products.CMFCore.tests.base.testcase import RequestTest
+from Products.CMFCore.tests.base.dummy import DummyContent
+from Products.CMFTopic.Topic import Topic
 from common import CriterionTestCase
 
 
 class FriendlyDateCriterionTests(CriterionTestCase):
 
-    lessThanFiveDaysOld = { 'value': 4
-                          , 'operation': 'min'
+    lessThanFiveDaysOld = { 'value': 5
+                          , 'operation': 'max'
                           , 'daterange': 'old'
                           }
 
-    lessThanOneMonthAhead = { 'value': 30
+    lessThanOneMonthAhead = { 'value': 31
                             , 'operation': 'max'
                             , 'daterange': 'ahead'
                             }
@@ -76,8 +79,8 @@
         friendly = self._makeOne('foo', 'foofield')
 
         friendly.apply( self.lessThanFiveDaysOld )
-        self.assertEqual( friendly.value, 4 )
-        self.assertEqual( friendly.operation, 'min' )
+        self.assertEqual( friendly.value, 5 )
+        self.assertEqual( friendly.operation, 'max' )
         self.assertEqual( friendly.daterange, 'old' )
 
     def test_BadInput( self ):
@@ -120,6 +123,7 @@
         self.assertEqual( result[0][1]['range'], 'min:max' )
 
     def test_FiveDaysOld( self ):
+        # This should create a query 
         friendly = self._makeOne('foo', 'foofield')
 
         friendly.apply( self.lessThanFiveDaysOld )
@@ -128,9 +132,10 @@
         result = friendly.getCriteriaItems()
         self.assertEqual( len(result), 1 )
         self.assertEqual( result[0][0], 'foofield' )
-        self.assertEqual( result[0][1]['query'].Date(),
-                          ( DateTime() - 4 ).Date() )
-        self.assertEqual( result[0][1]['range'], 'min' )
+        expect_earliest, expect_now = result[0][1]['query']
+        self.assertEqual( expect_earliest.Date(),
+                          ( DateTime() - 5 ).Date() )
+        self.assertEqual( result[0][1]['range'], 'min:max' )
 
     def test_OneMonthAhead( self ):
         friendly = self._makeOne('foo', 'foofield')
@@ -139,14 +144,233 @@
         self.assertEqual( friendly.daterange, 'ahead' )
 
         result = friendly.getCriteriaItems()
-        self.assertEqual( result[0][1]['query'].Date(),
-                          ( DateTime() + 30 ).Date() )
-        self.assertEqual( result[0][1]['range'], 'max' )
+        expect_now, expect_latest = result[0][1]['query']
+        self.assertEqual( expect_latest.Date(), ( DateTime() + 31 ).Date() )
+        self.assertEqual( expect_now.Date(), DateTime().Date() )
+        self.assertEqual( result[0][1]['range'], 'min:max' )
 
+class FriendlyDateCriterionFunctionalTests(RequestTest):
+    # Test the date criterion using a "real CMF" with catalog etc.
+    selectable_diffs = [0, 1, 2, 5, 7, 14, 31, 93, 186, 365, 730]
+    nonzero_diffs = [1, 2, 5, 7, 14, 31, 93, 186, 365, 730]
+    day_diffs = [-730, -365, -186, -93, -31, -14, -7, -5, -2, -1]
+    day_diffs.extend(selectable_diffs)
 
+    def setUp(self):
+        from Products.CMFDefault.factory import addConfiguredSite
+        RequestTest.setUp(self)
+        addConfiguredSite(self.root,'site','CMFDefault:default',snapshot=False)
+        self.site = self.root.site
+        self.site._setObject( 'topic', Topic('topic') )
+        self.topic = self.site.topic
+        self.topic.addCriterion('modified', 'Friendly Date Criterion')
+        self.criterion = self.topic.getCriterion('modified')
+        self.now = DateTime()
+
+        for i in self.day_diffs:
+            dummy_id = 'dummy%i' % i
+            self.site._setObject( dummy_id, DummyContent( id=dummy_id
+                                                        , catalog=1
+                                                        ) )
+            dummy_ob = getattr(self.site, dummy_id)
+            dummy_ob.modified_date = self.now + i
+            dummy_ob.reindexObject()
+
+
+    def test_Harness(self):
+        # Make sure the test harness is set up OK
+        ob_values = self.site.objectValues(['Dummy'])
+        self.assertEqual(len(ob_values), len(self.day_diffs))
+
+        catalog_results = self.site.portal_catalog(portal_type='Dummy Content')
+        self.assertEqual(len(catalog_results), len(self.day_diffs))
+
+    def test_WithinDayAgo(self):
+        # What items were modified "On the day X days ago"
+        for diff in self.selectable_diffs:
+            self.criterion.edit( value=abs(diff)
+                               , operation='within_day'
+                               , daterange='old'
+                               )
+            results = self.topic.queryCatalog()
+
+            # There is only one item with an modified date for this day
+            self.assertEquals(len(results), 1)
+            self.assertEquals( results[0].modified.Date()
+                             , (self.now-diff).Date()
+                             )
+
+    def test_WithinDayAhead(self):
+        # What items were modified "On the day X days ahead"
+        for diff in self.selectable_diffs:
+            self.criterion.edit( value=abs(diff)
+                               , operation='within_day'
+                               , daterange='ahead'
+                               )
+            results = self.topic.queryCatalog()
+
+            # There is only one item with an modified date for this day
+            self.assertEquals(len(results), 1)
+            self.assertEquals( results[0].modified.Date()
+                             , (self.now+diff).Date()
+                             )
+
+    def test_MoreThanDaysAgo(self):
+        # What items are modified "More than X days ago"
+        resultset_size = len(self.nonzero_diffs)
+
+        for diff in self.nonzero_diffs:
+            self.criterion.edit( value=diff
+                               , operation='min'
+                               , daterange='old'
+                               )
+            results = self.topic.queryCatalog()
+            
+            # As we move up in our date difference range, we must find as 
+            # many items as we have "modified" values <= the current value 
+            # in our sequence of user-selectable time differences. As we 
+            # increase the "value", we actually move backwards in time, so 
+            # the expected count of results *decreases*
+            self.assertEquals(len(results), resultset_size)
+            for brain in results:
+                self.failUnless(brain.modified <= self.now-diff)
+
+            resultset_size -= 1
+
+    def test_MoreThanZeroDaysAgo(self):
+        # What items are modified "More than 0 days ago"?
+        # This represents a special case. The "special munging"
+        # that corrects the query terms to what a human would expect
+        # are not applied and the search is a simple 
+        # "everything in the future" search.
+        resultset_size = len(self.selectable_diffs)
+        self.criterion.edit( value=0
+                           , operation='min'
+                           , daterange='old'
+                           )
+        results = self.topic.queryCatalog()
+        self.assertEquals(len(results), resultset_size)
+        for brain in results:
+            self.failUnless(brain.modified >= self.now)
+ 
+
+    def test_MoreThanDaysAhead(self):
+        # What items are modified "More than X days ahead"
+        resultset_size = len(self.nonzero_diffs)
+
+        for diff in self.nonzero_diffs:
+            self.criterion.edit( value=diff
+                               , operation='min'
+                               , daterange='ahead'
+                               )
+            results = self.topic.queryCatalog()
+            
+            # As we move up in our date difference range, we must find as 
+            # many items as we have "modified" values >= the current value 
+            # in our sequence of user-selectable time differences. As we 
+            # increase the "value", we actually move formward in time, so 
+            # the expected count of results *decreases*
+            self.assertEquals(len(results), resultset_size)
+            for brain in results:
+                self.failUnless(brain.modified >= self.now+diff)
+
+            resultset_size -= 1
+
+    def test_MoreThanZeroDaysAhead(self):
+        # What items are modified "More than 0 days ahead"?
+        # This represents a special case. The "special munging"
+        # that corrects the query terms to what a human would expect
+        # are not applied and the search is a simple 
+        # "everything in the future" search.
+        resultset_size = len(self.selectable_diffs)
+        self.criterion.edit( value=0
+                           , operation='min'
+                           , daterange='ahead'
+                           )
+        results = self.topic.queryCatalog()
+        self.assertEquals(len(results), resultset_size)
+        for brain in results:
+            self.failUnless(brain.modified >= self.now)
+
+    def test_LessThanDaysAgo(self):
+        # What items are modified "Less than X days ago"
+        resultset_size = 2
+
+        for diff in self.nonzero_diffs:
+            self.criterion.edit( value=diff
+                               , operation='max'
+                               , daterange='old'
+                               )
+            results = self.topic.queryCatalog()
+            
+            # With this query we are looking for items modified "less than
+            # X days ago", meaning between the given time and now. As we move
+            # through the selectable day values we increase the range to
+            # search through and thus increase the resultset size.
+            self.assertEquals(len(results), resultset_size)
+            for brain in results:
+                self.failUnless(self.now-diff <= brain.modified <= self.now)
+
+            resultset_size += 1
+
+    def test_LessThanZeroDaysAgo(self):
+        # What items are modified "Less than 0 days ago"?
+        # This represents a special case. The "special munging"
+        # that corrects the query terms to what a human would expect
+        # are not applied and the search is a simple 
+        # "everything in the past" search.
+        resultset_size = len(self.selectable_diffs)
+        self.criterion.edit( value=0
+                           , operation='max'
+                           , daterange='old'
+                           )
+        results = self.topic.queryCatalog()
+        self.assertEquals(len(results), resultset_size)
+        for brain in results:
+            self.failUnless(brain.modified <= self.now)
+            
+    def test_LessThanDaysAhead(self):
+        # What items are modified "Less than X days ahead"
+        resultset_size = 2
+
+        for diff in self.nonzero_diffs:
+            self.criterion.edit( value=diff
+                               , operation='max'
+                               , daterange='ahead'
+                               )
+            results = self.topic.queryCatalog()
+            
+            # With this query we are looking for items modified "less than
+            # X days ahead", meaning between now and the given time. As we move
+            # through the selectable day values we increase the range to
+            # search through and thus increase the resultset size.
+            self.assertEquals(len(results), resultset_size)
+            for brain in results:
+                self.failUnless(self.now+diff >= brain.modified >= self.now)
+
+            resultset_size += 1
+
+    def test_LessThanZeroDaysAhead(self):
+        # What items are modified "Less than 0 days ahead"?
+        # This represents a special case. The "special munging"
+        # that corrects the query terms to what a human would expect
+        # are not applied and the search is a simple 
+        # "everything in the past" search.
+        resultset_size = len(self.selectable_diffs)
+        self.criterion.edit( value=0
+                           , operation='max'
+                           , daterange='ahead'
+                           )
+        results = self.topic.queryCatalog()
+        self.assertEquals(len(results), resultset_size)
+        for brain in results:
+            self.failUnless(brain.modified <= self.now)
+
+
 def test_suite():
     return TestSuite((
         makeSuite(FriendlyDateCriterionTests),
+        makeSuite(FriendlyDateCriterionFunctionalTests),
         ))
 
 if __name__ == '__main__':



More information about the CMF-checkins mailing list