Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,8 @@ Quick start
3. For more information on those libraries, check the following docs::
1. [django-axes](https://django-axes.readthedocs.io/en/latest/)
2. [django-recaptcha](https://github.com/praekelt/django-recaptcha)


8. If you have i18n enabled within your application, you can set a preferred language for the user
1. If you define a default language `LANGUAGE_CODE` it will be used as default or `en`
2. Languages supported are those languages you define in your application in `LANGUAGES` setting
2 changes: 1 addition & 1 deletion accountsplus/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__version__ = '1.4.2'
__version__ = '1.4.8'

default_app_config = 'accountsplus.apps.AccountsConfig'
15 changes: 15 additions & 0 deletions accountsplus/context_managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from __future__ import unicode_literals

from contextlib import contextmanager

from django.utils import translation


@contextmanager
def language(lang):
old_language = translation.get_language()
try:
translation.activate(lang)
yield
finally:
translation.activate(old_language)
12 changes: 11 additions & 1 deletion accountsplus/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import django.forms
from django.conf import settings
from django.apps import apps
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm
from django.contrib.admin.forms import AdminAuthenticationForm

from captcha.fields import ReCaptchaField

import context_managers

class CaptchaForm(django.forms.Form):
captcha = ReCaptchaField()
Expand All @@ -31,3 +32,12 @@ class EmailBasedAdminAuthenticationForm(AdminAuthenticationForm):

def clean_username(self):
return self.data['username'].lower()


class CustomPasswordResetForm(PasswordResetForm):

def send_mail(self, subject_template_name, email_template_name,
context, from_email, to_email, html_email_template_name=None):
with context_managers.language(context['email_lang']):
super(CustomPasswordResetForm, self).send_mail(subject_template_name, email_template_name, context,
from_email, to_email, html_email_template_name)
24 changes: 24 additions & 0 deletions accountsplus/middleware.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
from __future__ import unicode_literals
import django.utils.timezone
from django.utils.deprecation import MiddlewareMixin
from django.utils import translation


class TimezoneMiddleware(MiddlewareMixin):

def process_request(self, request):
if request.user.is_authenticated() and request.user.timezone:
django.utils.timezone.activate(request.user.timezone)
else:
django.utils.timezone.deactivate()


class UserLanguageMiddleware(MiddlewareMixin):

# Update user preferred language each time a request has a new language and activate translation for that user.
# Should be added after LocaleMiddleware as it depends on having request.LANGUAGE_CODE configured there.
def process_request(self, request):
if hasattr(request, 'user'):
user = request.user
if hasattr(user, 'preferred_language'):
if not user.preferred_language or user.preferred_language != request.LANGUAGE_CODE:
user.preferred_language = request.LANGUAGE_CODE
user.save()
else:
translation.activate(user.preferred_language)
request.LANGUAGE_CODE = translation.get_language()

lang_in_url = request.GET.get('lang')
if lang_in_url:
translation.activate(lang_in_url)
request.LANGUAGE_CODE = translation.get_language()

4 changes: 4 additions & 0 deletions accountsplus/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import timezone_field
import localflavor.us.models

import settings

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -103,6 +105,8 @@ class BaseUser(django.contrib.auth.base_user.AbstractBaseUser, django.contrib.au
last_name = django.db.models.CharField(_('Last Name'), max_length=50)
email = django.db.models.EmailField(_('Email'), unique=True)
timezone = timezone_field.TimeZoneField(default='America/New_York')
preferred_language = django.db.models.CharField(_('Preferred Language'), choices=settings.SUPPORTED_LANGUAGES,
blank=True, null=True, max_length=10)

objects = UserManager()

Expand Down
19 changes: 19 additions & 0 deletions accountsplus/settings.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.apps import apps
from django.utils.translation import ugettext_lazy as _


# Default values
LOCKOUT_TEMPLATE = 'accounts/locked_out.html'

ENGLISH_LANGUAGE = 'en-us'
SPANISH_LANGUAGE = 'es'
FRENCH_LANGUAGE = 'fr'
PORTUGUESE_LANGUAGE = 'pt'
ARABIC_LANGUAGE = 'ar'
DEFAULT_SUPPORTED_LANGUAGES = (
(ENGLISH_LANGUAGE, _('English')),
(SPANISH_LANGUAGE, _('Spanish')),
(FRENCH_LANGUAGE, _('French')),
(PORTUGUESE_LANGUAGE, _('Portuguese')),
(ARABIC_LANGUAGE, _('Arabic')),
)


def get_setting(setting_str, is_required, default_value=None):
try:
return getattr(settings, setting_str)
Expand Down Expand Up @@ -60,3 +75,7 @@ def get_lockout_template():
LOGIN_FAILURE_LIMIT = int(get_login_failure_limit())
LOCKOUT_URL = str(get_lockout_url())
LOCKOUT_TEMPLATE = get_lockout_template()


SUPPORTED_LANGUAGES = get_setting('LANGUAGES', False, DEFAULT_SUPPORTED_LANGUAGES)
DEFAULT_LANGUAGE = get_setting('LANGUAGE_CODE', False, ENGLISH_LANGUAGE)
11 changes: 11 additions & 0 deletions accountsplus/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,14 @@ def validate(self, password, user=None):
def get_help_text(self):
return _('Password should contain uppercase, lowercase, numeric values and at least '
'one of the following $@#!%*?&')


class CustomPasswordValidator(object):

def validate(self, password, user=None):
regex = '(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d\-\.\`\/\\#\^!\|~\*<>\?=\+@\{}_\$%\(\)\[]]*'
if not re.match(regex, password):
raise ValidationError(_('Must be 8 digits, including at least 1 uppercase letter and 1 number. Can include special characters.'), code='password_is_weak')

def get_help_text(self):
return _('Must be 8 digits, including at least 1 uppercase letter and 1 number. Can include special characters.')
11 changes: 10 additions & 1 deletion accountsplus/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def password_reset(request,
template_name='registration/password_reset_form.html',
email_template_name='registration/password_reset_email.html',
subject_template_name='registration/password_reset_subject.txt',
password_reset_form=django.contrib.auth.forms.PasswordResetForm,
password_reset_form=forms.CustomPasswordResetForm,
token_generator=django.contrib.auth.views.default_token_generator,
post_reset_redirect=None,
from_email=None,
Expand All @@ -172,6 +172,15 @@ def password_reset(request,
html_email_template_name=None,
extra_email_context=None):
User = django.contrib.auth.get_user_model()
# We set this always in the middleware to the preferred language of the user
if request.method == 'POST':
email = request.POST['email']
extra_email_context = extra_email_context or {}
try:
user = User.objects.get(email=email)
extra_email_context['email_lang'] = user.preferred_language or request.LANGUAGE_CODE
except User.DoesNotExist:
pass

response = django.contrib.auth.views.password_reset(
request, template_name, email_template_name,
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ flake8
pep8
pyflakes
django-timezone-field>=2.0rc1
django-localflavor
django-localflavor==1.3
bcrypt==3.1.0
mock
git+https://github.com/foundertherapy/django-axes.git@remove_dependency_on_ip
Expand Down