Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-django-timezone-field for
openSUSE:Factory checked in at 2021-06-01 10:35:09
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-django-timezone-field (Old)
and /work/SRC/openSUSE:Factory/.python-django-timezone-field.new.1898
(New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-django-timezone-field"
Tue Jun 1 10:35:09 2021 rev:2 rq:895199 version:4.1.2
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-django-timezone-field/python-django-timezone-field.changes
2020-11-23 10:42:54.042147899 +0100
+++
/work/SRC/openSUSE:Factory/.python-django-timezone-field.new.1898/python-django-timezone-field.changes
2021-06-01 10:35:37.740625829 +0200
@@ -1,0 +2,10 @@
+Mon May 10 00:25:27 UTC 2021 - Daniel Molkentin <[email protected]>
+
+- Update to to 4.1.2
+ * Avoid NonExistentTimeError during DST transition (#70)
+ * Don't import rest_framework from package root (#67)
+ * Add Django REST Framework serializer field
+ * Add new choices_display kwarg with supported values WITH_GMT_OFFSET and
STANDARD
+ * Deprecate display_GMT_offset kwarg
+
+-------------------------------------------------------------------
Old:
----
django-timezone-field-4.0.tar.gz
New:
----
django-timezone-field-4.1.2.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-django-timezone-field.spec ++++++
--- /var/tmp/diff_new_pack.21DcaQ/_old 2021-06-01 10:35:38.372626906 +0200
+++ /var/tmp/diff_new_pack.21DcaQ/_new 2021-06-01 10:35:38.376626913 +0200
@@ -1,7 +1,7 @@
#
# spec file for package python-django-timezone-field
#
-# Copyright (c) 2020 SUSE LLC
+# Copyright (c) 2021 SUSE LINUX GmbH, Nuernberg, Germany.
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -19,13 +19,14 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%define skip_python2 1
Name: python-django-timezone-field
-Version: 4.0
+Version: 4.1.2
Release: 0
Summary: Django app providing database and form fields for pytz
timezone objects
License: BSD-2-Clause
Group: Development/Languages/Python
URL: https://github.com/mfogel/django-timezone-field/
Source:
https://github.com/mfogel/django-timezone-field/archive/%{version}.tar.gz#/django-timezone-field-%{version}.tar.gz
+BuildRequires: %{python_module djangorestframework}
BuildRequires: %{python_module setuptools}
BuildRequires: fdupes
BuildRequires: python-rpm-macros
++++++ django-timezone-field-4.0.tar.gz -> django-timezone-field-4.1.2.tar.gz
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-timezone-field-4.0/.flake8
new/django-timezone-field-4.1.2/.flake8
--- old/django-timezone-field-4.0/.flake8 1970-01-01 01:00:00.000000000
+0100
+++ new/django-timezone-field-4.1.2/.flake8 2021-03-17 19:45:53.000000000
+0100
@@ -0,0 +1,3 @@
+[flake8]
+ignore=W503
+max-line-length=100
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-timezone-field-4.0/.travis.yml
new/django-timezone-field-4.1.2/.travis.yml
--- old/django-timezone-field-4.0/.travis.yml 2019-12-03 04:26:44.000000000
+0100
+++ new/django-timezone-field-4.1.2/.travis.yml 2021-03-17 19:45:53.000000000
+0100
@@ -11,6 +11,7 @@
- 3.6
- 3.7
- 3.8
+ - 3.9
install:
- pip install tox-travis coveralls
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-timezone-field-4.0/README.rst
new/django-timezone-field-4.1.2/README.rst
--- old/django-timezone-field-4.0/README.rst 2019-12-03 04:26:44.000000000
+0100
+++ new/django-timezone-field-4.1.2/README.rst 2021-03-17 19:45:53.000000000
+0100
@@ -25,20 +25,18 @@
from timezone_field import TimeZoneField
class MyModel(models.Model):
- timezone1 = TimeZoneField(default='Europe/London') # defaults supported
- timezone2 = TimeZoneField()
- timezone3 = TimeZoneField()
-
- my_inst = MyModel(
- timezone1='America/Los_Angeles', # assignment of a string
- timezone2=pytz.timezone('Turkey'), # assignment of a pytz.DstTzInfo
- timezone3=pytz.UTC, # assignment of pytz.UTC singleton
+ tz1 = TimeZoneField(default='Europe/London') # defaults
supported
+ tz2 = TimeZoneField() # in ModelForm
displays like "America/Los Angeles"
+ tz3 = TimeZoneField(choices_display='WITH_GMT_OFFSET') # in ModelForm
displays like "GMT-08:00 America/Los Angeles"
+
+ my_model = MyModel(
+ tz1='America/Los_Angeles', # assignment of a string
+ tz2=pytz.timezone('Turkey'), # assignment of a pytz.DstTzInfo
+ tz3=pytz.UTC, # assignment of pytz.UTC singleton
)
- my_inst.full_clean() # validates against pytz.common_timezones
- my_inst.save() # values stored in DB as strings
-
- tz = my_inst.timezone1 # values retrieved as pytz objects
- repr(tz) # "<DstTzInfo 'America/Los_Angeles' PST-1 day,
16:00:00 STD>"
+ my_model.full_clean() # validates against pytz.common_timezones by default
+ my_model.save() # values stored in DB as strings
+ my_model.tz1 # values retrieved as pytz objects: <DstTzInfo
'America/Los_Angeles' PST-1 day, 16:00:00 STD>
Form Field
@@ -50,16 +48,34 @@
from timezone_field import TimeZoneFormField
class MyForm(forms.Form):
- timezone = TimeZoneFormField() # displays like "America/Los_Angeles"
- timezone2 = TimeZoneFormField(display_GMT_offset=True) # displays like
"GMT-08:00 America/Los_Angeles"
+ tz = TimeZoneFormField() # displays
like "America/Los Angeles"
+ tz2 = TimeZoneFormField(choices_display='WITH_GMT_OFFSET') # displays
like "GMT-08:00 America/Los Angeles"
- my_form = MyForm({
- 'timezone': 'America/Los_Angeles',
- })
- my_form.full_clean() # validates against pytz.common_timezones
+ my_form = MyForm({'tz': 'America/Los_Angeles'})
+ my_form.full_clean() # validates against pytz.common_timezones by
default
+ my_form.cleaned_data['tz'] # values retrieved as pytz objects: <DstTzInfo
'America/Los_Angeles' PST-1 day, 16:00:00 STD>
+
+
+REST Framework Serializer Field
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- tz = my_form.cleaned_data['timezone'] # values retrieved as pytz objects
- repr(tz) # "<DstTzInfo 'America/Los_Angeles'
PST-1 day, 16:00:00 STD>"
+.. code:: python
+
+ import pytz
+ from rest_framework import serializers
+ from timezone_field.rest_framework import TimeZoneSerializerField
+
+ class MySerializer(serializers.Serializer):
+ tz1 = TimeZoneSerializerField()
+ tz2 = TimeZoneSerializerField()
+
+ my_serializer = MySerializer(data={
+ 'tz1': 'America/Argentina/Buenos_Aires',
+ 'tz2': pytz.timezone('America/Argentina/Buenos_Aires'),
+ })
+ my_serializer.is_valid() # true
+ my_serializer.validated_data['tz1'] # <DstTzInfo
'America/Argentina/Buenos_Aires' LMT-1 day, 20:06:00 STD>
+ my_serializer.validated_data['tz2'] # <DstTzInfo
'America/Argentina/Buenos_Aires' LMT-1 day, 20:06:00 STD>
Installation
@@ -84,6 +100,20 @@
Changelog
------------
+* 4.1.2 (2021-03-17)
+
+ * Avoid `NonExistentTimeError` during DST transition (`#70`__)
+
+* 4.1.1 (2020-11-28)
+
+ * Don't import `rest_framework` from package root (`#67`__)
+
+* 4.1 (2020-11-28)
+
+ * Add Django REST Framework serializer field
+ * Add new `choices_display` kwarg with supported values
`WITH_GMT_OFFSET` and `STANDARD`
+ * Deprecate `display_GMT_offset` kwarg
+
* 4.0 (2019-12-03)
* Add support for django 3.0, python 3.8
@@ -171,6 +201,8 @@
__ http://pypi.python.org/pypi/django-timezone-field/
__ http://www.pip-installer.org/
__ https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
+__ https://github.com/mfogel/django-timezone-field/issues/70
+__ https://github.com/mfogel/django-timezone-field/issues/67
__ https://github.com/mfogel/django-timezone-field/issues/46
__ https://github.com/mfogel/django-timezone-field/issues/32
__ https://github.com/mfogel/django-timezone-field/issues/37
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-timezone-field-4.0/setup.py
new/django-timezone-field-4.1.2/setup.py
--- old/django-timezone-field-4.0/setup.py 2019-12-03 04:26:44.000000000
+0100
+++ new/django-timezone-field-4.1.2/setup.py 2021-03-17 19:45:53.000000000
+0100
@@ -36,6 +36,9 @@
'timezone_field',
],
install_requires=['django>=2.2', 'pytz'],
+ extras_require={
+ 'rest_framework': ['djangorestframework>=3.0.0']
+ },
python_requires='>=3.5',
classifiers=[
'Development Status :: 4 - Beta',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-timezone-field-4.0/tests/models.py
new/django-timezone-field-4.1.2/tests/models.py
--- old/django-timezone-field-4.0/tests/models.py 2019-12-03
04:26:44.000000000 +0100
+++ new/django-timezone-field-4.1.2/tests/models.py 2021-03-17
19:45:53.000000000 +0100
@@ -8,3 +8,23 @@
tz_opt = TimeZoneField(blank=True)
tz_opt_default = TimeZoneField(blank=True, default='America/Los_Angeles')
tz_gmt_offset = TimeZoneField(blank=True, display_GMT_offset=True)
+
+
+class TestChoicesDisplayModel(models.Model):
+ limited_tzs = [
+ 'Asia/Tokyo',
+ 'Asia/Dubai',
+ 'America/Argentina/Buenos_Aires',
+ 'Africa/Nairobi',
+ ]
+ limited_choices = [(tz, tz) for tz in limited_tzs]
+
+ tz_none = TimeZoneField()
+ tz_standard = TimeZoneField(choices_display='STANDARD')
+ tz_with_gmt_offset = TimeZoneField(choices_display='WITH_GMT_OFFSET')
+ tz_limited_none = TimeZoneField(choices=limited_choices)
+ tz_limited_standard = TimeZoneField(choices=limited_choices,
choices_display='STANDARD')
+ tz_limited_with_gmt_offset = TimeZoneField(
+ choices=limited_choices,
+ choices_display='WITH_GMT_OFFSET',
+ )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-timezone-field-4.0/tests/settings.py
new/django-timezone-field-4.1.2/tests/settings.py
--- old/django-timezone-field-4.0/tests/settings.py 2019-12-03
04:26:44.000000000 +0100
+++ new/django-timezone-field-4.1.2/tests/settings.py 2021-03-17
19:45:53.000000000 +0100
@@ -37,6 +37,7 @@
'django.contrib.staticfiles',
'timezone_field',
'tests',
+ 'rest_framework'
)
MIDDLEWARE_CLASSES = (
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-timezone-field-4.0/tests/tests.py
new/django-timezone-field-4.1.2/tests/tests.py
--- old/django-timezone-field-4.0/tests/tests.py 2019-12-03
04:26:44.000000000 +0100
+++ new/django-timezone-field-4.1.2/tests/tests.py 2021-03-17
19:45:53.000000000 +0100
@@ -1,4 +1,5 @@
from __future__ import absolute_import
+from datetime import datetime
import pytz
@@ -7,10 +8,13 @@
from django.db import models
from django.db.migrations.writer import MigrationWriter
from django.test import TestCase
+from rest_framework import serializers
from timezone_field import TimeZoneField, TimeZoneFormField
+from timezone_field.choices import standard, with_gmt_offset
+from timezone_field.rest_framework import TimeZoneSerializerField
from timezone_field.utils import add_gmt_offset_to_choices
-from tests.models import TestModel
+from tests.models import TestModel, TestChoicesDisplayModel
PST = 'America/Los_Angeles' # pytz.tzinfo.DstTzInfo
@@ -74,15 +78,15 @@
def test_default_human_readable_choices_dont_have_underscores(self):
form = TestForm()
- pst_choice = [c for c in form.fields['tz'].choices if c[0] == PST]
- self.assertEqual(pst_choice[0][1], 'America/Los Angeles')
+ for choice in form.fields['tz'].choices:
+ self.assertFalse('_' in choice[1])
class TestFormInvalidChoice(forms.Form):
tz = TimeZoneFormField(
choices=(
- [(tz, tz) for tz in pytz.all_timezones] +
- [(INVALID_TZ, pytz.UTC)]
+ [(tz, tz) for tz in pytz.all_timezones]
+ + [(INVALID_TZ, pytz.UTC)]
)
)
@@ -453,3 +457,301 @@
]
for i in range(len(expected)):
self.assertEqual(expected[i], result[i][1])
+
+
+class ChoicesStandardTestCase(TestCase):
+
+ tz_names = [
+ 'America/Los_Angeles',
+ 'Europe/London',
+ 'America/Argentina/Buenos_Aires',
+ ]
+ tz_objects = [pytz.timezone(tz) for tz in tz_names]
+ tz_displays = [
+ 'America/Los Angeles',
+ 'Europe/London',
+ 'America/Argentina/Buenos Aires',
+ ]
+
+ def test_using_timezone_objects(self):
+ result = standard(self.tz_objects)
+ self.assertEqual(result, list(zip(self.tz_objects, self.tz_displays)))
+
+ def test_using_timezone_names(self):
+ result = standard(self.tz_names)
+ self.assertEqual(result, list(zip(self.tz_names, self.tz_displays)))
+
+
+class ChoicesWithGMTOffsetTestCase(TestCase):
+
+ # test timezones out of order, but they should appear in order in result.
+ # avoiding an timezones that go through a Daylight Savings change here
+ tz_names = [
+ 'Asia/Kathmandu', # 45 min off the hour
+ 'Asia/Kolkata', # 30 min off the hour
+ 'America/Argentina/Buenos_Aires', # on the hour
+ 'Asia/Qatar', # on the hour
+ ]
+ tz_objects = [pytz.timezone(name) for name in tz_names]
+
+ def test_using_timezone_objects(self):
+ result = with_gmt_offset(self.tz_objects)
+ self.assertEqual(result, [
+ (
+ pytz.timezone('America/Argentina/Buenos_Aires'),
+ 'GMT-03:00 America/Argentina/Buenos Aires',
+ ),
+ (pytz.timezone('Asia/Qatar'), 'GMT+03:00 Asia/Qatar'),
+ (pytz.timezone('Asia/Kolkata'), 'GMT+05:30 Asia/Kolkata'),
+ (pytz.timezone('Asia/Kathmandu'), 'GMT+05:45 Asia/Kathmandu'),
+ ])
+
+ def test_using_timezone_names(self):
+ result = with_gmt_offset(self.tz_names)
+ self.assertEqual(result, [
+ ('America/Argentina/Buenos_Aires', 'GMT-03:00
America/Argentina/Buenos Aires'),
+ ('Asia/Qatar', 'GMT+03:00 Asia/Qatar'),
+ ('Asia/Kolkata', 'GMT+05:30 Asia/Kolkata'),
+ ('Asia/Kathmandu', 'GMT+05:45 Asia/Kathmandu'),
+ ])
+
+
+class ChoicesWithGMTOffsetDaylightSavingsTimeTestCase(TestCase):
+
+ # test timezones out of order, but they should appear in order in result.
+ tz_names = [
+ 'Europe/London',
+ 'Canada/Newfoundland', # 30 min off the hour
+ 'America/Los_Angeles', # on the hour
+ 'America/Santiago', # southern hemisphere
+ ]
+
+ def test_in_northern_summer(self):
+ now = datetime(2020, 7, 15, tzinfo=pytz.utc)
+ result = with_gmt_offset(self.tz_names, now=now)
+ self.assertEqual(result, [
+ ('America/Los_Angeles', 'GMT-07:00 America/Los Angeles'),
+ ('America/Santiago', 'GMT-04:00 America/Santiago'),
+ ('Canada/Newfoundland', 'GMT-02:30 Canada/Newfoundland'),
+ ('Europe/London', 'GMT+01:00 Europe/London'),
+ ])
+
+ def test_in_northern_winter(self):
+ now = datetime(2020, 1, 15, tzinfo=pytz.utc)
+ result = with_gmt_offset(self.tz_names, now=now)
+ self.assertEqual(result, [
+ ('America/Los_Angeles', 'GMT-08:00 America/Los Angeles'),
+ ('Canada/Newfoundland', 'GMT-03:30 Canada/Newfoundland'),
+ ('America/Santiago', 'GMT-03:00 America/Santiago'),
+ ('Europe/London', 'GMT+00:00 Europe/London'),
+ ])
+
+ def test_transition_forward(self):
+ tz_names = ['Europe/London']
+ before = datetime(2021, 3, 28, 0, 59, 59, 999999, tzinfo=pytz.utc)
+ after = datetime(2021, 3, 28, 1, 0, 0, 0, tzinfo=pytz.utc)
+ self.assertEqual(
+ with_gmt_offset(tz_names, now=before),
+ [('Europe/London', 'GMT+00:00 Europe/London')]
+ )
+ self.assertEqual(
+ with_gmt_offset(tz_names, now=after),
+ [('Europe/London', 'GMT+01:00 Europe/London')]
+ )
+
+ def test_transition_backward(self):
+ tz_names = ['Europe/London']
+ before = datetime(2021, 10, 31, 0, 59, 59, 999999, tzinfo=pytz.utc)
+ after = datetime(2021, 10, 31, 1, 0, 0, 0, tzinfo=pytz.utc)
+ self.assertEqual(
+ with_gmt_offset(tz_names, now=before),
+ [('Europe/London', 'GMT+01:00 Europe/London')]
+ )
+ self.assertEqual(
+ with_gmt_offset(tz_names, now=after),
+ [('Europe/London', 'GMT+00:00 Europe/London')]
+ )
+
+
+class TimeZoneSerializer(serializers.Serializer):
+ tz = TimeZoneSerializerField()
+
+
+class TimeZoneSerializerFieldTestCase(TestCase):
+ def test_invalid_str(self):
+ serializer = TimeZoneSerializer(data={'tz': INVALID_TZ})
+ self.assertFalse(serializer.is_valid())
+
+ def test_valid(self):
+ serializer = TimeZoneSerializer(data={'tz': PST})
+ self.assertTrue(serializer.is_valid())
+ self.assertEqual(serializer.validated_data['tz'], PST_tz)
+
+ def test_valid_representation(self):
+ serializer = TimeZoneSerializer(data={'tz': PST})
+ self.assertTrue(serializer.is_valid())
+ self.assertEqual(serializer.data['tz'], PST)
+
+ def test_valid_with_timezone_object(self):
+ serializer = TimeZoneSerializer(data={'tz': PST_tz})
+ self.assertTrue(serializer.is_valid())
+ self.assertEqual(serializer.data['tz'], PST)
+ self.assertEqual(serializer.validated_data['tz'], PST_tz)
+
+
+class TestChoicesDisplayForm(forms.Form):
+ limited_tzs = [
+ 'Asia/Tokyo',
+ 'Asia/Dubai',
+ 'America/Argentina/Buenos_Aires',
+ 'Africa/Nairobi',
+ ]
+ limited_choices = [(tz, tz) for tz in limited_tzs]
+
+ tz_none = TimeZoneFormField()
+ tz_standard = TimeZoneFormField(choices_display='STANDARD')
+ tz_with_gmt_offset = TimeZoneFormField(choices_display='WITH_GMT_OFFSET')
+ tz_limited_none = TimeZoneFormField(choices=limited_choices)
+ tz_limited_standard = TimeZoneFormField(choices=limited_choices,
choices_display='STANDARD')
+ tz_limited_with_gmt_offset = TimeZoneFormField(
+ choices=limited_choices,
+ choices_display='WITH_GMT_OFFSET',
+ )
+
+
+class TestChoicesDisplayTestCase(TestCase):
+
+ common_tzs = tuple(tz for tz in pytz.common_timezones)
+
+ def test_invalid_choices_display(self):
+ self.assertRaises(ValueError, lambda:
TimeZoneFormField(choices_display='invalid'))
+
+ def test_none(self):
+ form = TestChoicesDisplayForm()
+ values, displays = zip(*form.fields['tz_none'].choices)
+ self.assertEqual(values, self.common_tzs)
+ self.assertEqual(displays[values.index('America/Los_Angeles')],
'America/Los Angeles')
+ self.assertEqual(displays[values.index('Asia/Kolkata')],
'Asia/Kolkata')
+
+ def test_standard(self):
+ form = TestChoicesDisplayForm()
+ self.assertEqual(form.fields['tz_standard'].choices,
form.fields['tz_none'].choices)
+
+ def test_with_gmt_offset(self):
+ form = TestChoicesDisplayForm()
+ values, displays = zip(*form.fields['tz_with_gmt_offset'].choices)
+ self.assertNotEqual(values, self.common_tzs)
+ self.assertEqual(sorted(values), sorted(self.common_tzs))
+ self.assertEqual(
+ displays[values.index('America/Argentina/Buenos_Aires')],
+ 'GMT-03:00 America/Argentina/Buenos Aires',
+ )
+ self.assertEqual(displays[values.index('Europe/Moscow')], 'GMT+03:00
Europe/Moscow')
+
+ def test_limited_none(self):
+ form = TestChoicesDisplayForm()
+ self.assertEqual(form.fields['tz_limited_none'].choices, [
+ ('Asia/Tokyo', 'Asia/Tokyo'),
+ ('Asia/Dubai', 'Asia/Dubai'),
+ ('America/Argentina/Buenos_Aires',
'America/Argentina/Buenos_Aires'),
+ ('Africa/Nairobi', 'Africa/Nairobi'),
+ ])
+
+ def test_limited_standard(self):
+ form = TestChoicesDisplayForm()
+ self.assertEqual(form.fields['tz_limited_standard'].choices, [
+ ('Asia/Tokyo', 'Asia/Tokyo'),
+ ('Asia/Dubai', 'Asia/Dubai'),
+ ('America/Argentina/Buenos_Aires', 'America/Argentina/Buenos
Aires'),
+ ('Africa/Nairobi', 'Africa/Nairobi'),
+ ])
+
+ def test_limited_with_gmt_offset(self):
+ form = TestChoicesDisplayForm()
+ self.assertEqual(form.fields['tz_limited_with_gmt_offset'].choices, [
+ ('America/Argentina/Buenos_Aires', 'GMT-03:00
America/Argentina/Buenos Aires'),
+ ('Africa/Nairobi', 'GMT+03:00 Africa/Nairobi'),
+ ('Asia/Dubai', 'GMT+04:00 Asia/Dubai'),
+ ('Asia/Tokyo', 'GMT+09:00 Asia/Tokyo'),
+ ])
+
+
+class TestChoicesDisplayModelForm(forms.ModelForm):
+ class Meta:
+ model = TestChoicesDisplayModel
+ fields = '__all__'
+
+
+class TestChoicesDisplayModelFormTestCase(TestCase):
+
+ common_tzs = tuple(pytz.timezone(tz) for tz in pytz.common_timezones)
+
+ def test_invalid_choices_display(self):
+ self.assertRaises(ValueError, lambda:
TimeZoneField(choices_display='invalid'))
+
+ def test_none(self):
+ form = TestChoicesDisplayModelForm()
+ values, displays = zip(*form.fields['tz_none'].choices)
+ self.assertEqual(values, ('',) + self.common_tzs)
+ self.assertEqual(
+ displays[values.index(pytz.timezone('America/Los_Angeles'))],
+ 'America/Los Angeles',
+ )
+ self.assertEqual(
+ displays[values.index(pytz.timezone('Asia/Kolkata'))],
+ 'Asia/Kolkata',
+ )
+
+ def test_standard(self):
+ form = TestChoicesDisplayModelForm()
+ self.assertEqual(form.fields['tz_standard'].choices,
form.fields['tz_none'].choices)
+
+ def test_with_gmt_offset(self):
+ form = TestChoicesDisplayModelForm()
+ values, displays = zip(*form.fields['tz_with_gmt_offset'].choices)
+ self.assertNotEqual(values, self.common_tzs)
+ self.assertEqual(
+ sorted(str(v) for v in values),
+ sorted([''] + [str(tz) for tz in self.common_tzs]),
+ )
+ self.assertEqual(
+
displays[values.index(pytz.timezone('America/Argentina/Buenos_Aires'))],
+ 'GMT-03:00 America/Argentina/Buenos Aires',
+ )
+ self.assertEqual(
+ displays[values.index(pytz.timezone('Europe/Moscow'))],
+ 'GMT+03:00 Europe/Moscow',
+ )
+
+ def test_limited_none(self):
+ form = TestChoicesDisplayModelForm()
+ self.assertEqual(form.fields['tz_limited_none'].choices, [
+ ('', '---------'),
+ (pytz.timezone('Asia/Tokyo'), 'Asia/Tokyo'),
+ (pytz.timezone('Asia/Dubai'), 'Asia/Dubai'),
+ (pytz.timezone('America/Argentina/Buenos_Aires'),
'America/Argentina/Buenos_Aires'),
+ (pytz.timezone('Africa/Nairobi'), 'Africa/Nairobi'),
+ ])
+
+ def test_limited_standard(self):
+ form = TestChoicesDisplayModelForm()
+ self.assertEqual(form.fields['tz_limited_standard'].choices, [
+ ('', '---------'),
+ (pytz.timezone('Asia/Tokyo'), 'Asia/Tokyo'),
+ (pytz.timezone('Asia/Dubai'), 'Asia/Dubai'),
+ (pytz.timezone('America/Argentina/Buenos_Aires'),
'America/Argentina/Buenos Aires'),
+ (pytz.timezone('Africa/Nairobi'), 'Africa/Nairobi'),
+ ])
+
+ def test_limited_with_gmt_offset(self):
+ form = TestChoicesDisplayModelForm()
+ self.assertEqual(form.fields['tz_limited_with_gmt_offset'].choices, [
+ ('', '---------'),
+ (
+ pytz.timezone('America/Argentina/Buenos_Aires'),
+ 'GMT-03:00 America/Argentina/Buenos Aires',
+ ),
+ (pytz.timezone('Africa/Nairobi'), 'GMT+03:00 Africa/Nairobi'),
+ (pytz.timezone('Asia/Dubai'), 'GMT+04:00 Asia/Dubai'),
+ (pytz.timezone('Asia/Tokyo'), 'GMT+09:00 Asia/Tokyo'),
+ ])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-timezone-field-4.0/timezone_field/__init__.py
new/django-timezone-field-4.1.2/timezone_field/__init__.py
--- old/django-timezone-field-4.0/timezone_field/__init__.py 2019-12-03
04:26:44.000000000 +0100
+++ new/django-timezone-field-4.1.2/timezone_field/__init__.py 2021-03-17
19:45:53.000000000 +0100
@@ -1,5 +1,5 @@
from timezone_field.fields import TimeZoneField
from timezone_field.forms import TimeZoneFormField
-__version__ = '4.0'
+__version__ = '4.1.2'
__all__ = ['TimeZoneField', 'TimeZoneFormField']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-timezone-field-4.0/timezone_field/choices.py
new/django-timezone-field-4.1.2/timezone_field/choices.py
--- old/django-timezone-field-4.0/timezone_field/choices.py 1970-01-01
01:00:00.000000000 +0100
+++ new/django-timezone-field-4.1.2/timezone_field/choices.py 2021-03-17
19:45:53.000000000 +0100
@@ -0,0 +1,42 @@
+import pytz
+from datetime import datetime
+
+
+def standard(timezones):
+ """
+ Given a list of timezones (either strings of timezone objects),
+ return a list of choices with
+ * values equal to what was passed in
+ * display strings as the timezone name without underscores
+ """
+ choices = []
+ for tz in timezones:
+ tz_str = str(tz)
+ choices.append((tz, tz_str.replace('_', ' ')))
+ return choices
+
+
+def with_gmt_offset(timezones, now=None):
+ """
+ Given a list of timezones (either strings of timezone objects),
+ return a list of choices with
+ * values equal to what was passed in
+ * display strings formated with GMT offsets and without
+ underscores. For example: "GMT-05:00 America/New York"
+ * sorted by their timezone offset
+ """
+ now = now or datetime.now(pytz.utc)
+ _choices = []
+ for tz in timezones:
+ tz_str = str(tz)
+ now_tz = now.astimezone(pytz.timezone(tz_str))
+ delta = now_tz.replace(tzinfo=pytz.utc) - now
+ display = "GMT{sign}{gmt_diff} {timezone}".format(
+ sign='+' if delta == abs(delta) else '-',
+ gmt_diff=str(abs(delta)).zfill(8)[:-3],
+ timezone=tz_str.replace('_', ' ')
+ )
+ _choices.append((delta, tz, display))
+ _choices.sort(key=lambda x: x[0])
+ choices = [(one, two) for zero, one, two in _choices]
+ return choices
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-timezone-field-4.0/timezone_field/fields.py
new/django-timezone-field-4.1.2/timezone_field/fields.py
--- old/django-timezone-field-4.0/timezone_field/fields.py 2019-12-03
04:26:44.000000000 +0100
+++ new/django-timezone-field-4.1.2/timezone_field/fields.py 2021-03-17
19:45:53.000000000 +0100
@@ -4,6 +4,7 @@
from django.db import models
from django.utils.encoding import force_str
+from timezone_field.choices import standard, with_gmt_offset
from timezone_field.utils import is_pytz_instance, add_gmt_offset_to_choices
@@ -35,11 +36,9 @@
# NOTE: these defaults are excluded from migrations. If these are changed,
# existing migration files will need to be accomodated.
- CHOICES = [
- (pytz.timezone(tz), tz.replace('_', ' '))
- for tz in pytz.common_timezones
- ]
- MAX_LENGTH = 63
+ default_tzs = [pytz.timezone(tz) for tz in pytz.common_timezones]
+ default_choices = standard(default_tzs)
+ default_max_length = 63
def __init__(self, *args, **kwargs):
# allow some use of positional args up until the args we customize
@@ -47,30 +46,45 @@
#
https://github.com/django/django/blob/1.11.11/django/db/models/fields/__init__.py#L145
if len(args) > 3:
raise ValueError('Cannot specify max_length by positional arg')
+ kwargs.setdefault('max_length', self.default_max_length)
- kwargs.setdefault('choices', self.CHOICES)
- kwargs.setdefault('max_length', self.MAX_LENGTH)
- kwargs.setdefault('display_GMT_offset', False)
-
- # Choices can be specified in two forms: either
- # [<pytz.timezone>, <str>] or [<str>, <str>]
- #
- # The [<pytz.timezone>, <str>] format is the one we actually
- # store the choices in memory because of
- # https://github.com/mfogel/django-timezone-field/issues/24
- #
- # The [<str>, <str>] format is supported because since django
- # can't deconstruct pytz.timezone objects, migration files must
- # use an alternate format. Representing the timezones as strings
- # is the obvious choice.
- choices = kwargs['choices']
- if isinstance(choices[0][0], (str, bytes)):
- kwargs['choices'] = [(pytz.timezone(n1), n2) for n1, n2 in choices]
-
- if kwargs['display_GMT_offset']:
- kwargs['choices'] = add_gmt_offset_to_choices(kwargs['choices'])
- kwargs.pop('display_GMT_offset', None)
+ if 'choices' in kwargs:
+ values, displays = zip(*kwargs['choices'])
+ # Choices can be specified in two forms: either
+ # [<pytz.timezone>, <str>] or [<str>, <str>]
+ #
+ # The [<pytz.timezone>, <str>] format is the one we actually
+ # store the choices in memory because of
+ # https://github.com/mfogel/django-timezone-field/issues/24
+ #
+ # The [<str>, <str>] format is supported because since django
+ # can't deconstruct pytz.timezone objects, migration files must
+ # use an alternate format. Representing the timezones as strings
+ # is the obvious choice.
+ if not is_pytz_instance(values[0]):
+ values = [pytz.timezone(v) for v in values]
+ else:
+ values = self.default_tzs
+ displays = None
+
+ choices_display = kwargs.pop('choices_display', None)
+ if choices_display == 'WITH_GMT_OFFSET':
+ choices = with_gmt_offset(values)
+ elif choices_display == 'STANDARD':
+ choices = standard(values)
+ elif choices_display is None:
+ choices = zip(values, displays) if displays else standard(values)
+ else:
+ raise ValueError(
+ "Unrecognized value for kwarg 'choices_display' of '"
+ + choices_display + "'"
+ )
+
+ # 'display_GMT_offset' is deprecated, use 'choices_display' instead
+ if kwargs.pop('display_GMT_offset', False):
+ choices = add_gmt_offset_to_choices(choices)
+ kwargs['choices'] = choices
super(TimeZoneField, self).__init__(*args, **kwargs)
def validate(self, value, model_instance):
@@ -80,16 +94,15 @@
def deconstruct(self):
name, path, args, kwargs = super(TimeZoneField, self).deconstruct()
- if kwargs.get('choices') == self.CHOICES:
+ if kwargs.get('choices') == self.default_choices:
del kwargs['choices']
- if kwargs.get('max_length') == self.MAX_LENGTH:
+ if kwargs.get('max_length') == self.default_max_length:
del kwargs['max_length']
# django can't decontruct pytz objects, so transform choices
# to [<str>, <str>] format for writing out to the migration
if 'choices' in kwargs:
kwargs['choices'] = [(tz.zone, n) for tz, n in kwargs['choices']]
-
return name, path, args, kwargs
def get_internal_type(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-timezone-field-4.0/timezone_field/forms.py
new/django-timezone-field-4.1.2/timezone_field/forms.py
--- old/django-timezone-field-4.0/timezone_field/forms.py 2019-12-03
04:26:44.000000000 +0100
+++ new/django-timezone-field-4.1.2/timezone_field/forms.py 2021-03-17
19:45:53.000000000 +0100
@@ -2,22 +2,40 @@
from django.core.exceptions import ValidationError
from django import forms
+from timezone_field.choices import standard, with_gmt_offset
+
+
+def coerce_to_pytz(val):
+ try:
+ return pytz.timezone(val)
+ except pytz.UnknownTimeZoneError:
+ raise ValidationError("Unknown time zone: '%s'" % val)
+
class TimeZoneFormField(forms.TypedChoiceField):
+
def __init__(self, *args, **kwargs):
+ kwargs.setdefault('coerce', coerce_to_pytz)
+ kwargs.setdefault('empty_value', None)
+
+ if 'choices' in kwargs:
+ values, displays = zip(*kwargs['choices'])
+ else:
+ values = pytz.common_timezones
+ displays = None
+
+ choices_display = kwargs.pop('choices_display', None)
+ if choices_display == 'WITH_GMT_OFFSET':
+ choices = with_gmt_offset(values)
+ elif choices_display == 'STANDARD':
+ choices = standard(values)
+ elif choices_display is None:
+ choices = zip(values, displays) if displays else standard(values)
+ else:
+ raise ValueError(
+ "Unrecognized value for kwarg 'choices_display' of '"
+ + choices_display + "'"
+ )
- def coerce_to_pytz(val):
- try:
- return pytz.timezone(val)
- except pytz.UnknownTimeZoneError:
- raise ValidationError("Unknown time zone: '%s'" % val)
-
- defaults = {
- 'coerce': coerce_to_pytz,
- 'choices': [
- (tz, tz.replace('_', ' ')) for tz in pytz.common_timezones
- ],
- 'empty_value': None,
- }
- defaults.update(kwargs)
- super(TimeZoneFormField, self).__init__(*args, **defaults)
+ kwargs['choices'] = choices
+ super(TimeZoneFormField, self).__init__(*args, **kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django-timezone-field-4.0/timezone_field/rest_framework.py
new/django-timezone-field-4.1.2/timezone_field/rest_framework.py
--- old/django-timezone-field-4.0/timezone_field/rest_framework.py
1970-01-01 01:00:00.000000000 +0100
+++ new/django-timezone-field-4.1.2/timezone_field/rest_framework.py
2021-03-17 19:45:53.000000000 +0100
@@ -0,0 +1,19 @@
+import pytz
+from django.utils.translation import gettext_lazy as _
+from django.utils.encoding import force_str
+from rest_framework.fields import Field
+
+
+class TimeZoneSerializerField(Field):
+ default_error_messages = {
+ 'invalid': _('A valid timezone is required.'),
+ }
+
+ def to_internal_value(self, data):
+ try:
+ return pytz.timezone(force_str(data))
+ except pytz.UnknownTimeZoneError:
+ self.fail('invalid')
+
+ def to_representation(self, value):
+ return str(value)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-timezone-field-4.0/timezone_field/utils.py
new/django-timezone-field-4.1.2/timezone_field/utils.py
--- old/django-timezone-field-4.0/timezone_field/utils.py 2019-12-03
04:26:44.000000000 +0100
+++ new/django-timezone-field-4.1.2/timezone_field/utils.py 2021-03-17
19:45:53.000000000 +0100
@@ -6,14 +6,13 @@
return value is pytz.UTC or isinstance(value, pytz.tzinfo.BaseTzInfo)
+# DEPRECATED. Use choices.with_gmt_offset instead
def add_gmt_offset_to_choices(timezone_tuple_set):
"""
Currently timezone choices items show up like this:
'America/New_York'
-
But this function formats the choices to display in this format:
GMT-05:00 America/New_York
-
:return:
A list of tuples in this format:
(<pytz.timezone>, <str>)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-timezone-field-4.0/tox.ini
new/django-timezone-field-4.1.2/tox.ini
--- old/django-timezone-field-4.0/tox.ini 2019-12-03 04:26:44.000000000
+0100
+++ new/django-timezone-field-4.1.2/tox.ini 2021-03-17 19:45:53.000000000
+0100
@@ -2,18 +2,21 @@
envlist =
coverage-clean,
py35-{django22}-{sqlite,postgres},
- py36-{django22,django30}-{sqlite,postgres},
- py37-{django22,django30}-{sqlite,postgres},
- py38-{django22,django30}-{sqlite,postgres},
+ py36-{django22,django30,django31}-{sqlite,postgres},
+ py37-{django22,django30,django31}-{sqlite,postgres},
+ py38-{django22,django30,django31}-{sqlite,postgres},
+ py39-{django22,django30,django31}-{sqlite,postgres},
coverage-report,
- py38-flake8
+ py39-flake8
[testenv]
commands = coverage run --include='*/timezone_field/*'
{envbindir}/django-admin.py test tests
deps =
coverage
- django22: django>=2.2.8,<2.3
+ djangorestframework>=3.0.0
+ django22: django>=2.2.17,<2.3
django30: django>=3.0.0,<3.1
+ django31: django>=3.1.0,<3.2
postgres: psycopg2-binary
setenv =
PYTHONPATH = {toxinidir}
@@ -21,7 +24,7 @@
sqlite: TEST_DB_ENGINE=sqlite
postgres: TEST_DB_ENGINE=postgres
-[testenv:py38-flake8]
+[testenv:py39-flake8]
commands = flake8
deps = flake8