Commits

Peter Sagerson  committed 02d6b58

Major unit test cleanup.

Unit tests should now pass or skip in all supported Django versions, with or without custom user models and timezone support. Three new test projects cover the default user model, a custom model, and an exotic model that we can't instantiate. New versions of all packages.

  • Participants
  • Parent commits e1b50b1

Comments (0)

Files changed (113)

File django-otp-agents/CHANGES

 .. vim: ft=rst nospell tw=80
 
+v0.1.2 - May 9, 2013 - Unit test improvements
+---------------------------------------------
+
+Major unit test cleanup. Tests should pass or be skipped under all supported
+versions of Django, with or without custom users and timzeone support.
+
 
 v0.1.1 - October 8, 2012 - Django < 1.4
 ---------------------------------------

File django-otp-agents/docs/source/conf.py

 # The short X.Y version.
 version = '0.1'
 # The full version, including alpha/beta/rc tags.
-release = '0.1.1'
+release = '0.1.2'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.

File django-otp-agents/otp_agents/fixtures/test/alice.yaml

-- fields:
-    date_joined: 2012-07-15 16:03:02.420965
-    email: alice@example.com
-    first_name: ''
-    groups: []
-    is_active: true
-    is_staff: false
-    is_superuser: false
-    last_login: 2012-07-17 19:00:09.753345
-    last_name: ''
-    password: '6384e2b2184bcbf58eccf10ca7a6563c'
-    user_permissions: []
-    username: alice
-  model: auth.user
-  pk: 1
-
-- fields: {confirmed: true, name: Backup Codes, user: 1}
-  model: otp_static.staticdevice
-  pk: 1
-- fields: {device: 1, token: alice1}
-  model: otp_static.statictoken
-  pk: 1
-- fields: {device: 1, token: alice2}
-  model: otp_static.statictoken
-  pk: 2

File django-otp-agents/otp_agents/tests/__init__.py

-try:
-    from unittest import skipIf
-except ImportError:
-    skipIf = lambda *args, **kwargs: (lambda v: v)
+import django
+from django.db import IntegrityError
+from django.db.models import get_app
+from django.utils.unittest import skipIf
 
-import django
-from django.contrib.auth.models import User
-from django.test import TestCase
+from django_otp.tests import TestCase
 
 
-@skipIf(django.VERSION < (1, 4), 'Requires Django 1.4')
+@skipIf(django.VERSION < (1, 4), u"Requires Django 1.4")
 class OTPAgentsTestCase(TestCase):
-    fixtures = ['test/alice.yaml']
     urls = 'otp_agents.tests.urls'
 
     def setUp(self):
-        self.alice = User.objects.get()
+        try:
+            get_app('otp_static')
+        except:
+            self.skipTest(u"Requires django_otp.plugins.otp_static")
+
+        try:
+            self.alice = self.create_user('alice', 'alice')
+        except IntegrityError:
+            self.skipTest(u"Unable to create a test user")
+        else:
+            device = self.alice.staticdevice_set.create()
+            device.token_set.create(token='alice1')
+            device.token_set.create(token='alice2')
 
     def test_otp_anonymous(self):
         response = self.client.get('/otp/')

File django-otp-agents/setup.py

 
 setup(
     name='django-otp-agents',
-    version='0.1.1',
+    version='0.1.2',
     description="Integration of django-otp and django-agent-trust.",
     long_description=open('README').read(),
     author='Peter Sagerson',
         "Topic :: Security",
         "Topic :: Software Development :: Libraries :: Python Modules",
     ],
-) 
+)

File django-otp-agents/test/django_otp

-../../django-otp/django_otp

File django-otp-agents/test/manage.py

-#!/usr/bin/env python
-import os
-import sys
-
-
-if __name__ == "__main__":
-    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
-
-    from django.core.management import execute_from_command_line
-
-    execute_from_command_line(sys.argv)

File django-otp-agents/test/otp_agents

-../otp_agents

File django-otp-agents/test/settings.py

-# django-otp-agents test project
-
-from os.path import dirname, join, abspath
-
-def project_path(path):
-    return abspath(join(dirname(__file__), path))
-
-DEBUG = True
-
-DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3'}}
-
-INSTALLED_APPS = [
-    'django.contrib.auth',
-    'django.contrib.contenttypes',
-    'django.contrib.sessions',
-    'django.contrib.messages',
-
-    'django_otp',
-    'django_otp.plugins.otp_static',
-    'django_agent_trust',
-    'otp_agents',
-]
-
-MIDDLEWARE_CLASSES = [
-    'django.middleware.common.CommonMiddleware',
-    'django.contrib.sessions.middleware.SessionMiddleware',
-    'django.contrib.auth.middleware.AuthenticationMiddleware',
-    'django_otp.middleware.OTPMiddleware',
-    'django_agent_trust.middleware.AgentMiddleware',
-    'django.contrib.messages.middleware.MessageMiddleware',
-]
-
-TEMPLATE_DIRS = [
-    project_path('templates'),
-]
-
-SECRET_KEY = 'iNkqvGrLybbwdtUSWgTeutLPUp4pe0Y1Mhfdo05x6OIeDHhbUI9uCJA1gNVAdLhp'
-
-ROOT_URLCONF = 'otp_agents.tests.urls'
-LOGIN_URL = '/login/'

File django-otp-agents/test/templates/registration/logged_out.html

Empty file removed.

File django-otp-agents/test/templates/registration/login.html

-<html>
-    <head></head>
-    <body>
-        <form action="." method="POST">
-            {% csrf_token %}
-            {{ form.as_p }}
-        </form>
-    </body>
-</html>

File django-otp-twilio/CHANGES

 .. vim: ft=rst nospell tw=80
 
+v0.1.3 - May 9, 2013 - Unit test improvements
+---------------------------------------------
+
+Major unit test cleanup. Tests should pass or be skipped under all supported
+versions of Django, with or without custom users and timzeone support.
+
+
 v0.1.2 - March 24, 2013 - Bug fix
 ---------------------------------
 

File django-otp-twilio/MANIFEST.in

 
 recursive-include docs *.rst *.py Makefile
 prune docs/build
-
-recursive-include otp_twilio/fixtures *
-
-global-exclude .DS_Store *.pyc *.pyo .*.sw?

File django-otp-twilio/docs/source/conf.py

 # The short X.Y version.
 version = '0.1'
 # The full version, including alpha/beta/rc tags.
-release = '0.1.2'
+release = '0.1.3'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.

