[CMF-checkins] SVN: CMF/trunk/CMFTopic/ - CMFTopic.DateCriteria:
The behavior and naming of date criteria
Jens Vagelpohl
jens at dataflake.org
Thu Aug 18 18:15:43 EDT 2005
Log message for revision 37998:
- CMFTopic.DateCriteria: The behavior and naming of date criteria
operators was very confusing and in many cases unexpected and
wrong. For that reason e.g. Plone replaced the implementation
with a more user-friendly handling which is hereby introduced
to CMF proper. (http://www.zope.org/Collectors/CMF/339)
Changed:
U CMF/trunk/CMFTopic/DateCriteria.py
U CMF/trunk/CMFTopic/skins/zpt_topic/friendlydatec_editform.pt
U CMF/trunk/CMFTopic/tests/test_DateC.py
-=-
Modified: CMF/trunk/CMFTopic/DateCriteria.py
===================================================================
--- CMF/trunk/CMFTopic/DateCriteria.py 2005-08-18 22:09:10 UTC (rev 37997)
+++ CMF/trunk/CMFTopic/DateCriteria.py 2005-08-18 22:15:42 UTC (rev 37998)
@@ -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/trunk/CMFTopic/skins/zpt_topic/friendlydatec_editform.pt
===================================================================
--- CMF/trunk/CMFTopic/skins/zpt_topic/friendlydatec_editform.pt 2005-08-18 22:09:10 UTC (rev 37997)
+++ CMF/trunk/CMFTopic/skins/zpt_topic/friendlydatec_editform.pt 2005-08-18 22:15:42 UTC (rev 37998)
@@ -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/trunk/CMFTopic/tests/test_DateC.py
===================================================================
--- CMF/trunk/CMFTopic/tests/test_DateC.py 2005-08-18 22:09:10 UTC (rev 37997)
+++ CMF/trunk/CMFTopic/tests/test_DateC.py 2005-08-18 22:15:42 UTC (rev 37998)
@@ -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,236 @@
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):
+ RequestTest.setUp(self)
+ factory = self.root.manage_addProduct['CMFDefault'].addConfiguredSite
+ factory('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.topic.addCriterion('portal_type', 'String Criterion')
+ type_crit = self.topic.getCriterion('portal_type')
+ type_crit.edit(value='Dummy Content')
+ 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