Code

added support for searching on ranges of dates
authorkedder <kedder@57a73879-2fb5-44c3-a270-3262357dd7e2>
Sat, 8 Mar 2003 20:41:45 +0000 (20:41 +0000)
committerkedder <kedder@57a73879-2fb5-44c3-a270-3262357dd7e2>
Sat, 8 Mar 2003 20:41:45 +0000 (20:41 +0000)
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1576 57a73879-2fb5-44c3-a270-3262357dd7e2

CHANGES.txt
doc/user_guide.txt
roundup/backends/back_anydbm.py
roundup/backends/rdbms_common.py
roundup/date.py

index 6dd8699c0fa68ea3774aa9cb0a86459e765b6ee7..a5d04c9f0af291d5dcada3dca09162559e6c29ab 100644 (file)
@@ -41,6 +41,8 @@ Feature:
 - added Node.get() method
 - nicer page titles (sf feature 65197)
 - relaxed CVS importing (sf feature 693277)
+- added support for searching on ranges of dates (see doc/user_guide.txt in
+  chapter "Searching Page" for details)
 
 
 Fixed:
index 567be4323943392fa00831f0433963df3cd42674..49ca3a3fa9d639575157a999c4bc17d4358edc6f 100644 (file)
@@ -2,7 +2,7 @@
 User Guide
 ==========
 
-:Version: $Revision: 1.13 $
+:Version: $Revision: 1.14 $
 
 .. contents::
 
@@ -105,6 +105,46 @@ Searching Page
 
 XXX: some information about how searching works
 
+Some fields in the search page (e.g. "Activity" or "Creation date") accept
+ranges of dates. You can specify range of dates in one of two formats:
+
+       1. Native english syntax: 
+               [[From] <value>][ To <value>]
+          Keywords "From" and "To" are case insensitive. Keyword "From" is optional.
+
+       2. "Geek" syntax:
+               [<value>][; <value>]
+
+Either first or second <value> can be omitted in both syntaxes.
+
+For example:
+
+if you enter string "from 9:00" to "Creation date" field, roundup
+will find  all issues, that were created today since 9 AM.
+
+Searching of "-2m; -1m" on activity field gives you issues, which were
+active between period of time since 2 months up-till month ago.
+
+Other possible examples (consider local time is Sat Mar  8 22:07:48 EET 2003):
+
+       >>> Range("from 2-12 to 4-2")
+       <Range from 2003-02-12.00:00:00 to 2003-04-02.00:00:00>
+       
+       >>> Range("18:00 TO +2m")
+       <Range from 2003-03-08.18:00:00 to 2003-05-08.20:07:48>
+       
+       >>> Range("12:00")
+       <Range from 2003-03-08.12:00:00 to None>
+       
+       >>> Range("tO +3d")
+       <Range from None to 2003-03-11.20:07:48>
+       
+       >>> Range("2002-11-10; 2002-12-12")
+       <Range from 2002-11-10.00:00:00 to 2002-12-12.00:00:00>
+
+       >>> Range("; 20:00 +1d")
+       <Range from None to 2003-03-09.20:00:00>
+
 
 Under the covers
 ----------------
index 3297e2f52f885187003b8b7463e5cc3ce16c7f90..417e3d25a3a8abda04a0e2924930793671a072bd 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: back_anydbm.py,v 1.109 2003-03-06 07:33:29 richard Exp $
+#$Id: back_anydbm.py,v 1.110 2003-03-08 20:41:45 kedder Exp $
 '''
 This module defines a backend that saves the hyperdatabase in a database
 chosen by anydbm. It is guaranteed to always be available in python
@@ -31,6 +31,7 @@ from roundup.indexer import Indexer
 from roundup.backends import locking
 from roundup.hyperdb import String, Password, Date, Interval, Link, \
     Multilink, DatabaseError, Boolean, Number, Node
+from roundup.date import Range
 
 #
 # Now the database
@@ -1582,7 +1583,10 @@ class Class(hyperdb.Class):
         LINK = 0
         MULTILINK = 1
         STRING = 2
+        DATE = 3
         OTHER = 6
+        
+        timezone = self.db.getUserTimezone()
         for k, v in filterspec.items():
             propclass = props[k]
             if isinstance(propclass, Link):
@@ -1623,14 +1627,22 @@ class Class(hyperdb.Class):
                 v = v.replace('?', '.')
                 v = v.replace('*', '.*?')
                 l.append((STRING, k, re.compile(v, re.I)))
+            elif isinstance(propclass, Date):
+                try:
+                    date_rng = Range(v, date.Date, offset=timezone)
+                    l.append((DATE, k, date_rng))
+                except ValueError:
+                    # If range creation fails - ignore that search parameter
+                    pass                            
             elif isinstance(propclass, Boolean):
                 if type(v) is type(''):
                     bv = v.lower() in ('yes', 'true', 'on', '1')
                 else:
                     bv = v
                 l.append((OTHER, k, bv))
-            elif isinstance(propclass, Date):
-                l.append((OTHER, k, date.Date(v)))
+            # kedder: dates are filtered by ranges
+            #elif isinstance(propclass, Date):
+            #    l.append((OTHER, k, date.Date(v)))
             elif isinstance(propclass, Interval):
                 l.append((OTHER, k, date.Interval(v)))
             elif isinstance(propclass, Number):
@@ -1680,6 +1692,14 @@ class Class(hyperdb.Class):
                         # RE search
                         if node[k] is None or not v.search(node[k]):
                             break
+                    elif t == DATE:
+                        if node[k] is None: break
+                        if v.to_value:
+                            if not (v.from_value < node[k] and v.to_value > node[k]):
+                                break
+                        else:
+                            if not (v.from_value < node[k]):
+                                break
                     elif t == OTHER:
                         # straight value comparison for the other types
                         if node[k] != v:
index 6d0fc1e7acb21090a0828ef9d4541cc4ad958b69..ce6455c02838bed48e505207fcffa315fc49817c 100644 (file)
@@ -1,4 +1,4 @@
-# $Id: rdbms_common.py,v 1.40 2003-03-06 07:33:29 richard Exp $
+# $Id: rdbms_common.py,v 1.41 2003-03-08 20:41:45 kedder Exp $
 ''' Relational database (SQL) backend common code.
 
 Basics:
