From f763edc7657054320c43a59f394a6fb89f77f894 Mon Sep 17 00:00:00 2001 From: Ian Foote Date: Tue, 27 Oct 2015 12:58:38 +0000 Subject: [PATCH 1/5] Add manage.py to allow creating test migrations Add test settings file too. --- manage.py | 10 +++++++++ user_management/tests/settings.py | 35 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100755 manage.py create mode 100644 user_management/tests/settings.py diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..0eae09c --- /dev/null +++ b/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "user_management.tests.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/user_management/tests/settings.py b/user_management/tests/settings.py new file mode 100644 index 0000000..4d12821 --- /dev/null +++ b/user_management/tests/settings.py @@ -0,0 +1,35 @@ +SECRET_KEY = 'not-for-production' +DEBUG = True + +ALLOWED_HOSTS = [] + +INSTALLED_APPS = ( + # Put contenttypes before auth to work around test issue. + # See: https://code.djangoproject.com/ticket/10827#comment:12 + 'django.contrib.sites', + 'django.contrib.contenttypes', + 'django.contrib.auth', + 'django.contrib.sessions', + 'django.contrib.admin', + + 'rest_framework.authtoken', + + # Added for templates + 'user_management.api', + 'user_management.models.tests', +) + +AUTH_USER_MODEL = 'tests.User' + +MIDDLEWARE_CLASSES = () + +LANGUAGE_CODE = 'en-us' +TIME_ZONE = 'UTC' +USE_I18N = True +USE_L10N = True +USE_TZ = True + +MIGRATION_MODULES = { + 'api': 'user_management.tests.testmigrations.api', + 'tests': 'user_management.tests.testmigrations.tests', +} From 971c2c8dfc1b02fd84b68b7c2b358cd560177073 Mon Sep 17 00:00:00 2001 From: Ian Foote Date: Tue, 27 Oct 2015 12:59:31 +0000 Subject: [PATCH 2/5] Add TimeZoneMixin with timezone field --- user_management/models/mixins.py | 8 ++++++++ user_management/models/tests/models.py | 19 ++++++++++++++----- user_management/models/tests/test_models.py | 1 + .../tests/0002_user_timezone.py | 19 +++++++++++++++++++ 4 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 user_management/tests/testmigrations/tests/0002_user_timezone.py diff --git a/user_management/models/mixins.py b/user_management/models/mixins.py index bd3dd90..d493541 100644 --- a/user_management/models/mixins.py +++ b/user_management/models/mixins.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.contrib.auth.models import BaseUserManager from django.contrib.auth.tokens import default_token_generator from django.contrib.sites.models import Site @@ -55,6 +56,13 @@ class Meta: abstract = True +class TimeZoneMixin(models.Model): + timezone = models.CharField(max_length=255, default=settings.TIME_ZONE) + + class Meta: + abstract = True + + class EmailUserMixin(models.Model): email = models.EmailField( verbose_name=_('Email address'), diff --git a/user_management/models/tests/models.py b/user_management/models/tests/models.py index 1bb2221..24f1f7c 100644 --- a/user_management/models/tests/models.py +++ b/user_management/models/tests/models.py @@ -11,20 +11,23 @@ EmailVerifyUserMixin, IsStaffUserMixin, NameUserMethodsMixin, + TimeZoneMixin, VerifyEmailMixin, ) -class User(AvatarMixin, VerifyEmailMixin, PermissionsMixin, AbstractBaseUser): - pass +class User( + AvatarMixin, TimeZoneMixin, VerifyEmailMixin, PermissionsMixin, + AbstractBaseUser): + """A User model using all the custom mixins.""" class BasicUser(BasicUserFieldsMixin, AbstractBaseUser): - pass + """A User model using just the BasicUserFieldsMixin.""" class VerifyEmailUser(VerifyEmailMixin, AbstractBaseUser): - pass + """A User model using just the VerifyEmailMixin.""" class CustomVerifyEmailUser(VerifyEmailMixin, AbstractBaseUser): @@ -35,6 +38,12 @@ class CustomVerifyEmailUser(VerifyEmailMixin, AbstractBaseUser): class CustomBasicUserFieldsMixin( NameUserMethodsMixin, EmailUserMixin, DateJoinedUserMixin, IsStaffUserMixin): + """ + A replacement for BasicUserFieldsMixin with a custom name field. + + Uses NameUserMethodsMixin instead of NameUserMixin. + """ + name = models.TextField() USERNAME_FIELD = 'email' @@ -46,4 +55,4 @@ class Meta: class CustomNameUser( AvatarMixin, EmailVerifyUserMixin, CustomBasicUserFieldsMixin, AbstractBaseUser): - pass + """A User model using the CustomBasicUserFieldsMixin.""" diff --git a/user_management/models/tests/test_models.py b/user_management/models/tests/test_models.py index 23d6b2f..cecd9d1 100644 --- a/user_management/models/tests/test_models.py +++ b/user_management/models/tests/test_models.py @@ -48,6 +48,7 @@ def test_fields(self): 'last_login', 'password', 'avatar', + 'timezone', # Incoming 'groups', # Django permission groups diff --git a/user_management/tests/testmigrations/tests/0002_user_timezone.py b/user_management/tests/testmigrations/tests/0002_user_timezone.py new file mode 100644 index 0000000..4a7168e --- /dev/null +++ b/user_management/tests/testmigrations/tests/0002_user_timezone.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tests', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='timezone', + field=models.CharField(default='UTC', max_length=255), + ), + ] From 41e8371620b2e7c3c93db021361d520dde5d049a Mon Sep 17 00:00:00 2001 From: Ian Foote Date: Tue, 27 Oct 2015 14:20:00 +0000 Subject: [PATCH 3/5] Add TimeZoneMixin to changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08c035e..5a8933d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## Upcoming + +* Add `TimeZoneMixin` for custom `User` models. + ## v12.0.1 * Ensure new and old passwords differ when changing password. From 959678ca84e8715bff1a621099083cab33abf68e Mon Sep 17 00:00:00 2001 From: Ian Foote Date: Tue, 27 Oct 2015 15:45:14 +0000 Subject: [PATCH 4/5] Add timezone_choices helper function --- user_management/models/tests/test_utils.py | 53 ++++++++++++++++++++++ user_management/models/utils.py | 20 ++++++++ 2 files changed, 73 insertions(+) create mode 100644 user_management/models/tests/test_utils.py create mode 100644 user_management/models/utils.py diff --git a/user_management/models/tests/test_utils.py b/user_management/models/tests/test_utils.py new file mode 100644 index 0000000..03b7ad8 --- /dev/null +++ b/user_management/models/tests/test_utils.py @@ -0,0 +1,53 @@ +from unittest import TestCase + +from ..utils import timezone_choices + + +class TestTimeZoneChoices(TestCase): + timezones = [ + 'Arctic/Longyearbyen', + 'Asia/Qyzylorda', + 'America/Argentina/Ushuaia', + 'America/Iqaluit', + 'GMT', + 'Indian/Christmas', + 'UTC', + ] + + def setUp(self): + self.choices = timezone_choices(self.timezones) + + def test_exclude_GMT_UTC(self): + self.assertNotIn('GMT', self.choices) + self.assertNotIn('UTC', self.choices) + + def test_choice_groups(self): + expected_choices = ( + ( + 'Arctic', + ( + ('Arctic/Longyearbyen', 'Longyearbyen'), + ), + ), + ( + 'Asia', + ( + ('Asia/Qyzylorda', 'Qyzylorda'), + ), + ), + ( + 'America', + ( + ('America/Argentina/Ushuaia', 'Argentina/Ushuaia'), + ('America/Iqaluit', 'Iqaluit'), + ), + ), + ( + 'Indian', + ( + ('Indian/Christmas', 'Christmas'), + ), + ), + ) + + self.assertEqual(self.choices, expected_choices) diff --git a/user_management/models/utils.py b/user_management/models/utils.py new file mode 100644 index 0000000..8e148e2 --- /dev/null +++ b/user_management/models/utils.py @@ -0,0 +1,20 @@ +from collections import OrderedDict, defaultdict + + +class DefaultOrderedDict(OrderedDict, defaultdict): + def __init__(self, default_factory=None, *args, **kwargs): + super().__init__(*args, **kwargs) + self.default_factory = default_factory + + +def timezone_choices(timezones, exclude=('GMT', 'UTC')): + choices = DefaultOrderedDict(list) + + for timezone in timezones: + if timezone in exclude: + continue + + continent, _sep, town = timezone.partition('/') + choices[continent].append((timezone, town)) + + return tuple((c, tuple(t)) for c, t in choices.items()) From 82855e8ab46cf7a125b7bf45f3792cf48749daf5 Mon Sep 17 00:00:00 2001 From: Ian Foote Date: Tue, 27 Oct 2015 15:53:00 +0000 Subject: [PATCH 5/5] Add choices to TimeZoneMixin.timezone field Use `pytz.common_timezones` to allow simple updates if timezones change. --- requirements.txt | 2 +- user_management/models/mixins.py | 8 +++++-- user_management/models/utils.py | 2 +- .../tests/0003_user_timezone_choices.py | 21 +++++++++++++++++++ 4 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 user_management/tests/testmigrations/tests/0003_user_timezone_choices.py diff --git a/requirements.txt b/requirements.txt index 8771cdb..8545813 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ coverage==3.7.1 dj-database-url==0.3.0 dj-inmemorystorage==1.3.0 factory_boy==2.5.2 -flake8==2.3.0 +flake8==2.5.0 flake8-import-order==0.5.1 incuna-test-utils==6.0.0 mkdocs==0.11.1 diff --git a/user_management/models/mixins.py b/user_management/models/mixins.py index d493541..1725336 100644 --- a/user_management/models/mixins.py +++ b/user_management/models/mixins.py @@ -1,4 +1,4 @@ -from django.conf import settings +import pytz from django.contrib.auth.models import BaseUserManager from django.contrib.auth.tokens import default_token_generator from django.contrib.sites.models import Site @@ -10,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _ from user_management.utils import notifications +from .utils import timezone_choices class UserManager(BaseUserManager): @@ -57,7 +58,10 @@ class Meta: class TimeZoneMixin(models.Model): - timezone = models.CharField(max_length=255, default=settings.TIME_ZONE) + timezone = models.CharField( + max_length=255, + choices=timezone_choices(pytz.common_timezones), + ) class Meta: abstract = True diff --git a/user_management/models/utils.py b/user_management/models/utils.py index 8e148e2..6fb0b76 100644 --- a/user_management/models/utils.py +++ b/user_management/models/utils.py @@ -1,4 +1,4 @@ -from collections import OrderedDict, defaultdict +from collections import defaultdict, OrderedDict class DefaultOrderedDict(OrderedDict, defaultdict): diff --git a/user_management/tests/testmigrations/tests/0003_user_timezone_choices.py b/user_management/tests/testmigrations/tests/0003_user_timezone_choices.py new file mode 100644 index 0000000..a835076 --- /dev/null +++ b/user_management/tests/testmigrations/tests/0003_user_timezone_choices.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import pytz +from django.db import migrations, models +from user_management.models.utils import timezone_choices + + +class Migration(migrations.Migration): + + dependencies = [ + ('tests', '0002_user_timezone'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='timezone', + field=models.CharField(choices=timezone_choices(pytz.common_timezones), max_length=255), + ), + ]