Skip to content

Commit

Permalink
Fixes #1 — handle validation better at the model field level and made…
Browse files Browse the repository at this point in the history
… 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 ba4485e
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 20 deletions.
15 changes: 10 additions & 5 deletions timezones/fields.py
Expand Up @@ -5,6 +5,7 @@
from django.db.models import signals from django.db.models import signals


from timezones import forms from timezones import forms
from timezones.utils import coerce_timezone_value


import pytz import pytz


Expand All @@ -27,11 +28,19 @@ def __init__(self, *args, **kwargs):
defaults.update(kwargs) defaults.update(kwargs)
return super(TimeZoneField, self).__init__(*args, **defaults) 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): def to_python(self, value):
value = super(TimeZoneField, self).to_python(value) value = super(TimeZoneField, self).to_python(value)
if value is None: if value is None:
return None # null=True return None # null=True
return pytz.timezone(value) return coerce_timezone_value(value)


def get_prep_value(self, value): def get_prep_value(self, value):
if value is not None: if value is not None:
Expand All @@ -50,10 +59,6 @@ def flatten_data(self, follow, obj=None):
value = "" value = ""
return {self.attname: smart_unicode(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): class LocalizedDateTimeField(models.DateTimeField):
""" """
Expand Down
19 changes: 5 additions & 14 deletions timezones/forms.py
Expand Up @@ -5,7 +5,7 @@
from django.conf import settings from django.conf import settings
from django import forms 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)) ALL_TIMEZONE_CHOICES = tuple(zip(pytz.all_timezones, pytz.all_timezones))
COMMON_TIMEZONE_CHOICES = tuple(zip(pytz.common_timezones, pytz.common_timezones)) COMMON_TIMEZONE_CHOICES = tuple(zip(pytz.common_timezones, pytz.common_timezones))
Expand All @@ -14,22 +14,13 @@
now = datetime.datetime.now(pytz.timezone(tz)) now = datetime.datetime.now(pytz.timezone(tz))
PRETTY_TIMEZONE_CHOICES.append((tz, "(GMT%s) %s" % (now.strftime("%z"), tz))) PRETTY_TIMEZONE_CHOICES.append((tz, "(GMT%s) %s" % (now.strftime("%z"), tz)))


class TimeZoneField(forms.ChoiceField): class TimeZoneField(forms.TypedChoiceField):
def __init__(self, choices=None, max_length=None, min_length=None, def __init__(self, *args, **kwargs):
*args, **kwargs): if not "choices" in kwargs:
self.max_length, self.min_length = max_length, min_length
if choices is not None:
kwargs["choices"] = choices
else:
kwargs["choices"] = PRETTY_TIMEZONE_CHOICES kwargs["choices"] = PRETTY_TIMEZONE_CHOICES
kwargs["coerce"] = coerce_timezone_value
super(TimeZoneField, self).__init__(*args, **kwargs) 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): class LocalizedDateTimeField(forms.DateTimeField):
""" """
Expand Down
17 changes: 16 additions & 1 deletion timezones/timezones_tests/tests.py
Expand Up @@ -71,6 +71,13 @@ def test_forms_clean_not_required(self):
) )
self.assertEqual(f.clean(""), "") 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): def test_models_as_a_form(self):
class ProfileForm(forms.ModelForm): class ProfileForm(forms.ModelForm):
class Meta: class Meta:
Expand All @@ -82,12 +89,20 @@ class Meta:
"Did not find pattern in rendered form" "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 ProfileForm(forms.ModelForm):
class Meta: class Meta:
model = test_models.Profile model = test_models.Profile
form = ProfileForm({"name": "Brian Rosner", "timezone": "America/Denver"}) form = ProfileForm({"name": "Brian Rosner", "timezone": "America/Denver"})
self.assertFormIsValid(form) self.assertFormIsValid(form)
p = form.save()


def test_models_string_value(self): def test_models_string_value(self):
p = test_models.Profile(name="Brian Rosner", timezone="America/Denver") p = test_models.Profile(name="Brian Rosner", timezone="America/Denver")
Expand Down
7 changes: 7 additions & 0 deletions timezones/utils.py
Expand Up @@ -3,6 +3,7 @@


from django.conf import settings from django.conf import settings
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
from django.core.exceptions import ValidationError


def localtime_for_timezone(value, timezone): def localtime_for_timezone(value, timezone):
""" """
Expand All @@ -23,3 +24,9 @@ def adjust_datetime_to_timezone(value, from_tz, to_tz=None):
from_tz = pytz.timezone(smart_str(from_tz)) from_tz = pytz.timezone(smart_str(from_tz))
value = from_tz.localize(value) value = from_tz.localize(value)
return value.astimezone(pytz.timezone(smart_str(to_tz))) 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.