Permalink
Browse files

Fixes #1 — handle validation better at the model field level and mad…

…e validation much more consistent across the form field and model field

This was a doozy. TimeZoneField does not actually use TimeZoneField defined
via formfield because it has choices. Django forces TypedChoiceField. This
at least makes them behave mostly consistetly. Needed to handle validation
at the model field level for 1.2 since we want to deal with a pytz timezone
object normally, but when validating we need to work with its string
representation. Tests are now passing. There may be some extra minor bits
tossed in too for good measure.
  • Loading branch information...
brosner committed Mar 21, 2010
1 parent ffa1339 commit ba4485e2828935bb2221cb1c39d797f2b28ce9ec
Showing with 38 additions and 20 deletions.
  1. +10 −5 timezones/fields.py
  2. +5 −14 timezones/forms.py
  3. +16 −1 timezones/timezones_tests/tests.py
  4. +7 −0 timezones/utils.py
View
@@ -5,6 +5,7 @@
from django.db.models import signals
from timezones import forms
+from timezones.utils import coerce_timezone_value
import pytz
@@ -27,11 +28,19 @@ def __init__(self, *args, **kwargs):
defaults.update(kwargs)
return super(TimeZoneField, self).__init__(*args, **defaults)
+ def validate(self, value, model_instance):
+ # coerce value back to a string to validate correctly
+ return super(TimeZoneField, self).validate(smart_str(value), model_instance)
+
+ def run_validators(self, value):
+ # coerce value back to a string to validate correctly
+ return super(TimeZoneField, self).run_validators(smart_str(value))
+
def to_python(self, value):
value = super(TimeZoneField, self).to_python(value)
if value is None:
return None # null=True
- return pytz.timezone(value)
+ return coerce_timezone_value(value)
def get_prep_value(self, value):
if value is not None:
@@ -50,10 +59,6 @@ def flatten_data(self, follow, obj=None):
value = ""
return {self.attname: smart_unicode(value)}
- def formfield(self, **kwargs):
- defaults = {"form_class": forms.TimeZoneField}
- defaults.update(kwargs)
- return super(TimeZoneField, self).formfield(**defaults)
class LocalizedDateTimeField(models.DateTimeField):
"""
View
@@ -5,7 +5,7 @@
from django.conf import settings
from django import forms
-from timezones.utils import adjust_datetime_to_timezone
+from timezones.utils import adjust_datetime_to_timezone, coerce_timezone_value
ALL_TIMEZONE_CHOICES = tuple(zip(pytz.all_timezones, pytz.all_timezones))
COMMON_TIMEZONE_CHOICES = tuple(zip(pytz.common_timezones, pytz.common_timezones))
@@ -14,22 +14,13 @@
now = datetime.datetime.now(pytz.timezone(tz))
PRETTY_TIMEZONE_CHOICES.append((tz, "(GMT%s) %s" % (now.strftime("%z"), tz)))
-class TimeZoneField(forms.ChoiceField):
- def __init__(self, choices=None, max_length=None, min_length=None,
- *args, **kwargs):
- self.max_length, self.min_length = max_length, min_length
- if choices is not None:
- kwargs["choices"] = choices
- else:
+class TimeZoneField(forms.TypedChoiceField):
+ def __init__(self, *args, **kwargs):
+ if not "choices" in kwargs:
kwargs["choices"] = PRETTY_TIMEZONE_CHOICES
+ kwargs["coerce"] = coerce_timezone_value
super(TimeZoneField, self).__init__(*args, **kwargs)
- def clean(self, value):
- value = super(TimeZoneField, self).clean(value)
- if value:
- return pytz.timezone(value)
- else:
- return value
class LocalizedDateTimeField(forms.DateTimeField):
"""
@@ -71,6 +71,13 @@ def test_forms_clean_not_required(self):
)
self.assertEqual(f.clean(""), "")
+ def test_forms_clean_bad_value(self):
+ f = timezones.forms.TimeZoneField()
+ try:
+ f.clean("BAD VALUE")
+ except forms.ValidationError, e:
+ self.assertEqual(e.messages, ["Select a valid choice. BAD VALUE is not one of the available choices."])
+
def test_models_as_a_form(self):
class ProfileForm(forms.ModelForm):
class Meta:
@@ -82,12 +89,20 @@ class Meta:
"Did not find pattern in rendered form"
)
- def test_models_modelform_data(self):
+ def test_models_modelform_validation(self):
+ class ProfileForm(forms.ModelForm):
+ class Meta:
+ model = test_models.Profile
+ form = ProfileForm({"name": "Brian Rosner", "timezone": "America/Denver"})
+ self.assertFormIsValid(form)
+
+ def test_models_modelform_save(self):
class ProfileForm(forms.ModelForm):
class Meta:
model = test_models.Profile
form = ProfileForm({"name": "Brian Rosner", "timezone": "America/Denver"})
self.assertFormIsValid(form)
+ p = form.save()
def test_models_string_value(self):
p = test_models.Profile(name="Brian Rosner", timezone="America/Denver")
View
@@ -3,6 +3,7 @@
from django.conf import settings
from django.utils.encoding import smart_str
+from django.core.exceptions import ValidationError
def localtime_for_timezone(value, timezone):
"""
@@ -23,3 +24,9 @@ def adjust_datetime_to_timezone(value, from_tz, to_tz=None):
from_tz = pytz.timezone(smart_str(from_tz))
value = from_tz.localize(value)
return value.astimezone(pytz.timezone(smart_str(to_tz)))
+
+def coerce_timezone_value(value):
+ try:
+ return pytz.timezone(value)
+ except pytz.UnknownTimeZoneError:
+ raise ValidationError("Unknown timezone")

0 comments on commit ba4485e

Please sign in to comment.