Commits

Guillaume Libersat committed d7c1fb6 Merge

merge with schinkel upstream

Comments (0)

Files changed (12)

+MANIFEST
+dist/
+ba620d1fdab61e60601aed515ee5dd09ea6d6143 0.3
+a17b2a73c5f4f1e7807fe1cf5ff9a3b7dc9ac611 0.4.2
+f06f26e2ffe15beaabc9f1c16d5f9261f820ae3b 0.4.3
+4408389242cec30950ed3e818d5cb7cb45f7b26f 0.4.4
+358e6483ae00dec19a286f55e8d675d687753811 0.4.5
+4279ab61489d30dc58a1dbaeb879739b1f179bbb 0.4.6
+3e5ca936944b2ec7890b13adb54be3b8d472a2db 0.4.7
+django-timedelta-field
+==========================
+
+PostgreSQL can store data as INTERVAL type, which is close to meaning the
+same as python's timedelta object (although better in a couple of ways).
+
+I have lots of use for timedelta objects, and having code that basically
+wrapped integer objects as a number of seconds was common. This module
+combines the two:
+
+    * a timedelta.TimedeltaField() object that transparently converts
+      to and from datetime.timedelta
+    
+    * storage of the data as an INTERVAL in PostgreSQL, or a string in
+      other databases. (Other databases will be considered if I ever
+      use them, or receive patches).
+
+The coolest part of this package is the way it manipulates strings entered
+by users, and presents them. Any string of the format:
+
+    [X weeks,] [Y days,] [Z hours,] [A minutes,] [B seconds]
+
+will be converted to a timedelta object. Even shortened versions can be used:
+hrs, hr or h will also suffice.  The parsing ignores trailing 's', but is
+smart about adding them in when presenting the data to the user.
+
+To use, install the package, and use the field::
+
+    from django.db import models
+    import timedelta
+    
+    class MyModel(models.Model):
+        the_timedelta = timedelta.TimedeltaField()
+
+There are also some useful methods in helpers.py to work with timedelta
+objects. (eg, multiply, divide, modulo and percentages).
+
+
+Todo
+-------------
+
+Handle strings with times in other languages. I'm not really sure about how
+to do this, but it may be useful.
+
+Changelog
+----------
+0.4.7: Bugfix from savemu: use unicode() instead of str()
+
+0.4.6: Add in support for PostGIS database.
+	Make it easier to add in other column types for other databases.
+
+0.4.5: Restore functionality for django <1.2 (thanks Yoav Aner).
+
+0.4.3: Added helpers.modulo, to allow remainder division of timedlelta objects.
+
+0.4.1: changed get_db_prep_value() code to be in get_prep_value(), since I
+    was calling it in get_default(), without a connection value.
+
+0.4: added the connection and prepared arguments to get_db_prep_value(),
+    so that django 1.3+ will not complain of DeprecationWarnings.

README.rst