File django-otp-twilio/docs/source/index.rst

 Useful for development.
 
 
+Changes
+-------
+
+:doc:`changes`
+
+
 License
 -------
 

File django-otp-twilio/otp_twilio/fixtures/tests/alice_and_bob.yaml

-- fields:
-    date_joined: 2012-07-15 16:03:02.420965
-    email: alice@example.com
-    first_name: ''
-    groups: []
-    is_active: true
-    is_staff: false
-    is_superuser: false
-    last_login: 2012-07-17 19:00:09.753345
-    last_name: ''
-    password: sha1$76223$c4f1daa1f2f3a7a3a6737538873ba335c60b5b7d
-    user_permissions: []
-    username: alice
-  model: auth.user
-  pk: 1
-
-- fields:
-    date_joined: 2012-07-15 16:03:02.420965
-    email: bob@example.com
-    first_name: ''
-    groups: []
-    is_active: true
-    is_staff: false
-    is_superuser: false
-    last_login: 2012-07-17 19:00:09.753345
-    last_name: ''
-    password: sha1$76223$c4f1daa1f2f3a7a3a6737538873ba335c60b5b7d
-    user_permissions: []
-    username: bob
-  model: auth.user
-  pk: 2
-
-- model: otp_twilio.twiliosmsdevice
-  pk: 1
-  fields:
-    user: 1
-    name: SMS
-    confirmed: true
-    number: test
-    key: '01234567890123456789'
-
-- model: otp_twilio.twiliosmsdevice
-  pk: 2
-  fields:
-    user: 2
-    name: SMS
-    confirmed: true
-    number: test
-    key: '98765432109876543210'

File django-otp-twilio/otp_twilio/tests.py

 from django.contrib.auth.models import User
-from django.test import TestCase
+from django.db import IntegrityError
 
 from django_otp.oath import totp
+from django_otp.tests import TestCase
 
 
 class TestTwilioSMS(TestCase):
-    fixtures = ['tests/alice_and_bob']
-
     def setUp(self):
-        self.alice = User.objects.get(username='alice')
-        self.bob = User.objects.get(username='bob')
+        try:
+            self.alice = self.create_user('alice', 'password')
+            self.bob = self.create_user('bob', 'password')
+        except IntegrityError:
+            self.skipTest(u"Unable to create test users.")
+        else:
+            self.alice.twiliosmsdevice_set.create(number='test',
+                key='01234567890123456789')
+            self.bob.twiliosmsdevice_set.create(number='test',
+                key='98765432109876543210')
 
     def test_current(self):
         device = self.alice.twiliosmsdevice_set.get()