@@ -34,6 +34,7 @@ from roundup.backends import locking
 from blobfiles import FileStorage
 from roundup.indexer import Indexer
 from sessions import Sessions, OneTimeKeys
+from roundup.date import Range
 
 # number of rows to keep in memory
 ROW_CACHE_SIZE = 100
@@ -1731,6 +1732,8 @@ class Class(hyperdb.Class):
 
         cn = self.classname
 
+        timezone = self.db.getUserTimezone()
+        
         # figure the WHERE clause from the filterspec
         props = self.getprops()
         frum = ['_'+cn]
@@ -1796,8 +1799,18 @@ class Class(hyperdb.Class):
                     where.append('_%s in (%s)'%(k, s))
                     args = args + [date.Date(x).serialise() for x in v]
                 else:
-                    where.append('_%s=%s'%(k, a))
-                    args.append(date.Date(v).serialise())
+                    try:
+                        # Try to filter on range of dates
+                        date_rng = Range(v, date.Date, offset=timezone)
+                        if (date_rng.from_value):
+                            where.append('_%s > %s'%(k, a))                            
+                            args.append(date_rng.from_value.serialise())
+                        if (date_rng.to_value):
+                            where.append('_%s < %s'%(k, a))
+                            args.append(date_rng.to_value.serialise())
+                    except ValueError:
+                        # If range creation fails - ignore that search parameter
+                        pass                        
             elif isinstance(propclass, Interval):
                 if isinstance(v, type([])):
                     s = ','.join([a for x in v])
index 9c479175f7a3c74cdc89f42ac916a2a5dadebd95..3e6559ff0bad626efeee0264823ee649d33b00ef 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: date.py,v 1.45 2003-03-06 06:12:30 richard Exp $
+# $Id: date.py,v 1.46 2003-03-08 20:41:45 kedder Exp $
 
 __doc__ = """
 Date, time and time interval handling.
@@ -588,6 +588,79 @@ def fixTimeOverflow(time):
 
     return (sign, y, m, d, H, M, S)
 
+class Range:
+    """
+    Represents range between two values
+    Ranges can be created using one of theese two alternative syntaxes:
+        
+        1. Native english syntax: 
+            [[From] <value>][ To <value>]
+           Keywords "From" and "To" are case insensitive. Keyword "From" is optional.
+
+        2. "Geek" syntax:
+            [<value>][; <value>]
+
+    Either first or second <value> can be omitted in both syntaxes.
+
+    Examples (consider local time is Sat Mar  8 22:07:48 EET 2003):
+        >>> Range("from 2-12 to 4-2")
+        <Range from 2003-02-12.00:00:00 to 2003-04-02.00:00:00>
+        
+        >>> Range("18:00 TO +2m")
+        <Range from 2003-03-08.18:00:00 to 2003-05-08.20:07:48>
+        
+        >>> Range("12:00")
+        <Range from 2003-03-08.12:00:00 to None>
+        
+        >>> Range("tO +3d")
+        <Range from None to 2003-03-11.20:07:48>
+        
+        >>> Range("2002-11-10; 2002-12-12")
+        <Range from 2002-11-10.00:00:00 to 2002-12-12.00:00:00>
+        
+        >>> Range("; 20:00 +1d")
+        <Range from None to 2003-03-09.20:00:00>
+
+    """
+    def __init__(self, spec, type, **params):
+        """Initializes Range of type <type> from given <spec> string.
+        
+        Sets two properties - from_value and to_value. None assigned to any of
+        this properties means "infinitum" (-infinitum to from_value and
+        +infinitum to to_value)        
+        """
+        self.range_type = type
+        re_range = r'(?:^|(?:from)?(.+?))(?:to(.+?)$|$)'
+        re_geek_range = r'(?:^|(.+?))(?:;(.+?)$|$)'
+        # Check which syntax to use
+        if  spec.find(';') == -1:
+            # Native english
+            mch_range = re.search(re_range, spec.strip(), re.IGNORECASE)
+        else:
+            # Geek
+            mch_range = re.search(re_geek_range, spec.strip())
+        if mch_range:
+            self.from_value, self.to_value = mch_range.groups()
+            if self.from_value:
+                self.from_value = type(self.from_value.strip(), **params)
+            if self.to_value:
+                self.to_value = type(self.to_value.strip(), **params)
+        else:
+            raise ValueError, "Invalid range"
+
+    def __str__(self):
+        return "from %s to %s" % (self.from_value, self.to_value)
+
+    def __repr__(self):
+        return "<Range %s>" % self.__str__()
+def test_range():
+    rspecs = ("from 2-12 to 4-2", "18:00 TO +2m", "12:00", "tO +3d",
+        "2002-11-10; 2002-12-12", "; 20:00 +1d")
+    for rspec in rspecs:
+        print '>>> Range("%s")' % rspec
+        print `Range(rspec, Date)`
+        print
 
 def test():
     intervals = ("  3w  1  d  2:00", " + 2d", "3w")
@@ -607,6 +680,6 @@ def test():
         print `Date(date) + Interval(interval)`
 
 if __name__ == '__main__':
-    test()
+    test_range()
 
 # vim: set filetype=python ts=4 sw=4 et si