-django-timedelta-field
-==========================
-
-PostgreSQL can store data as INTERVAL type, which is close to meaning the
-same as python's timedelta object (although better in a couple of ways).
-
-I have lots of use for timedelta objects, and having code that basically
-wrapped integer objects as a number of seconds was common. This module
-combines the two:
-
-    * a timedelta.TimedeltaField() object that transparently converts
-      to and from datetime.timedelta
-    
-    * storage of the data as an INTERVAL in PostgreSQL, or a string in
-      other databases. (Other databases will be considered if I ever
-      use them, or receive patches).
-
-The coolest part of this package is the way it manipulates strings entered
-by users, and presents them. Any string of the format:
-
-    [X weeks,] [Y days,] [Z hours,] [A minutes,] [B seconds]
-
-will be converted to a timedelta object. Even shortened versions can be used:
-hrs, hr or h will also suffice.  The parsing ignores trailing 's', but is
-smart about adding them in when presenting the data to the user.
-
-To use, install the package, and use the field::
-
-    from django.db import models
-    import timedelta
-    
-    class MyModel(models.Model):
-        the_timedelta = timedelta.TimedeltaField()
-
-There are also some useful methods in helpers.py to work with timedelta
-objects.
-
-Todo
--------------
-
-Handle strings with times in other languages. I'm not really sure about how
-to do this, but it may be useful.
 from distutils.core import setup
 
 setup(
-    name = "timedelta",
-    version = "0.2",
+    name = "django-timedeltafield",
+    version = "0.4.7",
     description = "TimedeltaField for django models",
-    url = "http://bitbucket.org/schinckel/django-timedelta-field/",
+    url = "http://hg.schinckel.net/django-timedelta-field/",
     author = "Matthew Schinckel",
     author_email = "matt@schinckel.net",
     packages = [

timedelta/__init__.py

 from fields import TimedeltaField
 
-from helpers import divide, multiply, parse, nice_repr, percentage
+from helpers import divide, multiply, modulo, parse, nice_repr, percentage

timedelta/fields.py

 from django.db import models
 
+from collections import defaultdict
 import datetime
-from collections import defaultdict
-import re
 
-from helpers import nice_repr, parse
+from helpers import parse
 from forms import TimedeltaFormField
 
 SECS_PER_DAY = 60*60*24
 
 # TODO: Figure out why django admin thinks fields of this type have changed every time an object is saved.
 
+# Define the different column types that different databases can use.
+COLUMN_TYPES = defaultdict(lambda:"char(20)")
+COLUMN_TYPES["django.db.backends.postgresql_psycopg2"] = "interval"
+COLUMN_TYPES["django.contrib.gis.db.backends.postgis"] = "interval"
+
 class TimedeltaField(models.Field):
     """
-    Store a datetime.timedelta as an integer.
-    
-    We don't subclass models.IntegerField, as that would then use the
-    AdminIntegerWidget or whatever in the admin, and we want to use
-    our custom widget.
+    Store a datetime.timedelta as an INTERVAL in postgres, or a 
+    CHAR(20) in other database backends.
     """
     __metaclass__ = models.SubfieldBase
     _south_introspects = True
             return datetime.timedelta(0)
         return parse(value)
     
-    def get_db_prep_value(self, value):
+    def get_prep_value(self, value):
         if (value is None) or isinstance(value, (str, unicode)):
             return value
         return str(value).replace(',', '')
-    
+        
+    def get_db_prep_value(self, value, connection=None, prepared=None):
+        return self.get_prep_value(value)
+        
     def formfield(self, *args, **kwargs):
         defaults = {'form_class':TimedeltaFormField}
         defaults.update(kwargs)
         if self.has_default():
             if callable(self.default):
                 return self.default()
-            return self.get_db_prep_value(self.default)
-        if not self.empty_strings_allowed or (
-            self.null #and not \
-            #connection.features.interprets_empty_strings_as_nulls
-        ):
+            return self.get_prep_value(self.default)
+        if not self.empty_strings_allowed or (self.null):
             return None
         return ""
         
     def db_type(self, connection):
-        """
-        Postgres allows us to store stuff as an INTERVAL type. This is 
-        useful, and we can then use database logic to do tests.
-        """
-        if connection.settings_dict['ENGINE'] == "django.db.backends.postgresql_psycopg2":
-            return 'interval'
-        else:
-            return 'char(20)'
+        return COLUMN_TYPES[connection.settings_dict['ENGINE']]
 
+

timedelta/forms.py

             raise forms.ValidationError(self.error_messages['invalid'])
             
         return datetime.timedelta(**data)
+
+class TimedeltaChoicesField(TimedeltaFormField):
+    def __init__(self, *args, **kwargs):
+        choices = kwargs.pop('choices')
+        defaults = {'widget':forms.Select(choices=choices)}
+        defaults.update(kwargs)
+        super(TimedeltaChoicesField, self).__init__(*args, **defaults)

timedelta/helpers.py

     # This is the format we sometimes get from Postgres.
     d = re.match(r'((?P<days>\d+) days )?(?P<hours>\d+):'
                  r'(?P<minutes>\d+)(:(?P<seconds>\d+))?',
-                 str(string))
+                 unicode(string))
     if d: 
         d = d.groupdict(0)
     else:
                      r'((?P<hours>((\d*\.\d+)|\d+))\W*h(ou)?r(s)?(,)?\W*)?'
                      r'((?P<minutes>((\d*\.\d+)|\d+))\W*m(in(ute)?)?(s)?(,)?\W*)?'
                      r'((?P<seconds>((\d*\.\d+)|\d+))\W*s(ec(ond)?(s)?)?)?\W*$',
-                     str(string))
+                     unicode(string))
         if not d:
             raise TypeError("'%s' is not a valid time interval" % string)
         d = d.groupdict()
         if v is not None )))
 
 
-def divide(obj1, obj2, float=False):
+def divide(obj1, obj2, as_float=False):
     """
     Allows for the division of timedeltas by other timedeltas, or by
     floats/Decimals
     """
     assert isinstance(obj1, datetime.timedelta), "First argument must be a timedelta."
-    #assert isinstance(obj2, (datetime.timedelta, int, float, Decimal)), "Second argument must be a timedelta or number"
+    assert isinstance(obj2, (datetime.timedelta, int, float, Decimal)), "Second argument must be a timedelta or number"
     
     sec1 = obj1.days * 86400 + obj1.seconds
     if isinstance(obj2, datetime.timedelta):
         sec2 = obj2.days * 86400 + obj2.seconds
-        if float:
+        if as_float:
             sec1 *= 1.0
         return sec1 / sec2
     else:
-        if float:
-            assert None, "float=True is inappropriate when dividing timedelta by a number."
+        if as_float:
+            assert None, "as_float=True is inappropriate when dividing timedelta by a number."
         secs = sec1 / obj2
         if isinstance(secs, Decimal):
             secs = float(secs)
         return datetime.timedelta(seconds=secs)
 
+def modulo(obj1, obj2):
+    """
+    Allows for remainder division of timedelta by timedelta or integer.
+    """
+    assert isinstance(obj1, datetime.timedelta), "First argument must be a timedelta."
+    assert isinstance(obj2, (datetime.timedelta, int)), "Second argument must be a timedelta or int."
+    
+    sec1 = obj1.days * 86400 + obj1.seconds
+    if isinstance(obj2, datetime.timedelta):
+        sec2 = obj2.days * 86400 + obj2.seconds
+        return datetime.timedelta(seconds=sec1 % sec2)
+    else:
+        return datetime.timedelta(seconds=(sec1 % obj2))
+    
 def percentage(obj1, obj2):
     """
     What percentage of obj2 is obj1? We want the answer as a float.
     assert isinstance(obj1, datetime.timedelta), "First argument must be a timedelta."
     assert isinstance(obj2, datetime.timedelta), "Second argument must be a timedelta."
     
-    return divide(obj1 * 100, obj2, float=True)
+    return divide(obj1 * 100, obj2, as_float=True)
 
 def multiply(obj, val):
     """
     else:
         return result
 
+def decimal_hours(timedelta, decimal_places=None):
+    """
+    Return a decimal value of the number of hours that this timedelta
+    object refers to.
+    """
+    hours = Decimal(timedelta.days*24) + Decimal(timedelta.seconds) / 3600
+    if decimal_places:
+        return hours.quantize(Decimal(str(10**-decimal_places)))
+    return hours
+
+def week_containing(date):
+    if date.weekday():
+        date -= datetime.timedelta(date.weekday())
+    
+    return date, date + datetime.timedelta(6)

timedelta/templatetags/decimal_hours.py

+from django import template
+register = template.Library()
+
+from ..helpers import decimal_hours as dh
+
+@register.filter(name='decimal_hours')
+def decimal_hours(value, decimal_places=None):
+    return dh(value, decimal_places)

timedelta/tests.py

         datetime.timedelta(7)
         >>> t.clean('2 weeks, 2 days')
         datetime.timedelta(16)
+        >>> t.clean(u'2 we\xe8k, 2 days')
+        Traceback (most recent call last):
+        ValidationError: [u'Enter a valid time span: e.g. "3 days, 4 hours, 2 minutes"']
         """
 
 class TimedeltaHelpersTest(TestCase):
         4
         >>> divide(datetime.timedelta(2), datetime.timedelta(3))
         0
-        >>> divide(datetime.timedelta(8), datetime.timedelta(3), float=True)
+        >>> divide(datetime.timedelta(8), datetime.timedelta(3), as_float=True)
         2.6666666666666665
         >>> divide(datetime.timedelta(8), 2.0)
         datetime.timedelta(4)
-        >>> divide(datetime.timedelta(8), 2, float=True)
+        >>> divide(datetime.timedelta(8), 2, as_float=True)
         Traceback (most recent call last):
             ...
-        AssertionError: float=True is inappropriate when dividing timedelta by a number.
+        AssertionError: as_float=True is inappropriate when dividing timedelta by a number.
         """
     
     def percentage(self):
         
         TODO: test with tzinfo (non-naive) datetimes/times.
         """
-    
+    
+    def test_decimal_hours(self):
+        """
+        >>> decimal_hours(datetime.timedelta(hours=5, minutes=30))
+        Decimal('5.5')
+        >>> decimal_hours(datetime.timedelta(hours=5))
+        Decimal('5')
+        >>> decimal_hours(datetime.timedelta(hours=9, minutes=20))
+        Decimal('9.333333333333333333333333333')
+        """

timedelta/widgets.py

         """
         if initial in ["", None] and data in ["", None]:
             return False
+            
+        if initial in ["", None] or data in ["", None]:
+            return True
         
         if initial:
             if not isinstance(initial, datetime.timedelta):
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.