Skip to content

Commit 6fb53cc

Browse files
Surgomichaeljohnbarr
authored andcommitted
Merged Django 1.8 changes from Surgo. May need to clean up landscape.io warnings via ignoring lines with #noqa due to the unique nature of SubfieldBase.
1 parent d1a00cd commit 6fb53cc

File tree

5 files changed

+70
-7
lines changed

5 files changed

+70
-7
lines changed

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
packages=['timezone_utils'],
2222
install_requires=[
2323
'pytz',
24+
'django>=1.4,<1.9'
2425
],
2526
zip_safe=False,
2627
platforms='any',

tests/test_invalid_timezonefield.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,24 @@ def test_location_max_length(self):
2626
def test_bad_location_default_string(self):
2727
with self.assertRaises(ValidationError):
2828
TZWithBadStringDefault.objects.create()
29+
30+
def test_run_validators(self):
31+
with self.assertRaises(ValidationError):
32+
TZWithLowMaxLength._meta.get_field('timezone').run_validators('Bad')
33+
34+
def test_validate(self):
35+
instance = TZWithLowMaxLength.objects.create(timezone='US/Eastern')
36+
with self.assertRaises(ValidationError):
37+
TZWithLowMaxLength._meta.get_field('timezone').validate(
38+
value='Bad',
39+
model_instance=instance
40+
)
41+
42+
def test_validate_no_error(self):
43+
instance = TZWithLowMaxLength.objects.create(timezone='US/Eastern')
44+
self.assertIsNone(
45+
obj=TZWithLowMaxLength._meta.get_field('timezone').validate(
46+
value='US/Eastern',
47+
model_instance=instance
48+
)
49+
)

tests/test_valid_timezonefield.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def test_location_object_set_invalid_object_string(self):
4848
location = LocationTimeZone.objects.get(timezone='US/Eastern')
4949
with self.assertRaises(ValidationError):
5050
location.timezone = 'Bad/Timezone'
51+
location.save()
5152

5253
def test_location_is_equals_correct_timezone(self):
5354
"""Location should return a datetime.tzinfo instance, not a string."""

timezone_utils/fields.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
import warnings
99

1010
# Django
11+
import django
1112
try:
1213
from django.core import checks
1314
except ImportError: # pragma: no cover
1415
pass
1516
from django.core.exceptions import ValidationError
16-
from django.db.models import SubfieldBase
17+
from django.db import models
1718
from django.db.models.fields import DateTimeField, CharField
1819
from django.utils.six import with_metaclass
1920
from django.utils.timezone import get_default_timezone, is_naive, make_aware
@@ -22,13 +23,16 @@
2223
# App
2324
from timezone_utils import forms
2425

26+
27+
TimeZoneFieldBase = type if django.VERSION >= (1, 8) else models.SubfieldBase
28+
2529
__all__ = ('TimeZoneField', 'LinkedTZDateTimeField')
2630

2731

2832
# ==============================================================================
2933
# MODEL FIELDS
3034
# ==============================================================================
31-
class TimeZoneField(with_metaclass(SubfieldBase, CharField)):
35+
class TimeZoneField(with_metaclass(TimeZoneFieldBase, CharField)):
3236
# Enforce the minimum length of max_length to be the length of the longest
3337
# pytz timezone string
3438
MIN_LENGTH = max(map(len, pytz.all_timezones))
@@ -58,16 +62,43 @@ def __init__(self, *args, **kwargs):
5862

5963
super(TimeZoneField, self).__init__(*args, **kwargs)
6064

65+
def validate(self, value, model_instance):
66+
"""
67+
Validates value and throws ValidationError. Subclasses should override
68+
this to provide validation logic.
69+
"""
70+
super(TimeZoneField, self).validate(
71+
value=self.get_prep_value(value),
72+
model_instance=model_instance
73+
)
74+
75+
# Insure the value is can be converted to a timezone
76+
self.to_python(value)
77+
78+
def run_validators(self, value):
79+
super(TimeZoneField, self).run_validators(self.get_prep_value(value))
80+
6181
def get_prep_value(self, value):
6282
"""Converts timezone instances to strings for db storage."""
83+
value = super(TimeZoneField, self).get_prep_value(value)
6384

6485
if isinstance(value, tzinfo):
6586
return value.zone
87+
88+
return value
89+
90+
def from_db_value(self, value, expression, connection, context): # noqa
91+
"""
92+
Converts a value as returned by the database to a Python object. It is
93+
the reverse of get_prep_value(). - New in Django 1.8
94+
"""
95+
if value:
96+
value = self.to_python(value)
97+
6698
return value
6799

68100
def to_python(self, value):
69101
"""Returns a datetime.tzinfo instance for the value."""
70-
71102
value = super(TimeZoneField, self).to_python(value)
72103

73104
if not value:
@@ -190,14 +221,19 @@ def _check_choices_attribute(self): # pragma: no cover
190221
return []
191222

192223

193-
class LinkedTZDateTimeField(with_metaclass(SubfieldBase, DateTimeField)):
224+
class LinkedTZDateTimeField(with_metaclass(TimeZoneFieldBase, DateTimeField)):
194225
def __init__(self, *args, **kwargs):
195226
self.populate_from = kwargs.pop('populate_from', None)
196227
self.time_override = kwargs.pop('time_override', None)
197228
self.timezone = get_default_timezone()
198229

199230
super(LinkedTZDateTimeField, self).__init__(*args, **kwargs)
200231

232+
def from_db_value(self, value, expression, connection, context): # noqa
233+
if value:
234+
value = self.to_python(value)
235+
return value
236+
201237
def to_python(self, value):
202238
"""Convert the value to the appropriate timezone."""
203239

@@ -206,9 +242,6 @@ def to_python(self, value):
206242
if not value:
207243
return value
208244

209-
if is_naive(value):
210-
return make_aware(value=value, timezone=self.timezone)
211-
212245
return value.astimezone(self.timezone)
213246

214247
def pre_save(self, model_instance, add):
@@ -315,6 +348,9 @@ def _convert_value(self, value, model_instance, add):
315348
if self.populate_from is not None:
316349
tz = self._get_populate_from(model_instance)
317350

351+
if is_naive(value):
352+
value = make_aware(value=value, timezone=tz)
353+
318354
# Convert the value to a datetime object in the correct timezone. This
319355
# insures that we will have the correct date if we are performing a time
320356
# override below.

timezone_utils/forms.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# Django
99
from django.core.exceptions import ValidationError
1010
from django.forms import CharField
11+
from django.utils.encoding import force_text
1112
from django.utils.translation import ugettext_lazy as _
1213

1314
__all__ = ('TimeZoneField', )
@@ -21,6 +22,9 @@ class TimeZoneField(CharField):
2122
'invalid': _("'%(value)s' is not a valid time zone."),
2223
}
2324

25+
def run_validators(self, value):
26+
return super(TimeZoneField, self).run_validators(force_text(value))
27+
2428
def to_python(self, value):
2529
value = super(TimeZoneField, self).to_python(value)
2630

0 commit comments

Comments
 (0)