Permalink
Browse files

Merge branch 'develop': release v0.3.0 beta 2

  • Loading branch information...
2 parents 1f849f1 + 649c682 commit 4439236a70af4d831a1eea4cb7aad02bd04292db @mfogel mfogel committed Apr 18, 2012
Showing with 134 additions and 83 deletions.
  1. +1 −1 timezone_field/__init__.py
  2. +42 −45 timezone_field/fields.py
  3. +86 −37 timezone_field/tests.py
  4. +5 −0 timezone_field/validators.py
@@ -1,5 +1,5 @@
# modeled after django's versioning scheme, PEP 386
-VERSION = (0, 3, 0, 'beta', 1)
+VERSION = (0, 3, 0, 'beta', 2)
# adapted from django's get_version()
# see django.git/django/__init__.py
View
@@ -1,29 +1,44 @@
import pytz
from django.core.exceptions import ValidationError
-from django.db import models, IntegrityError
+from django.db import models
from django.utils.encoding import smart_unicode
+from timezone_field.validators import TzMaxLengthValidator
-class TimeZoneField(models.CharField):
+
+class TimeZoneField(models.Field):
"""
- A TimeZoneField stores pytz DstTzInfo objects to the database.
+ Stores pytz timezone objects to the database.
+
+ Valid inputs:
+ * any instance of pytz.tzinfo.DstTzInfo or pytz.tzinfo.StaticTzInfo
+ * any string that validates against pytz.all_timezones. pytz will
+ be used to build a timezone object from the string.
+ * None and the empty string both represent 'no timezone'
+
+ Outputs:
+ * None
+ * instances of pytz.tzinfo.DstTzInfo and pytz.tzinfo.StaticTzInfo
- Examples of valid inputs:
- '' # if blank == True
- 'America/Los_Angles' # validated against pytz.all_timezones
- None # if blank == True
- pytz.tzinfo.DstTzInfo # an instance of
+ Note that blank values ('' and None) are stored as an empty string
+ in the db. Specifiying null=True makes your db column not have a NOT
+ NULL constraint, but from the perspective of this field, has no effect.
If you choose to add validators at runtime, they need to accept
- pytz.tzinfo objects as input.
+ pytz.tzinfo.DstTzInfo and pytz.tzinfo.StaticTzInfo objects as input.
+
+ If you choose to override the 'choices' kwarg argument, and you specify
+ choices that can't be consumed by pytz.timezone(unicode(YOUR_NEW_CHOICE)),
+ wierdness will ensue. Don't do this. It's okay to further limit CHOICES,
+ but not expand it.
"""
- description = "A pytz.tzinfo.DstTzInfo object"
+ description = "A pytz timezone object"
__metaclass__ = models.SubfieldBase
- CHOICES = tuple(zip(pytz.all_timezones, pytz.all_timezones))
+ CHOICES = [(pytz.timezone(tz), tz) for tz in pytz.all_timezones]
MAX_LENGTH = 63
def __init__(self, validators=[], **kwargs):
@@ -32,56 +47,38 @@ def __init__(self, validators=[], **kwargs):
'choices': TimeZoneField.CHOICES,
}
defaults.update(kwargs)
-
super(TimeZoneField, self).__init__(**defaults)
+ self.validators.append(TzMaxLengthValidator(self.max_length))
- # validators our parent (CharField) register aren't going to
- # work right out of the box. They expect a string, while our
- # python type is a pytz.tzinfo
- # So, we'll wrap them in a small conversion object.
-
- class ValidateTimeZoneAsString(object):
- def __init__(self, org_validator):
- self.org_validator = org_validator
- def __call__(self, timezone):
- self.org_validator(smart_unicode(timezone))
-
- self.validators = [
- ValidateTimeZoneAsString(validator)
- for validator in self.validators
- ]
-
- self.validators += validators
+ def get_internal_type(self):
+ return 'CharField'
def validate(self, value, model_instance):
- # ensure we can consume the value
value = self.to_python(value)
- # parent validation works on strings
- str_value = smart_unicode(value)
- return super(TimeZoneField, self).validate(str_value, model_instance)
+ return super(TimeZoneField, self).validate(value, model_instance)
def to_python(self, value):
- "Returns pytz.tzinfo objects"
+ "Convert to pytz timezone object"
# inspriation from django's Datetime field
if value is None or value == '':
return None
- if isinstance(value, pytz.tzinfo.DstTzInfo):
+ if isinstance(value, pytz.tzinfo.BaseTzInfo):
return value
- try:
- return pytz.timezone(smart_unicode(value))
- except pytz.UnknownTimeZoneError:
- raise ValidationError("Invalid timezone '{}'".format(value))
+ if isinstance(value, basestring):
+ try:
+ return pytz.timezone(value)
+ except pytz.UnknownTimeZoneError:
+ pass
+ raise ValidationError("Invalid timezone '{}'".format(value))
def get_prep_value(self, value):
- "Accepts both a pytz.info object or a string representing a timezone"
+ "Convert to string describing a valid pytz timezone object"
# inspriation from django's Datetime field
value = self.to_python(value)
- # doing some validation
- if isinstance(value, pytz.tzinfo.DstTzInfo):
+ if value is None:
+ return ''
+ if isinstance(value, pytz.tzinfo.BaseTzInfo):
return smart_unicode(value)
- if value is None or value == '':
- return None
- raise IntegrityError("Invalid timezone '{}'".format(value))
def value_to_string(self, value):
return self.get_prep_value(value)
View
@@ -2,7 +2,7 @@
from django import forms
from django.core.exceptions import ValidationError
-from django.db import models, IntegrityError
+from django.db import models
from django.test import TestCase
from timezone_field.fields import TimeZoneField
@@ -14,8 +14,10 @@
class TestModel(models.Model):
- tz_not_blank = TimeZoneField()
- tz_blank = TimeZoneField(blank=True, null=True)
+ tz = TimeZoneField()
+ tz_null = TimeZoneField(null=True)
+ tz_blank = TimeZoneField(blank=True)
+ tz_blank_null = TimeZoneField(blank=True, null=True)
class TestModelForm(forms.ModelForm):
@@ -26,95 +28,142 @@ class Meta:
class TimeZoneFieldModelFormTestCase(TestCase):
def test_valid1(self):
- form = TestModelForm({'tz_not_blank': PST})
+ form = TestModelForm({'tz': PST, 'tz_null': EST})
self.assertTrue(form.is_valid())
form.save()
self.assertEqual(TestModel.objects.count(), 1)
def test_valid2(self):
form = TestModelForm({
- 'tz_not_blank': PST,
+ 'tz': PST,
+ 'tz_null': PST,
'tz_blank': EST,
+ 'tz_blank_null': EST,
})
self.assertTrue(form.is_valid())
form.save()
self.assertEqual(TestModel.objects.count(), 1)
def test_invalid_not_blank(self):
- form = TestModelForm()
- self.assertFalse(form.is_valid())
-
- def test_invalid_not_blank2(self):
- form = TestModelForm({'tz_blank': EST})
- self.assertFalse(form.is_valid())
+ form1 = TestModelForm({'tz': EST})
+ form2 = TestModelForm({'tz_null': EST})
+ form3 = TestModelForm()
+ self.assertFalse(form1.is_valid())
+ self.assertFalse(form2.is_valid())
+ self.assertFalse(form3.is_valid())
def test_invalid_invalid_str(self):
- form = TestModelForm({'tz_not_blank': INVALID_TZ})
- self.assertFalse(form.is_valid())
+ form1 = TestModelForm({'tz': INVALID_TZ})
+ form2 = TestModelForm({'tz_blank_null': INVALID_TZ})
+ self.assertFalse(form1.is_valid())
+ self.assertFalse(form2.is_valid())
+
+ def test_invalid_type(self):
+ form1 = TestModelForm({'tz': 4})
+ form2 = TestModelForm({'tz_blank_null': object()})
+ self.assertFalse(form1.is_valid())
+ self.assertFalse(form2.is_valid())
class TimeZoneFieldDBTestCase(TestCase):
def test_valid_strings(self):
m = TestModel.objects.create(
- tz_not_blank=PST,
+ tz=PST,
+ tz_null=PST,
tz_blank=EST,
+ tz_blank_null=EST,
)
m = TestModel.objects.get(pk=m.pk)
- self.assertEqual(m.tz_not_blank, pytz.timezone(PST))
+ self.assertEqual(m.tz, pytz.timezone(PST))
+ self.assertEqual(m.tz_null, pytz.timezone(PST))
self.assertEqual(m.tz_blank, pytz.timezone(EST))
+ self.assertEqual(m.tz_blank_null, pytz.timezone(EST))
def test_valid_tzinfos(self):
m = TestModel.objects.create(
- tz_not_blank=pytz.timezone(PST),
- tz_blank=pytz.timezone(EST),
+ tz=pytz.timezone(PST),
+ tz_null=pytz.timezone(EST),
+ tz_blank=pytz.timezone(PST),
+ tz_blank_null=pytz.timezone(EST),
)
m = TestModel.objects.get(pk=m.pk)
- self.assertEqual(m.tz_not_blank, pytz.timezone(PST))
- self.assertEqual(m.tz_blank, pytz.timezone(EST))
+ self.assertEqual(m.tz, pytz.timezone(PST))
+ self.assertEqual(m.tz_null, pytz.timezone(EST))
+ self.assertEqual(m.tz_blank, pytz.timezone(PST))
+ self.assertEqual(m.tz_blank_null, pytz.timezone(EST))
def test_valid_blank_str(self):
m = TestModel.objects.create(
- tz_not_blank=PST,
+ tz=PST,
+ tz_null=EST,
tz_blank='',
+ tz_blank_null='',
)
m = TestModel.objects.get(pk=m.pk)
- self.assertEqual(m.tz_not_blank, pytz.timezone(PST))
+ self.assertEqual(m.tz, pytz.timezone(PST))
+ self.assertEqual(m.tz_null, pytz.timezone(EST))
self.assertIsNone(m.tz_blank)
+ self.assertIsNone(m.tz_blank_null)
def test_valid_blank_none(self):
m = TestModel.objects.create(
- tz_not_blank=PST,
+ tz=PST,
tz_blank=None,
+ tz_blank_null=None,
)
m = TestModel.objects.get(pk=m.pk)
- self.assertEqual(m.tz_not_blank, pytz.timezone(PST))
+ self.assertEqual(m.tz, pytz.timezone(PST))
self.assertIsNone(m.tz_blank)
+ self.assertIsNone(m.tz_blank_null)
def test_string_value_lookup(self):
- TestModel.objects.create(tz_not_blank=EST)
- qs = TestModel.objects.filter(tz_not_blank=EST)
+ TestModel.objects.create(tz=EST)
+ qs = TestModel.objects.filter(tz=EST)
self.assertEqual(qs.count(), 1)
def test_tz_value_lookup(self):
- TestModel.objects.create(tz_not_blank=EST)
- qs = TestModel.objects.filter(tz_not_blank=pytz.timezone(EST))
+ TestModel.objects.create(tz=EST)
+ qs = TestModel.objects.filter(tz=pytz.timezone(EST))
self.assertEqual(qs.count(), 1)
def test_invalid_blank_str(self):
- m = TestModel(tz_not_blank='')
- with self.assertRaises(ValidationError):
- m.full_clean()
- with self.assertRaises(IntegrityError):
- m.save()
+ m1 = TestModel(tz='')
+ m2 = TestModel(tz_null='')
+ self.assertRaises(ValidationError, m1.full_clean)
+ self.assertRaises(ValidationError, m2.full_clean)
def test_invalid_blank_none(self):
- m = TestModel(tz_not_blank=None)
+ m1 = TestModel(tz=None)
+ m2 = TestModel(tz_null=None)
+ self.assertRaises(ValidationError, m1.full_clean)
+ self.assertRaises(ValidationError, m2.full_clean)
+
+ def test_invalid_type(self):
with self.assertRaises(ValidationError):
- m.full_clean()
- with self.assertRaises(IntegrityError):
- m.save()
+ TestModel(tz=4)
+ with self.assertRaises(ValidationError):
+ TestModel(tz_null=object())
def test_invalid_string(self):
with self.assertRaises(ValidationError):
- TestModel(tz_not_blank=INVALID_TZ)
+ TestModel(tz=INVALID_TZ)
+ with self.assertRaises(ValidationError):
+ TestModel(tz_null=INVALID_TZ)
+ with self.assertRaises(ValidationError):
+ TestModel(tz_blank=INVALID_TZ)
+ with self.assertRaises(ValidationError):
+ TestModel(tz_blank_null=INVALID_TZ)
+
+ def test_invalid_max_length(self):
+ class TestModelML(models.Model):
+ tz = TimeZoneField(max_length=4)
+ m1 = TestModelML(tz=PST)
+ self.assertRaises(ValidationError, m1.full_clean)
+
+ def test_invalid_choice(self):
+ class TestModelChoice(models.Model):
+ CHOICES = [(pytz.timezone(tz), tz) for tz in pytz.common_timezones]
+ tz = TimeZoneField(choices=CHOICES)
+ m1 = TestModelChoice(tz='Europe/Nicosia')
+ self.assertRaises(ValidationError, m1.full_clean)
@@ -0,0 +1,5 @@
+from django.core.validators import MaxLengthValidator
+
+class TzMaxLengthValidator(MaxLengthValidator):
+ "Validate a timezone's string representation does not exceed max_length"
+ clean = lambda self, x: len(unicode(x))

0 comments on commit 4439236

Please sign in to comment.