Skip to content
Snippets Groups Projects
Commit f7df5b04 authored by Michael Fladischer's avatar Michael Fladischer
Browse files

importing python-django-timezone-field_3.0.orig.tar.gz

parents
No related branches found
No related tags found
No related merge requests found
.coverage
.tox
*.pyc
*.egg-info
htmlcov
build
dist
sudo: false
language: python
python:
- 2.7
- 3.4
- 3.5
- 3.6
install:
- pip install tox-travis coveralls
before_script:
- psql -c 'create database timezone_field_tests;' -U postgres
script:
- tox
after_success:
- coveralls
Copyright (c) 2014, Mike Fogel <mike@fogel.ca>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
include LICENSE.txt MANIFEST.in README.rst
recursive-exclude tests *
django-timezone-field
=====================
.. image:: https://img.shields.io/travis/mfogel/django-timezone-field/develop.svg
:target: https://travis-ci.org/mfogel/django-timezone-field/
.. image:: https://img.shields.io/coveralls/mfogel/django-timezone-field/develop.svg
:target: https://coveralls.io/r/mfogel/django-timezone-field/
.. image:: https://img.shields.io/pypi/dm/django-timezone-field.svg
:target: https://pypi.python.org/pypi/django-timezone-field/
A Django app providing database and form fields for `pytz`__ timezone objects.
Examples
--------
Database Field
~~~~~~~~~~~~~~
.. code:: python
import pytz
from django.db import models
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
)
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>"
Form Field
~~~~~~~~~~
.. code:: python
from django import forms
from timezone_field import TimeZoneFormField
class MyForm(forms.Form):
timezone = TimeZoneFormField()
my_form = MyForm({
'timezone': 'America/Los_Angeles',
})
my_form.full_clean() # validates against pytz.common_timezones
tz = my_form.cleaned_data['timezone'] # values retrieved as pytz objects
repr(tz) # "<DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>"
Installation
------------
#. From `pypi`__ using `pip`__:
.. code:: sh
pip install django-timezone-field
#. Add `timezone_field` to your `settings.INSTALLED_APPS`__:
.. code:: python
INSTALLED_APPS = (
...
'timezone_field',
...
)
Changelog
------------
* 3.0 (2018-09-15)
* Support django 1.11, 2.0, 2.1
* Add support for python 3.7
* Change default human-readable timezone names to exclude underscores
(`#32`__ & `#37`__)
* 2.1 (2018-03-01)
* Add support for django 1.10, 1.11
* Add support for python 3.6
* Add wheel support
* Support bytes in DB fields (`#38`__ & `#39`__)
* 2.0 (2016-01-31)
* Drop support for django 1.7, add support for django 1.9
* Drop support for python 3.2, 3.3, add support for python 3.5
* Remove tests from source distribution
* 1.3 (2015-10-12)
* Drop support for django 1.6, add support for django 1.8
* Various `bug fixes`__
* 1.2 (2015-02-05)
* For form field, changed default list of accepted timezones from
`pytz.all_timezones` to `pytz.common_timezones`, to match DB field
behavior.
* 1.1 (2014-10-05)
* Django 1.7 compatibility
* Added support for formating `choices` kwarg as `[[<str>, <str>], ...]`,
in addition to previous format of `[[<pytz.timezone>, <str>], ...]`.
* Changed default list of accepted timezones from `pytz.all_timezones` to
`pytz.common_timezones`. If you have timezones in your DB that are in
`pytz.all_timezones` but not in `pytz.common_timezones`, this is a
backward-incompatible change. Old behavior can be restored by
specifying `choices=[(tz, tz) for tz in pytz.all_timezones]` in your
model definition.
* 1.0 (2013-08-04)
* Initial release as `timezone_field`.
Running the Tests
-----------------
#. Install `tox`__.
#. From the repository root, run
.. code:: sh
tox
Postgres will need to be running locally, and sqlite will need to be
installed in order for tox to do its job.
Found a Bug?
------------
To file a bug or submit a patch, please head over to `django-timezone-field on github`__.
Credits
-------
Originally adapted from `Brian Rosner's django-timezones`__. The full list of contributors is available on `github`__.
__ http://pypi.python.org/pypi/pytz/
__ 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/32
__ https://github.com/mfogel/django-timezone-field/issues/37
__ https://github.com/mfogel/django-timezone-field/issues/38
__ https://github.com/mfogel/django-timezone-field/issues/39
__ https://github.com/mfogel/django-timezone-field/issues?q=milestone%3A1.3
__ https://tox.readthedocs.org/
__ https://github.com/mfogel/django-timezone-field/
__ https://github.com/brosner/django-timezones/
__ https://github.com/mfogel/django-timezone-field/graphs/contributors
[bdist_wheel]
universal = 1
setup.py 0 → 100644
import re
from os import path
from setuptools import setup
# read() and find_version() taken from jezdez's python apps, ex:
# https://github.com/jezdez/django_compressor/blob/develop/setup.py
def read(*parts):
return open(path.join(path.dirname(__file__), *parts)).read()
def find_version(*file_paths):
version_file = read(*file_paths)
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError("Unable to find version string.")
setup(
name='django-timezone-field',
version=find_version('timezone_field', '__init__.py'),
author='Mike Fogel',
author_email='mike@fogel.ca',
description=(
'A Django app providing database and form fields for '
'pytz timezone objects.'
),
long_description=read('README.rst'),
url='http://github.com/mfogel/django-timezone-field/',
license='BSD',
packages=[
'timezone_field',
],
install_requires=['django>=1.8', 'pytz'],
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Topic :: Utilities',
'Framework :: Django',
],
)
from django.db import models
from timezone_field import TimeZoneField
class TestModel(models.Model):
tz = TimeZoneField()
tz_opt = TimeZoneField(blank=True)
tz_opt_default = TimeZoneField(blank=True, default='America/Los_Angeles')
"""
Django settings for tests project.
For more information on this file, see
https://docs.djangoproject.com/en/1.7/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.7/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'unused'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
TEMPLATE_DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'timezone_field',
'tests',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
WSGI_APPLICATION = 'tests.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
test_db_engine = os.environ.get('TEST_DB_ENGINE', 'sqlite')
if test_db_engine == 'sqlite':
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
},
}
if test_db_engine == 'postgres':
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'timezone_field_tests',
'USER': 'postgres',
},
}
# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/
STATIC_URL = '/static/'
from __future__ import absolute_import
import pytz
from django import forms
from django.core.exceptions import ValidationError
from django.db import models
from django.db.migrations.writer import MigrationWriter
from django.test import TestCase
from django.utils import six
from timezone_field import TimeZoneField, TimeZoneFormField
from tests.models import TestModel
PST = 'America/Los_Angeles' # pytz.tzinfo.DstTzInfo
GMT = 'GMT' # pytz.tzinfo.StaticTzInfo
UTC = 'UTC' # pytz.UTC singleton
PST_tz = pytz.timezone(PST)
GMT_tz = pytz.timezone(GMT)
UTC_tz = pytz.timezone(UTC)
INVALID_TZ = 'ogga booga'
UNCOMMON_TZ = 'Singapore'
USA_TZS = [
'US/Alaska',
'US/Arizona',
'US/Central',
'US/Eastern',
'US/Hawaii',
'US/Mountain',
'US/Pacific',
]
class TestForm(forms.Form):
tz = TimeZoneFormField()
tz_opt = TimeZoneFormField(required=False)
class TestModelForm(forms.ModelForm):
class Meta:
model = TestModel
fields = '__all__'
class TimeZoneFormFieldTestCase(TestCase):
def test_valid1(self):
form = TestForm({'tz': PST})
self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data['tz'], PST_tz)
self.assertEqual(form.cleaned_data['tz_opt'], None)
def test_valid2(self):
form = TestForm({'tz': GMT, 'tz_opt': UTC})
self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data['tz'], GMT_tz)
self.assertEqual(form.cleaned_data['tz_opt'], UTC_tz)
def test_invalid_invalid_str(self):
form = TestForm({'tz': INVALID_TZ})
self.assertFalse(form.is_valid())
def test_invalid_uncommon_tz(self):
form = TestForm({'tz': UNCOMMON_TZ})
self.assertFalse(form.is_valid())
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')
class TimeZoneFieldModelFormTestCase(TestCase):
def test_valid_with_defaults(self):
# seems there should be a better way to get a form's default values...?
# http://stackoverflow.com/questions/7399490/
data = dict(
(field_name, field.initial)
for field_name, field in six.iteritems(TestModelForm().fields)
)
data.update({'tz': GMT})
form = TestModelForm(data=data)
self.assertTrue(form.is_valid())
form.save()
self.assertEqual(TestModel.objects.count(), 1)
m = TestModel.objects.get()
self.assertEqual(m.tz, GMT_tz)
self.assertEqual(m.tz_opt, None)
self.assertEqual(m.tz_opt_default, PST_tz)
def test_valid_specify_all(self):
form = TestModelForm({
'tz': UTC,
'tz_opt': PST,
'tz_opt_default': GMT,
})
self.assertTrue(form.is_valid())
form.save()
self.assertEqual(TestModel.objects.count(), 1)
m = TestModel.objects.get()
self.assertEqual(m.tz, UTC_tz)
self.assertEqual(m.tz_opt, PST_tz)
self.assertEqual(m.tz_opt_default, GMT_tz)
def test_invalid_not_blank(self):
form = TestModelForm({})
self.assertFalse(form.is_valid())
self.assertTrue(any('required' in e for e in form.errors['tz']))
def test_invalid_choice(self):
form = TestModelForm({'tz': INVALID_TZ})
self.assertFalse(form.is_valid())
self.assertTrue(any('choice' in e for e in form.errors['tz']))
def test_invalid_uncommmon_tz(self):
form = TestModelForm({'tz': UNCOMMON_TZ})
self.assertFalse(form.is_valid())
self.assertTrue(any('choice' in e for e in form.errors['tz']))
def test_default_human_readable_choices_dont_have_underscores(self):
form = TestModelForm()
pst_choice = [c for c in form.fields['tz'].choices if c[0] == PST_tz]
self.assertEqual(pst_choice[0][1], 'America/Los Angeles')
class TimeZoneFieldTestCase(TestCase):
def test_valid_dst_tz_objects(self):
m = TestModel.objects.create(tz=PST_tz, tz_opt=PST_tz,
tz_opt_default=PST_tz)
m.full_clean()
m = TestModel.objects.get(pk=m.pk)
self.assertEqual(m.tz, PST_tz)
self.assertEqual(m.tz_opt, PST_tz)
self.assertEqual(m.tz_opt_default, PST_tz)
def test_valid_dst_tz_strings(self):
m = TestModel.objects.create(tz=PST, tz_opt=PST, tz_opt_default=PST)
m.full_clean()
m = TestModel.objects.get(pk=m.pk)
self.assertEqual(m.tz, PST_tz)
self.assertEqual(m.tz_opt, PST_tz)
self.assertEqual(m.tz_opt_default, PST_tz)
def test_valid_static_tz_objects(self):
m = TestModel.objects.create(tz=GMT_tz, tz_opt=GMT_tz,
tz_opt_default=GMT_tz)
m.full_clean()
m = TestModel.objects.get(pk=m.pk)
self.assertEqual(m.tz, GMT_tz)
self.assertEqual(m.tz_opt, GMT_tz)
self.assertEqual(m.tz_opt_default, GMT_tz)
def test_valid_static_tz_strings(self):
m = TestModel.objects.create(tz=GMT, tz_opt=GMT, tz_opt_default=GMT)
m.full_clean()
m = TestModel.objects.get(pk=m.pk)
self.assertEqual(m.tz, GMT_tz)
self.assertEqual(m.tz_opt, GMT_tz)
self.assertEqual(m.tz_opt_default, GMT_tz)
def test_valid_UTC_object(self):
m = TestModel.objects.create(tz=UTC_tz, tz_opt=UTC_tz,
tz_opt_default=UTC_tz)
m.full_clean()
m = TestModel.objects.get(pk=m.pk)
self.assertEqual(m.tz, UTC_tz)
self.assertEqual(m.tz_opt, UTC_tz)
self.assertEqual(m.tz_opt_default, UTC_tz)
def test_valid_UTC_string(self):
m = TestModel.objects.create(tz=UTC, tz_opt=UTC, tz_opt_default=UTC)
m.full_clean()
m = TestModel.objects.get(pk=m.pk)
self.assertEqual(m.tz, UTC_tz)
self.assertEqual(m.tz_opt, UTC_tz)
self.assertEqual(m.tz_opt_default, UTC_tz)
def test_valid_default_values(self):
m = TestModel.objects.create(tz=UTC_tz)
m.full_clean()
m = TestModel.objects.get(pk=m.pk)
self.assertEqual(m.tz_opt, None)
self.assertEqual(m.tz_opt_default, PST_tz)
def test_valid_default_values_without_saving_to_db(self):
m = TestModel(tz=UTC_tz)
m.full_clean()
self.assertEqual(m.tz_opt, None)
self.assertEqual(m.tz_opt_default, PST_tz)
def test_valid_blank_str(self):
m = TestModel.objects.create(tz=PST, tz_opt='')
m.full_clean()
m = TestModel.objects.get(pk=m.pk)
self.assertEqual(m.tz_opt, None)
def test_valid_blank_none(self):
m = TestModel.objects.create(tz=PST, tz_opt=None)
m.full_clean()
m = TestModel.objects.get(pk=m.pk)
self.assertEqual(m.tz_opt, None)
def test_string_value_lookup(self):
TestModel.objects.create(tz=PST)
qs = TestModel.objects.filter(tz=PST)
self.assertEqual(qs.count(), 1)
def test_tz_value_lookup(self):
TestModel.objects.create(tz=PST)
qs = TestModel.objects.filter(tz=PST_tz)
self.assertEqual(qs.count(), 1)
def test_invalid_blank(self):
m = TestModel()
self.assertRaises(ValidationError, m.full_clean)
def test_invalid_blank_str(self):
m = TestModel(tz='')
self.assertRaises(ValidationError, m.full_clean)
def test_invalid_blank_none(self):
m = TestModel(tz=None)
self.assertRaises(ValidationError, m.full_clean)
def test_invalid_choice(self):
m = TestModel(tz=INVALID_TZ)
self.assertRaises(ValidationError, m.full_clean)
m = TestModel(tz=4)
self.assertRaises(ValidationError, m.full_clean)
m = TestModel(tz=object())
self.assertRaises(ValidationError, m.full_clean)
def test_some_positional_args_ok(self):
TimeZoneField('a verbose name', 'a name', True)
def test_too_many_positional_args_not_ok(self):
def createField():
TimeZoneField('a verbose name', 'a name', True, 42)
self.assertRaises(ValueError, createField)
def test_default_human_readable_choices_dont_have_underscores(self):
m = TestModel(tz=PST_tz)
self.assertEqual(m.get_tz_display(), 'America/Los Angeles')
class TimeZoneFieldLimitedChoicesTestCase(TestCase):
invalid_superset_tz = 'not a tz'
invalid_subset_tz = 'Europe/Brussels'
class TestModelChoice(models.Model):
tz_superset = TimeZoneField(
choices=[(tz, tz) for tz in pytz.all_timezones],
blank=True,
)
tz_subset = TimeZoneField(
choices=[(tz, tz) for tz in USA_TZS],
blank=True,
)
class TestModelOldChoiceFormat(models.Model):
tz_superset = TimeZoneField(
choices=[(pytz.timezone(tz), tz) for tz in pytz.all_timezones],
blank=True,
)
tz_subset = TimeZoneField(
choices=[(pytz.timezone(tz), tz) for tz in USA_TZS],
blank=True,
)
def test_valid_choice(self):
self.TestModelChoice.objects.create(tz_superset=PST, tz_subset=PST)
m = self.TestModelChoice.objects.get()
self.assertEqual(m.tz_superset, PST_tz)
self.assertEqual(m.tz_subset, PST_tz)
def test_invalid_choice(self):
m = self.TestModelChoice(tz_superset=self.invalid_superset_tz)
self.assertRaises(ValidationError, m.full_clean)
m = self.TestModelChoice(tz_subset=self.invalid_subset_tz)
self.assertRaises(ValidationError, m.full_clean)
def test_valid_choice_old_format(self):
self.TestModelOldChoiceFormat.objects.create(
tz_superset=PST, tz_subset=PST,
)
m = self.TestModelOldChoiceFormat.objects.get()
self.assertEqual(m.tz_superset, PST_tz)
self.assertEqual(m.tz_subset, PST_tz)
def test_invalid_choice_old_format(self):
m = self.TestModelOldChoiceFormat(tz_superset=self.invalid_superset_tz)
self.assertRaises(ValidationError, m.full_clean)
m = self.TestModelOldChoiceFormat(tz_subset=self.invalid_subset_tz)
self.assertRaises(ValidationError, m.full_clean)
class TimeZoneFieldDeconstructTestCase(TestCase):
test_fields = (
TimeZoneField(),
TimeZoneField(default='UTC'),
TimeZoneField(max_length=42),
TimeZoneField(choices=[
(pytz.timezone('US/Pacific'), 'US/Pacific'),
(pytz.timezone('US/Eastern'), 'US/Eastern'),
]),
TimeZoneField(choices=[
(pytz.timezone(b'US/Pacific'), b'US/Pacific'),
(pytz.timezone(b'US/Eastern'), b'US/Eastern'),
]),
TimeZoneField(choices=[
('US/Pacific', 'US/Pacific'),
('US/Eastern', 'US/Eastern'),
]),
TimeZoneField(choices=[
(b'US/Pacific', b'US/Pacific'),
(b'US/Eastern', b'US/Eastern'),
]),
)
def test_deconstruct(self):
for org_field in self.test_fields:
name, path, args, kwargs = org_field.deconstruct()
new_field = TimeZoneField(*args, **kwargs)
self.assertEqual(org_field.max_length, new_field.max_length)
self.assertEqual(org_field.choices, new_field.choices)
def test_full_serialization(self):
# ensure the values passed to kwarg arguments can be serialized
# the recommended 'deconstruct' testing by django docs doesn't cut it
# https://docs.djangoproject.com/en/1.7/howto/custom-model-fields/#field-deconstruction
# replicates https://github.com/mfogel/django-timezone-field/issues/12
for field in self.test_fields:
# ensuring the following call doesn't throw an error
MigrationWriter.serialize(field)
def test_from_db_value(self):
"""
Verify that the field can handle data coming back as bytes from the
db.
"""
field = TimeZoneField()
# django 1.11 signuature
value = field.from_db_value(b'UTC', None, None, None)
self.assertEqual(pytz.UTC, value)
# django 2.0+ signuature
value = field.from_db_value(b'UTC', None, None)
self.assertEqual(pytz.UTC, value)
def test_default_kwargs_not_frozen(self):
"""
Ensure the deconstructed representation of the field does not contain
kwargs if they match the default.
Don't want to bloat everyone's migration files.
"""
field = TimeZoneField()
name, path, args, kwargs = field.deconstruct()
self.assertNotIn('choices', kwargs)
self.assertNotIn('max_length', kwargs)
def test_specifying_defaults_not_frozen(self):
"""
If someone's matched the default values with their kwarg args, we
shouldn't bothering freezing those.
"""
field = TimeZoneField(max_length=63)
name, path, args, kwargs = field.deconstruct()
self.assertNotIn('max_length', kwargs)
choices = [
(pytz.timezone(tz), tz.replace('_', ' '))
for tz in pytz.common_timezones
]
field = TimeZoneField(choices=choices)
name, path, args, kwargs = field.deconstruct()
self.assertNotIn('choices', kwargs)
choices = [(tz, tz.replace('_', ' ')) for tz in pytz.common_timezones]
field = TimeZoneField(choices=choices)
name, path, args, kwargs = field.deconstruct()
self.assertNotIn('choices', kwargs)
from timezone_field.fields import TimeZoneField
from timezone_field.forms import TimeZoneFormField
__version__ = '3.0'
__all__ = ['TimeZoneField', 'TimeZoneFormField']
import pytz
from django.core.exceptions import ValidationError
from django.db import models
from django.utils import six
from django.utils.encoding import force_text
from timezone_field.utils import is_pytz_instance
class TimeZoneField(models.Field):
"""
Provides database store for pytz timezone objects.
Valid inputs:
* any instance of pytz.tzinfo.DstTzInfo or pytz.tzinfo.StaticTzInfo
* the pytz.UTC singleton
* any string that validates against pytz.common_timezones. pytz will
be used to build a timezone object from the string.
* None and the empty string both represent 'no timezone'
Valid outputs:
* None
* instances of pytz.tzinfo.DstTzInfo and pytz.tzinfo.StaticTzInfo
* the pytz.UTC singleton
Blank values are stored in the DB as the empty string. Timezones are stored
in their string representation.
The `choices` kwarg can be specified as a list of either
[<pytz.timezone>, <str>] or [<str>, <str>]. Internally, it is stored as
[<pytz.timezone>, <str>].
"""
description = "A pytz timezone object"
# 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
def __init__(self, *args, **kwargs):
# allow some use of positional args up until the args we customize
# https://github.com/mfogel/django-timezone-field/issues/42
# 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('choices', self.CHOICES)
kwargs.setdefault('max_length', self.MAX_LENGTH)
# 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], (six.string_types, six.binary_type)):
kwargs['choices'] = [(pytz.timezone(n1), n2) for n1, n2 in choices]
super(TimeZoneField, self).__init__(*args, **kwargs)
def validate(self, value, model_instance):
if not is_pytz_instance(value):
raise ValidationError("'%s' is not a pytz timezone object" % value)
super(TimeZoneField, self).validate(value, model_instance)
def deconstruct(self):
name, path, args, kwargs = super(TimeZoneField, self).deconstruct()
if kwargs['choices'] == self.CHOICES:
del kwargs['choices']
if kwargs['max_length'] == self.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):
return 'CharField'
def get_default(self):
# allow defaults to be still specified as strings. Allows for easy
# serialization into migration files
value = super(TimeZoneField, self).get_default()
return self._get_python_and_db_repr(value)[0]
def from_db_value(self, value, *args):
"Convert to pytz timezone object"
return self._get_python_and_db_repr(value)[0]
def to_python(self, value):
"Convert to pytz timezone object"
return self._get_python_and_db_repr(value)[0]
def get_prep_value(self, value):
"Convert to string describing a valid pytz timezone object"
return self._get_python_and_db_repr(value)[1]
def _get_python_and_db_repr(self, value):
"Returns a tuple of (python representation, db representation)"
if value is None or value == '':
return (None, '')
if is_pytz_instance(value):
return (value, value.zone)
try:
return (pytz.timezone(force_text(value)), force_text(value))
except pytz.UnknownTimeZoneError:
pass
raise ValidationError("Invalid timezone '%s'" % value)
import pytz
from django.core.exceptions import ValidationError
from django import forms
class TimeZoneFormField(forms.TypedChoiceField):
def __init__(self, *args, **kwargs):
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)
# intentionally left blank
import pytz
def is_pytz_instance(value):
return value is pytz.UTC or isinstance(value, pytz.tzinfo.BaseTzInfo)
tox.ini 0 → 100644
[tox]
envlist =
coverage-clean,
py27-django111-{sqlite,postgres},
py34-{django111,django20}-{sqlite,postgres},
py35-{django111,django20,django21}-{sqlite,postgres},
py36-{django111,django20,django21}-{sqlite,postgres},
py37-{django20,django21}-{sqlite,postgres},
coverage-report,
flake8
[testenv]
commands = coverage run --include='*/timezone_field/*' {envbindir}/django-admin.py test tests
deps =
coverage
django111: django>=1.11,<2.0
django20: django>=2.0,<2.1
django21: django>=2.1,<2.2
postgres: psycopg2-binary
setenv =
PYTHONPATH = {toxinidir}
DJANGO_SETTINGS_MODULE=tests.settings
sqlite: TEST_DB_ENGINE=sqlite
postgres: TEST_DB_ENGINE=postgres
[testenv:flake8]
basepython = python2.7
commands = flake8
deps = flake8
[testenv:coverage-clean]
commands = coverage erase
[testenv:coverage-report]
commands =
coverage report
coverage html
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment