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
 

Reply via email to