From f0bdce392004be4af23c297952712496495c61e7 Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 09:49:26 -0600 Subject: [PATCH 01/18] Split long urls lines into shorter lines Also split the short lines. Having a consistent pattern of linebreaks seems more readable. --- authtools/urls.py | 54 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/authtools/urls.py b/authtools/urls.py index c62ae9c..79ceec2 100644 --- a/authtools/urls.py +++ b/authtools/urls.py @@ -4,16 +4,48 @@ urlpatterns = [ - url(r'^login/$', views.LoginView.as_view(), name='login'), - url(r'^logout/$', views.LogoutView.as_view(), name='logout'), - url(r'^password_change/$', views.PasswordChangeView.as_view(), name='password_change'), - url(r'^password_change/done/$', views.PasswordChangeDoneView.as_view(), name='password_change_done'), - url(r'^password_reset/$', views.PasswordResetView.as_view(), name='password_reset'), - url(r'^password_reset/done/$', views.PasswordResetDoneView.as_view(), name='password_reset_done'), - url(r'^reset/done/$', views.PasswordResetCompleteView.as_view(), name='password_reset_complete'), - url(r'^reset/(?P[0-9A-Za-z]{1,13})-(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', - views.PasswordResetConfirmView.as_view()), - url(r'^reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', + url( + r'^login/$', + views.LoginView.as_view(), + name='login' + ), + url( + r'^logout/$' + , views.LogoutView.as_view(), + name='logout' + ), + url( + r'^password_change/$', + views.PasswordChangeView.as_view(), + name='password_change' + ), + url( + r'^password_change/done/$', + views.PasswordChangeDoneView.as_view(), + name='password_change_done' + ), + url( + r'^password_reset/$', + views.PasswordResetView.as_view(), + name='password_reset' + ), + url( + r'^password_reset/done/$', + views.PasswordResetDoneView.as_view(), + name='password_reset_done' + ), + url( + r'^reset/done/$', + views.PasswordResetCompleteView.as_view(), + name='password_reset_complete' + ), + url( + r'^reset/(?P[0-9A-Za-z]{1,13})-(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', + views.PasswordResetConfirmView.as_view() + ), + url( + r'^reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', views.PasswordResetConfirmView.as_view(), - name='password_reset_confirm'), + name='password_reset_confirm' + ), ] From ee4d9b9e76757073df22f866b917ced71162e76d Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 09:56:39 -0600 Subject: [PATCH 02/18] We only support Django 1.8+ --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 77aafc3..3242973 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ django-authtools :alt: Build Status -A custom user model app for Django 1.5+ that features email as username and +A custom user model app for Django 1.8+ that features email as username and other things. It tries to stay true to the built-in user model for the most part. From 8ce26d6878ecce3f881f8eb2d295b5b572514a1d Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 09:58:56 -0600 Subject: [PATCH 03/18] Update classifiers to list supported versions of Python --- setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.py b/setup.py index 49e4b75..bd2da54 100644 --- a/setup.py +++ b/setup.py @@ -36,8 +36,13 @@ def read(fname): 'License :: OSI Approved :: BSD License', 'Natural Language :: English', 'Programming Language :: Python', + 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', ], ) From 4c688a17c1db204f56cfa7247a5028c12a6c4c97 Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 10:01:21 -0600 Subject: [PATCH 04/18] Reorder imports --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index bd2da54..08d2aaf 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,8 @@ #!/usr/bin/env python -from setuptools import setup, find_packages -import os import io +import os + +from setuptools import setup, find_packages __doc__ = ("Custom user model app for Django featuring email as username and" " class-based views for authentication.") From 8a5486efc58bc26096a254e8a4d0316e2054ff5e Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 10:15:20 -0600 Subject: [PATCH 05/18] Split long line --- authtools/backends.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/authtools/backends.py b/authtools/backends.py index 61bf2d1..dea5ffd 100644 --- a/authtools/backends.py +++ b/authtools/backends.py @@ -42,7 +42,9 @@ def authenticate(self, username=None, password=None, **kwargs): ) -class CaseInsensitiveUsernameFieldModelBackend(CaseInsensitiveUsernameFieldBackendMixin, ModelBackend): +class CaseInsensitiveUsernameFieldModelBackend( + CaseInsensitiveUsernameFieldBackendMixin, + ModelBackend): pass From 6dcd517f2e994bd33aec18fab2a708f65021d719 Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 10:15:31 -0600 Subject: [PATCH 06/18] Add Python 3.5 tests to tox --- tox.ini | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index b1e6c38..2447249 100644 --- a/tox.ini +++ b/tox.ini @@ -1,15 +1,16 @@ [tox] envlist= - py{27,33}-dj18, - py{27,34}-dj19, - py{27,34}-dj110, - py{27,34}-dj111 + py{27,33,35}-dj18, + py{27,34,35}-dj19, + py{27,34,35}-dj110, + py{27,34,35}-dj111 [testenv] basepython= py27: python2.7 py33: python3.3 py34: python3.4 + py35: python3.5 commands= /usr/bin/env make test From 45167f1ccea6c9130b32fd2f78d07b7a6dd7446d Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 10:45:52 -0600 Subject: [PATCH 07/18] Remove conditions for the 1.5/1.6 upgrade --- authtools/forms.py | 40 +++++++++++++++------------------------- authtools/models.py | 9 ++++----- 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/authtools/forms.py b/authtools/forms.py index b34491d..d9f6d63 100644 --- a/authtools/forms.py +++ b/authtools/forms.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from django import forms, VERSION as DJANGO_VERSION +from django.forms.utils import flatatt from django.contrib.auth.forms import ( ReadOnlyPasswordHashField, ReadOnlyPasswordHashWidget, PasswordResetForm as OldPasswordResetForm, @@ -8,7 +9,7 @@ AuthenticationForm as DjangoAuthenticationForm, ) from django.contrib.auth import get_user_model -from django.contrib.auth.hashers import identify_hasher +from django.contrib.auth.hashers import identify_hasher, UNUSABLE_PASSWORD_PREFIX from django.utils.translation import ugettext_lazy as _, ugettext from django.utils.html import format_html @@ -18,14 +19,7 @@ def is_password_usable(pw): # like Django's is_password_usable, but only checks for unusable # passwords, not invalidly encoded passwords too. - try: - # 1.5 - from django.contrib.auth.hashers import UNUSABLE_PASSWORD - return pw != UNUSABLE_PASSWORD - except ImportError: - # 1.6 - from django.contrib.auth.hashers import UNUSABLE_PASSWORD_PREFIX - return not pw.startswith(UNUSABLE_PASSWORD_PREFIX) + return not pw.startswith(UNUSABLE_PASSWORD_PREFIX) class BetterReadOnlyPasswordHashWidget(ReadOnlyPasswordHashWidget): @@ -33,10 +27,6 @@ class BetterReadOnlyPasswordHashWidget(ReadOnlyPasswordHashWidget): A ReadOnlyPasswordHashWidget that has a less intimidating output. """ def render(self, name, value, attrs=None, renderer=None): - try: - from django.forms.utils import flatatt - except ImportError: - from django.forms.util import flatatt # Django < 1.7 final_attrs = flatatt(self.build_attrs(attrs)) if not value or not is_password_usable(value): @@ -143,8 +133,7 @@ class UserChangeForm(forms.ModelForm): class Meta: model = User - if DJANGO_VERSION >= (1, 6): - fields = '__all__' + fields = '__all__' def __init__(self, *args, **kwargs): super(UserChangeForm, self).__init__(*args, **kwargs) @@ -174,16 +163,17 @@ class FriendlyPasswordResetForm(OldPasswordResetForm): "sure you've registered?") def clean_email(self): - super_clean_email = getattr( - super(FriendlyPasswordResetForm, self), 'clean_email', None) - if callable(super_clean_email): # Django == 1.5 - # Django 1.5 sets self.user_cache - return super_clean_email() - - # Simulate Django 1.5 behavior in Django >= 1.6. - # This is not as efficient as in Django 1.5, since clean_email() and - # save() will be running the same query twice. - # Whereas Django 1.5 just caches it. + """Return an error message if the email address being reset is unknown. + + This is to revert https://code.djangoproject.com/ticket/19758 + The bug #19758 tries not to leak emails through password reset because + only usernames are unique in Django's default user model. + + django-authtools leaks email addresses through the registration form. + In the case of django-authtools not warning the user doesn't add any + security, and worsen user experience. + """ + email = self.cleaned_data['email'] qs = User._default_manager.filter(is_active=True, email__iexact=email) results = [user for user in qs if user.has_usable_password()] diff --git a/authtools/models.py b/authtools/models.py index 98df009..b70dc99 100644 --- a/authtools/models.py +++ b/authtools/models.py @@ -1,11 +1,11 @@ from __future__ import unicode_literals +from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin from django.core.mail import send_mail from django.db import models -from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin -from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.utils.encoding import python_2_unicode_compatible +from django.utils.translation import ugettext_lazy as _ class UserManager(BaseUserManager): @@ -51,9 +51,8 @@ def get_short_name(self): return self.email def email_user(self, subject, message, from_email=None, **kwargs): - """ - Sends an email to this User. - """ + """Sends an email to this User.""" + send_mail(subject, message, from_email, [self.email], **kwargs) @python_2_unicode_compatible From e32c40bf4b9e0da8f3d079a5e0cced689b501811 Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 10:48:16 -0600 Subject: [PATCH 08/18] Update changelog --- CHANGES.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 93ca47a..3cdce4e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,9 @@ CHANGES 1.6.0 (unreleased) ------------------ -- Nothing changed yet. +- Add support for Django 1.9, 1.10, 1.11 (Jared Proffitt #82) +- Remove old conditional imports dating as far back as Django 1.5 +- Update readme 1.5.0 (2016-03-26) From 8cfae97d5255f502babae7d32d7e5946cc87b786 Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 11:04:41 -0600 Subject: [PATCH 09/18] Include Python 3.6 in tox tests --- tox.ini | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 2447249..4cd4486 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,9 @@ [tox] envlist= - py{27,33,35}-dj18, - py{27,34,35}-dj19, - py{27,34,35}-dj110, - py{27,34,35}-dj111 + py{27,33,35,36}-dj18, + py{27,34,35,36}-dj19, + py{27,34,35,36}-dj110, + py{27,34,35,36}-dj111 [testenv] basepython= @@ -11,6 +11,7 @@ basepython= py33: python3.3 py34: python3.4 py35: python3.5 + py36: python3.6 commands= /usr/bin/env make test From 3dbdd068534a498962f395707e66b210e84d713b Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 11:20:57 -0600 Subject: [PATCH 10/18] Neither authtools nor Django support Python 2.6 anymore --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 08d2aaf..4ea2e21 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,6 @@ def read(fname): 'Natural Language :: English', 'Programming Language :: Python', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', From a80e3e60ef2fd190886c67ddcc871f87fc38e4ba Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 12:53:31 -0600 Subject: [PATCH 11/18] Update docstring --- authtools/forms.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/authtools/forms.py b/authtools/forms.py index d9f6d63..88c9fd7 100644 --- a/authtools/forms.py +++ b/authtools/forms.py @@ -17,8 +17,14 @@ def is_password_usable(pw): - # like Django's is_password_usable, but only checks for unusable - # passwords, not invalidly encoded passwords too. + """Decide whether a password is usable only by the unusable password prefix. + + + We can't use django.contrib.auth.hashers.is_password_usable either, because + it not only checks against the unusable password, but checks for a valid + hasher too. We need different error messages in those cases. + """ + return not pw.startswith(UNUSABLE_PASSWORD_PREFIX) From d40ab7a42368b2cec9cf14e8cbcadf8c20b57175 Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 13:11:39 -0600 Subject: [PATCH 12/18] Update friendly password reset form The old password reset form has a handy get_users method we weren't using --- authtools/forms.py | 12 ++++++------ tests/tests/tests.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/authtools/forms.py b/authtools/forms.py index 88c9fd7..486febd 100644 --- a/authtools/forms.py +++ b/authtools/forms.py @@ -164,9 +164,9 @@ def __init__(self, *args, **kwargs): class FriendlyPasswordResetForm(OldPasswordResetForm): error_messages = dict(getattr(OldPasswordResetForm, 'error_messages', {})) - error_messages['unknown'] = _("This email address doesn't have an " - "associated user account. Are you " - "sure you've registered?") + error_messages['unknown_email'] = _("This email address doesn't have an " + "associated user account. Are you " + "sure you've registered?") def clean_email(self): """Return an error message if the email address being reset is unknown. @@ -181,10 +181,10 @@ def clean_email(self): """ email = self.cleaned_data['email'] - qs = User._default_manager.filter(is_active=True, email__iexact=email) - results = [user for user in qs if user.has_usable_password()] + results = list(self.get_users(email)) + if not results: - raise forms.ValidationError(self.error_messages['unknown']) + raise forms.ValidationError(self.error_messages['unknown_email']) return email diff --git a/tests/tests/tests.py b/tests/tests/tests.py index 424e841..a349c76 100644 --- a/tests/tests/tests.py +++ b/tests/tests/tests.py @@ -240,7 +240,7 @@ def test_email_not_found_in_friendly_password_reset_form(self): self.assertEqual(response.status_code, 200) response = self.client.post('/friendly_password_reset/', {'email': 'not_a_real_email@email.com'}) - self.assertFormError(response, FriendlyPasswordResetForm.error_messages['unknown']) + self.assertFormError(response, FriendlyPasswordResetForm.error_messages['unknown_email']) self.assertEqual(len(mail.outbox), 0) def test_user_only_fetched_once(self): From e46592a5c1013fe925a79a99c10fe555d544fa52 Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 13:13:37 -0600 Subject: [PATCH 13/18] Continue with legacy code removal --- authtools/views.py | 34 ++++++++++++---------------------- tests/manage.py | 5 ----- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/authtools/views.py b/authtools/views.py index 5083032..3513685 100644 --- a/authtools/views.py +++ b/authtools/views.py @@ -14,10 +14,7 @@ from django.contrib import auth from django.http import HttpResponseRedirect -try: - from django.contrib.sites.shortcuts import get_current_site -except ImportError: - from django.contrib.sites.models import get_current_site # Django < 1.7 +from django.contrib.sites.shortcuts import get_current_site try: # django >= 1.10 @@ -40,16 +37,11 @@ class SuccessURLAllowedHostsMixin(object): # skip since this was not available before django 1.11 pass -try: - from django.contrib.auth import update_session_auth_hash -except ImportError: - # Django < 1.7 - def update_session_auth_hash(request, user): - pass +from django.contrib.auth import update_session_auth_hash from django.shortcuts import redirect, resolve_url from django.utils.functional import lazy -from django.utils.http import base36_to_int, is_safe_url +from django.utils.http import base36_to_int, is_safe_url, urlsafe_base64_decode from django.utils import six from django.views.decorators.cache import never_cache from django.views.decorators.csrf import csrf_protect @@ -112,7 +104,12 @@ def get_next_url(self): except AttributeError: pass - url_is_safe = is_safe_url(redirect_to, allowed_hosts=allowed_hosts, require_https=self.request.is_secure()) + url_is_safe = is_safe_url( + redirect_to, + allowed_hosts=allowed_hosts, + require_https=self.request.is_secure() + ) + except TypeError: # django < 1.11 url_is_safe = is_safe_url(redirect_to, host=host) @@ -172,7 +169,8 @@ class AuthDecoratorsMixin(NeverCacheMixin, CsrfProtectMixin, SensitivePostParame pass -class LoginView(AuthDecoratorsMixin, SuccessURLAllowedHostsMixin, WithCurrentSiteMixin, WithNextUrlMixin, FormView): +class LoginView(AuthDecoratorsMixin, SuccessURLAllowedHostsMixin, + WithCurrentSiteMixin, WithNextUrlMixin, FormView): form_class = AuthenticationForm authentication_form = None template_name = 'registration/login.html' @@ -351,17 +349,9 @@ def get_queryset(self): return User._default_manager.all() def get_user(self): - # django 1.5 uses uidb36, django 1.6 uses uidb64 - uidb36 = self.kwargs.get('uidb36') uidb64 = self.kwargs.get('uidb64') - assert bool(uidb36) ^ bool(uidb64) try: - if uidb36: - uid = base36_to_int(uidb36) - else: - # urlsafe_base64_decode is not available in django 1.5 - from django.utils.http import urlsafe_base64_decode - uid = urlsafe_base64_decode(uidb64) + uid = urlsafe_base64_decode(uidb64) return self.get_queryset().get(pk=uid) except (TypeError, ValueError, OverflowError, User.DoesNotExist): return None diff --git a/tests/manage.py b/tests/manage.py index 097f006..182b3e2 100755 --- a/tests/manage.py +++ b/tests/manage.py @@ -4,11 +4,6 @@ import warnings import django -if django.VERSION[:2] == (1, 6): - # This is only necessary for Django 1.6 - from django.contrib.auth.tests import custom_user - custom_user.AbstractUser._meta.local_many_to_many = [] - custom_user.PermissionsMixin._meta.local_many_to_many = [] warnings.simplefilter('error') From f2b04f0674ff469110dd8c8ff58a90cfc332f3b5 Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 13:21:08 -0600 Subject: [PATCH 14/18] Whitespace fix --- authtools/forms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/authtools/forms.py b/authtools/forms.py index 486febd..62c3c56 100644 --- a/authtools/forms.py +++ b/authtools/forms.py @@ -19,7 +19,6 @@ def is_password_usable(pw): """Decide whether a password is usable only by the unusable password prefix. - We can't use django.contrib.auth.hashers.is_password_usable either, because it not only checks against the unusable password, but checks for a valid hasher too. We need different error messages in those cases. From 142d44e995cd589964a03b0d18011bfd38405854 Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 13:34:11 -0600 Subject: [PATCH 15/18] Revert backwards-incompatible change --- authtools/forms.py | 8 ++++---- tests/tests/tests.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/authtools/forms.py b/authtools/forms.py index 62c3c56..8ab1f99 100644 --- a/authtools/forms.py +++ b/authtools/forms.py @@ -163,9 +163,9 @@ def __init__(self, *args, **kwargs): class FriendlyPasswordResetForm(OldPasswordResetForm): error_messages = dict(getattr(OldPasswordResetForm, 'error_messages', {})) - error_messages['unknown_email'] = _("This email address doesn't have an " - "associated user account. Are you " - "sure you've registered?") + error_messages['unknown'] = _("This email address doesn't have an " + "associated user account. Are you " + "sure you've registered?") def clean_email(self): """Return an error message if the email address being reset is unknown. @@ -183,7 +183,7 @@ def clean_email(self): results = list(self.get_users(email)) if not results: - raise forms.ValidationError(self.error_messages['unknown_email']) + raise forms.ValidationError(self.error_messages['unknown']) return email diff --git a/tests/tests/tests.py b/tests/tests/tests.py index a349c76..424e841 100644 --- a/tests/tests/tests.py +++ b/tests/tests/tests.py @@ -240,7 +240,7 @@ def test_email_not_found_in_friendly_password_reset_form(self): self.assertEqual(response.status_code, 200) response = self.client.post('/friendly_password_reset/', {'email': 'not_a_real_email@email.com'}) - self.assertFormError(response, FriendlyPasswordResetForm.error_messages['unknown_email']) + self.assertFormError(response, FriendlyPasswordResetForm.error_messages['unknown']) self.assertEqual(len(mail.outbox), 0) def test_user_only_fetched_once(self): From 623d2e3917c4c053546b77da6d2fe7760c89c5de Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 13:34:24 -0600 Subject: [PATCH 16/18] Remove unused view --- authtools/urls.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/authtools/urls.py b/authtools/urls.py index 79ceec2..788a213 100644 --- a/authtools/urls.py +++ b/authtools/urls.py @@ -39,10 +39,6 @@ views.PasswordResetCompleteView.as_view(), name='password_reset_complete' ), - url( - r'^reset/(?P[0-9A-Za-z]{1,13})-(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', - views.PasswordResetConfirmView.as_view() - ), url( r'^reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', views.PasswordResetConfirmView.as_view(), From 4f36222d06a77db160396926c585e997439bf1e3 Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 13:42:33 -0600 Subject: [PATCH 17/18] Add tox envs to travis --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6b8cdd9..c6fb221 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,12 +8,21 @@ cache: env: - TOX_ENV=py27-dj18 - TOX_ENV=py33-dj18 + - TOX_ENV=py34-dj18 + - TOX_ENV=py35-dj18 + - TOX_ENV=py36-dj18 - TOX_ENV=py27-dj19 - TOX_ENV=py34-dj19 + - TOX_ENV=py35-dj19 + - TOX_ENV=py36-dj19 - TOX_ENV=py27-dj110 - TOX_ENV=py34-dj110 + - TOX_ENV=py35-dj110 + - TOX_ENV=py36-dj110 - TOX_ENV=py27-dj111 - TOX_ENV=py34-dj111 + - TOX_ENV=py35-dj111 + - TOX_ENV=py36-dj111 install: - pip install --upgrade pip - pip install tox==1.8.0 From c5c9979c25d6eaed93578eeb9ada8789a8274633 Mon Sep 17 00:00:00 2001 From: Alexander Bliskovsky Date: Wed, 14 Jun 2017 14:11:22 -0600 Subject: [PATCH 18/18] Revert "Add tox envs to travis" Travis does not support multiple testing environments with Python 3.5 and 3.6 yet. This reverts commit 4f36222d06a77db160396926c585e997439bf1e3. --- .travis.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index c6fb221..6b8cdd9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,21 +8,12 @@ cache: env: - TOX_ENV=py27-dj18 - TOX_ENV=py33-dj18 - - TOX_ENV=py34-dj18 - - TOX_ENV=py35-dj18 - - TOX_ENV=py36-dj18 - TOX_ENV=py27-dj19 - TOX_ENV=py34-dj19 - - TOX_ENV=py35-dj19 - - TOX_ENV=py36-dj19 - TOX_ENV=py27-dj110 - TOX_ENV=py34-dj110 - - TOX_ENV=py35-dj110 - - TOX_ENV=py36-dj110 - TOX_ENV=py27-dj111 - TOX_ENV=py34-dj111 - - TOX_ENV=py35-dj111 - - TOX_ENV=py36-dj111 install: - pip install --upgrade pip - pip install tox==1.8.0