Commit aa97e5d6 authored by Bruno Renié's avatar Bruno Renié Committed by GitHub
Browse files

Merge pull request #50 from timgraham/drop-old-djangos

Drop support for Django < 1.8
parents 6cf1b71b 22b0c0b2
......@@ -3,24 +3,19 @@ python: "3.5"
sudo: false
cache: pip
env:
- TOXENV=py26-django15
- TOXENV=py26-django16
- TOXENV=py27-django15
- TOXENV=py27-django16
- TOXENV=py27-django17
- TOXENV=py27-django18
- TOXENV=py27-django19
- TOXENV=py33-django15
- TOXENV=py33-django16
- TOXENV=py33-django17
- TOXENV=py27-django110
- TOXENV=py27-django111
- TOXENV=py33-django18
- TOXENV=py34-django15
- TOXENV=py34-django16
- TOXENV=py34-django17
- TOXENV=py34-django18
- TOXENV=py34-django19
- TOXENV=py34-django110
- TOXENV=py34-django111
- TOXENV=py35-django18
- TOXENV=py35-django19
- TOXENV=py35-django110
- TOXENV=py35-django111
- TOXENV=docs
- TOXENV=lint
install:
......
......@@ -16,7 +16,7 @@ functionality.
* Author: Bruno Renié and `contributors`_
* Licence: BSD
* Compatibility: Django 1.4+ (cryptographic signing needed)
* Compatibility: Django 1.8+
.. _contributors: https://github.com/brutasse/django-password-reset/contributors
......
......@@ -28,6 +28,10 @@ Contents:
Changelog
---------
* 1.0 (TBA):
* Drop support for Django < 1.8 and confirm support for Django 1.10 and 1.11.
* 0.9 (2016-06-01):
* Allow token expiration time to be customized with a setting.
......
from django import forms
from django.contrib.auth import get_user_model
from django.core.validators import validate_email
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from .utils import get_user_model
class PasswordRecoveryForm(forms.Form):
username_or_email = forms.CharField()
......
from django.contrib.auth.models import (
AbstractBaseUser, AbstractUser, BaseUserManager, Group, Permission,
PermissionsMixin, UserManager,
)
from django.db import models
# The custom User uses email as the unique identifier, and requires
# that every user provide a date of birth. This lets us test
# changes in username datatype, and non-text required fields.
class CustomUserManager(BaseUserManager):
def create_user(self, email, date_of_birth, password=None):
"""
Creates and saves a User with the given email and password.
"""
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=self.normalize_email(email),
date_of_birth=date_of_birth,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password, date_of_birth):
u = self.create_user(
email, password=password, date_of_birth=date_of_birth,
)
u.is_admin = True
u.save(using=self._db)
return u
class CustomUser(AbstractBaseUser):
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
date_of_birth = models.DateField()
custom_objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['date_of_birth']
def get_full_name(self):
return self.email
def get_short_name(self):
return self.email
def __str__(self):
return self.email
# Maybe required?
def get_group_permissions(self, obj=None):
return set()
def get_all_permissions(self, obj=None):
return set()
def has_perm(self, perm, obj=None):
return True
def has_perms(self, perm_list, obj=None):
return True
def has_module_perms(self, app_label):
return True
# Admin required fields
@property
def is_staff(self):
return self.is_admin
class RemoveGroupsAndPermissions(object):
"""
A context manager to temporarily remove the groups and user_permissions M2M
fields from the AbstractUser class, so they don't clash with the
related_name sets.
"""
def __enter__(self):
self._old_au_local_m2m = AbstractUser._meta.local_many_to_many
self._old_pm_local_m2m = PermissionsMixin._meta.local_many_to_many
groups = models.ManyToManyField(Group, blank=True)
groups.contribute_to_class(PermissionsMixin, "groups")
user_permissions = models.ManyToManyField(Permission, blank=True)
user_permissions.contribute_to_class(
PermissionsMixin, "user_permissions"
)
PermissionsMixin._meta.local_many_to_many = [groups, user_permissions]
AbstractUser._meta.local_many_to_many = [groups, user_permissions]
def __exit__(self, exc_type, exc_value, traceback):
AbstractUser._meta.local_many_to_many = self._old_au_local_m2m
PermissionsMixin._meta.local_many_to_many = self._old_pm_local_m2m
# The extension user is a simple extension of the built-in user class,
# adding a required date_of_birth field.
with RemoveGroupsAndPermissions():
class ExtensionUser(AbstractUser):
date_of_birth = models.DateField()
custom_objects = UserManager()
REQUIRED_FIELDS = AbstractUser.REQUIRED_FIELDS + ['date_of_birth']
......@@ -16,9 +16,7 @@ INSTALLED_APPS = (
'password_reset.tests',
)
MIGRATION_MODULES = {
'auth': 'django.contrib.auth.tests.migrations',
}
MIDDLEWARE = []
TEMPLATES = [{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
......
import django
from unittest import SkipTest
from django.contrib.auth import get_user_model
from django.core import mail
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.test.utils import override_settings
from django.utils import timezone
from django.utils.six import with_metaclass
try:
from django.utils.unittest import SkipTest
except ImportError:
from unittest import SkipTest
from ..forms import PasswordRecoveryForm, PasswordResetForm
from ..utils import get_user_model
if django.VERSION >= (1, 5):
from django.contrib.auth.tests.custom_user import ( # noqa
CustomUser, ExtensionUser)
else:
CustomUser = None # noqa
ExtensionUser = None # noqa
from .models import CustomUser, ExtensionUser
class CustomUserVariants(type):
def __new__(cls, name, bases, dct):
if django.VERSION >= (1, 5):
for custom_user in ['auth.CustomUser', 'auth.ExtensionUser']:
suffix = custom_user.lower().replace('.', '_')
for key, fn in list(dct.items()):
if key.startswith('test') and '_CUSTOM_' not in key:
name = '{0}_CUSTOM_{1}'.format(key, suffix)
dct[name] = override_settings(
AUTH_USER_MODEL=custom_user)(fn)
for custom_user in ['tests.CustomUser', 'tests.ExtensionUser']:
suffix = custom_user.lower().replace('.', '_')
for key, fn in list(dct.items()):
if key.startswith('test') and '_CUSTOM_' not in key:
name = '{0}_CUSTOM_{1}'.format(key, suffix)
dct[name] = override_settings(
AUTH_USER_MODEL=custom_user)(fn)
return super(CustomUserVariants, cls).__new__(cls, name, bases, dct)
......
......@@ -3,14 +3,20 @@ from .. import views
class EmailRecover(views.Recover):
search_fields = ['email']
email_recover = EmailRecover.as_view()
class UsernameRecover(views.Recover):
search_fields = ['username']
username_recover = UsernameRecover.as_view()
class InsensitiveRecover(views.Recover):
case_sensitive = False
insensitive_recover = InsensitiveRecover.as_view()
try:
from django.contrib.auth import get_user_model
except ImportError:
from django.contrib.auth.models import User
def get_user_model():
return User
def get_username(user):
username_field = getattr(user, 'USERNAME_FIELD', 'username')
return getattr(user, username_field)
import datetime
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.sites.shortcuts import get_current_site
from django.core import signing
from django.core.mail import send_mail
from django.core.urlresolvers import reverse, reverse_lazy
......@@ -12,14 +14,9 @@ from django.utils.decorators import method_decorator
from django.views import generic
from django.views.decorators.debug import sensitive_post_parameters
try:
from django.contrib.sites.shortcuts import get_current_site
except ImportError:
from django.contrib.sites.models import get_current_site
from .forms import PasswordRecoveryForm, PasswordResetForm
from .signals import user_recovers_password
from .utils import get_user_model, get_username
class SaltMixin(object):
......@@ -50,6 +47,8 @@ class RecoverDone(SaltMixin, generic.TemplateView):
except signing.BadSignature:
raise Http404
return ctx
recover_done = RecoverDone.as_view()
......@@ -84,7 +83,7 @@ class Recover(SaltMixin, generic.FormView):
context = {
'site': self.get_site(),
'user': self.user,
'username': get_username(self.user),
'username': self.user.get_username(),
'token': signing.dumps(self.user.pk, salt=self.salt),
'secure': self.request.is_secure(),
}
......@@ -109,6 +108,8 @@ class Recover(SaltMixin, generic.FormView):
email = self.user.email
self.mail_signature = signing.dumps(email, salt=self.url_salt)
return super(Recover, self).form_valid(form)
recover = Recover.as_view()
......@@ -154,7 +155,7 @@ class Reset(SaltMixin, generic.FormView):
ctx = super(Reset, self).get_context_data(**kwargs)
if 'invalid' not in ctx:
ctx.update({
'username': get_username(self.user),
'username': self.user.get_username(),
'token': self.kwargs['token'],
})
return ctx
......@@ -167,6 +168,8 @@ class Reset(SaltMixin, generic.FormView):
request=self.request
)
return redirect(self.get_success_url())
reset = Reset.as_view()
......
......@@ -4,23 +4,18 @@ import sys
import warnings
import django
from django.test.runner import DiscoverRunner
warnings.simplefilter('always')
os.environ['DJANGO_SETTINGS_MODULE'] = 'password_reset.tests.settings'
try:
from django.test.runner import DiscoverRunner
except ImportError:
from discover_runner import DiscoverRunner
def runtests():
parent = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, parent)
if django.VERSION >= (1, 7):
django.setup()
django.setup()
runner = DiscoverRunner(verbosity=1, interactive=True,
failfast=bool(os.environ.get('FAILFAST')))
......
......@@ -19,12 +19,16 @@ setup(
description='Class-based views for password reset.',
long_description=open('README.rst').read(),
install_requires=[
'Django>=1.4',
'Django>=1.8',
],
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
'Framework :: Django',
'Framework :: Django :: 1.8',
'Framework :: Django :: 1.9',
'Framework :: Django :: 1.10',
'Framework :: Django :: 1.11',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Natural Language :: English',
......
[tox]
envlist =
py26-django1{5,6},
py27-django1{5,6,7,8,9},
py33-django1{5,6,7,8},
py34-django1{5,6,7,8,9},
py35-django1{8,9},
py27-django1{8,9,10,11},
py33-django18,
py34-django1{8,9,10,11},
py35-django1{8,9,10,11},
docs, lint
[testenv]
commands = python -Wall setup.py test
basepython =
py26: python2.6
py27: python2.7
py33: python3.3
py34: python3.4
py35: python3.5
deps =
django-discover-runner
django15: Django>=1.5,<1.6
django16: Django>=1.6,<1.7
django17: Django>=1.7,<1.8
django18: Django>=1.8,<1.9
django19: Django>=1.9,<1.10
django110: Django>=1.10,<1.11
django111: Django>=1.11,<2.0
[testenv:docs]
basepython = python3.5
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment