From adbdd0c9a6f210e9bd4c8c3096e4e9c3cedc94ec Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 10:53:14 +0100 Subject: [PATCH 01/29] Remove unsupported Django imports --- docs/conf.py | 1 - password_policies/forms/__init__.py | 17 ++-------- password_policies/forms/validators.py | 32 ++++--------------- password_policies/middleware.py | 7 +--- .../south_migrations/0001_initial.py | 11 ++----- password_policies/tests/test_forms.py | 14 +++----- password_policies/urls.py | 18 +---------- password_policies/views.py | 17 ++-------- 8 files changed, 23 insertions(+), 94 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 9b29770..70dad4e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,7 +22,6 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "_ext"))) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "password_policies.tests.test_settings") -from password_policies.tests import test_settings django.setup() diff --git a/password_policies/forms/__init__.py b/password_policies/forms/__init__.py index b4f71cb..fbfe208 100644 --- a/password_policies/forms/__init__.py +++ b/password_policies/forms/__init__.py @@ -7,20 +7,9 @@ from django.core.exceptions import ObjectDoesNotExist from django.template import loader -try: - # SortedDict is deprecated as of Django 1.7 and will be removed in Django 1.9. - # https://code.djangoproject.com/wiki/SortedDict - from collections import OrderedDict as SortedDict -except ImportError: - from django.utils.datastructures import SortedDict - - -try: - from django.contrib.sites.shortcuts import get_current_site -except ImportError: - # Before Django 1.9 - from django.contrib.sites.models import get_current_site +from collections import OrderedDict +from django.contrib.sites.shortcuts import get_current_site from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode try: @@ -168,7 +157,7 @@ def save(self, commit=True): return user -PasswordPoliciesChangeForm.base_fields = SortedDict( +PasswordPoliciesChangeForm.base_fields = OrderedDict( [ (k, PasswordPoliciesChangeForm.base_fields[k]) for k in ["old_password", "new_password1", "new_password2"] diff --git a/password_policies/forms/validators.py b/password_policies/forms/validators.py index 69b0185..26bbef6 100644 --- a/password_policies/forms/validators.py +++ b/password_policies/forms/validators.py @@ -6,17 +6,8 @@ from django.core.exceptions import ValidationError -try: - from django.utils.encoding import smart_str as smart_text -except ImportError: - # Before in Django 2.0 - from django.utils.encoding import smart_text +from django.utils.encoding import smart_str, force_str -try: - from django.utils.encoding import force_str as force_text -except ImportError: - # Before in Django 2.0 - from django.utils.encoding import force_text try: from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext as ungettext @@ -27,15 +18,6 @@ from password_policies.conf import settings -try: - # Python 3 does not have an xrange, this will throw a NameError - xrange -except NameError: - pass -else: - # alias range to xrange for Python 2 - range = xrange # noqa - class BaseCountValidator: """ @@ -48,7 +30,7 @@ def __call__(self, value): if not self.get_min_count(): return counter = 0 - for character in force_text(value): + for character in force_str(value): category = unicodedata.category(character) if category in self.categories: counter += 1 @@ -78,13 +60,13 @@ class BaseRFC4013Validator: r_and_al_cat = False def __call__(self, value): - value = force_text(value) + value = force_str(value) self.first = value[0] self.last = value[:-1] self._process(value) def _process(self, value): - for code in force_text(value): + for code in force_str(value): # TODO: Is this long enough? if ( stringprep.in_table_c12(code) @@ -123,7 +105,7 @@ def __init__(self, haystacks=[]): self.haystacks = haystacks def __call__(self, value): - needle = force_text(value) + needle = force_str(value) for haystack in self.haystacks: distance = self.fuzzy_substring(needle, haystack) longest = max(len(needle), len(haystack)) @@ -212,7 +194,7 @@ def __call__(self, value): if not self.get_max_count(): return consecutive_found = False - for _ign, group in itertools.groupby(force_text(value)): + for _ign, group in itertools.groupby(force_str(value)): if len(list(group)) > self.get_max_count(): consecutive_found = True if consecutive_found: @@ -398,7 +380,7 @@ def __init__(self, dictionary="", words=[]): if self.dictionary: with open(self.dictionary) as dictionary: haystacks.extend( - [smart_text(x.strip()) for x in dictionary.readlines()] + [smart_str(x.strip()) for x in dictionary.readlines()] ) if self.words: haystacks.extend(self.words) diff --git a/password_policies/middleware.py b/password_policies/middleware.py index 2a1bb03..5228f25 100644 --- a/password_policies/middleware.py +++ b/password_policies/middleware.py @@ -1,12 +1,7 @@ import re from datetime import timedelta -try: - from django.urls.base import reverse, resolve, NoReverseMatch, Resolver404 -except ImportError: - # Before Django 2.0 - from django.core.urlresolvers import NoReverseMatch, Resolver404, resolve, reverse - +from django.urls.base import reverse, resolve, NoReverseMatch, Resolver404 from django.http import HttpResponseRedirect from django.utils import timezone diff --git a/password_policies/south_migrations/0001_initial.py b/password_policies/south_migrations/0001_initial.py index dc0620c..375c51d 100644 --- a/password_policies/south_migrations/0001_initial.py +++ b/password_policies/south_migrations/0001_initial.py @@ -1,15 +1,10 @@ # -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime from south.db import db from south.v2 import SchemaMigration -from django.db import models -try: - from django.contrib.auth import get_user_model -except ImportError: # django < 1.5 - from django.contrib.auth.models import User -else: - User = get_user_model() +from django.contrib.auth import get_user_model + +User = get_user_model() class Migration(SchemaMigration): diff --git a/password_policies/tests/test_forms.py b/password_policies/tests/test_forms.py index 1e38b7c..428cbec 100644 --- a/password_policies/tests/test_forms.py +++ b/password_policies/tests/test_forms.py @@ -1,10 +1,6 @@ from django.test import TestCase from django.test.utils import override_settings - -try: - from django.utils.encoding import force_text -except ImportError: - from django.utils.encoding import force_str as force_text +from django.utils.encoding import force_str from password_policies.forms import ( PasswordPoliciesChangeForm, @@ -136,7 +132,7 @@ def test_reused_password(self): self.assertFalse(form.is_valid()) self.assertEqual( form["new_password1"].errors, - [force_text(form.error_messages["password_used"])], + [force_str(form.error_messages["password_used"])], ) def test_password_mismatch(self): @@ -145,7 +141,7 @@ def test_password_mismatch(self): self.assertFalse(form.is_valid()) self.assertEqual( form["new_password2"].errors, - [force_text(form.error_messages["password_mismatch"])], + [force_str(form.error_messages["password_mismatch"])], ) def test_password_verification_unicode(self): @@ -176,7 +172,7 @@ def test_password_invalid(self): self.assertFalse(form.is_valid()) self.assertEqual( form["old_password"].errors, - [force_text(form.error_messages["password_incorrect"])], + [force_str(form.error_messages["password_incorrect"])], ) self.assertFalse(form.is_valid()) @@ -202,7 +198,7 @@ def test_unusable_password(self): form = PasswordResetForm(data) self.assertFalse(form.is_valid()) self.assertEqual( - form["email"].errors, [force_text(form.error_messages["unusable"])] + form["email"].errors, [force_str(form.error_messages["unusable"])] ) self.assertFalse(form.is_valid()) diff --git a/password_policies/urls.py b/password_policies/urls.py index 37515db..8d2e5f6 100644 --- a/password_policies/urls.py +++ b/password_policies/urls.py @@ -1,16 +1,4 @@ -try: - from django.urls import re_path as url -except ImportError: - # Before Django 2.0 - from django.conf.urls import url - -import django.conf.urls -if hasattr(django.conf.urls, 'patterns'): - # patterns was deprecated in Django 1.8 - from django.conf.urls import patterns -else: - # patterns is unavailable in Django 1.10+ - patterns = False +from django.urls import re_path as url from password_policies.views import ( PasswordChangeDoneView, @@ -39,7 +27,3 @@ ), url(r"^reset/done/$", PasswordResetDoneView.as_view(), name="password_reset_done"), ] - -if patterns: - # Django 1.7 - urlpatterns = patterns("", *urlpatterns) diff --git a/password_policies/views.py b/password_policies/views.py index a630c27..278d073 100644 --- a/password_policies/views.py +++ b/password_policies/views.py @@ -4,22 +4,11 @@ from django.utils import timezone from password_policies.exceptions import MustBeLoggedOutException - -try: - from django.urls.base import reverse -except ImportError: - # Before Django 2.0 - from django.core.urlresolvers import reverse - +from django.urls.base import reverse from django.shortcuts import resolve_url from django.utils.decorators import method_decorator -try: - from django.utils.encoding import force_str as force_text -except ImportError: - # Before in Django 2.0 - from django.utils.encoding import force_text - +from django.utils.encoding import force_str from django.utils.http import urlsafe_base64_decode from django.views.decorators.cache import never_cache from django.views.decorators.csrf import csrf_protect @@ -172,7 +161,7 @@ def dispatch(self, request, *args, **kwargs): self.validlink = False if self.uidb64 and self.timestamp and self.signature: try: - uid = force_text(urlsafe_base64_decode(self.uidb64)) + uid = force_str(urlsafe_base64_decode(self.uidb64)) self.user = get_user_model().objects.get(id=uid) except (ValueError, get_user_model().DoesNotExist): self.user = None From 23f203898643e8efa98fe87f121473d83997420b Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 10:55:48 +0100 Subject: [PATCH 02/29] Replace various pre-commit tasks with ruff --- .pre-commit-config.yaml | 61 ++++++----------------------------------- ruff.toml | 1 + 2 files changed, 9 insertions(+), 53 deletions(-) create mode 100644 ruff.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 63d431e..5327caa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,36 +8,8 @@ repos: entry: 'breakpoint\(\)|set_trace' language: pygrep - - repo: https://github.com/myint/autoflake - rev: 'v1.4' - hooks: - - id: autoflake - args: ['--in-place', '--remove-all-unused-imports',] - - - repo: https://github.com/pre-commit/mirrors-autopep8 - rev: 'v1.6.0' - hooks: - - id: autopep8 - args: ['--in-place', '-aaa',] - - - repo: https://github.com/hadialqattan/pycln - rev: 'v1.1.0' - hooks: - - id: pycln - - - repo: https://github.com/pre-commit/mirrors-isort - rev: 'v5.10.1' # Use the revision sha / tag you want to point at - hooks: - - id: isort - args: ["--profile", "black"] - - - repo: https://github.com/psf/black - rev: '22.3.0' - hooks: - - id: black - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: 'v4.1.0' + rev: 'v4.6.0' hooks: - id: check-yaml - id: check-toml @@ -45,28 +17,11 @@ repos: - id: trailing-whitespace - id: mixed-line-ending - - repo: https://github.com/PyCQA/flake8 - rev: '6.0.0' - hooks: - - id: flake8 - args: ["--exclude=*/migrations/*"] - -# -# - repo: local -# hooks: -# - id: pylint -# name: pylint -# entry: pylint -# language: system -# types: [python] -# args: -# [ -# "-rn", # Only display messages -# "-sn", # Don't display the score -# ] -# - - repo: https://github.com/asottile/pyupgrade - rev: v2.31.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.5 hooks: - - id: pyupgrade - args: ["--py37-plus"] + # Run the linter. + - id: ruff + args: [ --fix ] + # Run the formatter. + - id: ruff-format diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..7f36bb4 --- /dev/null +++ b/ruff.toml @@ -0,0 +1 @@ +target-version = "py37" From 60c153d70e99c9fcdc39f9203cf61b0a1b8a584d Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 11:13:43 +0100 Subject: [PATCH 03/29] Run ruff on project --- docs/_ext/exts.py | 222 ++++++------ docs/_ext/fields.py | 323 ++++++++++-------- docs/conf.py | 143 ++++---- password_policies/admin.py | 1 + password_policies/conf/settings.py | 22 +- password_policies/context_processors.py | 38 +-- password_policies/forms/__init__.py | 3 +- password_policies/forms/admin.py | 46 +-- password_policies/forms/fields.py | 29 +- password_policies/forms/validators.py | 4 +- password_policies/managers.py | 52 +-- password_policies/middleware.py | 6 +- password_policies/migrations/0001_initial.py | 92 ++++- .../migrations/0002_passwordprofile.py | 52 ++- .../migrations/0003_update_passwordprofile.py | 25 +- password_policies/models.py | 1 + password_policies/receivers.py | 3 +- .../south_migrations/0001_initial.py | 196 ++++++++--- password_policies/tests/test_views.py | 16 +- password_policies/utils.py | 20 +- 20 files changed, 773 insertions(+), 521 deletions(-) diff --git a/docs/_ext/exts.py b/docs/_ext/exts.py index aa0504f..49d962e 100644 --- a/docs/_ext/exts.py +++ b/docs/_ext/exts.py @@ -15,123 +15,129 @@ def process_docstring(app, what, name, obj, options, lines): if inspect.isclass(obj) and issubclass(obj, models.Model): # Grab the field list from the meta class fields = obj._meta.fields - lines.append(u'') + lines.append("") for field in fields: # Do not document AutoFields - if type(field).__name__ == 'AutoField' and field.primary_key: + if type(field).__name__ == "AutoField" and field.primary_key: continue - k = type(field).__name__ # Decode and strip any html out of the field's help text help_text = strip_tags(force_unicode(field.help_text)) - # Decode and capitalize the verbose name, for use if there isn't - # any help text - verbose_name = force_unicode(field.verbose_name).capitalize() - - lines.append(u'.. attribute:: %s' % field.name) - lines.append(u' ') + lines.append(".. attribute:: %s" % field.name) + lines.append(" ") # Add the field's type to the docstring if isinstance(field, models.ForeignKey): to = field.rel.to - l = u' %s(\':class:`~%s.%s`\')' % (type(field).__name__, - to.__module__, - to.__name__) + msg = " %s(':class:`~%s.%s`')" % ( + type(field).__name__, + to.__module__, + to.__name__, + ) elif isinstance(field, models.OneToOneField): to = field.rel.to - l = u' %s(\':class:`~%s.%s`\')' % (type(field).__name__, - to.__module__, - to.__name__) + msg = " %s(':class:`~%s.%s`')" % ( + type(field).__name__, + to.__module__, + to.__name__, + ) else: - l = u' %s' % type(field).__name__ + msg = " %s" % type(field).__name__ if not field.blank: - l = l + ' (Required)' - if hasattr(field, 'auto_now') and field.auto_now: - l = l + ' (Automatically set when updated)' - if hasattr(field, 'auto_now_add') and field.auto_now_add: - l = l + ' (Automatically set when created)' - lines.append(l) + msg = msg + " (Required)" + if hasattr(field, "auto_now") and field.auto_now: + msg = msg + " (Automatically set when updated)" + if hasattr(field, "auto_now_add") and field.auto_now_add: + msg = msg + " (Automatically set when created)" + lines.append(msg) if help_text: - lines.append(u'') + lines.append("") # Add the model field to the end of the docstring as a param # using the help text as the description - lines.append(u' %s' % help_text) - lines.append(u' ') + lines.append(" %s" % help_text) + lines.append(" ") f = model_fields[type(field).__name__] for key in sorted(f.iterkeys()): - - if hasattr(field, key) and getattr(field, key) != f[key] and getattr(field, key): + if ( + hasattr(field, key) + and getattr(field, key) != f[key] + and getattr(field, key) + ): attr = getattr(field, key) - if key == 'error_messages': + if key == "error_messages": error_dict = {} for i in sorted(attr.iterkeys()): error_dict[i] = force_unicode(attr[i]) attr = error_dict - if key == 'validators': + if key == "validators": v = [] for i in sorted(attr): - n = ':class:`~%s.%s`' % (type(i).__module__, - type(i).__name__) + n = ":class:`~%s.%s`" % ( + type(i).__module__, + type(i).__name__, + ) v.append(n) attr = v - lines.append(u' :param %s: %s' % (key, attr)) - lines.append(u'') - lines.append(u'.. attribute:: Meta') - lines.append(u'') + lines.append(" :param %s: %s" % (key, attr)) + lines.append("") + lines.append(".. attribute:: Meta") + lines.append("") for key in sorted(model_meta_fields.iterkeys()): - if hasattr(obj._meta, key) and getattr(obj._meta, key) != model_meta_fields[key]: - lines.append(u' %s = %s' % (key, getattr(obj._meta, key))) - lines.append(u'') - + if ( + hasattr(obj._meta, key) + and getattr(obj._meta, key) != model_meta_fields[key] + ): + lines.append(" %s = %s" % (key, getattr(obj._meta, key))) + lines.append("") # Only look at objects that inherit from Django's base model class if inspect.isclass(obj): if issubclass(obj, forms.Form) or issubclass(obj, forms.ModelForm): # Grab the field list from the meta class fields = obj.base_fields - lines.append(u'') + lines.append("") for field in fields: f = obj.base_fields[field] # Decode and strip any html out of the field's help text - if hasattr(f, 'help_text'): + if hasattr(f, "help_text"): help_text = strip_tags(force_unicode(f.help_text)) - # Decode and capitalize the verbose name, for use if there isn't - # any help text - label = force_unicode(f.label).capitalize() - lines.append(u'.. attribute:: %s' % field) - lines.append(u'') + lines.append(".. attribute:: %s" % field) + lines.append("") # Add the field's type to the docstring field_inst = obj.base_fields[field] - l = u' :class:`~%s.%s`' % (type(field_inst).__module__, - type(field_inst).__name__) + info = " :class:`~%s.%s`" % ( + type(field_inst).__module__, + type(field_inst).__name__, + ) if field_inst.required: - l = l + ' (Required)' - lines.append(l) - lines.append(u'') - if hasattr(f, 'error_messages') and f.error_messages: + info = info + " (Required)" + lines.append(info) + lines.append("") + if hasattr(f, "error_messages") and f.error_messages: msgs = {} for key, value in f.error_messages.items(): msgs[key] = force_unicode(value) - lines.append(u':kwarg error_messages: %s' % msgs) + lines.append(":kwarg error_messages: %s" % msgs) if f.help_text: # Add the model field to the end of the docstring as a param # using the help text as the description - lines.append(u':kwarg help_text: %s' % help_text) - if hasattr(f, 'initial') and f.initial: - lines.append(u':kwarg initial: %s' % f.initial) - if hasattr(f, 'localize'): - lines.append(u':kwarg localize: %s' % f.localize) - if hasattr(f, 'validators') and f.validators: - l = [] + lines.append(":kwarg help_text: %s" % help_text) + if hasattr(f, "initial") and f.initial: + lines.append(":kwarg initial: %s" % f.initial) + if hasattr(f, "localize"): + lines.append(":kwarg localize: %s" % f.localize) + if hasattr(f, "validators") and f.validators: + line_array = [] for v in f.validators: - l.append(':class:`~%s.%s`' % (type(v).__module__, - type(v).__name__)) - lines.append(u':kwarg validators: %s' % l) - lines.append(u':kwarg widget: %s' % type(f.widget).__name__) - lines.append(u'') + line_array.append( + ":class:`~%s.%s`" % (type(v).__module__, type(v).__name__) + ) + lines.append(":kwarg validators: %s" % line_array) + lines.append(":kwarg widget: %s" % type(f.widget).__name__) + lines.append("") # Return the extended docstring return lines @@ -139,74 +145,74 @@ def process_docstring(app, what, name, obj, options, lines): def setup(app): # Register the docstring processor with sphinx - app.connect('autodoc-process-docstring', process_docstring) + app.connect("autodoc-process-docstring", process_docstring) app.add_crossref_type( - directivename = "admin", - rolename = "admin", - indextemplate = "pair: %s; admin", + directivename="admin", + rolename="admin", + indextemplate="pair: %s; admin", ) app.add_crossref_type( - directivename = "command", - rolename = "command", - indextemplate = "pair: %s; command", + directivename="command", + rolename="command", + indextemplate="pair: %s; command", ) app.add_crossref_type( - directivename = "context_processors", - rolename = "context_processors", - indextemplate = "pair: %s; context_processors", + directivename="context_processors", + rolename="context_processors", + indextemplate="pair: %s; context_processors", ) app.add_crossref_type( - directivename = "form", - rolename = "form", - indextemplate = "pair: %s; form", + directivename="form", + rolename="form", + indextemplate="pair: %s; form", ) app.add_crossref_type( - directivename = "formfield", - rolename = "formfield", - indextemplate = "pair: %s; formfield", + directivename="formfield", + rolename="formfield", + indextemplate="pair: %s; formfield", ) app.add_crossref_type( - directivename = "manager", - rolename = "manager", - indextemplate = "pair: %s; manager", + directivename="manager", + rolename="manager", + indextemplate="pair: %s; manager", ) app.add_crossref_type( - directivename = "middleware", - rolename = "middleware", - indextemplate = "pair: %s; middleware", + directivename="middleware", + rolename="middleware", + indextemplate="pair: %s; middleware", ) app.add_crossref_type( - directivename = "model", - rolename = "model", - indextemplate = "pair: %s; model", + directivename="model", + rolename="model", + indextemplate="pair: %s; model", ) app.add_crossref_type( - directivename = "setting", - rolename = "setting", - indextemplate = "pair: %s; setting", + directivename="setting", + rolename="setting", + indextemplate="pair: %s; setting", ) app.add_crossref_type( - directivename = "settings", - rolename = "settings", - indextemplate = "pair: %s; settings", + directivename="settings", + rolename="settings", + indextemplate="pair: %s; settings", ) app.add_crossref_type( - directivename = "signal", - rolename = "signal", - indextemplate = "pair: %s; signal", + directivename="signal", + rolename="signal", + indextemplate="pair: %s; signal", ) app.add_crossref_type( - directivename = "token", - rolename = "token", - indextemplate = "pair: %s; token", + directivename="token", + rolename="token", + indextemplate="pair: %s; token", ) app.add_crossref_type( - directivename = "validator", - rolename = "validator", - indextemplate = "pair: %s; validator", + directivename="validator", + rolename="validator", + indextemplate="pair: %s; validator", ) app.add_crossref_type( - directivename = "view", - rolename = "view", - indextemplate = "pair: %s; view", + directivename="view", + rolename="view", + indextemplate="pair: %s; view", ) diff --git a/docs/_ext/fields.py b/docs/_ext/fields.py index d98c778..382e384 100644 --- a/docs/_ext/fields.py +++ b/docs/_ext/fields.py @@ -1,167 +1,194 @@ from django.db import models from django.db.models.fields import NOT_PROVIDED -_core_model_field_attr = {'blank': False, - 'choices': [], - 'db_column': None, - 'db_index': False, - 'db_tablespace': None, - 'default': NOT_PROVIDED, - 'editable': True, - 'error_messages': None, - 'help_text': None, - 'null': False, - 'primary_key': False, - 'unique': False, - 'validators': None, - 'verbose_name': None} +_core_model_field_attr = { + "blank": False, + "choices": [], + "db_column": None, + "db_index": False, + "db_tablespace": None, + "default": NOT_PROVIDED, + "editable": True, + "error_messages": None, + "help_text": None, + "null": False, + "primary_key": False, + "unique": False, + "validators": None, + "verbose_name": None, +} -_model_fields = {'AutoField': _core_model_field_attr, - 'BigIntegerField': _core_model_field_attr, - 'BooleanField': _core_model_field_attr, - 'CharField': _core_model_field_attr, - 'CommaSeparatedIntegerField': _core_model_field_attr, - 'DateField': _core_model_field_attr, - 'DateTimeField': _core_model_field_attr, - 'DecimalField': _core_model_field_attr, - 'EmailField': _core_model_field_attr, - 'FileField': _core_model_field_attr, - 'FilePathField': _core_model_field_attr, - 'FloatField': _core_model_field_attr, - 'ForeignKey': _core_model_field_attr, - 'GenericIPAddressField': _core_model_field_attr, - 'IPAddressField': _core_model_field_attr, - 'ImageField': _core_model_field_attr, - 'IntegerField': _core_model_field_attr, - 'ManyToManyField': _core_model_field_attr, - 'NullBooleanField': _core_model_field_attr, - 'OneToOneField': _core_model_field_attr, - 'PositiveIntegerField': _core_model_field_attr, - 'PositiveSmallIntegerField': _core_model_field_attr, - 'SlugField': _core_model_field_attr, - 'SmallIntegerField': _core_model_field_attr, - 'TextField': _core_model_field_attr, - 'TimeField': _core_model_field_attr, - 'URLField': _core_model_field_attr} +_model_fields = { + "AutoField": _core_model_field_attr, + "BigIntegerField": _core_model_field_attr, + "BooleanField": _core_model_field_attr, + "CharField": _core_model_field_attr, + "CommaSeparatedIntegerField": _core_model_field_attr, + "DateField": _core_model_field_attr, + "DateTimeField": _core_model_field_attr, + "DecimalField": _core_model_field_attr, + "EmailField": _core_model_field_attr, + "FileField": _core_model_field_attr, + "FilePathField": _core_model_field_attr, + "FloatField": _core_model_field_attr, + "ForeignKey": _core_model_field_attr, + "GenericIPAddressField": _core_model_field_attr, + "IPAddressField": _core_model_field_attr, + "ImageField": _core_model_field_attr, + "IntegerField": _core_model_field_attr, + "ManyToManyField": _core_model_field_attr, + "NullBooleanField": _core_model_field_attr, + "OneToOneField": _core_model_field_attr, + "PositiveIntegerField": _core_model_field_attr, + "PositiveSmallIntegerField": _core_model_field_attr, + "SlugField": _core_model_field_attr, + "SmallIntegerField": _core_model_field_attr, + "TextField": _core_model_field_attr, + "TimeField": _core_model_field_attr, + "URLField": _core_model_field_attr, +} -_add_model_attr = {'CharField': {'max_length': None}, - 'CommaSeparatedIntegerField': {'max_length': None}, - 'DateField': {'auto_now': False, - 'auto_now_add': False, - 'unique_for_date': None, - 'unique_for_month': None, - 'unique_for_year': None,}, - 'DateTimeField': {'auto_now': False, - 'auto_now_add': False, - 'unique_for_date': None, - 'unique_for_month': None, - 'unique_for_year': None,}, - 'DecimalField': {'max_digits': None, - 'decimal_places': None}, - 'EmailField': {'max_length': 75}, - 'FileField': {'max_length': 100, 'upload_to': None, - 'storage': None}, - 'FilePathField': {'allow_files': True, - 'allow_folders': False, - 'match': None, - 'path': None, - 'recursive': False}, - 'ForeignKey': {'limit_choices_to': None, - 'on_delete': models.CASCADE, - 'related_name': None, - 'to_field': None}, - 'GenericIPAddressField': {'protocol': 'both', - 'unpack_ipv4': False}, - 'ImageField': {'max_length': 100, 'upload_to': None, - 'height_field': None, 'width_field': None,}, - 'OneToOneField': {}, - 'SlugField': {'max_length': 50}, - 'TimeField': {'auto_now': False, - 'auto_now_add': False, - 'unique_for_date': None, - 'unique_for_month': None, - 'unique_for_year': None,}, - 'URLField': {'max_length': 200}} +_add_model_attr = { + "CharField": {"max_length": None}, + "CommaSeparatedIntegerField": {"max_length": None}, + "DateField": { + "auto_now": False, + "auto_now_add": False, + "unique_for_date": None, + "unique_for_month": None, + "unique_for_year": None, + }, + "DateTimeField": { + "auto_now": False, + "auto_now_add": False, + "unique_for_date": None, + "unique_for_month": None, + "unique_for_year": None, + }, + "DecimalField": {"max_digits": None, "decimal_places": None}, + "EmailField": {"max_length": 75}, + "FileField": {"max_length": 100, "upload_to": None, "storage": None}, + "FilePathField": { + "allow_files": True, + "allow_folders": False, + "match": None, + "path": None, + "recursive": False, + }, + "ForeignKey": { + "limit_choices_to": None, + "on_delete": models.CASCADE, + "related_name": None, + "to_field": None, + }, + "GenericIPAddressField": {"protocol": "both", "unpack_ipv4": False}, + "ImageField": { + "max_length": 100, + "upload_to": None, + "height_field": None, + "width_field": None, + }, + "OneToOneField": {}, + "SlugField": {"max_length": 50}, + "TimeField": { + "auto_now": False, + "auto_now_add": False, + "unique_for_date": None, + "unique_for_month": None, + "unique_for_year": None, + }, + "URLField": {"max_length": 200}, +} for k, v in _add_model_attr.items(): - _model_fields[k]= dict(_model_fields[k], **v) + _model_fields[k] = dict(_model_fields[k], **v) model_fields = _model_fields -model_meta_fields = {'abstract': False, - 'db_tablespace': '', - 'get_latest_by': None, - 'managed': True, - 'order_with_respect_to': None, - 'ordering': None, - 'permissions': [], - 'proxy': False, - 'unique_together': [], - 'verbose_name': None, - 'verbose_name_plural': None} +model_meta_fields = { + "abstract": False, + "db_tablespace": "", + "get_latest_by": None, + "managed": True, + "order_with_respect_to": None, + "ordering": None, + "permissions": [], + "proxy": False, + "unique_together": [], + "verbose_name": None, + "verbose_name_plural": None, +} -_core_form_field_attr = {'required': True, - 'label': None, - 'initial': None, - 'widget': None, - 'help_text': None, - 'error_messages': None, - 'localize': False, - 'validators': None,} +_core_form_field_attr = { + "required": True, + "label": None, + "initial": None, + "widget": None, + "help_text": None, + "error_messages": None, + "localize": False, + "validators": None, +} -_form_fields = {'BooleanField': _core_form_field_attr, - 'CharField': _core_form_field_attr, - 'ChoiceField': _core_form_field_attr, - 'DateField': _core_form_field_attr, - 'DateTimeField': _core_form_field_attr, - 'DecimalField': _core_form_field_attr, - 'EmailField': _core_form_field_attr, - 'FileField': _core_form_field_attr, - 'FilePathField': _core_form_field_attr, - 'FloatField': _core_form_field_attr, - 'GenericIPAddressField': _core_form_field_attr, - 'IPAddressField': _core_form_field_attr, - 'ImageField': _core_form_field_attr, - 'IntegerField': _core_form_field_attr, - 'MultipleChoiceField': _core_form_field_attr, - 'NullBooleanField': _core_form_field_attr, - 'RegexField': _core_form_field_attr, - 'SlugField': _core_form_field_attr, - 'TimeField': _core_form_field_attr, - 'TypedChoiceField': _core_form_field_attr, - 'TypedMultipleChoiceField': _core_form_field_attr, - 'URLField': _core_form_field_attr} +_form_fields = { + "BooleanField": _core_form_field_attr, + "CharField": _core_form_field_attr, + "ChoiceField": _core_form_field_attr, + "DateField": _core_form_field_attr, + "DateTimeField": _core_form_field_attr, + "DecimalField": _core_form_field_attr, + "EmailField": _core_form_field_attr, + "FileField": _core_form_field_attr, + "FilePathField": _core_form_field_attr, + "FloatField": _core_form_field_attr, + "GenericIPAddressField": _core_form_field_attr, + "IPAddressField": _core_form_field_attr, + "ImageField": _core_form_field_attr, + "IntegerField": _core_form_field_attr, + "MultipleChoiceField": _core_form_field_attr, + "NullBooleanField": _core_form_field_attr, + "RegexField": _core_form_field_attr, + "SlugField": _core_form_field_attr, + "TimeField": _core_form_field_attr, + "TypedChoiceField": _core_form_field_attr, + "TypedMultipleChoiceField": _core_form_field_attr, + "URLField": _core_form_field_attr, +} -_add_form_attr = {'CharField': {'max_length': None, 'min_length': None}, - 'ChoiceField': {'choices': None}, - 'DateField': {'input_formats': None}, - 'DateTimeField': {'input_formats': None}, - 'DecimalField': {'max_value': None, 'min_value': None, - 'max_digits': None, 'decimal_places': None}, - 'EmailField': {'max_length': None, 'min_length': None}, - 'FileField': {'max_length': None, 'allow_empty_file': False}, - 'FilePathField': {'allow_files': True, - 'allow_folders': False, - 'match': None, - 'path': None, - 'recursive': False}, - 'FloatField': {'max_value': None, 'min_value': None}, - 'GenericIPAddressField': {'protocol': 'both', - 'unpack_ipv4': False}, - 'IntegerField': {'max_value': None, 'min_value': None}, - 'MultipleChoiceField': {'choices': None}, - 'RegexField': {'regex': None}, - 'TimeField': _core_form_field_attr, - 'TypedChoiceField': {'choices': None, 'empty_value': None, - 'coerce': None}, - 'TypedMultipleChoiceField': {'choices': None, - 'empty_value': None, - 'coerce': None}, - 'URLField': {'max_length': None, 'min_length': None}} +_add_form_attr = { + "CharField": {"max_length": None, "min_length": None}, + "ChoiceField": {"choices": None}, + "DateField": {"input_formats": None}, + "DateTimeField": {"input_formats": None}, + "DecimalField": { + "max_value": None, + "min_value": None, + "max_digits": None, + "decimal_places": None, + }, + "EmailField": {"max_length": None, "min_length": None}, + "FileField": {"max_length": None, "allow_empty_file": False}, + "FilePathField": { + "allow_files": True, + "allow_folders": False, + "match": None, + "path": None, + "recursive": False, + }, + "FloatField": {"max_value": None, "min_value": None}, + "GenericIPAddressField": {"protocol": "both", "unpack_ipv4": False}, + "IntegerField": {"max_value": None, "min_value": None}, + "MultipleChoiceField": {"choices": None}, + "RegexField": {"regex": None}, + "TimeField": _core_form_field_attr, + "TypedChoiceField": {"choices": None, "empty_value": None, "coerce": None}, + "TypedMultipleChoiceField": {"choices": None, "empty_value": None, "coerce": None}, + "URLField": {"max_length": None, "min_length": None}, +} for k, v in _add_form_attr.items(): - _form_fields[k]= dict(_form_fields[k], **v) + _form_fields[k] = dict(_form_fields[k], **v) form_fields = _form_fields diff --git a/docs/conf.py b/docs/conf.py index 70dad4e..a3d20ba 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # password_policies documentation build configuration file, created by # sphinx-quickstart on Thu Aug 23 13:18:53 2012. # @@ -25,7 +23,7 @@ django.setup() -password_policies = __import__('password_policies') +password_policies = __import__("password_policies") # -- General configuration ----------------------------------------------------- @@ -34,23 +32,28 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.ifconfig', 'exts'] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.ifconfig", + "exts", +] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'contents' +master_doc = "contents" # General information about the project. -project = u'django-password-policies' -copyright = u'2012, Tarak Blah' +project = "django-password-policies" +copyright = "2012, Tarak Blah" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -63,158 +66,161 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['build'] +exclude_patterns = ["build"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'djangodocs' +html_theme = "djangodocs" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -html_theme_path = ['_theme'] +html_theme_path = ["_theme"] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'password_policiesdoc' +htmlhelp_basename = "password_policiesdoc" # -- Options for LaTeX output -------------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'password_policies.tex', u'change\\_email Documentation', - u'Tarak Blah', 'manual'), + ( + "index", + "password_policies.tex", + "change\\_email Documentation", + "Tarak Blah", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- @@ -222,12 +228,11 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'password_policies', u'password_policies Documentation', - [u'Tarak Blah'], 1) + ("index", "password_policies", "password_policies Documentation", ["Tarak Blah"], 1) ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ @@ -236,20 +241,26 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'password_policies', u'password_policies Documentation', - u'Tarak Blah', 'password_policies', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "password_policies", + "password_policies Documentation", + "Tarak Blah", + "password_policies", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} +intersphinx_mapping = {"http://docs.python.org/": None} diff --git a/password_policies/admin.py b/password_policies/admin.py index 7099fdc..004846b 100644 --- a/password_policies/admin.py +++ b/password_policies/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + try: from django.utils.translation import gettext_lazy as _ except ImportError: diff --git a/password_policies/conf/settings.py b/password_policies/conf/settings.py index ae7d78e..cead90b 100644 --- a/password_policies/conf/settings.py +++ b/password_policies/conf/settings.py @@ -33,7 +33,7 @@ #: be performed if the user's password has expired. #: #: Defaults to 1 hour. -PASSWORD_CHECK_SECONDS = getattr(settings, "PASSWORD_CHECK_SECONDS", 60 ** 2) +PASSWORD_CHECK_SECONDS = getattr(settings, "PASSWORD_CHECK_SECONDS", 60**2) #: Specifies a list of common sequences to attempt to #: match a password against. @@ -41,15 +41,15 @@ settings, "PASSWORD_COMMON_SEQUENCES", [ - u"0123456789", - u"`1234567890-=", - u"~!@#$%^&*()_+", - u"abcdefghijklmnopqrstuvwxyz", - u"quertyuiop[]\\asdfghjkl;'zxcvbnm,./", - u'quertyuiop{}|asdfghjkl;"zxcvbnm<>?', - u"quertyuiopasdfghjklzxcvbnm", - u"1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-['=]\\", - u"qazwsxedcrfvtgbyhnujmikolp", + "0123456789", + "`1234567890-=", + "~!@#$%^&*()_+", + "abcdefghijklmnopqrstuvwxyz", + "quertyuiop[]\\asdfghjkl;'zxcvbnm,./", + 'quertyuiop{}|asdfghjkl;"zxcvbnm<>?', + "quertyuiopasdfghjklzxcvbnm", + "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-['=]\\", + "qazwsxedcrfvtgbyhnujmikolp", ], ) PASSWORD_DICTIONARY = getattr(settings, "PASSWORD_DICTIONARY", None) @@ -72,7 +72,7 @@ #: to change his/her password. #: #: Defaults to 60 days. -PASSWORD_DURATION_SECONDS = getattr(settings, "PASSWORD_DURATION_SECONDS", 24 * 60 ** 3) +PASSWORD_DURATION_SECONDS = getattr(settings, "PASSWORD_DURATION_SECONDS", 24 * 60**3) #: The field on the user model as defined by settings.AUTH_USER_MODEL #: where the password is stored. PASSWORD_MODEL_FIELD = getattr(settings, "PASSWORD_MODEL_FIELD", "password") diff --git a/password_policies/context_processors.py b/password_policies/context_processors.py index 9a5f41b..339b56b 100644 --- a/password_policies/context_processors.py +++ b/password_policies/context_processors.py @@ -3,35 +3,35 @@ def password_status(request): """ -Adds a variable determining the state of a user's password -to the context if the user has authenticated: + Adds a variable determining the state of a user's password + to the context if the user has authenticated: -* ``password_change_required`` - Determines if the user needs to change his/her password. + * ``password_change_required`` + Determines if the user needs to change his/her password. - Set to ``True`` if the user has to change his/her password, - ``False`` otherwise. + Set to ``True`` if the user has to change his/her password, + ``False`` otherwise. -To use it add it to the list of ``TEMPLATE_CONTEXT_PROCESSORS`` -in a project's settings file:: + To use it add it to the list of ``TEMPLATE_CONTEXT_PROCESSORS`` + in a project's settings file:: - TEMPLATE_CONTEXT_PROCESSORS = ( - 'django.contrib.auth.context_processors.auth', - 'django.core.context_processors.debug', - 'django.core.context_processors.i18n', - 'django.contrib.messages.context_processors.messages', - 'password_policies.context_processors.password_status', - ) -""" + TEMPLATE_CONTEXT_PROCESSORS = ( + 'django.contrib.auth.context_processors.auth', + 'django.core.context_processors.debug', + 'django.core.context_processors.i18n', + 'django.contrib.messages.context_processors.messages', + 'password_policies.context_processors.password_status', + ) + """ d = {} auth = request.user.is_authenticated if callable(auth): # Before Django 1.10 auth = auth() if auth: - if '_password_policies_change_required' not in request.session: + if "_password_policies_change_required" not in request.session: r = PasswordHistory.objects.change_required(request.user) else: - r = request.session['_password_policies_change_required'] - d['password_change_required'] = r + r = request.session["_password_policies_change_required"] + d["password_change_required"] = r return d diff --git a/password_policies/forms/__init__.py b/password_policies/forms/__init__.py index fbfe208..8419362 100644 --- a/password_policies/forms/__init__.py +++ b/password_policies/forms/__init__.py @@ -12,6 +12,7 @@ from django.contrib.sites.shortcuts import get_current_site from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode + try: from django.utils.translation import gettext_lazy as _ except ImportError: @@ -110,7 +111,7 @@ class PasswordPoliciesChangeForm(PasswordPoliciesForm): ), "password_similar": _("The old and the new password are too similar."), "password_identical": _("The old and the new password are the same."), - } + }, ) old_password = forms.CharField(label=_("Old password"), widget=forms.PasswordInput) diff --git a/password_policies/forms/admin.py b/password_policies/forms/admin.py index 3d55d0f..312166e 100644 --- a/password_policies/forms/admin.py +++ b/password_policies/forms/admin.py @@ -1,5 +1,6 @@ from django import forms from django.contrib.auth.forms import AdminPasswordChangeForm + try: from django.utils.translation import gettext_lazy as _ except ImportError: @@ -15,46 +16,51 @@ class PasswordPoliciesAdminForm(AdminPasswordChangeForm): """Enforces password policies in the admin interface. -Use this form to enforce strong passwords in the admin interface. -""" + Use this form to enforce strong passwords in the admin interface. + """ + error_messages = { - 'password_mismatch': _("The two password fields didn't match."), - 'password_used': _("The new password was used before. Please enter another one.") + "password_mismatch": _("The two password fields didn't match."), + "password_used": _( + "The new password was used before. Please enter another one." + ), } - password1 = PasswordPoliciesField(label=_("Password new"), - max_length=settings.PASSWORD_MAX_LENGTH, - min_length=settings.PASSWORD_MIN_LENGTH - ) + password1 = PasswordPoliciesField( + label=_("Password new"), + max_length=settings.PASSWORD_MAX_LENGTH, + min_length=settings.PASSWORD_MIN_LENGTH, + ) def clean_password1(self): """ -Validates that a given password was not used before. -""" - password1 = self.cleaned_data.get('password1') + Validates that a given password was not used before. + """ + password1 = self.cleaned_data.get("password1") if settings.PASSWORD_USE_HISTORY: if self.user.check_password(password1): - raise forms.ValidationError( - self.error_messages['password_used']) - if not PasswordHistory.objects.check_password(self.user, - password1): - raise forms.ValidationError( - self.error_messages['password_used']) + raise forms.ValidationError(self.error_messages["password_used"]) + if not PasswordHistory.objects.check_password(self.user, password1): + raise forms.ValidationError(self.error_messages["password_used"]) return password1 class ForceChangeAdminForm(PasswordPoliciesAdminForm): - change_required = forms.BooleanField(initial=True, required=False, label=_('Must change?')) + change_required = forms.BooleanField( + initial=True, required=False, label=_("Must change?") + ) def save(self, commit=True): user = super(ForceChangeAdminForm, self).save(commit=commit) - if self.cleaned_data["change_required"] and not PasswordChangeRequired.objects.filter(user=user).count(): + if ( + self.cleaned_data["change_required"] + and not PasswordChangeRequired.objects.filter(user=user).count() + ): PasswordChangeRequired.objects.create(user=user) return user class ForceChangeRequiredAdminForm(PasswordPoliciesAdminForm): - def save(self, commit=True): user = super(ForceChangeRequiredAdminForm, self).save(commit=commit) if not PasswordChangeRequired.objects.filter(user=user).count(): diff --git a/password_policies/forms/fields.py b/password_policies/forms/fields.py index 1d9d0d4..6170a56 100644 --- a/password_policies/forms/fields.py +++ b/password_policies/forms/fields.py @@ -15,19 +15,22 @@ class PasswordPoliciesField(forms.CharField): """ -A form field that validates a password using :ref:`api-validators`. -""" - default_validators = [validate_common_sequences, - validate_consecutive_count, - validate_cracklib, - validate_dictionary_words, - validate_letter_count, - validate_lowercase_letter_count, - validate_uppercase_letter_count, - validate_number_count, - validate_symbol_count, - validate_entropy, - validate_not_email] + A form field that validates a password using :ref:`api-validators`. + """ + + default_validators = [ + validate_common_sequences, + validate_consecutive_count, + validate_cracklib, + validate_dictionary_words, + validate_letter_count, + validate_lowercase_letter_count, + validate_uppercase_letter_count, + validate_number_count, + validate_symbol_count, + validate_entropy, + validate_not_email, + ] def __init__(self, *args, **kwargs): if "widget" not in kwargs: diff --git a/password_policies/forms/validators.py b/password_policies/forms/validators.py index 26bbef6..2d98ebc 100644 --- a/password_policies/forms/validators.py +++ b/password_policies/forms/validators.py @@ -379,9 +379,7 @@ def __init__(self, dictionary="", words=[]): haystacks = [] if self.dictionary: with open(self.dictionary) as dictionary: - haystacks.extend( - [smart_str(x.strip()) for x in dictionary.readlines()] - ) + haystacks.extend([smart_str(x.strip()) for x in dictionary.readlines()]) if self.words: haystacks.extend(self.words) super().__init__(haystacks=haystacks) diff --git a/password_policies/managers.py b/password_policies/managers.py index d5aa7d6..db2a0f6 100644 --- a/password_policies/managers.py +++ b/password_policies/managers.py @@ -13,29 +13,29 @@ class PasswordHistoryManager(models.Manager): def delete_expired(self, user, offset=None): """ -Deletes expired password history entries from the database(s). + Deletes expired password history entries from the database(s). -:arg user: A :class:`~django.contrib.auth.models.User` instance. -:arg int offset: A number specifying how much entries are to be kept - in the user's password history. Defaults - to :py:attr:`~password_policies.conf.Settings.PASSWORD_HISTORY_COUNT`. -""" + :arg user: A :class:`~django.contrib.auth.models.User` instance. + :arg int offset: A number specifying how much entries are to be kept + in the user's password history. Defaults + to :py:attr:`~password_policies.conf.Settings.PASSWORD_HISTORY_COUNT`. + """ if not offset: offset = self.default_offset qs = self.filter(user=user) if qs.count() > offset: - entry = qs[offset:offset + 1].get() + entry = qs[offset : offset + 1].get() qs.filter(created__lte=entry.created).delete() def change_required(self, user): """ -Checks if the user needs to change his/her password. + Checks if the user needs to change his/her password. -:arg object user: A :class:`~django.contrib.auth.models.User` instance. -:returns: ``True`` if the user needs to change his/her password, - ``False`` otherwise. -:rtype: bool -""" + :arg object user: A :class:`~django.contrib.auth.models.User` instance. + :returns: ``True`` if the user needs to change his/her password, + ``False`` otherwise. + :rtype: bool + """ newest = self.get_newest(user) if newest: last_change = newest.created @@ -50,19 +50,19 @@ def change_required(self, user): def check_password(self, user, raw_password): """ -Compares a raw (UNENCRYPTED!!!) password to entries in the users's -password history. + Compares a raw (UNENCRYPTED!!!) password to entries in the users's + password history. -:arg object user: A :class:`~django.contrib.auth.models.User` instance. -:arg str raw_password: A unicode string representing a password. -:returns: ``False`` if a password has been used before, ``True`` if not. -:rtype: bool -""" + :arg object user: A :class:`~django.contrib.auth.models.User` instance. + :arg str raw_password: A unicode string representing a password. + :returns: ``False`` if a password has been used before, ``True`` if not. + :rtype: bool + """ result = True if user.check_password(raw_password): result = False else: - entries = self.filter(user=user).all()[:self.default_offset] + entries = self.filter(user=user).all()[: self.default_offset] for entry in entries: hasher = identify_hasher(entry.password) if hasher.verify(raw_password, entry.password): @@ -72,12 +72,12 @@ def check_password(self, user, raw_password): def get_newest(self, user): """ -Gets the newest password history entry. + Gets the newest password history entry. -:arg object user: A :class:`~django.contrib.auth.models.User` instance. -:returns: A :class:`~password_policies.models.PasswordHistory` instance - if found, ``None`` if not. -""" + :arg object user: A :class:`~django.contrib.auth.models.User` instance. + :returns: A :class:`~password_policies.models.PasswordHistory` instance + if found, ``None`` if not. + """ try: entry = self.filter(user=user).latest() except ObjectDoesNotExist: diff --git a/password_policies/middleware.py b/password_policies/middleware.py index 5228f25..dc0b67c 100644 --- a/password_policies/middleware.py +++ b/password_policies/middleware.py @@ -6,7 +6,8 @@ from django.utils import timezone import django.utils.deprecation -if hasattr(django.utils.deprecation, 'MiddlewareMixin'): + +if hasattr(django.utils.deprecation, "MiddlewareMixin"): from django.utils.deprecation import MiddlewareMixin else: MiddlewareMixin = object @@ -88,7 +89,6 @@ def _check_history(self, request): request.session[self.required] = False def _check_necessary(self, request): - if not request.session.get(self.checked, None): request.session[self.checked] = self.now @@ -136,7 +136,7 @@ def _is_excluded_path(self, actual_path): else: paths.append(r"^%s$" % logout_url) try: - logout_url = u"/admin/logout/" + logout_url = "/admin/logout/" resolve(logout_url) except Resolver404: pass diff --git a/password_policies/migrations/0001_initial.py b/password_policies/migrations/0001_initial.py index e9afa50..f206b47 100644 --- a/password_policies/migrations/0001_initial.py +++ b/password_policies/migrations/0001_initial.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ @@ -15,32 +14,89 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='PasswordChangeRequired', + name="PasswordChangeRequired", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(auto_now_add=True, db_index=True, help_text='The date the entry was created.', verbose_name='created')), - ('user', models.OneToOneField(help_text='The user who needs to change his/her password.', on_delete=django.db.models.deletion.CASCADE, related_name='password_change_required', to=settings.AUTH_USER_MODEL, verbose_name='user')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + models.DateTimeField( + auto_now_add=True, + db_index=True, + help_text="The date the entry was created.", + verbose_name="created", + ), + ), + ( + "user", + models.OneToOneField( + help_text="The user who needs to change his/her password.", + on_delete=django.db.models.deletion.CASCADE, + related_name="password_change_required", + to=settings.AUTH_USER_MODEL, + verbose_name="user", + ), + ), ], options={ - 'verbose_name': 'enforced password change', - 'verbose_name_plural': 'enforced password changes', - 'ordering': ['-created'], - 'get_latest_by': 'created', + "verbose_name": "enforced password change", + "verbose_name_plural": "enforced password changes", + "ordering": ["-created"], + "get_latest_by": "created", }, ), migrations.CreateModel( - name='PasswordHistory', + name="PasswordHistory", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(auto_now_add=True, db_index=True, help_text='The date the entry was created.', verbose_name='created')), - ('password', models.CharField(help_text='The encrypted password.', max_length=128, verbose_name='password')), - ('user', models.ForeignKey(help_text='The user this password history entry belongs to.', on_delete=django.db.models.deletion.CASCADE, related_name='password_history_entries', to=settings.AUTH_USER_MODEL, verbose_name='user')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + models.DateTimeField( + auto_now_add=True, + db_index=True, + help_text="The date the entry was created.", + verbose_name="created", + ), + ), + ( + "password", + models.CharField( + help_text="The encrypted password.", + max_length=128, + verbose_name="password", + ), + ), + ( + "user", + models.ForeignKey( + help_text="The user this password history entry belongs to.", + on_delete=django.db.models.deletion.CASCADE, + related_name="password_history_entries", + to=settings.AUTH_USER_MODEL, + verbose_name="user", + ), + ), ], options={ - 'verbose_name': 'password history entry', - 'verbose_name_plural': 'password history entries', - 'ordering': ['-created'], - 'get_latest_by': 'created', + "verbose_name": "password history entry", + "verbose_name_plural": "password history entries", + "ordering": ["-created"], + "get_latest_by": "created", }, ), ] diff --git a/password_policies/migrations/0002_passwordprofile.py b/password_policies/migrations/0002_passwordprofile.py index e506865..6add86f 100644 --- a/password_policies/migrations/0002_passwordprofile.py +++ b/password_policies/migrations/0002_passwordprofile.py @@ -6,26 +6,56 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('password_policies', '0001_initial'), + ("password_policies", "0001_initial"), ] operations = [ migrations.CreateModel( - name='PasswordProfile', + name="PasswordProfile", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, help_text='The date the entry was created.', verbose_name='created')), - ('last_changed', models.DateTimeField(db_index=True, help_text='The date the password was last changed.', verbose_name='last changed')), - ('user', models.OneToOneField(help_text='The user this password profile belongs to.', on_delete=django.db.models.deletion.CASCADE, related_name='password_profile', to=settings.AUTH_USER_MODEL, verbose_name='user')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + models.DateTimeField( + db_index=True, + help_text="The date the entry was created.", + verbose_name="created", + ), + ), + ( + "last_changed", + models.DateTimeField( + db_index=True, + help_text="The date the password was last changed.", + verbose_name="last changed", + ), + ), + ( + "user", + models.OneToOneField( + help_text="The user this password profile belongs to.", + on_delete=django.db.models.deletion.CASCADE, + related_name="password_profile", + to=settings.AUTH_USER_MODEL, + verbose_name="user", + ), + ), ], options={ - 'verbose_name': 'password profile', - 'verbose_name_plural': 'password profiles', - 'ordering': ['-created'], - 'get_latest_by': 'created', + "verbose_name": "password profile", + "verbose_name_plural": "password profiles", + "ordering": ["-created"], + "get_latest_by": "created", }, ), ] diff --git a/password_policies/migrations/0003_update_passwordprofile.py b/password_policies/migrations/0003_update_passwordprofile.py index ca29ac6..d10dcfa 100644 --- a/password_policies/migrations/0003_update_passwordprofile.py +++ b/password_policies/migrations/0003_update_passwordprofile.py @@ -4,20 +4,29 @@ class Migration(migrations.Migration): - dependencies = [ - ('password_policies', '0002_passwordprofile'), + ("password_policies", "0002_passwordprofile"), ] operations = [ migrations.AlterField( - model_name='passwordprofile', - name='created', - field=models.DateTimeField(auto_now_add=True, db_index=True, help_text='The date the entry was created.', verbose_name='created'), + model_name="passwordprofile", + name="created", + field=models.DateTimeField( + auto_now_add=True, + db_index=True, + help_text="The date the entry was created.", + verbose_name="created", + ), ), migrations.AlterField( - model_name='passwordprofile', - name='last_changed', - field=models.DateTimeField(auto_now=True, db_index=True, help_text='The date the password was last changed.', verbose_name='last changed'), + model_name="passwordprofile", + name="last_changed", + field=models.DateTimeField( + auto_now=True, + db_index=True, + help_text="The date the password was last changed.", + verbose_name="last changed", + ), ), ] diff --git a/password_policies/models.py b/password_policies/models.py index 9426d6b..027221d 100644 --- a/password_policies/models.py +++ b/password_policies/models.py @@ -3,6 +3,7 @@ from django.db import models from django.db.models import signals from django.utils import timezone + try: from django.utils.translation import gettext_lazy as _ except ImportError: diff --git a/password_policies/receivers.py b/password_policies/receivers.py index 95b56c4..61f7628 100644 --- a/password_policies/receivers.py +++ b/password_policies/receivers.py @@ -3,11 +3,12 @@ from django.dispatch import receiver from .conf import settings as password_settings + @receiver(setting_changed) def app_settings_reload_handler(**kwargs): """ When you modify settings in your test using override_settings, we need to reload the app settings module this receiver is in fact imported in tests/__init__.py module """ - if "PASSWORD_" in kwargs['setting']: + if "PASSWORD_" in kwargs["setting"]: importlib.reload(password_settings) diff --git a/password_policies/south_migrations/0001_initial.py b/password_policies/south_migrations/0001_initial.py index 375c51d..e3da952 100644 --- a/password_policies/south_migrations/0001_initial.py +++ b/password_policies/south_migrations/0001_initial.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from south.db import db from south.v2 import SchemaMigration @@ -8,69 +7,170 @@ class Migration(SchemaMigration): - def forwards(self, orm): # Adding model 'PasswordChangeRequired' - db.create_table(u'password_policies_passwordchangerequired', ( - (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, db_index=True, blank=True)), - ('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='password_change_required', unique=True, to=orm['%s.%s' % (User._meta.app_label, User._meta.object_name)])), - )) - db.send_create_signal(u'password_policies', ['PasswordChangeRequired']) + db.create_table( + "password_policies_passwordchangerequired", + ( + ("id", self.gf("django.db.models.fields.AutoField")(primary_key=True)), + ( + "created", + self.gf("django.db.models.fields.DateTimeField")( + auto_now_add=True, db_index=True, blank=True + ), + ), + ( + "user", + self.gf("django.db.models.fields.related.OneToOneField")( + related_name="password_change_required", + unique=True, + to=orm[ + "%s.%s" % (User._meta.app_label, User._meta.object_name) + ], + ), + ), + ), + ) + db.send_create_signal("password_policies", ["PasswordChangeRequired"]) # Adding model 'PasswordHistory' - db.create_table(u'password_policies_passwordhistory', ( - (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, db_index=True, blank=True)), - ('password', self.gf('django.db.models.fields.CharField')(max_length=128)), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='password_history_entries', to=orm['%s.%s' % (User._meta.app_label, User._meta.object_name)])), - )) - db.send_create_signal(u'password_policies', ['PasswordHistory']) + db.create_table( + "password_policies_passwordhistory", + ( + ("id", self.gf("django.db.models.fields.AutoField")(primary_key=True)), + ( + "created", + self.gf("django.db.models.fields.DateTimeField")( + auto_now_add=True, db_index=True, blank=True + ), + ), + ( + "password", + self.gf("django.db.models.fields.CharField")(max_length=128), + ), + ( + "user", + self.gf("django.db.models.fields.related.ForeignKey")( + related_name="password_history_entries", + to=orm[ + "%s.%s" % (User._meta.app_label, User._meta.object_name) + ], + ), + ), + ), + ) + db.send_create_signal("password_policies", ["PasswordHistory"]) def backwards(self, orm): # Deleting model 'PasswordChangeRequired' - db.delete_table(u'password_policies_passwordchangerequired') + db.delete_table("password_policies_passwordchangerequired") # Deleting model 'PasswordHistory' - db.delete_table(u'password_policies_passwordhistory') + db.delete_table("password_policies_passwordhistory") models = { - u'auth.group': { - 'Meta': {'object_name': 'Group'}, - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + "auth.group": { + "Meta": {"object_name": "Group"}, + "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}), + "name": ( + "django.db.models.fields.CharField", + [], + {"unique": "True", "max_length": "80"}, + ), + "permissions": ( + "django.db.models.fields.related.ManyToManyField", + [], + { + "to": "orm['auth.Permission']", + "symmetrical": "False", + "blank": "True", + }, + ), }, - u'auth.permission': { - 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + "auth.permission": { + "Meta": { + "ordering": "(u'content_type__app_label', u'content_type__model', u'codename')", + "unique_together": "((u'content_type', u'codename'),)", + "object_name": "Permission", + }, + "codename": ( + "django.db.models.fields.CharField", + [], + {"max_length": "100"}, + ), + "content_type": ( + "django.db.models.fields.related.ForeignKey", + [], + {"to": "orm['contenttypes.ContentType']"}, + ), + "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}), + "name": ("django.db.models.fields.CharField", [], {"max_length": "50"}), }, "%s.%s" % (User._meta.app_label, User._meta.module_name): { - 'Meta': {'object_name': User._meta.module_name, 'db_table': repr(User._meta.db_table)}, + "Meta": { + "object_name": User._meta.module_name, + "db_table": repr(User._meta.db_table), + }, + }, + "contenttypes.contenttype": { + "Meta": { + "ordering": "('name',)", + "unique_together": "(('app_label', 'model'),)", + "object_name": "ContentType", + "db_table": "'django_content_type'", + }, + "app_label": ( + "django.db.models.fields.CharField", + [], + {"max_length": "100"}, + ), + "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}), + "model": ("django.db.models.fields.CharField", [], {"max_length": "100"}), + "name": ("django.db.models.fields.CharField", [], {"max_length": "100"}), }, - u'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + "password_policies.passwordchangerequired": { + "Meta": { + "ordering": "['-created']", + "object_name": "PasswordChangeRequired", + }, + "created": ( + "django.db.models.fields.DateTimeField", + [], + {"auto_now_add": "True", "db_index": "True", "blank": "True"}, + ), + "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}), + "user": ( + "django.db.models.fields.related.OneToOneField", + [], + { + "related_name": "'password_change_required'", + "unique": "True", + "to": "orm['auth.User']", + }, + ), }, - u'password_policies.passwordchangerequired': { - 'Meta': {'ordering': "['-created']", 'object_name': 'PasswordChangeRequired'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'password_change_required'", 'unique': 'True', 'to': u"orm['auth.User']"}) + "password_policies.passwordhistory": { + "Meta": {"ordering": "['-created']", "object_name": "PasswordHistory"}, + "created": ( + "django.db.models.fields.DateTimeField", + [], + {"auto_now_add": "True", "db_index": "True", "blank": "True"}, + ), + "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}), + "password": ( + "django.db.models.fields.CharField", + [], + {"max_length": "128"}, + ), + "user": ( + "django.db.models.fields.related.ForeignKey", + [], + { + "related_name": "'password_history_entries'", + "to": "orm['auth.User']", + }, + ), }, - u'password_policies.passwordhistory': { - 'Meta': {'ordering': "['-created']", 'object_name': 'PasswordHistory'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'password_history_entries'", 'to': u"orm['auth.User']"}) - } } - complete_apps = ['password_policies'] + complete_apps = ["password_policies"] diff --git a/password_policies/tests/test_views.py b/password_policies/tests/test_views.py index 9aee9cc..b08ae69 100644 --- a/password_policies/tests/test_views.py +++ b/password_policies/tests/test_views.py @@ -79,12 +79,14 @@ def test_password_change_confirm(self): ) assert res.status_code == 200 - @override_settings(AUTH_PASSWORD_VALIDATORS=[ - { - "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", - "OPTIONS": {"min_length": 20}, - } - ]) + @override_settings( + AUTH_PASSWORD_VALIDATORS=[ + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + "OPTIONS": {"min_length": 20}, + } + ] + ) def test_password_change_wrong_validators(self): """ A ``POST`` to the ``change_email_create`` view with valid data properly @@ -96,7 +98,7 @@ def test_password_change_wrong_validators(self): "new_password1": "Chah+pher9k", "new_password2": "Chah+pher9k", } - msg = 'This password is too short. It must contain at least 20 characters.' + msg = "This password is too short. It must contain at least 20 characters." self.client.login(username="alice", password=data["old_password"]) response = self.client.post(reverse("password_change"), data=data) self.assertEqual(response.status_code, 200) diff --git a/password_policies/utils.py b/password_policies/utils.py index e404c8e..56bab1e 100644 --- a/password_policies/utils.py +++ b/password_policies/utils.py @@ -17,13 +17,13 @@ def __init__(self, user): def is_required(self): """Checks if a given user is forced to change his/her password. -If an instance of :class:`~password_policies.models.PasswordChangeRequired` -exists the verification is successful. + If an instance of :class:`~password_policies.models.PasswordChangeRequired` + exists the verification is successful. -:returns: ``True`` if the user needs to change his/her password, - ``False`` otherwise. -:rtype: bool -""" + :returns: ``True`` if the user needs to change his/her password, + ``False`` otherwise. + :rtype: bool + """ try: if self.user.password_change_required: return True @@ -34,10 +34,10 @@ def is_required(self): def is_expired(self): """Checks if a given user's password has expired. -:returns: ``True`` if the user's password has expired, - ``False`` otherwise. -:rtype: bool -""" + :returns: ``True`` if the user's password has expired, + ``False`` otherwise. + :rtype: bool + """ if PasswordHistory.objects.change_required(self.user): return True return False From e560c70bece5f7da5dbdb3d49459d034281311fd Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 11:15:19 +0100 Subject: [PATCH 04/29] Add Python 10/11 support to project meta --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index d5352b2..09654d5 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,8 @@ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Framework :: Django", "License :: OSI Approved :: BSD License", "Topic :: Software Development :: Libraries :: Python Modules", From 129d4d68b361e55a7c65790be249cedf9797710f Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 11:29:52 +0100 Subject: [PATCH 05/29] Configure ruff to use flake8, isort and pyupgrade --- ruff.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ruff.toml b/ruff.toml index 7f36bb4..d2d2cf8 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1 +1,9 @@ target-version = "py37" + +[lint] +select = [ + "DJ", # flake8-django + "F", # flake8 + "I", # isort + "UP" # pyupgrade +] From 6f26ddf6468c055836a19ccb9b038407ec5cfb6a Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 12:12:55 +0100 Subject: [PATCH 06/29] Run ruff on all files --- docs/_ext/exts.py | 57 +++++++------------ docs/conf.py | 3 +- docs/topics/custom.validation.rst | 2 +- password_policies/__init__.py | 2 +- password_policies/forms/__init__.py | 13 ++--- password_policies/forms/admin.py | 7 +-- password_policies/forms/fields.py | 26 +++++---- password_policies/forms/validators.py | 5 +- password_policies/managers.py | 4 +- password_policies/middleware.py | 39 ++++--------- password_policies/migrations/0001_initial.py | 2 +- .../migrations/0002_passwordprofile.py | 2 +- password_policies/receivers.py | 2 + .../south_migrations/0001_initial.py | 13 ++--- password_policies/tests/test_models.py | 6 +- password_policies/tests/test_utils.py | 2 +- password_policies/tests/test_views.py | 9 ++- password_policies/tests/views.py | 7 ++- password_policies/utils.py | 4 +- password_policies/views.py | 8 +-- 20 files changed, 87 insertions(+), 126 deletions(-) diff --git a/docs/_ext/exts.py b/docs/_ext/exts.py index 49d962e..af08ecf 100644 --- a/docs/_ext/exts.py +++ b/docs/_ext/exts.py @@ -1,15 +1,14 @@ import inspect -from django.utils.html import strip_tags -from django.utils.encoding import force_unicode -from fields import model_fields -from fields import model_meta_fields +from django.utils.encoding import force_unicode +from django.utils.html import strip_tags +from fields import model_fields, model_meta_fields def process_docstring(app, what, name, obj, options, lines): # This causes import errors if left outside the function - from django.db import models from django import forms + from django.db import models # Only look at objects that inherit from Django's base model class if inspect.isclass(obj) and issubclass(obj, models.Model): @@ -25,25 +24,17 @@ def process_docstring(app, what, name, obj, options, lines): # Decode and strip any html out of the field's help text help_text = strip_tags(force_unicode(field.help_text)) - lines.append(".. attribute:: %s" % field.name) + lines.append(f".. attribute:: {field.name}") lines.append(" ") # Add the field's type to the docstring if isinstance(field, models.ForeignKey): to = field.rel.to - msg = " %s(':class:`~%s.%s`')" % ( - type(field).__name__, - to.__module__, - to.__name__, - ) + msg = f" %s(':class:`~{type(field).__name__}.{to.__module__}`')" elif isinstance(field, models.OneToOneField): to = field.rel.to - msg = " %s(':class:`~%s.%s`')" % ( - type(field).__name__, - to.__module__, - to.__name__, - ) + msg = f" {type(field).__name__}(':class:`~{to.__module__}.{to.__name__}`')" else: - msg = " %s" % type(field).__name__ + msg = f" {type(field).__name__}" if not field.blank: msg = msg + " (Required)" if hasattr(field, "auto_now") and field.auto_now: @@ -55,7 +46,7 @@ def process_docstring(app, what, name, obj, options, lines): lines.append("") # Add the model field to the end of the docstring as a param # using the help text as the description - lines.append(" %s" % help_text) + lines.append(f" {help_text}") lines.append(" ") f = model_fields[type(field).__name__] for key in sorted(f.iterkeys()): @@ -73,13 +64,10 @@ def process_docstring(app, what, name, obj, options, lines): if key == "validators": v = [] for i in sorted(attr): - n = ":class:`~%s.%s`" % ( - type(i).__module__, - type(i).__name__, - ) + n = f":class:`~{type(i).__module__}.{type(i).__name__}`" v.append(n) attr = v - lines.append(" :param %s: %s" % (key, attr)) + lines.append(f" :param {key}: {attr}") lines.append("") lines.append(".. attribute:: Meta") lines.append("") @@ -88,7 +76,7 @@ def process_docstring(app, what, name, obj, options, lines): hasattr(obj._meta, key) and getattr(obj._meta, key) != model_meta_fields[key] ): - lines.append(" %s = %s" % (key, getattr(obj._meta, key))) + lines.append(f" {key} = {getattr(obj._meta, key)}") lines.append("") # Only look at objects that inherit from Django's base model class @@ -104,14 +92,11 @@ def process_docstring(app, what, name, obj, options, lines): if hasattr(f, "help_text"): help_text = strip_tags(force_unicode(f.help_text)) - lines.append(".. attribute:: %s" % field) + lines.append(f".. attribute:: {field}") lines.append("") # Add the field's type to the docstring field_inst = obj.base_fields[field] - info = " :class:`~%s.%s`" % ( - type(field_inst).__module__, - type(field_inst).__name__, - ) + info = f" :class:`~{type(field_inst).__module__}.{type(field_inst).__name__}`" if field_inst.required: info = info + " (Required)" lines.append(info) @@ -120,23 +105,23 @@ def process_docstring(app, what, name, obj, options, lines): msgs = {} for key, value in f.error_messages.items(): msgs[key] = force_unicode(value) - lines.append(":kwarg error_messages: %s" % msgs) + lines.append(f":kwarg error_messages: {msgs}") if f.help_text: # Add the model field to the end of the docstring as a param # using the help text as the description - lines.append(":kwarg help_text: %s" % help_text) + lines.append(f":kwarg help_text: {help_text}") if hasattr(f, "initial") and f.initial: - lines.append(":kwarg initial: %s" % f.initial) + lines.append(f":kwarg initial: {f.initial}") if hasattr(f, "localize"): - lines.append(":kwarg localize: %s" % f.localize) + lines.append(f":kwarg localize: {f.localize}") if hasattr(f, "validators") and f.validators: line_array = [] for v in f.validators: line_array.append( - ":class:`~%s.%s`" % (type(v).__module__, type(v).__name__) + f":class:`~{type(v).__module__}.{type(v).__name__}`" ) - lines.append(":kwarg validators: %s" % line_array) - lines.append(":kwarg widget: %s" % type(f.widget).__name__) + lines.append(f":kwarg validators: {line_array}") + lines.append(f":kwarg widget: {type(f.widget).__name__}") lines.append("") # Return the extended docstring diff --git a/docs/conf.py b/docs/conf.py index a3d20ba..30ef631 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,8 +9,9 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys import os +import sys + import django # If extensions (or modules to document with autodoc) are in another directory, diff --git a/docs/topics/custom.validation.rst b/docs/topics/custom.validation.rst index 9e28121..db0b592 100644 --- a/docs/topics/custom.validation.rst +++ b/docs/topics/custom.validation.rst @@ -49,7 +49,7 @@ Customizing password validation can be used by simply using a your_custom_validators.another_validator],) def clean(self): - cleaned_data = super(CustomPasswordPoliciesForm, self).clean() + cleaned_data = super().clean() new_password1 = cleaned_data.get("new_password1") new_password2 = cleaned_data.get("new_password2") diff --git a/password_policies/__init__.py b/password_policies/__init__.py index 650ac2d..3017b65 100644 --- a/password_policies/__init__.py +++ b/password_policies/__init__.py @@ -1,3 +1,3 @@ VERSION = (0, 8, 4) -__version__ = "%s.%s.%s" % VERSION +__version__ = ".".join(map(str, VERSION)) diff --git a/password_policies/forms/__init__.py b/password_policies/forms/__init__.py index 8419362..d7178fd 100644 --- a/password_policies/forms/__init__.py +++ b/password_policies/forms/__init__.py @@ -1,15 +1,12 @@ -from __future__ import unicode_literals +from collections import OrderedDict from django import forms from django.contrib.auth import get_user_model, password_validation from django.contrib.auth.hashers import is_password_usable, make_password +from django.contrib.sites.shortcuts import get_current_site from django.core import signing from django.core.exceptions import ObjectDoesNotExist from django.template import loader - -from collections import OrderedDict - -from django.contrib.sites.shortcuts import get_current_site from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode @@ -53,7 +50,7 @@ def __init__(self, user, *args, **kwargs): :arg user: A :class:`~django.contrib.auth.models.User` instance.""" self.user = user - super(PasswordPoliciesForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean_new_password1(self): """ @@ -126,7 +123,7 @@ def clean_old_password(self): def clean(self): """ Validates that old and new password are not too similar.""" - cleaned_data = super(PasswordPoliciesChangeForm, self).clean() + cleaned_data = super().clean() old_password = cleaned_data.get("old_password") new_password1 = cleaned_data.get("new_password1") @@ -148,7 +145,7 @@ def clean(self): return cleaned_data def save(self, commit=True): - user = super(PasswordPoliciesChangeForm, self).save(commit=commit) + user = super().save(commit=commit) try: # Checking the object id to prevent AssertionError id is None when deleting. if user.password_change_required and user.password_change_required.id: diff --git a/password_policies/forms/admin.py b/password_policies/forms/admin.py index 312166e..2f5c83f 100644 --- a/password_policies/forms/admin.py +++ b/password_policies/forms/admin.py @@ -9,8 +9,7 @@ from password_policies.conf import settings from password_policies.forms.fields import PasswordPoliciesField -from password_policies.models import PasswordHistory -from password_policies.models import PasswordChangeRequired +from password_policies.models import PasswordChangeRequired, PasswordHistory class PasswordPoliciesAdminForm(AdminPasswordChangeForm): @@ -51,7 +50,7 @@ class ForceChangeAdminForm(PasswordPoliciesAdminForm): ) def save(self, commit=True): - user = super(ForceChangeAdminForm, self).save(commit=commit) + user = super().save(commit=commit) if ( self.cleaned_data["change_required"] and not PasswordChangeRequired.objects.filter(user=user).count() @@ -62,7 +61,7 @@ def save(self, commit=True): class ForceChangeRequiredAdminForm(PasswordPoliciesAdminForm): def save(self, commit=True): - user = super(ForceChangeRequiredAdminForm, self).save(commit=commit) + user = super().save(commit=commit) if not PasswordChangeRequired.objects.filter(user=user).count(): PasswordChangeRequired.objects.create(user=user) return user diff --git a/password_policies/forms/fields.py b/password_policies/forms/fields.py index 6170a56..19f150a 100644 --- a/password_policies/forms/fields.py +++ b/password_policies/forms/fields.py @@ -1,16 +1,18 @@ from django import forms -from password_policies.forms.validators import validate_common_sequences -from password_policies.forms.validators import validate_consecutive_count -from password_policies.forms.validators import validate_cracklib -from password_policies.forms.validators import validate_dictionary_words -from password_policies.forms.validators import validate_entropy -from password_policies.forms.validators import validate_letter_count -from password_policies.forms.validators import validate_lowercase_letter_count -from password_policies.forms.validators import validate_uppercase_letter_count -from password_policies.forms.validators import validate_number_count -from password_policies.forms.validators import validate_symbol_count -from password_policies.forms.validators import validate_not_email +from password_policies.forms.validators import ( + validate_common_sequences, + validate_consecutive_count, + validate_cracklib, + validate_dictionary_words, + validate_entropy, + validate_letter_count, + validate_lowercase_letter_count, + validate_not_email, + validate_number_count, + validate_symbol_count, + validate_uppercase_letter_count, +) class PasswordPoliciesField(forms.CharField): @@ -35,4 +37,4 @@ class PasswordPoliciesField(forms.CharField): def __init__(self, *args, **kwargs): if "widget" not in kwargs: kwargs["widget"] = forms.PasswordInput(render_value=False) - super(PasswordPoliciesField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) diff --git a/password_policies/forms/validators.py b/password_policies/forms/validators.py index 2d98ebc..986fe78 100644 --- a/password_policies/forms/validators.py +++ b/password_policies/forms/validators.py @@ -5,8 +5,7 @@ import unicodedata from django.core.exceptions import ValidationError - -from django.utils.encoding import smart_str, force_str +from django.utils.encoding import force_str, smart_str try: from django.utils.translation import gettext_lazy as _ @@ -260,7 +259,7 @@ def __call__(self, value): crack.FascistCheck(value) except ValueError as reason: reason = _(str(reason)) - message = _("Please choose a different password, %s." % reason) + message = _(f"Please choose a different password, {reason}.") raise ValidationError(message, code=self.code) def __init__( diff --git a/password_policies/managers.py b/password_policies/managers.py index db2a0f6..5f842f6 100644 --- a/password_policies/managers.py +++ b/password_policies/managers.py @@ -1,9 +1,9 @@ from datetime import timedelta -from django.db import models -from django.utils import timezone from django.contrib.auth.hashers import identify_hasher from django.core.exceptions import ObjectDoesNotExist +from django.db import models +from django.utils import timezone from password_policies.conf import settings diff --git a/password_policies/middleware.py b/password_policies/middleware.py index dc0b67c..00b5d7b 100644 --- a/password_policies/middleware.py +++ b/password_policies/middleware.py @@ -1,18 +1,11 @@ import re from datetime import timedelta -from django.urls.base import reverse, resolve, NoReverseMatch, Resolver404 +from django.conf import settings as django_setings from django.http import HttpResponseRedirect +from django.urls.base import NoReverseMatch, Resolver404, resolve, reverse from django.utils import timezone - -import django.utils.deprecation - -if hasattr(django.utils.deprecation, "MiddlewareMixin"): - from django.utils.deprecation import MiddlewareMixin -else: - MiddlewareMixin = object - -from django.conf import settings as django_setings +from django.utils.deprecation import MiddlewareMixin from password_policies.conf import settings from password_policies.models import PasswordChangeRequired, PasswordHistory @@ -34,19 +27,7 @@ class PasswordChangeMiddleware(MiddlewareMixin): is not taken... To use this middleware you need to add it to the - ``MIDDLEWARE_CLASSES`` list in a project's settings:: - - MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'password_policies.middleware.PasswordChangeMiddleware', - # ... other middleware ... - ) - - - or ``MIDDLEWARE`` if using Django 1.10 or higher: + ``MIDDLEWARE`` setting: MIDDLEWARE = ( 'django.middleware.common.CommonMiddleware', @@ -120,28 +101,28 @@ def _check_necessary(self, request): def _is_excluded_path(self, actual_path): paths = settings.PASSWORD_CHANGE_MIDDLEWARE_EXCLUDED_PATHS[:] - path = r"^%s$" % self.url + path = rf"^{self.url}$" paths.append(path) media_url = django_setings.MEDIA_URL if media_url: - paths.append(r"^%s?" % media_url) + paths.append(rf"^{media_url}?") static_url = django_setings.STATIC_URL if static_url: - paths.append(r"^%s?" % static_url) + paths.append(rf"^{static_url}?") if settings.PASSWORD_CHANGE_MIDDLEWARE_ALLOW_LOGOUT: try: logout_url = reverse("logout") except NoReverseMatch: pass else: - paths.append(r"^%s$" % logout_url) + paths.append(rf"^{logout_url}$") try: logout_url = "/admin/logout/" resolve(logout_url) except Resolver404: pass else: - paths.append(r"^%s$" % logout_url) + paths.append(rf"^{logout_url}$") for path in paths: if re.match(path, actual_path): return True @@ -154,7 +135,7 @@ def _redirect(self, request): next_to = redirect_to else: next_to = request.get_full_path() - url = "%s?%s=%s" % (self.url, settings.REDIRECT_FIELD_NAME, next_to) + url = f"{self.url}?{settings.REDIRECT_FIELD_NAME}={next_to}" return HttpResponseRedirect(url) def process_request(self, request): diff --git a/password_policies/migrations/0001_initial.py b/password_policies/migrations/0001_initial.py index f206b47..4b6a77e 100644 --- a/password_policies/migrations/0001_initial.py +++ b/password_policies/migrations/0001_initial.py @@ -1,8 +1,8 @@ # Generated by Django 2.0 on 2017-12-08 19:54 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/password_policies/migrations/0002_passwordprofile.py b/password_policies/migrations/0002_passwordprofile.py index 6add86f..14b3849 100644 --- a/password_policies/migrations/0002_passwordprofile.py +++ b/password_policies/migrations/0002_passwordprofile.py @@ -1,8 +1,8 @@ # Generated by Django 3.1.5 on 2021-01-09 07:27 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/password_policies/receivers.py b/password_policies/receivers.py index 61f7628..3bdc551 100644 --- a/password_policies/receivers.py +++ b/password_policies/receivers.py @@ -1,6 +1,8 @@ import importlib + from django.core.signals import setting_changed from django.dispatch import receiver + from .conf import settings as password_settings diff --git a/password_policies/south_migrations/0001_initial.py b/password_policies/south_migrations/0001_initial.py index e3da952..9fbf5a7 100644 --- a/password_policies/south_migrations/0001_initial.py +++ b/password_policies/south_migrations/0001_initial.py @@ -1,8 +1,7 @@ +from django.contrib.auth import get_user_model from south.db import db from south.v2 import SchemaMigration -from django.contrib.auth import get_user_model - User = get_user_model() @@ -24,9 +23,7 @@ def forwards(self, orm): self.gf("django.db.models.fields.related.OneToOneField")( related_name="password_change_required", unique=True, - to=orm[ - "%s.%s" % (User._meta.app_label, User._meta.object_name) - ], + to=orm[f"{User._meta.app_label}.{User._meta.object_name}"], ), ), ), @@ -52,9 +49,7 @@ def forwards(self, orm): "user", self.gf("django.db.models.fields.related.ForeignKey")( related_name="password_history_entries", - to=orm[ - "%s.%s" % (User._meta.app_label, User._meta.object_name) - ], + to=orm[f"{User._meta.app_label}.{User._meta.object_name}"], ), ), ), @@ -106,7 +101,7 @@ def backwards(self, orm): "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}), "name": ("django.db.models.fields.CharField", [], {"max_length": "50"}), }, - "%s.%s" % (User._meta.app_label, User._meta.module_name): { + f"{User._meta.app_label}.{User._meta.module_name}": { "Meta": { "object_name": User._meta.module_name, "db_table": repr(User._meta.db_table), diff --git a/password_policies/tests/test_models.py b/password_policies/tests/test_models.py index b16f337..3c75530 100644 --- a/password_policies/tests/test_models.py +++ b/password_policies/tests/test_models.py @@ -9,7 +9,7 @@ class PasswordHistoryModelTestCase(TestCase): def setUp(self): self.user = create_user() create_password_history(self.user) - return super(PasswordHistoryModelTestCase, self).setUp() + return super().setUp() def test_password_history_expiration_with_offset(self): offset = settings.PASSWORD_HISTORY_COUNT + 2 @@ -23,4 +23,6 @@ def test_password_history_expiration(self): self.assertEqual(count, settings.PASSWORD_HISTORY_COUNT) def test_password_history_recent_passwords(self): - self.failIf(PasswordHistory.objects.check_password(self.user, passwords[-1])) + self.assertFalse( + PasswordHistory.objects.check_password(self.user, passwords[-1]) + ) diff --git a/password_policies/tests/test_utils.py b/password_policies/tests/test_utils.py index 4643049..14cb612 100644 --- a/password_policies/tests/test_utils.py +++ b/password_policies/tests/test_utils.py @@ -10,7 +10,7 @@ def setUp(self): self.user = create_user() self.check = PasswordCheck(self.user) create_password_history(self.user) - return super(PasswordPoliciesUtilsTest, self).setUp() + return super().setUp() def test_password_check_is_required(self): # by default no change is required diff --git a/password_policies/tests/test_views.py b/password_policies/tests/test_views.py index b08ae69..a2c78f2 100644 --- a/password_policies/tests/test_views.py +++ b/password_policies/tests/test_views.py @@ -12,8 +12,7 @@ class PasswordChangeViewsTestCase(TestCase): def setUp(self): self.user = create_user() - return super(PasswordChangeViewsTestCase, self).setUp() - # + return super().setUp() def test_password_change(self): """ @@ -23,7 +22,7 @@ def test_password_change(self): self.client.login(username="alice", password=passwords[-1]) response = self.client.get(reverse("password_change")) self.assertEqual(response.status_code, 200) - self.failUnless( + self.assertTrue( isinstance(response.context["form"], PasswordPoliciesChangeForm) ) self.assertTemplateUsed(response, "registration/password_change_form.html") @@ -43,7 +42,7 @@ def test_password_change_failure(self): self.client.login(username="alice", password=passwords[-1]) response = self.client.post(reverse("password_change"), data=data) self.assertEqual(response.status_code, 200) - self.failIf(response.context["form"].is_valid()) + self.assertFalse(response.context["form"].is_valid()) self.assertFormError(response, "form", field="old_password", errors=msg) self.client.logout() @@ -102,7 +101,7 @@ def test_password_change_wrong_validators(self): self.client.login(username="alice", password=data["old_password"]) response = self.client.post(reverse("password_change"), data=data) self.assertEqual(response.status_code, 200) - self.failIf(response.context["form"].is_valid()) + self.assertFalse(response.context["form"].is_valid()) self.assertFormError(response, "form", field="new_password2", errors=msg) self.client.logout() diff --git a/password_policies/tests/views.py b/password_policies/tests/views.py index e6ef35b..727b35c 100644 --- a/password_policies/tests/views.py +++ b/password_policies/tests/views.py @@ -1,7 +1,8 @@ -from django.http import HttpResponse -from django.views.generic.base import View from django.contrib.auth.decorators import login_required +from django.http import HttpResponse from django.utils.decorators import method_decorator +from django.views.generic.base import View + from password_policies.views import LoggedOutMixin @@ -13,7 +14,7 @@ def get(self, request): @method_decorator(login_required) def dispatch(self, *args, **kwargs): - return super(TestHomeView, self).dispatch(*args, **kwargs) + return super().dispatch(*args, **kwargs) class TestLoggedOutMixinView(LoggedOutMixin): diff --git a/password_policies/utils.py b/password_policies/utils.py index 56bab1e..85a8ae3 100644 --- a/password_policies/utils.py +++ b/password_policies/utils.py @@ -1,13 +1,13 @@ from datetime import timedelta -from django.utils import timezone from django.core.exceptions import ObjectDoesNotExist +from django.utils import timezone from password_policies.conf import settings from password_policies.models import PasswordHistory -class PasswordCheck(object): +class PasswordCheck: "Checks if a given user needs to change his/her password." def __init__(self, user): diff --git a/password_policies/views.py b/password_policies/views.py index 278d073..6a6cbc9 100644 --- a/password_policies/views.py +++ b/password_policies/views.py @@ -1,13 +1,10 @@ from django.contrib.auth import get_user_model, update_session_auth_hash from django.contrib.auth.decorators import login_required from django.core import signing -from django.utils import timezone - -from password_policies.exceptions import MustBeLoggedOutException -from django.urls.base import reverse from django.shortcuts import resolve_url +from django.urls.base import reverse +from django.utils import timezone from django.utils.decorators import method_decorator - from django.utils.encoding import force_str from django.utils.http import urlsafe_base64_decode from django.views.decorators.cache import never_cache @@ -18,6 +15,7 @@ from django.views.generic.edit import FormView from password_policies.conf import settings +from password_policies.exceptions import MustBeLoggedOutException from password_policies.forms import ( PasswordPoliciesChangeForm, PasswordPoliciesForm, From b7a792cf050e9bdf9a34f7588d3d64e5c3eea0ad Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 12:41:44 +0100 Subject: [PATCH 07/29] Run all pre-commit tasks --- HISTORY.rst | 2 +- docs/_ext/__init__.py | 1 - docs/_theme/djangodocs/genindex.html | 2 +- docs/_theme/djangodocs/layout.html | 18 +++++++++--------- docs/_theme/djangodocs/modindex.html | 2 +- docs/_theme/djangodocs/search.html | 2 +- docs/_theme/djangodocs/static/default.css | 2 +- docs/_theme/djangodocs/static/djangodocs.css | 4 ++-- docs/_theme/djangodocs/static/homepage.css | 2 +- .../djangodocs/static/reset-fonts-grids.css | 2 +- docs/license.rst | 1 - docs/topics/support.rst | 1 - docs/topics/testing.rst | 1 - password_policies/context_processors.py | 7 +++++-- password_policies/tests/templates/403.html | 2 +- .../registration/password_change_done.html | 2 +- .../registration/password_change_form.html | 2 +- .../registration/password_reset_complete.html | 2 +- .../registration/password_reset_confirm.html | 2 +- .../registration/password_reset_done.html | 2 +- .../registration/password_reset_form.html | 2 +- password_policies/tests/test_middleware.py | 15 +++------------ 22 files changed, 33 insertions(+), 43 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 1b8ec85..f473fc0 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,7 +3,7 @@ * Django 5 support * default to JSONSerializer - + 0.8.4 ----- diff --git a/docs/_ext/__init__.py b/docs/_ext/__init__.py index 8b13789..e69de29 100644 --- a/docs/_ext/__init__.py +++ b/docs/_ext/__init__.py @@ -1 +0,0 @@ - diff --git a/docs/_theme/djangodocs/genindex.html b/docs/_theme/djangodocs/genindex.html index 486994a..032b70d 100644 --- a/docs/_theme/djangodocs/genindex.html +++ b/docs/_theme/djangodocs/genindex.html @@ -1,4 +1,4 @@ {% extends "basic/genindex.html" %} {% block bodyclass %}{% endblock %} -{% block sidebarwrapper %}{% endblock %} \ No newline at end of file +{% block sidebarwrapper %}{% endblock %} diff --git a/docs/_theme/djangodocs/layout.html b/docs/_theme/djangodocs/layout.html index ef91dd7..aefc0fd 100644 --- a/docs/_theme/djangodocs/layout.html +++ b/docs/_theme/djangodocs/layout.html @@ -2,13 +2,13 @@ {%- macro secondnav() %} {%- if prev %} - « previous + « previous {{ reldelim2 }} {%- endif %} {%- if parents %} - up + up {%- else %} - up + up {%- endif %} {%- if next %} {{ reldelim2 }} @@ -65,13 +65,13 @@

{{ docstitle }}

- +
{% block body %}{% endblock %} -
+
{% block sidebarwrapper %} @@ -82,11 +82,11 @@

{{ docstitle }}

Last update:

{{ last_updated }}

{%- endif %} - + {% endif %} {% endblock %} - +
@@ -113,7 +113,7 @@

You are here:

  • {{ title }}
{% for p in parents %}{% endfor %} - + {% endblock %} {# Empty some default blocks out #} @@ -121,4 +121,4 @@

You are here:

{% block relbar2 %}{% endblock %} {% block sidebar1 %}{% endblock %} {% block sidebar2 %}{% endblock %} -{% block footer %}{% endblock %} \ No newline at end of file +{% block footer %}{% endblock %} diff --git a/docs/_theme/djangodocs/modindex.html b/docs/_theme/djangodocs/modindex.html index 59a5cb3..ca5a2d4 100644 --- a/docs/_theme/djangodocs/modindex.html +++ b/docs/_theme/djangodocs/modindex.html @@ -1,3 +1,3 @@ {% extends "basic/modindex.html" %} {% block bodyclass %}{% endblock %} -{% block sidebarwrapper %}{% endblock %} \ No newline at end of file +{% block sidebarwrapper %}{% endblock %} diff --git a/docs/_theme/djangodocs/search.html b/docs/_theme/djangodocs/search.html index 943478c..4fdc7f6 100644 --- a/docs/_theme/djangodocs/search.html +++ b/docs/_theme/djangodocs/search.html @@ -1,3 +1,3 @@ {% extends "basic/search.html" %} {% block bodyclass %}{% endblock %} -{% block sidebarwrapper %}{% endblock %} \ No newline at end of file +{% block sidebarwrapper %}{% endblock %} diff --git a/docs/_theme/djangodocs/static/default.css b/docs/_theme/djangodocs/static/default.css index 9dc69ee..8f1e38f 100644 --- a/docs/_theme/djangodocs/static/default.css +++ b/docs/_theme/djangodocs/static/default.css @@ -1,3 +1,3 @@ @import url(reset-fonts-grids.css); @import url(djangodocs.css); -@import url(homepage.css); \ No newline at end of file +@import url(homepage.css); diff --git a/docs/_theme/djangodocs/static/djangodocs.css b/docs/_theme/djangodocs/static/djangodocs.css index 4adb838..df8b409 100644 --- a/docs/_theme/djangodocs/static/djangodocs.css +++ b/docs/_theme/djangodocs/static/djangodocs.css @@ -1,7 +1,7 @@ /*** setup ***/ html { background:#092e20;} body { font:12px/1.5 Verdana,sans-serif; background:#092e20; color: white;} -#custom-doc { width:76.54em;*width:74.69em;min-width:995px; max-width:100em; margin:auto; text-align:left; padding-top:16px; margin-top:0;} +#custom-doc { width:76.54em;*width:74.69em;min-width:995px; max-width:100em; margin:auto; text-align:left; padding-top:16px; margin-top:0;} #hd { padding: 4px 0 12px 0; } #bd { background:#234F32; } #ft { color:#487858; font-size:90%; padding-bottom: 2em; } @@ -54,7 +54,7 @@ hr { color:#ccc; background-color:#ccc; height:1px; border:0; } p, ul, dl { margin-top:.6em; margin-bottom:1em; padding-bottom: 0.1em;} #yui-main div.yui-b img { max-width: 50em; margin-left: auto; margin-right: auto; display: block; } caption { font-size:1em; font-weight:bold; margin-top:0.5em; margin-bottom:0.5em; margin-left: 2px; text-align: center; } -blockquote { padding: 0 1em; margin: 1em 0; font:125%/1.2em "Trebuchet MS", sans-serif; color:#234f32; border-left:2px solid #94da3a; } +blockquote { padding: 0 1em; margin: 1em 0; font:125%/1.2em "Trebuchet MS", sans-serif; color:#234f32; border-left:2px solid #94da3a; } strong { font-weight: bold; } em { font-style: italic; } ins { font-weight: bold; text-decoration: none; } diff --git a/docs/_theme/djangodocs/static/homepage.css b/docs/_theme/djangodocs/static/homepage.css index 276c547..3f69f01 100644 --- a/docs/_theme/djangodocs/static/homepage.css +++ b/docs/_theme/djangodocs/static/homepage.css @@ -19,4 +19,4 @@ #index #s-solving-specific-problems, #index #s-reference, #index #s-and-all-the-rest - { clear: left; } \ No newline at end of file + { clear: left; } diff --git a/docs/_theme/djangodocs/static/reset-fonts-grids.css b/docs/_theme/djangodocs/static/reset-fonts-grids.css index f5238d7..3d44d85 100644 --- a/docs/_theme/djangodocs/static/reset-fonts-grids.css +++ b/docs/_theme/djangodocs/static/reset-fonts-grids.css @@ -5,4 +5,4 @@ http://developer.yahoo.net/yui/license.txt version: 2.5.1 */ html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;font-variant:normal;}sup {vertical-align:text-top;}sub {vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}body {font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}table {font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;} -body{text-align:center;}#ft{clear:both;}#doc,#doc2,#doc3,#doc4,.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7{margin:auto;text-align:left;width:57.69em;*width:56.25em;min-width:750px;}#doc2{width:73.076em;*width:71.25em;}#doc3{margin:auto 10px;width:auto;}#doc4{width:74.923em;*width:73.05em;}.yui-b{position:relative;}.yui-b{_position:static;}#yui-main .yui-b{position:static;}#yui-main{width:100%;}.yui-t1 #yui-main,.yui-t2 #yui-main,.yui-t3 #yui-main{float:right;margin-left:-25em;}.yui-t4 #yui-main,.yui-t5 #yui-main,.yui-t6 #yui-main{float:left;margin-right:-25em;}.yui-t1 .yui-b{float:left;width:12.30769em;*width:12.00em;}.yui-t1 #yui-main .yui-b{margin-left:13.30769em;*margin-left:13.05em;}.yui-t2 .yui-b{float:left;width:13.8461em;*width:13.50em;}.yui-t2 #yui-main .yui-b{margin-left:14.8461em;*margin-left:14.55em;}.yui-t3 .yui-b{float:left;width:23.0769em;*width:22.50em;}.yui-t3 #yui-main .yui-b{margin-left:24.0769em;*margin-left:23.62em;}.yui-t4 .yui-b{float:right;width:13.8456em;*width:13.50em;}.yui-t4 #yui-main .yui-b{margin-right:14.8456em;*margin-right:14.55em;}.yui-t5 .yui-b{float:right;width:18.4615em;*width:18.00em;}.yui-t5 #yui-main .yui-b{margin-right:19.4615em;*margin-right:19.125em;}.yui-t6 .yui-b{float:right;width:23.0769em;*width:22.50em;}.yui-t6 #yui-main .yui-b{margin-right:24.0769em;*margin-right:23.62em;}.yui-t7 #yui-main .yui-b{display:block;margin:0 0 1em 0;}#yui-main .yui-b{float:none;width:auto;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf,.yui-gc .yui-u,.yui-gd .yui-g,.yui-g .yui-gc .yui-u,.yui-ge .yui-u,.yui-ge .yui-g,.yui-gf .yui-g,.yui-gf .yui-u{float:right;}.yui-g div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first,.yui-ge div.first,.yui-gf div.first,.yui-g .yui-gc div.first,.yui-g .yui-ge div.first,.yui-gc div.first div.first{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf{width:49.1%;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{width:32%;margin-left:1.99%;}.yui-gb .yui-u{*margin-left:1.9%;*width:31.9%;}.yui-gc div.first,.yui-gd .yui-u{width:66%;}.yui-gd div.first{width:32%;}.yui-ge div.first,.yui-gf .yui-u{width:74.2%;}.yui-ge .yui-u,.yui-gf div.first{width:24%;}.yui-g .yui-gb div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first{margin-left:0;}.yui-g .yui-g .yui-u,.yui-gb .yui-g .yui-u,.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u,.yui-ge .yui-g .yui-u,.yui-gf .yui-g .yui-u{width:49%;*width:48.1%;*margin-left:0;}.yui-g .yui-gb div.first,.yui-gb .yui-gb div.first{*margin-right:0;*width:32%;_width:31.7%;}.yui-g .yui-gc div.first,.yui-gd .yui-g{width:66%;}.yui-gb .yui-g div.first{*margin-right:4%;_margin-right:1.3%;}.yui-gb .yui-gc div.first,.yui-gb .yui-gd div.first{*margin-right:0;}.yui-gb .yui-gb .yui-u,.yui-gb .yui-gc .yui-u{*margin-left:1.8%;_margin-left:4%;}.yui-g .yui-gb .yui-u{_margin-left:1.0%;}.yui-gb .yui-gd .yui-u{*width:66%;_width:61.2%;}.yui-gb .yui-gd div.first{*width:31%;_width:29.5%;}.yui-g .yui-gc .yui-u,.yui-gb .yui-gc .yui-u{width:32%;_float:right;margin-right:0;_margin-left:0;}.yui-gb .yui-gc div.first{width:66%;*float:left;*margin-left:0;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf .yui-u{margin:0;}.yui-gb .yui-gb .yui-u{_margin-left:.7%;}.yui-gb .yui-g div.first,.yui-gb .yui-gb div.first{*margin-left:0;}.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u{*width:48.1%;*margin-left:0;}s .yui-gb .yui-gd div.first{width:32%;}.yui-g .yui-gd div.first{_width:29.9%;}.yui-ge .yui-g{width:24%;}.yui-gf .yui-g{width:74.2%;}.yui-gb .yui-ge div.yui-u,.yui-gb .yui-gf div.yui-u{float:right;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf div.first{float:left;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf div.first{*width:24%;_width:20%;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;}.yui-ge div.first .yui-gd .yui-u{width:65%;}.yui-ge div.first .yui-gd div.first{width:32%;}#bd:after,.yui-g:after,.yui-gb:after,.yui-gc:after,.yui-gd:after,.yui-ge:after,.yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;}#bd,.yui-g,.yui-gb,.yui-gc,.yui-gd,.yui-ge,.yui-gf{zoom:1;} \ No newline at end of file +body{text-align:center;}#ft{clear:both;}#doc,#doc2,#doc3,#doc4,.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7{margin:auto;text-align:left;width:57.69em;*width:56.25em;min-width:750px;}#doc2{width:73.076em;*width:71.25em;}#doc3{margin:auto 10px;width:auto;}#doc4{width:74.923em;*width:73.05em;}.yui-b{position:relative;}.yui-b{_position:static;}#yui-main .yui-b{position:static;}#yui-main{width:100%;}.yui-t1 #yui-main,.yui-t2 #yui-main,.yui-t3 #yui-main{float:right;margin-left:-25em;}.yui-t4 #yui-main,.yui-t5 #yui-main,.yui-t6 #yui-main{float:left;margin-right:-25em;}.yui-t1 .yui-b{float:left;width:12.30769em;*width:12.00em;}.yui-t1 #yui-main .yui-b{margin-left:13.30769em;*margin-left:13.05em;}.yui-t2 .yui-b{float:left;width:13.8461em;*width:13.50em;}.yui-t2 #yui-main .yui-b{margin-left:14.8461em;*margin-left:14.55em;}.yui-t3 .yui-b{float:left;width:23.0769em;*width:22.50em;}.yui-t3 #yui-main .yui-b{margin-left:24.0769em;*margin-left:23.62em;}.yui-t4 .yui-b{float:right;width:13.8456em;*width:13.50em;}.yui-t4 #yui-main .yui-b{margin-right:14.8456em;*margin-right:14.55em;}.yui-t5 .yui-b{float:right;width:18.4615em;*width:18.00em;}.yui-t5 #yui-main .yui-b{margin-right:19.4615em;*margin-right:19.125em;}.yui-t6 .yui-b{float:right;width:23.0769em;*width:22.50em;}.yui-t6 #yui-main .yui-b{margin-right:24.0769em;*margin-right:23.62em;}.yui-t7 #yui-main .yui-b{display:block;margin:0 0 1em 0;}#yui-main .yui-b{float:none;width:auto;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf,.yui-gc .yui-u,.yui-gd .yui-g,.yui-g .yui-gc .yui-u,.yui-ge .yui-u,.yui-ge .yui-g,.yui-gf .yui-g,.yui-gf .yui-u{float:right;}.yui-g div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first,.yui-ge div.first,.yui-gf div.first,.yui-g .yui-gc div.first,.yui-g .yui-ge div.first,.yui-gc div.first div.first{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf{width:49.1%;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{width:32%;margin-left:1.99%;}.yui-gb .yui-u{*margin-left:1.9%;*width:31.9%;}.yui-gc div.first,.yui-gd .yui-u{width:66%;}.yui-gd div.first{width:32%;}.yui-ge div.first,.yui-gf .yui-u{width:74.2%;}.yui-ge .yui-u,.yui-gf div.first{width:24%;}.yui-g .yui-gb div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first{margin-left:0;}.yui-g .yui-g .yui-u,.yui-gb .yui-g .yui-u,.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u,.yui-ge .yui-g .yui-u,.yui-gf .yui-g .yui-u{width:49%;*width:48.1%;*margin-left:0;}.yui-g .yui-gb div.first,.yui-gb .yui-gb div.first{*margin-right:0;*width:32%;_width:31.7%;}.yui-g .yui-gc div.first,.yui-gd .yui-g{width:66%;}.yui-gb .yui-g div.first{*margin-right:4%;_margin-right:1.3%;}.yui-gb .yui-gc div.first,.yui-gb .yui-gd div.first{*margin-right:0;}.yui-gb .yui-gb .yui-u,.yui-gb .yui-gc .yui-u{*margin-left:1.8%;_margin-left:4%;}.yui-g .yui-gb .yui-u{_margin-left:1.0%;}.yui-gb .yui-gd .yui-u{*width:66%;_width:61.2%;}.yui-gb .yui-gd div.first{*width:31%;_width:29.5%;}.yui-g .yui-gc .yui-u,.yui-gb .yui-gc .yui-u{width:32%;_float:right;margin-right:0;_margin-left:0;}.yui-gb .yui-gc div.first{width:66%;*float:left;*margin-left:0;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf .yui-u{margin:0;}.yui-gb .yui-gb .yui-u{_margin-left:.7%;}.yui-gb .yui-g div.first,.yui-gb .yui-gb div.first{*margin-left:0;}.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u{*width:48.1%;*margin-left:0;}s .yui-gb .yui-gd div.first{width:32%;}.yui-g .yui-gd div.first{_width:29.9%;}.yui-ge .yui-g{width:24%;}.yui-gf .yui-g{width:74.2%;}.yui-gb .yui-ge div.yui-u,.yui-gb .yui-gf div.yui-u{float:right;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf div.first{float:left;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf div.first{*width:24%;_width:20%;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;}.yui-ge div.first .yui-gd .yui-u{width:65%;}.yui-ge div.first .yui-gd div.first{width:32%;}#bd:after,.yui-g:after,.yui-gb:after,.yui-gc:after,.yui-gd:after,.yui-ge:after,.yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;}#bd,.yui-g,.yui-gb,.yui-gc,.yui-gd,.yui-ge,.yui-gf{zoom:1;} diff --git a/docs/license.rst b/docs/license.rst index ce39e22..bd540cc 100644 --- a/docs/license.rst +++ b/docs/license.rst @@ -4,4 +4,3 @@ License ======= .. literalinclude:: ../LICENSE - diff --git a/docs/topics/support.rst b/docs/topics/support.rst index 128b94a..a9750dc 100644 --- a/docs/topics/support.rst +++ b/docs/topics/support.rst @@ -12,4 +12,3 @@ At the present time this help is available: * To report a bug or other type of issue, please use the `GitHub issue tracker`_. .. _`GitHub issue tracker`: https://github.com/iplweb/django-password-policies-iplweb/issues - diff --git a/docs/topics/testing.rst b/docs/topics/testing.rst index bfa696f..4cb010a 100644 --- a/docs/topics/testing.rst +++ b/docs/topics/testing.rst @@ -9,4 +9,3 @@ tests do not require to be run from within a project. They can be executed in the application's source directory:: $ python setup.py test - diff --git a/password_policies/context_processors.py b/password_policies/context_processors.py index e54e206..1e04cc6 100644 --- a/password_policies/context_processors.py +++ b/password_policies/context_processors.py @@ -30,9 +30,12 @@ def password_status(request): auth = auth() if auth: - if settings.PASSWORD_POLICIES_CHANGE_REQUIRED_SESSION_KEY not in request.session: + if ( + settings.PASSWORD_POLICIES_CHANGE_REQUIRED_SESSION_KEY + not in request.session + ): r = PasswordHistory.objects.change_required(request.user) else: r = request.session[settings.PASSWORD_POLICIES_CHANGE_REQUIRED_SESSION_KEY] - d['password_change_required'] = r + d["password_change_required"] = r return d diff --git a/password_policies/tests/templates/403.html b/password_policies/tests/templates/403.html index 051c9f6..d018624 100644 --- a/password_policies/tests/templates/403.html +++ b/password_policies/tests/templates/403.html @@ -7,4 +7,4 @@

403 Forbidden

- \ No newline at end of file + diff --git a/password_policies/tests/templates/registration/password_change_done.html b/password_policies/tests/templates/registration/password_change_done.html index d56b10f..49997a2 100644 --- a/password_policies/tests/templates/registration/password_change_done.html +++ b/password_policies/tests/templates/registration/password_change_done.html @@ -1 +1 @@ -E-mail sent \ No newline at end of file +E-mail sent diff --git a/password_policies/tests/templates/registration/password_change_form.html b/password_policies/tests/templates/registration/password_change_form.html index 027da71..5dd03cb 100644 --- a/password_policies/tests/templates/registration/password_change_form.html +++ b/password_policies/tests/templates/registration/password_change_form.html @@ -1 +1 @@ -{{ form.as_ul }} \ No newline at end of file +{{ form.as_ul }} diff --git a/password_policies/tests/templates/registration/password_reset_complete.html b/password_policies/tests/templates/registration/password_reset_complete.html index d81138b..c99b8da 100644 --- a/password_policies/tests/templates/registration/password_reset_complete.html +++ b/password_policies/tests/templates/registration/password_reset_complete.html @@ -1 +1 @@ -Password reset. \ No newline at end of file +Password reset. diff --git a/password_policies/tests/templates/registration/password_reset_confirm.html b/password_policies/tests/templates/registration/password_reset_confirm.html index 1db7762..420bf42 100644 --- a/password_policies/tests/templates/registration/password_reset_confirm.html +++ b/password_policies/tests/templates/registration/password_reset_confirm.html @@ -1 +1 @@ -{% if validlink %}Please enter your new password: {{ form }}{% else %}The password reset link was invalid.{% endif %} \ No newline at end of file +{% if validlink %}Please enter your new password: {{ form }}{% else %}The password reset link was invalid.{% endif %} diff --git a/password_policies/tests/templates/registration/password_reset_done.html b/password_policies/tests/templates/registration/password_reset_done.html index d56b10f..49997a2 100644 --- a/password_policies/tests/templates/registration/password_reset_done.html +++ b/password_policies/tests/templates/registration/password_reset_done.html @@ -1 +1 @@ -E-mail sent \ No newline at end of file +E-mail sent diff --git a/password_policies/tests/templates/registration/password_reset_form.html b/password_policies/tests/templates/registration/password_reset_form.html index 027da71..5dd03cb 100644 --- a/password_policies/tests/templates/registration/password_reset_form.html +++ b/password_policies/tests/templates/registration/password_reset_form.html @@ -1 +1 @@ -{{ form.as_ul }} \ No newline at end of file +{{ form.as_ul }} diff --git a/password_policies/tests/test_middleware.py b/password_policies/tests/test_middleware.py index 0cc9bc5..0a085e5 100644 --- a/password_policies/tests/test_middleware.py +++ b/password_policies/tests/test_middleware.py @@ -1,16 +1,7 @@ -from django.test import TestCase - -try: - from urllib.parse import urljoin -except ImportError: - from urlparse import urljoin +from urllib.parse import urljoin -try: - from django.core.urlresolvers import reverse -except ImportError: - from django.urls.base import reverse - -from django.test.utils import override_settings +from django.test import TestCase +from django.urls import reverse from django.utils import timezone from password_policies.conf import settings From 7575a08ffd2c277a257ad8c7a6f956eecd18b406 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 12:48:13 +0100 Subject: [PATCH 08/29] Add Ruff job to CI workflow --- .github/workflows/main.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7ef07d7..3b333f3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,6 +8,11 @@ on: pull_request: jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: chartboost/ruff-action@v1 tests: name: Python ${{ matrix.python-version }} runs-on: ubuntu-latest From fe6e95c73e1d37ff4e3720b7aa390c55169aab11 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 12:51:43 +0100 Subject: [PATCH 09/29] Allow workflow to be triggered manually --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3b333f3..61d4eac 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,6 +6,7 @@ on: - master - develop pull_request: + workflow_dispatch: jobs: ruff: From 02ed7cb709f69a1be25630d9c826348fb12e06e5 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 13:10:28 +0100 Subject: [PATCH 10/29] Add __str__() definitions to models --- password_policies/models.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/password_policies/models.py b/password_policies/models.py index 027221d..0a7b5ed 100644 --- a/password_policies/models.py +++ b/password_policies/models.py @@ -40,6 +40,9 @@ class Meta: verbose_name = _("enforced password change") verbose_name_plural = _("enforced password changes") + def __str__(self): + return f"{self.user} f{self.created}" + class PasswordHistory(models.Model): """ @@ -74,6 +77,9 @@ class Meta: verbose_name = _("password history entry") verbose_name_plural = _("password history entries") + def __str__(self): + return f"{self.user} f{self.created}" + class PasswordProfile(models.Model): """ @@ -107,6 +113,9 @@ class Meta: verbose_name = _("password profile") verbose_name_plural = _("password profiles") + def __str__(self): + return f"{self.user} last changed f{self.last_changed}" + def create_password_profile_signal(sender, instance, created, **kwargs): if created: From d4b11a06b850e91a98c47cb9531b68a5b2a56606 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 13:11:14 +0100 Subject: [PATCH 11/29] Add python 3.7 and 3.12 to setup classifiers --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 09654d5..50564c1 100644 --- a/setup.py +++ b/setup.py @@ -24,10 +24,12 @@ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Framework :: Django", "License :: OSI Approved :: BSD License", "Topic :: Software Development :: Libraries :: Python Modules", From 23dfa0fea70311f6e7dfe2469abd15185de2faa2 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 14:29:54 +0100 Subject: [PATCH 12/29] Update documentation to use forked library --- docs/topics/install.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/install.rst b/docs/topics/install.rst index fb4e037..a5ebeef 100644 --- a/docs/topics/install.rst +++ b/docs/topics/install.rst @@ -54,11 +54,11 @@ From Pypi To install from `PyPi`_:: - [sudo] pip install django-password-policies + [sudo] pip install django-password-policies-iplweb or:: - [sudo] easy_install django-password-policies + [sudo] easy_install django-password-policies-iplweb .. _`PyPi`: https://pypi.python.org/pypi/django-password-policies From 2fcc3eb72c5c0129b50f61f8dac5dbaef35ff8af Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 14:30:31 +0100 Subject: [PATCH 13/29] Fix documentation not compiling in Python 12 --- docs/_ext/exts.py | 20 ++++++++++---------- docs/api/password_policies.conf.rst | 2 +- docs/conf.py | 8 ++------ 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/docs/_ext/exts.py b/docs/_ext/exts.py index af08ecf..de3f405 100644 --- a/docs/_ext/exts.py +++ b/docs/_ext/exts.py @@ -1,6 +1,6 @@ import inspect -from django.utils.encoding import force_unicode +from django.utils.encoding import force_str from django.utils.html import strip_tags from fields import model_fields, model_meta_fields @@ -22,16 +22,16 @@ def process_docstring(app, what, name, obj, options, lines): continue # Decode and strip any html out of the field's help text - help_text = strip_tags(force_unicode(field.help_text)) + help_text = strip_tags(force_str(field.help_text)) lines.append(f".. attribute:: {field.name}") lines.append(" ") # Add the field's type to the docstring if isinstance(field, models.ForeignKey): - to = field.rel.to + to = field.related_model msg = f" %s(':class:`~{type(field).__name__}.{to.__module__}`')" elif isinstance(field, models.OneToOneField): - to = field.rel.to + to = field.related_model msg = f" {type(field).__name__}(':class:`~{to.__module__}.{to.__name__}`')" else: msg = f" {type(field).__name__}" @@ -49,7 +49,7 @@ def process_docstring(app, what, name, obj, options, lines): lines.append(f" {help_text}") lines.append(" ") f = model_fields[type(field).__name__] - for key in sorted(f.iterkeys()): + for key in sorted(f.keys()): if ( hasattr(field, key) and getattr(field, key) != f[key] @@ -58,8 +58,8 @@ def process_docstring(app, what, name, obj, options, lines): attr = getattr(field, key) if key == "error_messages": error_dict = {} - for i in sorted(attr.iterkeys()): - error_dict[i] = force_unicode(attr[i]) + for i in sorted(attr.keys()): + error_dict[i] = force_str(attr[i]) attr = error_dict if key == "validators": v = [] @@ -71,7 +71,7 @@ def process_docstring(app, what, name, obj, options, lines): lines.append("") lines.append(".. attribute:: Meta") lines.append("") - for key in sorted(model_meta_fields.iterkeys()): + for key in sorted(model_meta_fields.keys()): if ( hasattr(obj._meta, key) and getattr(obj._meta, key) != model_meta_fields[key] @@ -90,7 +90,7 @@ def process_docstring(app, what, name, obj, options, lines): f = obj.base_fields[field] # Decode and strip any html out of the field's help text if hasattr(f, "help_text"): - help_text = strip_tags(force_unicode(f.help_text)) + help_text = strip_tags(force_str(f.help_text)) lines.append(f".. attribute:: {field}") lines.append("") @@ -104,7 +104,7 @@ def process_docstring(app, what, name, obj, options, lines): if hasattr(f, "error_messages") and f.error_messages: msgs = {} for key, value in f.error_messages.items(): - msgs[key] = force_unicode(value) + msgs[key] = force_str(value) lines.append(f":kwarg error_messages: {msgs}") if f.help_text: # Add the model field to the end of the docstring as a param diff --git a/docs/api/password_policies.conf.rst b/docs/api/password_policies.conf.rst index 5a363e7..c5630e3 100644 --- a/docs/api/password_policies.conf.rst +++ b/docs/api/password_policies.conf.rst @@ -15,6 +15,6 @@ and its new value to the project's settings file: ``Settings`` ------------ -.. autoclass:: password_policies.conf.Settings +.. autoclass:: password_policies.conf.settings :members: :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py index 30ef631..dd6933b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,7 +53,7 @@ master_doc = "contents" # General information about the project. -project = "django-password-policies" +project = "django-password-policies-iplweb" copyright = "2012, Tarak Blah" # The version info for the project you're documenting, acts as replacement for @@ -61,7 +61,7 @@ # built documents. # # The short X.Y version. -version = password_policies.get_version() +version = password_policies.__version__ # The full version, including alpha/beta/rc tags. release = password_policies.__version__ @@ -261,7 +261,3 @@ # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' - - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {"http://docs.python.org/": None} From fbfdfbe46c80c5a4447387bb09fc7665523e5ebc Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 14:34:44 +0100 Subject: [PATCH 14/29] Update documentation link --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 6434bfb..acacb12 100644 --- a/README.rst +++ b/README.rst @@ -33,6 +33,6 @@ Documentation A detailled documentation is available on `the project's GitHub Pages`_. -.. _`the project's GitHub Pages`: http://github.com/iplweb/django-password-policies-iplweb +.. _`the project's GitHub Pages`: https://iplweb.github.io/django-password-policies-iplweb/ .. _`Django`: https://www.djangoproject.com/ .. -`IPLweb on github`: https://github.com/iplweb/ From 6b5c547a0845d33a2ddcc3d103afd6b67054ff6b Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 14:41:29 +0100 Subject: [PATCH 15/29] Update various URLs to use https --- .editorconfig | 2 +- docs/api/password_policies.forms.validators.rst | 2 +- docs/topics/contributing.rst | 6 +++--- docs/topics/install.rst | 2 +- docs/topics/overview.rst | 4 ++-- docs/topics/security.rst | 2 +- password_policies/forms/validators.py | 6 +++--- password_policies/locale/pl/LC_MESSAGES/django.po | 2 +- password_policies/locale/ru/LC_MESSAGES/django.po | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.editorconfig b/.editorconfig index 568b397..612e8db 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -# http://editorconfig.org +# https://editorconfig.org root = true diff --git a/docs/api/password_policies.forms.validators.rst b/docs/api/password_policies.forms.validators.rst index df33eee..a88322a 100644 --- a/docs/api/password_policies.forms.validators.rst +++ b/docs/api/password_policies.forms.validators.rst @@ -238,4 +238,4 @@ django-password-policies provides validators to check new passwords: A :class:`SymbolCountValidator` instance. -.. _`Python bindings for cracklib documentation`: http://www.nongnu.org/python-crack/doc/index.html +.. _`Python bindings for cracklib documentation`: https://www.nongnu.org/python-crack/doc/index.html diff --git a/docs/topics/contributing.rst b/docs/topics/contributing.rst index d708bec..2f5b1f1 100644 --- a/docs/topics/contributing.rst +++ b/docs/topics/contributing.rst @@ -33,7 +33,7 @@ Please note the following guidelines for contributing: * Documentation must be formatted to be used with `Sphinx`_. -.. _`PEP 8`: http://www.python.org/dev/peps/pep-0008/ -.. _`Django coding style`: http://docs.djangoproject.com/en/dev/internals/contributing/#coding-style +.. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/ +.. _`Django coding style`: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/ .. _`GitHub`: https://github.com/iplweb/django-password-policies-iplweb/ -.. _`Sphinx`: http://sphinx.pocoo.org/ +.. _`Sphinx`: https://www.sphinx-doc.org/ diff --git a/docs/topics/install.rst b/docs/topics/install.rst index a5ebeef..474328b 100644 --- a/docs/topics/install.rst +++ b/docs/topics/install.rst @@ -97,5 +97,5 @@ inside the Python path:: .. _`Django`: https://www.djangoproject.com/ -.. _`Python bindings for cracklib`: http://www.nongnu.org/python-crack/ +.. _`Python bindings for cracklib`: https://www.nongnu.org/python-crack/ .. _`Levenshtein Python C extension module`: https://github.com/miohtama/python-Levenshtein diff --git a/docs/topics/overview.rst b/docs/topics/overview.rst index 65245b6..d69036e 100644 --- a/docs/topics/overview.rst +++ b/docs/topics/overview.rst @@ -43,5 +43,5 @@ Features for password resets. .. _`Django`: https://www.djangoproject.com/ -.. _`RFC 4013`: http://tools.ietf.org/html/rfc4013 -.. _`Python bindings for cracklib`: http://www.nongnu.org/python-crack/ +.. _`RFC 4013`: https://datatracker.ietf.org/doc/html/rfc4013 +.. _`Python bindings for cracklib`: https://www.nongnu.org/python-crack/ diff --git a/docs/topics/security.rst b/docs/topics/security.rst index 5238354..8d1e201 100644 --- a/docs/topics/security.rst +++ b/docs/topics/security.rst @@ -59,4 +59,4 @@ works: includes a mechanism to compare a raw password with different encrypted passwords. No unencrypted password is saved to the database! -.. _`Python bindings for cracklib`: http://www.nongnu.org/python-crack/ +.. _`Python bindings for cracklib`: https://www.nongnu.org/python-crack/ diff --git a/password_policies/forms/validators.py b/password_policies/forms/validators.py index 986fe78..06561f0 100644 --- a/password_policies/forms/validators.py +++ b/password_policies/forms/validators.py @@ -50,7 +50,7 @@ class BaseRFC4013Validator: Validates that a given password passes the requirements as defined in `RFC 4013`_. - .. _`RFC 4013`: http://tools.ietf.org/html/rfc4013""" + .. _`RFC 4013`: https://datatracker.ietf.org/doc/html/rfc4013""" first = "" invalid = True @@ -152,7 +152,7 @@ class BidirectionalValidator(BaseRFC4013Validator): For more information read `RFC 4013, section 2.3`_. - .. _`RFC 4013, section 2.3`: http://tools.ietf.org/html/rfc4013#section-2.3""" + .. _`RFC 4013, section 2.3`: https://datatracker.ietf.org/doc/html/rfc4013#section-2.3""" #: The validator's error code. code = "invalid_bidirectional" @@ -389,7 +389,7 @@ class InvalidCharacterValidator(BaseRFC4013Validator): Validates that a given password does not contain invalid unicode characters as defined in `RFC 4013, section 2.3`_. - .. _`RFC 4013, section 2.3`: http://tools.ietf.org/html/rfc4013#section-2.3""" + .. _`RFC 4013, section 2.3`: https://datatracker.ietf.org/doc/html/rfc4013#section-2.3""" #: The validator's error code. code = "invalid_unicode" diff --git a/password_policies/locale/pl/LC_MESSAGES/django.po b/password_policies/locale/pl/LC_MESSAGES/django.po index be53c04..8269599 100644 --- a/password_policies/locale/pl/LC_MESSAGES/django.po +++ b/password_policies/locale/pl/LC_MESSAGES/django.po @@ -1,5 +1,5 @@ # This file is distributed under the same license as the django-password-policies. -# Url: http://tarak.github.io/django-password-policies/ +# Url: https://tarak.github.io/django-password-policies/ # Translators: # Jarek Miazga , 2014. msgid "" diff --git a/password_policies/locale/ru/LC_MESSAGES/django.po b/password_policies/locale/ru/LC_MESSAGES/django.po index 1d1c732..746862f 100644 --- a/password_policies/locale/ru/LC_MESSAGES/django.po +++ b/password_policies/locale/ru/LC_MESSAGES/django.po @@ -1,5 +1,5 @@ # This file is distributed under the same license as the django-password-policies. -# Url: http://tarak.github.io/django-password-policies/ +# Url: https://tarak.github.io/django-password-policies/ # Translators: # Dmitry Pyatkov , 2014. msgid "" From 020da42ab933e2e0ac9c778701941a0aca70a3d5 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 14:55:01 +0100 Subject: [PATCH 16/29] Add HISTORY note --- HISTORY.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index f473fc0..a9c3b75 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,9 @@ +Unreleased +---------- + +* Remove unsupported Django code +* Added ``__str__()`` definitions for models + 0.8.6 ----- From 00cfb814e7c37fe01ecb02b968c5817fc6a11eb6 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 15:04:36 +0100 Subject: [PATCH 17/29] Fix minor documentation issue on PasswordChangeMiddleware --- password_policies/middleware.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/password_policies/middleware.py b/password_policies/middleware.py index f2ccb42..527b870 100644 --- a/password_policies/middleware.py +++ b/password_policies/middleware.py @@ -49,7 +49,8 @@ class PasswordChangeMiddleware(MiddlewareMixin): .. warning:: This middleware does not try to redirect using the HTTPS - protocol.""" + protocol. + """ checked = settings.PASSWORD_POLICIES_LAST_CHECKED_SESSION_KEY expired = settings.PASSWORD_POLICIES_EXPIRED_SESSION_KEY From 09436f5f1159719cc6b2721da470c19272326517 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 15:12:06 +0100 Subject: [PATCH 18/29] Update min django version in install.rst --- docs/topics/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/install.rst b/docs/topics/install.rst index 474328b..74e8f7f 100644 --- a/docs/topics/install.rst +++ b/docs/topics/install.rst @@ -11,7 +11,7 @@ Requirements This application requires -* `Django`_ 3.0 or newer +* `Django`_ 2.2 or newer .. _install-cracklib: From d321227a975d73ff007d2e82813672be20ddbcb6 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 15:30:43 +0100 Subject: [PATCH 19/29] ngettext is supported in Django 2.2 --- password_policies/forms/validators.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/password_policies/forms/validators.py b/password_policies/forms/validators.py index 06561f0..44ec7da 100644 --- a/password_policies/forms/validators.py +++ b/password_policies/forms/validators.py @@ -6,14 +6,13 @@ from django.core.exceptions import ValidationError from django.utils.encoding import force_str, smart_str +from django.utils.translation import ngettext try: from django.utils.translation import gettext_lazy as _ - from django.utils.translation import ngettext as ungettext except ImportError: # Before in Django 3.0 from django.utils.translation import ugettext_lazy as _ - from django.utils.translation import ungettext from password_policies.conf import settings @@ -197,7 +196,7 @@ def __call__(self, value): if len(list(group)) > self.get_max_count(): consecutive_found = True if consecutive_found: - msg = ungettext( + msg = ngettext( "The new password contains consecutive" " characters. Only %(count)d consecutive character" " is allowed.", @@ -431,7 +430,7 @@ def get_error_message(self): """ Returns this validator's error message.""" msg = ( - ungettext( + ngettext( "The new password must contain %d or more letter.", "The new password must contain %d or more letters.", self.get_min_count(), @@ -472,7 +471,7 @@ def get_error_message(self): """ Returns this validator's error message.""" msg = ( - ungettext( + ngettext( "The new password must contain %d or more lowercase letter.", "The new password must contain %d or more lowercase letters.", self.get_min_count(), @@ -514,7 +513,7 @@ def get_error_message(self): """ Returns this validator's error message.""" msg = ( - ungettext( + ngettext( "The new password must contain %d or more uppercase letter.", "The new password must contain %d or more uppercase letters.", self.get_min_count(), @@ -598,7 +597,7 @@ def get_error_message(self): """ Returns this validator's error message.""" msg = ( - ungettext( + ngettext( "The new password must contain %d or more number.", "The new password must contain %d or more numbers.", self.get_min_count(), @@ -670,7 +669,7 @@ def get_error_message(self): """ Returns this validator's error message.""" msg = ( - ungettext( + ngettext( "The new password must contain %d or more symbol.", "The new password must contain %d or more symbols.", self.get_min_count(), From b05899c544064be928f573d9373fd6476840bf45 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 15:36:01 +0100 Subject: [PATCH 20/29] Add Python version to README --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index acacb12..3b4d7ef 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,7 @@ As for now (Jan 2021), this fork is actively maintained by |iplweb| Requirements ============= -This application requires `Django`_ 2.2 or newer +This application requires `Django`_ 2.2 and Python 3.7 or newer. .. _documentation: From b2a0ff6cb345751b5ee3894dc0c7b8b5aecd07ae Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 15:58:15 +0100 Subject: [PATCH 21/29] Use path instead of re_path in most cases --- HISTORY.rst | 1 + docs/topics/custom.validation.rst | 6 +++--- password_policies/tests/urls.py | 20 ++++---------------- password_policies/urls.py | 18 ++++++++---------- 4 files changed, 16 insertions(+), 29 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index a9c3b75..b365038 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,7 @@ Unreleased * Remove unsupported Django code * Added ``__str__()`` definitions for models +* Use path instead of re_path for some URLs 0.8.6 ----- diff --git a/docs/topics/custom.validation.rst b/docs/topics/custom.validation.rst index db0b592..5a8a0f0 100644 --- a/docs/topics/custom.validation.rst +++ b/docs/topics/custom.validation.rst @@ -68,6 +68,6 @@ URL pattern needs to be added to a project's ``URLconf``:: from your_app.forms import CustomPasswordPoliciesForm - urlpatterns = patterns('', - (r'^password/reset/', PasswordResetConfirmView.as_view(form_class=CustomPasswordPoliciesForm)), - ) + urlpatterns = [ + path("password/reset/", PasswordResetConfirmView.as_view(form_class=CustomPasswordPoliciesForm)), + ] diff --git a/password_policies/tests/urls.py b/password_policies/tests/urls.py index 4371bed..03fd8f5 100644 --- a/password_policies/tests/urls.py +++ b/password_policies/tests/urls.py @@ -1,21 +1,9 @@ -try: - from django.conf.urls import include, url -except ImportError: - from django.urls import include - from django.urls import re_path as url - -try: - from django.conf.urls import patterns -except ImportError: - patterns = False +from django.urls import include, path from password_policies.tests.views import TestHomeView, TestLoggedOutMixinView urlpatterns = [ - url(r"^password/", include("password_policies.urls")), - url(r"^$", TestHomeView.as_view(), name="home"), - url(r"^fubar/", TestLoggedOutMixinView.as_view(), name="loggedoutmixin"), + path("password/", include("password_policies.urls")), + path("", TestHomeView.as_view(), name="home"), + path("fubar/", TestLoggedOutMixinView.as_view(), name="loggedoutmixin"), ] - -if patterns: - urlpatterns = patterns("", *urlpatterns) # noqa diff --git a/password_policies/urls.py b/password_policies/urls.py index 8d2e5f6..430e1ea 100644 --- a/password_policies/urls.py +++ b/password_policies/urls.py @@ -1,4 +1,4 @@ -from django.urls import re_path as url +from django.urls import path, re_path from password_policies.views import ( PasswordChangeDoneView, @@ -10,20 +10,18 @@ ) urlpatterns = [ - url( - r"^change/done/$", PasswordChangeDoneView.as_view(), name="password_change_done" - ), - url(r"^change/$", PasswordChangeFormView.as_view(), name="password_change"), - url(r"^reset/$", PasswordResetFormView.as_view(), name="password_reset"), - url( - r"^reset/complete/$", + path("change/done/", PasswordChangeDoneView.as_view(), name="password_change_done"), + path("change/", PasswordChangeFormView.as_view(), name="password_change"), + path("reset/", PasswordResetFormView.as_view(), name="password_reset"), + path( + "reset/complete/", PasswordResetCompleteView.as_view(), name="password_reset_complete", ), - url( + re_path( r"^reset/confirm/([0-9A-Za-z_\-]+)/([0-9A-Za-z]{1,13})/([0-9A-Za-z-=_]{1,128})/$", PasswordResetConfirmView.as_view(), name="password_reset_confirm", ), - url(r"^reset/done/$", PasswordResetDoneView.as_view(), name="password_reset_done"), + path("reset/done/", PasswordResetDoneView.as_view(), name="password_reset_done"), ] From 9416c160c6fe3fe25ba9d61fb4326c0b8b75c1d9 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 16:11:17 +0100 Subject: [PATCH 22/29] Increase min Django version to 4.2 --- README.rst | 2 +- docs/topics/install.rst | 2 +- password_policies/admin.py | 7 +------ password_policies/forms/__init__.py | 7 +------ password_policies/forms/admin.py | 7 +------ password_policies/forms/validators.py | 7 +------ password_policies/models.py | 7 +------ password_policies/tests/test_views.py | 16 ++++------------ requirements.txt | 2 +- setup.py | 1 - tox.ini | 20 ++------------------ 11 files changed, 14 insertions(+), 64 deletions(-) diff --git a/README.rst b/README.rst index 3b4d7ef..bfa65e7 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,7 @@ As for now (Jan 2021), this fork is actively maintained by |iplweb| Requirements ============= -This application requires `Django`_ 2.2 and Python 3.7 or newer. +This application requires `Django`_ 4.2 and Python 3.8 or newer. .. _documentation: diff --git a/docs/topics/install.rst b/docs/topics/install.rst index 74e8f7f..b611b27 100644 --- a/docs/topics/install.rst +++ b/docs/topics/install.rst @@ -11,7 +11,7 @@ Requirements This application requires -* `Django`_ 2.2 or newer +* `Django`_ 4.2 or newer .. _install-cracklib: diff --git a/password_policies/admin.py b/password_policies/admin.py index 004846b..8d32267 100644 --- a/password_policies/admin.py +++ b/password_policies/admin.py @@ -1,10 +1,5 @@ from django.contrib import admin - -try: - from django.utils.translation import gettext_lazy as _ -except ImportError: - # Before Django 3.0 - from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from password_policies.conf import settings from password_policies.models import PasswordChangeRequired, PasswordHistory diff --git a/password_policies/forms/__init__.py b/password_policies/forms/__init__.py index d7178fd..b4c33ac 100644 --- a/password_policies/forms/__init__.py +++ b/password_policies/forms/__init__.py @@ -9,12 +9,7 @@ from django.template import loader from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode - -try: - from django.utils.translation import gettext_lazy as _ -except ImportError: - # Before in Django 3.0 - from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from password_policies.conf import settings from password_policies.forms.fields import PasswordPoliciesField diff --git a/password_policies/forms/admin.py b/password_policies/forms/admin.py index 2f5c83f..a344bd8 100644 --- a/password_policies/forms/admin.py +++ b/password_policies/forms/admin.py @@ -1,11 +1,6 @@ from django import forms from django.contrib.auth.forms import AdminPasswordChangeForm - -try: - from django.utils.translation import gettext_lazy as _ -except ImportError: - # Before in Django 3.0 - from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from password_policies.conf import settings from password_policies.forms.fields import PasswordPoliciesField diff --git a/password_policies/forms/validators.py b/password_policies/forms/validators.py index 44ec7da..0c88d28 100644 --- a/password_policies/forms/validators.py +++ b/password_policies/forms/validators.py @@ -6,14 +6,9 @@ from django.core.exceptions import ValidationError from django.utils.encoding import force_str, smart_str +from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext -try: - from django.utils.translation import gettext_lazy as _ -except ImportError: - # Before in Django 3.0 - from django.utils.translation import ugettext_lazy as _ - from password_policies.conf import settings diff --git a/password_policies/models.py b/password_policies/models.py index 0a7b5ed..0f92c3d 100644 --- a/password_policies/models.py +++ b/password_policies/models.py @@ -3,12 +3,7 @@ from django.db import models from django.db.models import signals from django.utils import timezone - -try: - from django.utils.translation import gettext_lazy as _ -except ImportError: - # Before in Django 3.0 - from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from password_policies.conf import settings from password_policies.managers import PasswordHistoryManager diff --git a/password_policies/tests/test_views.py b/password_policies/tests/test_views.py index 7a415c4..26b7341 100644 --- a/password_policies/tests/test_views.py +++ b/password_policies/tests/test_views.py @@ -50,12 +50,7 @@ def test_password_change_failure(self): response = self.client.post(reverse("password_change"), data=data) self.assertEqual(response.status_code, 200) self.assertFalse(response.context["form"].is_valid()) - if DJANGO_VERSION > (4, 1): - self.assertFormError( - response.context["form"], field="old_password", errors=msg - ) - else: - self.assertFormError(response, "form", field="old_password", errors=msg) + self.assertFormError(response.context["form"], field="old_password", errors=msg) self.client.logout() def test_password_change_success(self): @@ -114,12 +109,9 @@ def test_password_change_wrong_validators(self): response = self.client.post(reverse("password_change"), data=data) self.assertEqual(response.status_code, 200) self.assertFalse(response.context["form"].is_valid()) - if DJANGO_VERSION > (4, 1): - self.assertFormError( - response.context["form"], field="new_password2", errors=msg - ) - else: - self.assertFormError(response, "form", field="new_password2", errors=msg) + self.assertFormError( + response.context["form"], field="new_password2", errors=msg + ) self.client.logout() def test_password_reset_complete(self): diff --git a/requirements.txt b/requirements.txt index 8c8868a..0cb74bd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -django>=2.2 +django>=4.2 diff --git a/setup.py b/setup.py index 50564c1..a0f8a72 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,6 @@ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", diff --git a/tox.ini b/tox.ini index 2fcc06e..c25bf4c 100644 --- a/tox.ini +++ b/tox.ini @@ -3,18 +3,8 @@ ignore = D503 [tox] envlist = - py38-django22 - py39-django22 - py38-django30 - py39-django30 - py38-django31 - py39-django31 - py38-django32 - py39-django32 - py39-django40 - py310-django40 - py39-django41 - py310-django41 + py38-django42 + py39-django42 py310-django42 py311-django42 py312-django42 @@ -25,12 +15,6 @@ envlist = deps = django50: Django>=5.0,<5.1 django42: Django>=4.2,<4.3 - django41: Django>=4.1,<4.2 - django40: Django>=4.0,<4.1 - django30: Django>=3.0,<3.1 - django31: Django>=3.1,<3.2 - django32: Django>=3.2,<3.3 - django22: Django>=2.2,<2.3 freezegun pytest pytest-django From 6221e2b7fa0803a8d02f792b512f815dba191064 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 16:12:09 +0100 Subject: [PATCH 23/29] Test against Django 5.1 --- tox.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tox.ini b/tox.ini index c25bf4c..c913d38 100644 --- a/tox.ini +++ b/tox.ini @@ -11,8 +11,12 @@ envlist = py310-django50 py311-django50 py312-django50 + py310-django51 + py311-django51 + py312-django51 [testenv] deps = + django51: Django>=5.1rc1,<5.2 django50: Django>=5.0,<5.1 django42: Django>=4.2,<4.3 freezegun From 7fbb1c462b3ee371cd8c6e763aaa3684edb92881 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 16:50:02 +0100 Subject: [PATCH 24/29] Switch to pyproject.toml to config tox, ruff and setup --- pyproject.toml | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++ ruff.toml | 9 ------ setup.py | 38 ------------------------- tox.ini | 29 ------------------- 4 files changed, 76 insertions(+), 76 deletions(-) create mode 100644 pyproject.toml delete mode 100644 ruff.toml delete mode 100644 setup.py delete mode 100644 tox.ini diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..46d19a3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,76 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "django-password-policies-iplweb" +version = "0.8.6" + +authors = [ + {name = "Michal Pasternak", email = "michal.dtz@gmail.com"} +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Web Environment", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Framework :: Django", + "License :: OSI Approved :: BSD License", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Utilities" +] +description = "A Django application to implent password policies." +readme = "README" +requires-python = ">=3.8" +dependencies = ["django"] + +[tool.ruff] +target-version = "py38" + +[tool.ruff.lint] +select = [ + "DJ", # flake8-django + "F", # flake8 + "I", # isort + "UP" # pyupgrade +] + +[tool.tox] +legacy_tox_ini = """ +[flake8] +ignore = D503 + +[tox] +envlist = + py38-django42 + py39-django42 + py310-django42 + py311-django42 + py312-django42 + py310-django50 + py311-django50 + py312-django50 + py310-django51 + py311-django51 + py312-django51 +[testenv] +deps = + django51: Django>=5.1rc1,<5.2 + django50: Django>=5.0,<5.1 + django42: Django>=4.2,<4.3 + freezegun + pytest + pytest-django + pytest-cov +# package = editable +use_develop = true +commands = + pytest --cov password_policies --ds=password_policies.tests.test_settings password_policies/tests/ -s +""" diff --git a/ruff.toml b/ruff.toml deleted file mode 100644 index d2d2cf8..0000000 --- a/ruff.toml +++ /dev/null @@ -1,9 +0,0 @@ -target-version = "py37" - -[lint] -select = [ - "DJ", # flake8-django - "F", # flake8 - "I", # isort - "UP" # pyupgrade -] diff --git a/setup.py b/setup.py deleted file mode 100644 index a0f8a72..0000000 --- a/setup.py +++ /dev/null @@ -1,38 +0,0 @@ -from setuptools import find_packages, setup - -install_requires = ["django"] - -setup( - name="django-password-policies-iplweb", - version=__import__("password_policies").__version__, - description="A Django application to implent password policies.", - long_description="""\ -django-password-policies is an application for the Django framework that -provides unicode-aware password policies on password changes and resets -and a mechanism to force password changes. -""", - author="Michal Pasternak", - author_email="michal.dtz@gmail.com", - url="https://github.com/iplweb/django-password-policies-iplweb", - include_package_data=True, - packages=find_packages(), - zip_safe=False, - classifiers=[ - "Development Status :: 3 - Alpha", - "Environment :: Web Environment", - "Intended Audience :: Developers", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Framework :: Django", - "License :: OSI Approved :: BSD License", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: Utilities", - ], - install_requires=install_requires, -) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index c913d38..0000000 --- a/tox.ini +++ /dev/null @@ -1,29 +0,0 @@ -[flake8] -ignore = D503 - -[tox] -envlist = - py38-django42 - py39-django42 - py310-django42 - py311-django42 - py312-django42 - py310-django50 - py311-django50 - py312-django50 - py310-django51 - py311-django51 - py312-django51 -[testenv] -deps = - django51: Django>=5.1rc1,<5.2 - django50: Django>=5.0,<5.1 - django42: Django>=4.2,<4.3 - freezegun - pytest - pytest-django - pytest-cov -# package = editable -use_develop = true -commands = - pytest --cov password_policies --ds=password_policies.tests.test_settings password_policies/tests/ -s From ab5c337ae43272a4fc9f5f3258971e3139af5826 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 17:08:16 +0100 Subject: [PATCH 25/29] Remove confusing flake8 config from legacy tox config --- pyproject.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 46d19a3..f343b76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,9 +44,6 @@ select = [ [tool.tox] legacy_tox_ini = """ -[flake8] -ignore = D503 - [tox] envlist = py38-django42 @@ -72,5 +69,5 @@ deps = # package = editable use_develop = true commands = - pytest --cov password_policies --ds=password_policies.tests.test_settings password_policies/tests/ -s + pytest --cov password_policies --ds=password_policies.tests.test_settings password_policies/tests/ -s """ From 2a44792b346ecfaadc0116d92d73acefb058d764 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 15:58:15 +0100 Subject: [PATCH 26/29] Add django-upgrade pre-commit task and fix a couple of issues --- .pre-commit-config.yaml | 6 ++++++ HISTORY.rst | 1 + docs/topics/custom.validation.rst | 6 +++--- password_policies/admin.py | 6 ++---- password_policies/tests/urls.py | 20 ++++---------------- password_policies/urls.py | 18 ++++++++---------- password_policies/views.py | 2 +- 7 files changed, 25 insertions(+), 34 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5327caa..6056f91 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,6 +8,12 @@ repos: entry: 'breakpoint\(\)|set_trace' language: pygrep + - repo: https://github.com/adamchainz/django-upgrade + rev: "1.20.0" + hooks: + - id: django-upgrade + args: [--target-version, "2.2"] + - repo: https://github.com/pre-commit/pre-commit-hooks rev: 'v4.6.0' hooks: diff --git a/HISTORY.rst b/HISTORY.rst index a9c3b75..b365038 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,7 @@ Unreleased * Remove unsupported Django code * Added ``__str__()`` definitions for models +* Use path instead of re_path for some URLs 0.8.6 ----- diff --git a/docs/topics/custom.validation.rst b/docs/topics/custom.validation.rst index db0b592..5a8a0f0 100644 --- a/docs/topics/custom.validation.rst +++ b/docs/topics/custom.validation.rst @@ -68,6 +68,6 @@ URL pattern needs to be added to a project's ``URLconf``:: from your_app.forms import CustomPasswordPoliciesForm - urlpatterns = patterns('', - (r'^password/reset/', PasswordResetConfirmView.as_view(form_class=CustomPasswordPoliciesForm)), - ) + urlpatterns = [ + path("password/reset/", PasswordResetConfirmView.as_view(form_class=CustomPasswordPoliciesForm)), + ] diff --git a/password_policies/admin.py b/password_policies/admin.py index 004846b..71131cf 100644 --- a/password_policies/admin.py +++ b/password_policies/admin.py @@ -19,6 +19,7 @@ def force_password_change(modeladmin, request, queryset): ) +@admin.register(PasswordHistory) class PasswordHistoryAdmin(admin.ModelAdmin): date_hierarchy = "created" exclude = ("password",) @@ -34,6 +35,7 @@ def has_add_permission(self, request): return False +@admin.register(PasswordChangeRequired) class PasswordChangeRequiredAdmin(admin.ModelAdmin): date_hierarchy = "created" list_display = ("id", "user", "created") @@ -52,7 +54,3 @@ def get_readonly_fields(self, request, obj=None): return ["user"] else: return [] - - -admin.site.register(PasswordHistory, PasswordHistoryAdmin) -admin.site.register(PasswordChangeRequired, PasswordChangeRequiredAdmin) diff --git a/password_policies/tests/urls.py b/password_policies/tests/urls.py index 4371bed..03fd8f5 100644 --- a/password_policies/tests/urls.py +++ b/password_policies/tests/urls.py @@ -1,21 +1,9 @@ -try: - from django.conf.urls import include, url -except ImportError: - from django.urls import include - from django.urls import re_path as url - -try: - from django.conf.urls import patterns -except ImportError: - patterns = False +from django.urls import include, path from password_policies.tests.views import TestHomeView, TestLoggedOutMixinView urlpatterns = [ - url(r"^password/", include("password_policies.urls")), - url(r"^$", TestHomeView.as_view(), name="home"), - url(r"^fubar/", TestLoggedOutMixinView.as_view(), name="loggedoutmixin"), + path("password/", include("password_policies.urls")), + path("", TestHomeView.as_view(), name="home"), + path("fubar/", TestLoggedOutMixinView.as_view(), name="loggedoutmixin"), ] - -if patterns: - urlpatterns = patterns("", *urlpatterns) # noqa diff --git a/password_policies/urls.py b/password_policies/urls.py index 8d2e5f6..430e1ea 100644 --- a/password_policies/urls.py +++ b/password_policies/urls.py @@ -1,4 +1,4 @@ -from django.urls import re_path as url +from django.urls import path, re_path from password_policies.views import ( PasswordChangeDoneView, @@ -10,20 +10,18 @@ ) urlpatterns = [ - url( - r"^change/done/$", PasswordChangeDoneView.as_view(), name="password_change_done" - ), - url(r"^change/$", PasswordChangeFormView.as_view(), name="password_change"), - url(r"^reset/$", PasswordResetFormView.as_view(), name="password_reset"), - url( - r"^reset/complete/$", + path("change/done/", PasswordChangeDoneView.as_view(), name="password_change_done"), + path("change/", PasswordChangeFormView.as_view(), name="password_change"), + path("reset/", PasswordResetFormView.as_view(), name="password_reset"), + path( + "reset/complete/", PasswordResetCompleteView.as_view(), name="password_reset_complete", ), - url( + re_path( r"^reset/confirm/([0-9A-Za-z_\-]+)/([0-9A-Za-z]{1,13})/([0-9A-Za-z-=_]{1,128})/$", PasswordResetConfirmView.as_view(), name="password_reset_confirm", ), - url(r"^reset/done/$", PasswordResetDoneView.as_view(), name="password_reset_done"), + path("reset/done/", PasswordResetDoneView.as_view(), name="password_reset_done"), ] diff --git a/password_policies/views.py b/password_policies/views.py index bbad4d3..782c73a 100644 --- a/password_policies/views.py +++ b/password_policies/views.py @@ -261,7 +261,7 @@ def form_valid(self, form): "request": self.request, } if self.is_admin_site: - opts = dict(opts, domain_override=self.request.META["HTTP_HOST"]) + opts = dict(opts, domain_override=self.request.headers["host"]) form.save(**opts) return super().form_valid(form) From a40911f1e47cb833b1f41a7a7cd6d771d365119f Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 20:05:33 +0100 Subject: [PATCH 27/29] Bump django-upgrade to target 4.2 and fix issue --- .pre-commit-config.yaml | 2 +- password_policies/conf/settings.py | 2 +- password_policies/views.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6056f91..a92a4a7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: rev: "1.20.0" hooks: - id: django-upgrade - args: [--target-version, "2.2"] + args: [--target-version, "4.2"] - repo: https://github.com/pre-commit/pre-commit-hooks rev: 'v4.6.0' diff --git a/password_policies/conf/settings.py b/password_policies/conf/settings.py index a644f6a..270906d 100644 --- a/password_policies/conf/settings.py +++ b/password_policies/conf/settings.py @@ -183,7 +183,7 @@ TEMPLATE_403_PAGE = getattr(settings, "TEMPLATE_403_PAGE", "403.html") -PASSWORD_RESET_TIMEOUT_DAYS = 1 +PASSWORD_RESET_TIMEOUT = 60 * 60 * 24 * 1 PASSWORD_POLICIES_LAST_CHECKED_SESSION_KEY = "_password_policies_last_checked" PASSWORD_POLICIES_EXPIRED_SESSION_KEY = "_password_policies_expired" diff --git a/password_policies/views.py b/password_policies/views.py index 782c73a..b57f982 100644 --- a/password_policies/views.py +++ b/password_policies/views.py @@ -168,7 +168,7 @@ def dispatch(self, request, *args, **kwargs): self.user = None else: signer = signing.TimestampSigner() - max_age = settings.PASSWORD_RESET_TIMEOUT_DAYS * 24 * 60 * 60 + max_age = settings.PASSWORD_RESET_TIMEOUT il = (self.user.password, self.timestamp, self.signature) try: signer.unsign(":".join(il), max_age=max_age) From 1694e4e339e07120408613c02db219940319e158 Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 20:12:04 +0100 Subject: [PATCH 28/29] Update readme to fix link --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index bfa65e7..df7d133 100644 --- a/README.rst +++ b/README.rst @@ -7,7 +7,7 @@ django-password-policies provides unicode-aware password policies on password changes and resets and a mechanism to force password changes. -As for now (Jan 2021), this fork is actively maintained by |iplweb| +As for now (Jan 2021), this fork is actively maintained by `iplweb`_ .. |travis| image:: https://travis-ci.org/iplweb/django-password-policies.svg?branch=master :target: https://travis-ci.org/iplweb/django-password-policies-iplweb @@ -35,4 +35,4 @@ A detailled documentation is available on `the project's GitHub Pages`_. .. _`the project's GitHub Pages`: https://iplweb.github.io/django-password-policies-iplweb/ .. _`Django`: https://www.djangoproject.com/ -.. -`IPLweb on github`: https://github.com/iplweb/ +.. _`IPLweb on github`: https://github.com/iplweb/ From eea6d8ca4395ac910e23d913fd064d824c9e710a Mon Sep 17 00:00:00 2001 From: Mike Manger Date: Mon, 29 Jul 2024 20:58:52 +0100 Subject: [PATCH 29/29] Fix readme typo --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index df7d133..f975188 100644 --- a/README.rst +++ b/README.rst @@ -7,7 +7,7 @@ django-password-policies provides unicode-aware password policies on password changes and resets and a mechanism to force password changes. -As for now (Jan 2021), this fork is actively maintained by `iplweb`_ +As for now (Jan 2021), this fork is actively maintained by `IPLweb`_. .. |travis| image:: https://travis-ci.org/iplweb/django-password-policies.svg?branch=master :target: https://travis-ci.org/iplweb/django-password-policies-iplweb @@ -22,7 +22,7 @@ As for now (Jan 2021), this fork is actively maintained by `iplweb`_ .. _requirements: Requirements -============= +============ This application requires `Django`_ 4.2 and Python 3.8 or newer. @@ -35,4 +35,4 @@ A detailled documentation is available on `the project's GitHub Pages`_. .. _`the project's GitHub Pages`: https://iplweb.github.io/django-password-policies-iplweb/ .. _`Django`: https://www.djangoproject.com/ -.. _`IPLweb on github`: https://github.com/iplweb/ +.. _`IPLweb`: https://github.com/iplweb/