File django-otp-twilio/setup.py

 
 setup(
     name='django-otp-twilio',
-    version='0.1.2',
+    version='0.1.3',
     description="A django-otp plugin that delivers tokens via Twilio's SMS service.",
     long_description=open('README').read(),
     author='Peter Sagerson',

File django-otp-yubikey/CHANGES

+v0.1.2 - May 9, 2013 - Unit test improvements
+---------------------------------------------
+
+Major unit test cleanup. Tests should pass or be skipped under all supported
+versions of Django, with or without custom users and timzeone support.
+
+
 v0.1.1 - May 8, 2013 - Packaging and test cleanup
 -------------------------------------------------
 

File django-otp-yubikey/MANIFEST.in

 include CHANGES LICENSE README
 
-recursive-include otp_yubikey/fixtures *
-
 recursive-include docs *.rst *.py Makefile
 prune docs/build
-
-global-exclude *.pyc *.pyo .*.sw?

File django-otp-yubikey/docs/source/changes.rst

+Change Log
+==========
+
+.. include:: ../../CHANGES

File django-otp-yubikey/docs/source/conf.py

 # The short X.Y version.
 version = '0.1'
 # The full version, including alpha/beta/rc tags.
-release = '0.1.1'
+release = '0.1.2'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.

File django-otp-yubikey/docs/source/index.rst

 .. autoclass:: otp_yubikey.admin.RemoteYubikeyDeviceAdmin
 
 
+Changes
+-------
+
+:doc:`changes`
+
+
 License
 =======
 

File django-otp-yubikey/otp_yubikey/fixtures/otp_yubikey/alice_and_bob.yaml

-- fields:
-    date_joined: '2012-07-15 16:03:02Z'
-    email: alice@example.com
-    first_name: ''
-    groups: []
-    is_active: true
-    is_staff: false
-    is_superuser: false
-    last_login: '2012-07-17 19:00:09Z'
-    last_name: ''
-    password: sha1$76223$c4f1daa1f2f3a7a3a6737538873ba335c60b5b7d
-    user_permissions: []
-    username: alice
-  model: auth.user
-  pk: 1
-
-- fields:
-    date_joined: '2012-07-15 16:03:02Z'
-    email: bob@example.com
-    first_name: ''
-    groups: []
-    is_active: true
-    is_staff: false
-    is_superuser: false
-    last_login: '2012-07-17 19:00:09Z'
-    last_name: ''
-    password: sha1$76223$c4f1daa1f2f3a7a3a6737538873ba335c60b5b7d
-    user_permissions: []
-    username: bob
-  model: auth.user
-  pk: 2
-
-- model: otp_yubikey.yubikeydevice
-  pk: 1
-  fields:
-    user: 1
-    name: yubikey
-    confirmed: True
-    private_id: '5dc30490956b'
-    key: 'fb362a0853be5e5306d5cc2483f279cb'
-    session: 5
-    counter: 0
-
-- model: otp_yubikey.yubikeydevice
-  pk: 2
-  fields:
-    user: 2
-    name: yubikey
-    confirmed: True
-    private_id: '326f70826d31'
-    key: '11080a0e7a56d0a1546f327f20626308'
-    session: 10
-    counter: 3

File django-otp-yubikey/otp_yubikey/tests.py

 from binascii import unhexlify
 
-from django.test import TestCase
+from django.db import IntegrityError
 
-from otp_yubikey.models import YubikeyDevice
+from django_otp.tests import TestCase
 from yubiotp.otp import encode_otp, YubiKey
 
 
 class YubikeyTest(TestCase):
-    fixtures = ['otp_yubikey/alice_and_bob.yaml']
-
     alice_public = 'cccccccb'
     alice_aes = unhexlify('fb362a0853be5e5306d5cc2483f279cb')
     alice_key = YubiKey(unhexlify('5dc30490956b'), 6, 0)
     bob_aes = unhexlify('11080a0e7a56d0a1546f327f20626308')
 
     def setUp(self):
-        self.alice_device = YubikeyDevice.objects.get(user__username='alice')
-        self.bob_device = YubikeyDevice.objects.get(user__username='bob')
+        try:
+            alice = self.create_user('alice', 'password')
+            bob = self.create_user('bob', 'password')
+        except IntegrityError:
+            self.skipTest(u"Unable to create the test user")
+        else:
+            self.alice_device = alice.yubikeydevice_set.create(
+                private_id='5dc30490956b',
+                key='fb362a0853be5e5306d5cc2483f279cb', session=5, counter=0)
+            self.bob_device = bob.yubikeydevice_set.create(
+                private_id='326f70826d31',
+                key='11080a0e7a56d0a1546f327f20626308', session=10, counter=3)
 
     def test_verify_alice(self):
         _, token = self.alice_token()
 
         self.assert_(not ok)
 
-
     def alice_token(self, session=None, counter=None):
         otp = self.alice_key.generate()
 

File django-otp-yubikey/setup.py

 
 setup(
     name='django-otp-yubikey',
-    version='0.1.1',
+    version='0.1.2',
     description='A django-otp plugin that verifies YubiKey OTP tokens.',
     long_description=open('README').read(),
     author='Peter Sagerson',

File django-otp/CHANGES

+v0.1.6 - May 9, 2013 - Unit test improvements
+---------------------------------------------
+
+Major unit test cleanup. Tests should pass or be skipped under all supported
+versions of Django, with or without custom users and timzeone support.
+
+
 v0.1.5 - May 8, 2013 - OTPAdminSite improvement
 -----------------------------------------------
 

File django-otp/django_otp/fixtures/django_otp/alice_and_bob.yaml

-- fields:
-    date_joined: '2012-07-15 16:03:02Z'
-    email: alice@example.com
-    first_name: ''
-    groups: []
-    is_active: true
-    is_staff: false
-    is_superuser: false
-    last_login: '2012-07-17 19:00:09Z'
-    last_name: ''
-    password: sha1$76223$c4f1daa1f2f3a7a3a6737538873ba335c60b5b7d
-    user_permissions: []
-    username: alice
-  model: auth.user
-  pk: 1
-
-- fields:
-    date_joined: '2012-07-15 16:03:02Z'
-    email: bob@example.com
-    first_name: ''
-    groups: []
-    is_active: true
-    is_staff: false
-    is_superuser: false
-    last_login: '2012-07-17 19:00:09Z'
-    last_name: ''
-    password: sha1$76223$c4f1daa1f2f3a7a3a6737538873ba335c60b5b7d
-    user_permissions: []
-    username: bob
-  model: auth.user
-  pk: 2
-
-- fields: {confirmed: true, name: Emergency Tokens, user: 1}
-  model: otp_static.staticdevice
-  pk: 1
-- fields: {device: 1, token: alice1}
-  model: otp_static.statictoken
-  pk: 1
-- fields: {device: 1, token: alice1}
-  model: otp_static.statictoken
-  pk: 2
-- fields: {device: 1, token: alice2}
-  model: otp_static.statictoken
-  pk: 3
-
-- fields: {confirmed: true, name: Emergency Tokens, user: 2}
-  model: otp_static.staticdevice
-  pk: 10
-- fields: {device: 10, token: bob1}
-  model: otp_static.statictoken
-  pk: 11
-- fields: {device: 10, token: bob1}
-  model: otp_static.statictoken
-  pk: 12
-- fields: {device: 10, token: bob2}
-  model: otp_static.statictoken
-  pk: 13
-
-- fields: 
-    confirmed: true
-    key: c2153fc45ab56736a27fce502496c3ebd4ae2725
-    name: Console
-    user: 1
-  model: otp_email.emaildevice
-  pk: 1

File django-otp/django_otp/plugins/otp_email/tests.py

+from django.db import IntegrityError
+from django.core import mail
+
+from django_otp.forms import OTPAuthenticationForm
+from django_otp.tests import TestCase
+from .models import EmailDevice
+
+
+class AuthFormTest(TestCase):
+    def setUp(self):
+        try:
+            alice = self.create_user('alice', 'password')
+        except IntegrityError:
+            self.skipTest(u"Failed to create user.")
+        else:
+            alice.emaildevice_set.create()
+
+        if not hasattr(alice, 'email'):
+            self.skipTest(u"User model has no email.")
+
+    def test_email_interaction(self):
+        data = {
+            'username': 'alice',
+            'password': 'password',
+            'otp_device': 'django_otp.plugins.otp_email.models.EmailDevice/1',
+            'otp_token': '',
+            'otp_challenge': '1',
+        }
+        form = OTPAuthenticationForm(None, data)
+
+        self.assert_(not form.is_valid())
+        alice = form.get_user()
+        self.assert_(alice.get_username() == 'alice')
+        self.assert_(alice.otp_device is None)
+        self.assertEqual(len(mail.outbox), 1)
+
+        data['otp_token'] = mail.outbox[0].body
+        del data['otp_challenge']
+        form = OTPAuthenticationForm(None, data)
+
+        self.assert_(form.is_valid())
+        self.assert_(isinstance(form.get_user().otp_device, EmailDevice))

File django-otp/django_otp/plugins/otp_hotp/fixtures/otp_hotp/tests.yaml

-- fields:
-    date_joined: '2012-07-15 16:03:02Z'
-    email: alice@example.com
-    first_name: ''
-    groups: []
-    is_active: true
-    is_staff: false
-    is_superuser: false
-    last_login: '2012-07-17 19:00:09Z'
-    last_name: ''
-    password: sha1$76223$c4f1daa1f2f3a7a3a6737538873ba335c60b5b7d
-    user_permissions: []
-    username: alice
-  model: auth.user
-  pk: 1
-
-- model: otp_hotp.hotpdevice
-  pk: 1
-  fields:
-    user: 1
-    key: 'd2e8a68036f68960b1c30532bb6c56da5934d879'
-    digits: 6
-    tolerance: 1
-    counter: 0

File django-otp/django_otp/plugins/otp_hotp/tests.py

-from django.test import TestCase
+from django.db import IntegrityError
 
-from .models import HOTPDevice
+from django_otp.tests import TestCase
 
 
 class HOTPTest(TestCase):
-    fixtures = ['otp_hotp/tests.yaml']
-
     # The next three tokens
     tokens = [782373, 313268, 307722]
 
     def setUp(self):
-        self.device = HOTPDevice.objects.get()
+        try:
+            alice = self.create_user('alice', 'password')
+        except IntegrityError:
+            self.skipTest(u"Unable to create test user.")
+        else:
+            self.device = alice.hotpdevice_set.create(
+                key='d2e8a68036f68960b1c30532bb6c56da5934d879', digits=6,
+                tolerance=1, counter=0
+            )
 
     def test_normal(self):
         ok = self.device.verify_token(self.tokens[0])

File django-otp/django_otp/plugins/otp_static/tests.py

+from django.db import IntegrityError
+
+from django_otp.forms import OTPAuthenticationForm
+from django_otp.tests import TestCase
+from .models import StaticDevice
+
+
+class AuthFormTest(TestCase):
+    """
+    Test the auth form with static tokens.
+
+    We try to honor custom user models, but if we can't create users, we'll
+    skip the tests.
+    """
+    def setUp(self):
+        for device_id, username in enumerate(['alice', 'bob']):
+            try:
+                user = self.create_user(username, 'password')
+            except IntegrityError:
+                self.skipTest(u"Unable to create a test user.")
+            else:
+                device = user.staticdevice_set.create(id=device_id + 1)
+                device.token_set.create(token=username + '1')
+                device.token_set.create(token=username + '1')
+                device.token_set.create(token=username + '2')
+
+    def test_empty(self):
+        data = {}
+        form = OTPAuthenticationForm(None, data)
+
+        self.assert_(not form.is_valid())
+        self.assertEqual(form.get_user(), None)
+
+    def test_bad_password(self):
+        data = {
+            'username': 'alice',
+            'password': 'bogus',
+        }
+        form = OTPAuthenticationForm(None, data)
+
+        self.assert_(not form.is_valid())
+        self.assert_(form.get_user() is None)
+        self.assertEqual(form.errors.keys(), ['__all__'])
+
+    def test_no_token(self):
+        data = {
+            'username': 'alice',
+            'password': 'password',
+        }
+        form = OTPAuthenticationForm(None, data)
+
+        self.assert_(not form.is_valid())
+        self.assert_(form.get_user().get_username() == 'alice')
+
+    def test_passive_token(self):
+        data = {
+            'username': 'alice',
+            'password': 'password',
+            'otp_token': 'alice1',
+        }
+        form = OTPAuthenticationForm(None, data)
+
+        self.assert_(form.is_valid())
+        alice = form.get_user()
+        self.assert_(alice.get_username() == 'alice')
+        self.assert_(isinstance(alice.otp_device, StaticDevice))
+        self.assertEqual(alice.otp_device.token_set.count(), 2)
+
+    def test_spoofed_device(self):
+        data = {
+            'username': 'alice',
+            'password': 'password',
+            'otp_device': 'django_otp.plugins.otp_static.models.StaticDevice/10',
+            'otp_token': 'bob1',
+        }
+        form = OTPAuthenticationForm(None, data)
+
+        self.assert_(not form.is_valid())
+        alice = form.get_user()
+        self.assert_(alice.get_username() == 'alice')
+        self.assert_(alice.otp_device is None)
+
+    def test_specific_device_fail(self):
+        data = {
+            'username': 'alice',
+            'password': 'password',
+            'otp_device': 'django_otp.plugins.otp_email.models.StaticDevice/1',
+            'otp_token': 'bogus',
+        }
+        form = OTPAuthenticationForm(None, data)
+
+        self.assert_(not form.is_valid())
+        alice = form.get_user()
+        self.assert_(alice.get_username() == 'alice')
+        self.assert_(alice.otp_device is None)
+
+    def test_specific_device(self):
+        data = {
+            'username': 'alice',
+            'password': 'password',
+            'otp_device': 'django_otp.plugins.otp_static.models.StaticDevice/1',
+            'otp_token': 'alice1',
+        }
+        form = OTPAuthenticationForm(None, data)
+
+        self.assert_(form.is_valid())
+        alice = form.get_user()
+        self.assert_(alice.get_username() == 'alice')
+        self.assert_(alice.otp_device is not None)
+
+    def _test_email_interaction(self):
+        data = {
+            'username': 'alice',
+            'password': 'password',
+            'otp_device': 'django_otp.plugins.otp_email.models.EmailDevice/1',
+            'otp_token': '',
+            'otp_challenge': '1',
+        }
+        form = OTPAuthenticationForm(None, data)
+
+        self.assert_(not form.is_valid())
+        alice = form.get_user()
+        self.assert_(alice.get_username() == 'alice')
+        self.assert_(alice.otp_device is None)
+        self.assertEqual(len(mail.outbox), 1)
+
+        data['otp_token'] = mail.outbox[0].body
+        del data['otp_challenge']
+        form = OTPAuthenticationForm(None, data)
+
+        self.assert_(form.is_valid())
+        self.assert_(isinstance(form.get_user().otp_device, EmailDevice))

File django-otp/django_otp/plugins/otp_totp/conf.py

-import django.conf
-
-
-class Settings(object):
-    """
-    This is a simple class to take the place of the global settings object. An
-    instance will contain all of our settings as attributes, with default values
-    if they are not specified by the configuration.
-    """
-    defaults = {
-        'OTP_TOTP_SYNC': True,
-    }
-
-    def __init__(self):
-        """
-        Loads our settings from django.conf.settings, applying defaults for any
-        that are omitted.
-        """
-        for name, default in self.defaults.iteritems():
-            value = getattr(django.conf.settings, name, default)
-            setattr(self, name, value)
-
-
-settings = Settings()

File django-otp/django_otp/plugins/otp_totp/fixtures/otp_totp/tests.yaml

-- fields:
-    date_joined: '2012-07-15 16:03:02Z'
-    email: alice@example.com
-    first_name: ''
-    groups: []
-    is_active: true
-    is_staff: false
-    is_superuser: false
-    last_login: '2012-07-17 19:00:09Z'
-    last_name: ''
-    password: sha1$76223$c4f1daa1f2f3a7a3a6737538873ba335c60b5b7d
-    user_permissions: []
-    username: alice
-  model: auth.user
-  pk: 1
-
-- model: otp_totp.totpdevice
-  pk: 1
-  fields:
-    user: 1
-    key: '2a2bbba1092ffdd25a328ad1a0a5f5d61d7aacc4'
-    step: 30
-    t0: 0
-    digits: 6
-    tolerance: 0
-    drift: 0

File django-otp/django_otp/plugins/otp_totp/models.py

 from binascii import unhexlify
 
+from django.conf import settings
 from django.db import models
 
 from django_otp.models import Device
 from django_otp.oath import totp
 from django_otp.util import random_hex, hex_validator
 
-from .conf import settings
-
 
 class TOTPDevice(Device):
     """
         return unhexlify(self.key)
 
     def verify_token(self, token):
+        OTP_TOTP_SYNC = getattr(settings, 'OTP_TOTP_SYNC', True)
+
         try:
             token = int(token)
         except StandardError:
 
             for offset in range(-self.tolerance, self.tolerance + 1):
                 if totp(key, self.step, self.t0, self.digits, self.drift + offset) == token:
-                    if (offset != 0) and settings.OTP_TOTP_SYNC:
+                    if (offset != 0) and OTP_TOTP_SYNC:
                         self.drift += offset
                         self.save()
 

File django-otp/django_otp/plugins/otp_totp/tests.py

 from time import time
 
-from django.test import TestCase
+from django.db import IntegrityError
 
-from .models import TOTPDevice
-from .conf import settings
+try:
+    from django.test.utils import override_settings
+except ImportError:
+    # Django < 1.4 doesn't have override_settings. Just skip the tests in that
+    # case.
+    from django.utils.unittest import skip
+    override_settings = lambda *args, **kwargs: skip
 
+from django_otp.tests import TestCase
 
+
+@override_settings(OTP_TOTP_SYNC=False)
 class TOTPTest(TestCase):
-    fixtures = ['otp_totp/tests.yaml']
-
     # The next ten tokens
     tokens = [179225, 656163, 839400, 154567, 346912, 471576, 45675, 101397, 491039, 784503]
 
     def setUp(self):
         """
-        Load the device and move it to the fourth time step. The current token
-        is 154567.
+        Create a device at the fourth time step. The current token is 154567.
         """
-        settings.OTP_TOTP_SYNC = False
-        self.device = TOTPDevice.objects.get()
-        self.device.t0 = int(time() - (30 * 3))
+        try:
+            alice = self.create_user('alice', 'password')
+        except IntegrityError:
+            self.skipTest(u"Unable to create the test user.")
+        else:
+            self.device = alice.totpdevice_set.create(
+                key='2a2bbba1092ffdd25a328ad1a0a5f5d61d7aacc4', step=30,
+                t0=int(time() - (30 * 3)), digits=6, tolerance=0, drift=0
+            )
 
     def test_single(self):
         results = [self.device.verify_token(token) for token in self.tokens]
 
-        self.assertEqual(results, [False]*3 + [True] + [False]*6)
+        self.assertEqual(results, [False] * 3 + [True] + [False] * 6)
 
     def test_tolerance(self):
         self.device.tolerance = 1
         results = [self.device.verify_token(token) for token in self.tokens]
 
-        self.assertEqual(results, [False]*2 + [True]*3 + [False]*5)
+        self.assertEqual(results, [False] * 2 + [True] * 3 + [False] * 5)
 
     def test_drift(self):
         self.device.tolerance = 1
         self.device.drift = -1
         results = [self.device.verify_token(token) for token in self.tokens]
 
-        self.assertEqual(results, [False]*1 + [True]*3 + [False]*6)
+        self.assertEqual(results, [False] * 1 + [True] * 3 + [False] * 6)
 
     def test_sync_drift(self):
-        settings.OTP_TOTP_SYNC = True
         self.device.tolerance = 2
-        ok = self.device.verify_token(self.tokens[5])
+        with self.settings(OTP_TOTP_SYNC=True):
+            ok = self.device.verify_token(self.tokens[5])
 
         self.assert_(ok)
         self.assertEqual(self.device.drift, 2)
 
     def test_sync_results(self):
         self.device.tolerance = 1
-        settings.OTP_TOTP_SYNC = True
-        self.device.verify_token(self.tokens[4])
-        settings.OTP_TOTP_SYNC = False
+        with self.settings(OTP_TOTP_SYNC=True):
+            self.device.verify_token(self.tokens[4])
         results = [self.device.verify_token(token) for token in self.tokens]
 
         self.assertEqual(self.device.drift, 1)
-        self.assertEqual(results, [False]*3 + [True]*3 + [False]*4)
+        self.assertEqual(results, [False] * 3 + [True] * 3 + [False] * 4)

File django-otp/django_otp/tests.py

+import django.test
+from django.utils import unittest
+from doctest import DocTestSuite
+
+from django_otp import util
+from django_otp import oath
+
+
+def suite():
+    suite = unittest.TestSuite()
+
+    suite.addTest(DocTestSuite(util))
+    suite.addTest(DocTestSuite(oath))
+
+    return suite
+
+
+class TestCase(django.test.TestCase):
+    """
+    Utilities for dealing with custom user models.
+    """
+    @classmethod
+    def setUpClass(cls):
+        try:
+            from django.contrib.auth import get_user_model
+        except ImportError:
+            from django.contrib.auth.models import User
+            cls.User = User
+            cls.User.get_username = lambda self: self.username
+            cls.USERNAME_FIELD = 'username'
+        else:
+            cls.User = get_user_model()
+            cls.USERNAME_FIELD = cls.User.USERNAME_FIELD
+
+    def create_user(self, username, password):
+        """
+        Try to create a user, honoring the custom user model, if any. This may
+        raise an exception if the user model is too exotic for our purposes.
+        """
+        try:
+            user = self.User.objects.create_user(username, password=password)
+        except TypeError:
+            # Django < 1.4
+            user = self.User.objects.create_user(username, email='user@example.com', password=password)
+
+        return user

File django-otp/django_otp/tests/__init__.py

-from django.utils import unittest
-from doctest import DocTestSuite
-
-from django_otp import util
-from django_otp import oath
-
-from .forms import AuthFormTest
-
-
-def suite():
-    loader = unittest.TestLoader()
-    suite = unittest.TestSuite()
-
-    suite.addTest(DocTestSuite(util))
-    suite.addTest(DocTestSuite(oath))
-    suite.addTest(loader.loadTestsFromTestCase(AuthFormTest))
-
-    return suite

File django-otp/django_otp/tests/forms.py

-from django.test import TestCase
-from django.core import mail
-
-from django_otp.forms import OTPAuthenticationForm
-from django_otp.plugins.otp_static.models import StaticDevice
-from django_otp.plugins.otp_email.models import EmailDevice
-
-
-class AuthFormTest(TestCase):
-    fixtures = ['django_otp/alice_and_bob.yaml']
-
-    def test_empty(self):
-        data = {}
-        form = OTPAuthenticationForm(None, data)
-
-        self.assert_(not form.is_valid())
-        self.assertEqual(form.get_user(), None)
-
-    def test_bad_password(self):
-        data = {
-            'username': 'alice',
-            'password': 'bogus',
-        }
-        form = OTPAuthenticationForm(None, data)
-
-        self.assert_(not form.is_valid())
-        self.assert_(form.get_user() is None)
-        self.assertEqual(form.errors.keys(), ['__all__'])
-
-    def test_no_token(self):
-        data = {
-            'username': 'alice',
-            'password': 'password',
-        }
-        form = OTPAuthenticationForm(None, data)
-
-        self.assert_(not form.is_valid())
-        self.assert_(form.get_user().username == 'alice')
-
-    def test_passive_token(self):
-        data = {
-            'username': 'alice',
-            'password': 'password',
-            'otp_token': 'alice1',
-        }
-        form = OTPAuthenticationForm(None, data)
-
-        self.assert_(form.is_valid())
-        alice = form.get_user()
-        self.assert_(alice.username == 'alice')
-        self.assert_(isinstance(alice.otp_device, StaticDevice))
-        self.assertEqual(alice.otp_device.token_set.count(), 2)
-
-    def test_spoofed_device(self):
-        data = {
-            'username': 'alice',
-            'password': 'password',
-            'otp_device': 'django_otp.plugins.otp_static.models.StaticDevice/10',
-            'otp_token': 'bob1',
-        }
-        form = OTPAuthenticationForm(None, data)
-
-        self.assert_(not form.is_valid())
-        alice = form.get_user()
-        self.assert_(alice.username == 'alice')
-        self.assert_(alice.otp_device is None)
-
-    def test_specific_device_fail(self):
-        data = {
-            'username': 'alice',
-            'password': 'password',
-            'otp_device': 'django_otp.plugins.otp_email.models.EmailDevice/1',
-            'otp_token': 'alice1',
-        }
-        form = OTPAuthenticationForm(None, data)
-
-        self.assert_(not form.is_valid())
-        alice = form.get_user()
-        self.assert_(alice.username == 'alice')
-        self.assert_(alice.otp_device is None)
-
-    def test_specific_device(self):
-        data = {
-            'username': 'alice',
-            'password': 'password',
-            'otp_device': 'django_otp.plugins.otp_static.models.StaticDevice/1',
-            'otp_token': 'alice1',
-        }
-        form = OTPAuthenticationForm(None, data)
-
-        self.assert_(form.is_valid())
-        alice = form.get_user()
-        self.assert_(alice.username == 'alice')
-        self.assert_(alice.otp_device is not None)
-
-    def test_email_interaction(self):
-        data = {
-            'username': 'alice',
-            'password': 'password',
-            'otp_device': 'django_otp.plugins.otp_email.models.EmailDevice/1',
-            'otp_token': '',
-            'otp_challenge': '1',
-        }
-        form = OTPAuthenticationForm(None, data)
-
-        self.assert_(not form.is_valid())
-        alice = form.get_user()
-        self.assert_(alice.username == 'alice')
-        self.assert_(alice.otp_device is None)
-        self.assertEqual(len(mail.outbox), 1)
-
-        data['otp_token'] = mail.outbox[0].body
-        del data['otp_challenge']
-        form = OTPAuthenticationForm(None, data)
-
-        self.assert_(form.is_valid())
-        self.assert_(isinstance(form.get_user().otp_device, EmailDevice))

File django-otp/docs/source/conf.py

 # The short X.Y version.
 version = '0.1'
 # The full version, including alpha/beta/rc tags.
-release = '0.1.5'
+release = '0.1.6'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.

File django-otp/setup.py

             print('recursive-include {0} *'.format(dirpath), file=manifest)
             dirnames[:] = []
 
-    print('global-exclude .DS_Store *.pyc *.pyo .*.sw?', file=manifest)
-
 
 setup(
     name='django-otp',
-    version='0.1.5',
+    version='0.1.6',
     description='A pluggable framework for adding two-factor authentication to Django using one-time passwords.',
     long_description=open('README').read(),
     author='Peter Sagerson',
     author_email='psagersDjwublJf@ignorare.net',
-    zip_safe=False,
     packages=find_packages(),
     include_package_data=True,
+    zip_safe=False,
     url='https://bitbucket.org/psagers/django-otp',
     license='BSD',
     install_requires=[

File django-otp/test/django_otp

-../django_otp

File django-otp/test/manage.py

-#!/usr/bin/env python
-import os
-import sys
-
-
-if __name__ == "__main__":
-    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
-
-    from django.core.management import execute_from_command_line
-
-    execute_from_command_line(sys.argv)

File django-otp/test/otp_twilio

-../../django-otp-twilio/otp_twilio

File django-otp/test/otp_yubikey

-../../django-otp-yubikey/otp_yubikey

File django-otp/test/settings.py

-# django-agent-trust test project
-
-from os.path import dirname, join, abspath
-
-def project_path(path):
-    return abspath(join(dirname(__file__), path))
-
-DEBUG = True
-
-DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3'}}
-
-INSTALLED_APPS = [
-    'django.contrib.auth',
-    'django.contrib.contenttypes',
-    'django.contrib.sessions',
-    'django.contrib.messages',
-
-    'django_otp',
-    'django_otp.plugins.otp_hotp',
-    'django_otp.plugins.otp_totp',
-    'django_otp.plugins.otp_static',
-    'django_otp.plugins.otp_email',
-]
-
-MIDDLEWARE_CLASSES = [
-    'django.middleware.common.CommonMiddleware',
-    'django.contrib.sessions.middleware.SessionMiddleware',
-    'django.contrib.auth.middleware.AuthenticationMiddleware',
-    'django_otp.middleware.OTPMiddleware',
-    'django.contrib.messages.middleware.MessageMiddleware',
-]
-
-TEMPLATE_DIRS = [
-    project_path('templates'),
-]
-
-SECRET_KEY = 'cI4AHyAcIKQxcq2hI54YS7Bnn6vbojTxxlTWQdRiA2pky5oz8IEgJ1DcyvCDXnXn'
-
-ROOT_URLCONF = 'urls'

File django-otp/test/templates/registration/logged_out.html

Empty file removed.

File django-otp/test/urls.py

-from django.conf.urls import patterns
-
-
-urlpatterns = patterns('')

File django-otp/test15/README

-Additional tests for Django 1.5. There's no way to set AUTH_USER_MODEL for
-individual tests, so we need a separate project.

File django-otp/test15/django_otp

-../django_otp

File django-otp/test15/manage.py

-#!/usr/bin/env python
-import os
-import sys
-
-if __name__ == "__main__":
-    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test15.settings")
-
-    from django.core.management import execute_from_command_line
-
-    execute_from_command_line(sys.argv)

File django-otp/test15/test15/__init__.py

Empty file removed.

File django-otp/test15/test15/app/__init__.py

Empty file removed.

File django-otp/test15/test15/app/fixtures/alice_and_bob.yaml

-- fields:
-    last_login: 2012-07-17 19:00:09.753345
-    password: sha1$76223$c4f1daa1f2f3a7a3a6737538873ba335c60b5b7d
-    identifier: alice
-  model: app.testuser
-  pk: 1
-
-- fields:
-    last_login: 2012-07-17 19:00:09.753345
-    password: sha1$76223$c4f1daa1f2f3a7a3a6737538873ba335c60b5b7d
-    identifier: bob
-  model: app.testuser
-  pk: 2
-
-- fields: {confirmed: true, name: Emergency Tokens, user: 1}
-  model: otp_static.staticdevice
-  pk: 1
-- fields: {device: 1, token: alice1}
-  model: otp_static.statictoken
-  pk: 1
-- fields: {device: 1, token: alice1}
-  model: otp_static.statictoken
-  pk: 2
-- fields: {device: 1, token: alice2}
-  model: otp_static.statictoken
-  pk: 3
-
-- fields: {confirmed: true, name: Emergency Tokens, user: 2}
-  model: otp_static.staticdevice
-  pk: 10
-- fields: {device: 10, token: bob1}
-  model: otp_static.statictoken
-  pk: 11
-- fields: {device: 10, token: bob1}
-  model: otp_static.statictoken
-  pk: 12
-- fields: {device: 10, token: bob2}
-  model: otp_static.statictoken
-  pk: 13

File django-otp/test15/test15/app/models.py

-from django.contrib.auth.hashers import make_password
-from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
-from django.db import models
-
-
-class TestUserManager(BaseUserManager):
-    def create_user(self, identifier, password=None):
-        if password is not None:
-            password = make_password(password)
-
-        return self.create(identifier=identifier, password=password)
-
-    create_superuser = create_user
-
-
-class TestUser(AbstractBaseUser):
-    identifier = models.CharField(max_length=40, unique=True, db_index=True)
-
-    objects = TestUserManager()
-
-    USERNAME_FIELD = 'identifier'
-
-    def get_full_name(self):
-        return self.identifier
-
-    def get_short_name(self):
-        return self.identifier

File django-otp/test15/test15/app/tests.py

-from django.test import TestCase
-
-from django_otp.forms import OTPAuthenticationForm
-from .models import TestUser
-
-
-class AuthFormTest(TestCase):
-    fixtures = ['alice_and_bob.yaml']
-
-    def test_custom_user(self):
-        data = {
-            'username': 'alice',
-            'password': 'password',
-            'otp_device': 'django_otp.plugins.otp_static.models.StaticDevice/1',
-            'otp_token': 'alice1',
-        }
-        form = OTPAuthenticationForm(None, data)
-
-        self.assert_(form.is_valid())
-        alice = form.get_user()
-        self.assert_(isinstance(alice, TestUser))
-        if hasattr(alice, 'get_username'):
-            self.assertEqual(alice.get_username(), 'alice')
-        else:
-            self.assertEqual(alice.username, 'alice')
-        self.assert_(alice.otp_device is not None)

File django-otp/test15/test15/settings.py

-from os.path import dirname, join, abspath
-
-
-def project_path(path):
-    return abspath(join(dirname(__file__), path))
-
-
-DEBUG = True
-
-DATABASES = {
-    'default': {
-        'ENGINE': 'django.db.backends.sqlite3',
-        'NAME': 'db.sqlite3',
-    }
-}
-
-INSTALLED_APPS = [
-    'django.contrib.auth',
-    'django.contrib.contenttypes',
-    'django.contrib.sessions',
-    'django.contrib.messages',
-
-    'django_otp',
-    'django_otp.plugins.otp_hotp',
-    'django_otp.plugins.otp_totp',
-    'django_otp.plugins.otp_static',
-    'django_otp.plugins.otp_email',
-
-    'test15.app',
-]
-
-MIDDLEWARE_CLASSES = [
-    'django.middleware.common.CommonMiddleware',
-    'django.contrib.sessions.middleware.SessionMiddleware',
-    'django.contrib.auth.middleware.AuthenticationMiddleware',
-    'django_otp.middleware.OTPMiddleware',
-    'django.contrib.messages.middleware.MessageMiddleware',
-]
-
-TEMPLATE_DIRS = [
-    project_path('templates'),
-]
-
-SECRET_KEY = '9xZjgb6lM998dPRNQ3j7au86X5ZL17Jtme5N910Cp06u7j0QLWara6BH7N90clGQ'
-
-ROOT_URLCONF = 'test15.urls'
-
-AUTH_USER_MODEL = 'app.TestUser'

File django-otp/test15/test15/urls.py

-from django.conf.urls import patterns
-
-
-urlpatterns = patterns('')

File django-otp/test15/test15/wsgi.py

-"""
-WSGI config for test15 project.
-
-This module contains the WSGI application used by Django's development server
-and any production WSGI deployments. It should expose a module-level variable
-named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
-this application via the ``WSGI_APPLICATION`` setting.
-
-Usually you will have the standard Django WSGI application here, but it also
-might make sense to replace the whole Django WSGI application with a custom one
-that later delegates to the Django one. For example, you could introduce WSGI
-middleware here, or combine a Django application with an application of another
-framework.
-
-"""
-import os
-
-# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
-# if running multiple sites in the same mod_wsgi process. To fix this, use
-# mod_wsgi daemon mode with each site in its own daemon process, or use
-# os.environ["DJANGO_SETTINGS_MODULE"] = "test15.settings"
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test15.settings")
-
-# This application object is used by any WSGI server configured to use this
-# file. This includes Django's development server, if the WSGI_APPLICATION
-# setting points here.
-from django.core.wsgi import get_wsgi_application
-application = get_wsgi_application()
-
-# Apply WSGI middleware here.
-# from helloworld.wsgi import HelloWorldApplication
-# application = HelloWorldApplication(application)
+This folder contains Django projects that are set up specifically for testing
+the django-otp (and related) apps. Some dependencies are expected to be checked
+out in the same directory as django-otp; one or two others will need to be
+installed.
+
+default_user should pass tests in Django 1.3 and 1.4. custom_user should pass
+tests in 1.5+. exotic_user should skip most tests in 1.5+.

File test/custom_user/django_agent_trust

+../../../django-agent-trust/django_agent_trust

File test/custom_user/django_otp

+../../django-otp/django_otp

File test/custom_user/manage.py

+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproj.settings")
+
+    from django.core.management import execute_from_command_line
+
+    execute_from_command_line(sys.argv)

File test/custom_user/otp_agents

+../../django-otp-agents/otp_agents

File test/custom_user/otp_twilio

+../../django-otp-twilio/otp_twilio

File test/custom_user/otp_yubikey

+../../django-otp-yubikey/otp_yubikey

File test/custom_user/testproj/__init__.py

Empty file added.

File test/custom_user/testproj/app/__init__.py

Empty file added.

File test/custom_user/testproj/app/fixtures/alice_and_bob.yaml

+- fields:
+    last_login: 2012-07-17 19:00:09.753345
+    password: sha1$76223$c4f1daa1f2f3a7a3a6737538873ba335c60b5b7d
+    identifier: alice
+  model: app.testuser
+  pk: 1
+
+- fields:
+    last_login: 2012-07-17 19:00:09.753345
+    password: sha1$76223$c4f1daa1f2f3a7a3a6737538873ba335c60b5b7d
+    identifier: bob
+  model: app.testuser
+  pk: 2
+
+- fields: {confirmed: true, name: Emergency Tokens, user: 1}
+  model: otp_static.staticdevice
+  pk: 1
+- fields: {device: 1, token: alice1}
+  model: otp_static.statictoken
+  pk: 1
+- fields: {device: 1, token: alice1}
+  model: otp_static.statictoken
+  pk: 2
+- fields: {device: 1, token: alice2}
+  model: otp_static.statictoken
+  pk: 3
+
+- fields: {confirmed: true, name: Emergency Tokens, user: 2}
+  model: otp_static.staticdevice
+  pk: 10
+- fields: {device: 10, token: bob1}
+  model: otp_static.statictoken
+  pk: 11
+- fields: {device: 10, token: bob1}
+  model: otp_static.statictoken
+  pk: 12
+- fields: {device: 10, token: bob2}
+  model: otp_static.statictoken
+  pk: 13

File test/custom_user/testproj/app/models.py

+from django.contrib.auth.hashers import make_password
+from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
+from django.db import models
+
+
+class TestUserManager(BaseUserManager):
+    def create_user(self, identifier, password=None):
+        if password is not None:
+            password = make_password(password)
+
+        return self.create(identifier=identifier, password=password)
+
+    create_superuser = create_user
+
+
+class TestUser(AbstractBaseUser):
+    identifier = models.CharField(max_length=40, unique=True, db_index=True)
+
+    objects = TestUserManager()
+
+    USERNAME_FIELD = 'identifier'
+
+    def get_full_name(self):
+        return self.identifier
+
+    def get_short_name(self):
+        return self.identifier

File test/custom_user/testproj/app/tests.py

+from django.test import TestCase
+
+from django_otp.forms import OTPAuthenticationForm
+from .models import TestUser
+
+
+class AuthFormTest(TestCase):
+    fixtures = ['alice_and_bob.yaml']
+
+    def test_custom_user(self):
+        data = {
+            'username': 'alice',
+            'password': 'password',
+            'otp_device': 'django_otp.plugins.otp_static.models.StaticDevice/1',
+            'otp_token': 'alice1',
+        }
+        form = OTPAuthenticationForm(None, data)
+
+        self.assert_(form.is_valid())
+        alice = form.get_user()
+        self.assert_(isinstance(alice, TestUser))
+        if hasattr(alice, 'get_username'):
+            self.assertEqual(alice.get_username(), 'alice')
+        else:
+            self.assertEqual(alice.username, 'alice')
+        self.assert_(alice.otp_device is not None)

File test/custom_user/testproj/settings.py

+from os.path import dirname, join, abspath
+
+
+def project_path(path):
+    return abspath(join(dirname(__file__), path))
+
+
+DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3'}}
+
+INSTALLED_APPS = [
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+
+    'django_otp',
+    'django_otp.plugins.otp_hotp',
+    'django_otp.plugins.otp_totp',
+    'django_otp.plugins.otp_static',
+    'django_otp.plugins.otp_email',
+
+    'testproj.app',
+]
+
+MIDDLEWARE_CLASSES = [
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django_otp.middleware.OTPMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+]
+
+TEMPLATE_DIRS = [
+    project_path('templates'),
+]
+
+SECRET_KEY = '9xZjgb6lM998dPRNQ3j7au86X5ZL17Jtme5N910Cp06u7j0QLWara6BH7N90clGQ'
+
+ROOT_URLCONF = 'test15.urls'
+
+AUTH_USER_MODEL = 'app.TestUser'

File test/custom_user/testproj/templates/registration/logged_out.html

Empty file added.

File test/custom_user/testproj/templates/registration/login.html

Empty file added.

File test/custom_user/testproj/urls.py

+from django.conf.urls import patterns, include, url
+
+# Uncomment the next two lines to enable the admin:
+# from django.contrib import admin
+# admin.autodiscover()
+
+urlpatterns = patterns('',
+    # Examples:
+    # url(r'^$', 'testproj.views.home', name='home'),
+    # url(r'^testproj/', include('testproj.foo.urls')),
+
+    # Uncomment the admin/doc line below to enable admin documentation:
+    # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
+
+    # Uncomment the next line to enable the admin:
+    # url(r'^admin/', include(admin.site.urls)),
+)

File test/custom_user/testproj/wsgi.py

+"""
+WSGI config for testproj project.
+
+This module contains the WSGI application used by Django's development server
+and any production WSGI deployments. It should expose a module-level variable
+named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
+this application via the ``WSGI_APPLICATION`` setting.
+
+Usually you will have the standard Django WSGI application here, but it also
+might make sense to replace the whole Django WSGI application with a custom one
+that later delegates to the Django one. For example, you could introduce WSGI
+middleware here, or combine a Django application with an application of another
+framework.
+
+"""
+import os
+
+# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks