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/.github/workflows/main.yml b/.github/workflows/main.yml
index 7ef07d7..61d4eac 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -6,8 +6,14 @@ on:
- master
- develop
pull_request:
+ workflow_dispatch:
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
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 63d431e..a92a4a7 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -8,36 +8,14 @@ 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/adamchainz/django-upgrade
+ rev: "1.20.0"
+ hooks:
+ - id: django-upgrade
+ args: [--target-version, "4.2"]
- 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 +23,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/HISTORY.rst b/HISTORY.rst
index 1b8ec85..b365038 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -1,9 +1,16 @@
+Unreleased
+----------
+
+* Remove unsupported Django code
+* Added ``__str__()`` definitions for models
+* Use path instead of re_path for some URLs
+
0.8.6
-----
* Django 5 support
* default to JSONSerializer
-
+
0.8.4
-----
diff --git a/README.rst b/README.rst
index 6434bfb..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,9 +22,9 @@ As for now (Jan 2021), this fork is actively maintained by |iplweb|
.. _requirements:
Requirements
-=============
+============
-This application requires `Django`_ 2.2 or newer
+This application requires `Django`_ 4.2 and Python 3.8 or newer.
.. _documentation:
@@ -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/
+.. _`IPLweb`: https://github.com/iplweb/
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/_ext/exts.py b/docs/_ext/exts.py
index aa0504f..de3f405 100644
--- a/docs/_ext/exts.py
+++ b/docs/_ext/exts.py
@@ -1,137 +1,128 @@
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_str
+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):
# 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))
+ help_text = strip_tags(force_str(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(f".. attribute:: {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__)
+ to = field.related_model
+ msg = f" %s(':class:`~{type(field).__name__}.{to.__module__}`')"
elif isinstance(field, models.OneToOneField):
- to = field.rel.to
- l = u' %s(\':class:`~%s.%s`\')' % (type(field).__name__,
- to.__module__,
- to.__name__)
+ to = field.related_model
+ msg = f" {type(field).__name__}(':class:`~{to.__module__}.{to.__name__}`')"
else:
- l = u' %s' % type(field).__name__
+ msg = f" {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(f" {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):
+ for key in sorted(f.keys()):
+ 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])
+ for i in sorted(attr.keys()):
+ error_dict[i] = force_str(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 = f":class:`~{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'')
- 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'')
-
+ lines.append(f" :param {key}: {attr}")
+ lines.append("")
+ lines.append(".. attribute:: Meta")
+ lines.append("")
+ for key in sorted(model_meta_fields.keys()):
+ if (
+ hasattr(obj._meta, key)
+ and getattr(obj._meta, key) != model_meta_fields[key]
+ ):
+ lines.append(f" {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'):
- 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()
+ if hasattr(f, "help_text"):
+ help_text = strip_tags(force_str(f.help_text))
- lines.append(u'.. attribute:: %s' % field)
- lines.append(u'')
+ lines.append(f".. attribute:: {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 = f" :class:`~{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)
+ 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
# 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(f":kwarg help_text: {help_text}")
+ if hasattr(f, "initial") and f.initial:
+ lines.append(f":kwarg initial: {f.initial}")
+ if hasattr(f, "localize"):
+ lines.append(f":kwarg localize: {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(
+ f":class:`~{type(v).__module__}.{type(v).__name__}`"
+ )
+ lines.append(f":kwarg validators: {line_array}")
+ lines.append(f":kwarg widget: {type(f.widget).__name__}")
+ lines.append("")
# Return the extended docstring
return lines
@@ -139,74 +130,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/_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 @@
{{ secondnav() }}
-
+
{% block body %}{% endblock %}
-
+
{% block sidebarwrapper %}
@@ -82,11 +82,11 @@
Last update:
{{ last_updated }}
{%- endif %}
-
+
{% endif %}
{% endblock %}
-
+
@@ -113,7 +113,7 @@ You are here:
{% 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/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/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/conf.py b/docs/conf.py
index 9b29770..dd6933b 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.
#
@@ -11,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,
@@ -22,11 +21,10 @@
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()
-password_policies = __import__('password_policies')
+password_policies = __import__("password_policies")
# -- General configuration -----------------------------------------------------
@@ -35,187 +33,195 @@
# 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-iplweb"
+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
# 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__
# 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 --------------------------------------------
@@ -223,12 +229,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 ------------------------------------------------
@@ -237,20 +242,22 @@
# (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'
-
-
-# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'http://docs.python.org/': None}
+# texinfo_show_urls = 'footnote'
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/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/custom.validation.rst b/docs/topics/custom.validation.rst
index 9e28121..5a8a0f0 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")
@@ -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/docs/topics/install.rst b/docs/topics/install.rst
index fb4e037..b611b27 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`_ 4.2 or newer
.. _install-cracklib:
@@ -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
@@ -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/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/__init__.py b/password_policies/__init__.py
index e0f37ac..0f64540 100644
--- a/password_policies/__init__.py
+++ b/password_policies/__init__.py
@@ -1,3 +1,3 @@
VERSION = (0, 8, 6)
-__version__ = "%s.%s.%s" % VERSION
+__version__ = ".".join(map(str, VERSION))
diff --git a/password_policies/admin.py b/password_policies/admin.py
index 7099fdc..fd2d0b7 100644
--- a/password_policies/admin.py
+++ b/password_policies/admin.py
@@ -1,9 +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
@@ -18,6 +14,7 @@ def force_password_change(modeladmin, request, queryset):
)
+@admin.register(PasswordHistory)
class PasswordHistoryAdmin(admin.ModelAdmin):
date_hierarchy = "created"
exclude = ("password",)
@@ -33,6 +30,7 @@ def has_add_permission(self, request):
return False
+@admin.register(PasswordChangeRequired)
class PasswordChangeRequiredAdmin(admin.ModelAdmin):
date_hierarchy = "created"
list_display = ("id", "user", "created")
@@ -51,7 +49,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/conf/settings.py b/password_policies/conf/settings.py
index 1a37f29..270906d 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")
@@ -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/context_processors.py b/password_policies/context_processors.py
index 50d37ef..1e04cc6 100644
--- a/password_policies/context_processors.py
+++ b/password_policies/context_processors.py
@@ -4,35 +4,38 @@
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 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/forms/__init__.py b/password_policies/forms/__init__.py
index b4f71cb..b4c33ac 100644
--- a/password_policies/forms/__init__.py
+++ b/password_policies/forms/__init__.py
@@ -1,33 +1,15 @@
-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
-
-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 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
@@ -63,7 +45,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):
"""
@@ -121,7 +103,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)
@@ -136,7 +118,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")
@@ -158,7 +140,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:
@@ -168,7 +150,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/admin.py b/password_policies/forms/admin.py
index 3d55d0f..a344bd8 100644
--- a/password_policies/forms/admin.py
+++ b/password_policies/forms/admin.py
@@ -1,62 +1,62 @@
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
-from password_policies.models import PasswordHistory
-from password_policies.models import PasswordChangeRequired
+from password_policies.models import PasswordChangeRequired, PasswordHistory
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():
+ user = super().save(commit=commit)
+ 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)
+ 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 1d9d0d4..19f150a 100644
--- a/password_policies/forms/fields.py
+++ b/password_policies/forms/fields.py
@@ -1,35 +1,40 @@
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):
"""
-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:
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 69b0185..0c88d28 100644
--- a/password_policies/forms/validators.py
+++ b/password_policies/forms/validators.py
@@ -5,37 +5,12 @@
import unicodedata
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
-
-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
-except ImportError:
- # Before in Django 3.0
- from django.utils.translation import ugettext_lazy as _
- from django.utils.translation import ungettext
+from django.utils.encoding import force_str, smart_str
+from django.utils.translation import gettext_lazy as _
+from django.utils.translation import ngettext
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 +23,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
@@ -69,7 +44,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
@@ -78,13 +53,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 +98,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))
@@ -171,7 +146,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"
@@ -212,11 +187,11 @@ 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:
- msg = ungettext(
+ msg = ngettext(
"The new password contains consecutive"
" characters. Only %(count)d consecutive character"
" is allowed.",
@@ -278,7 +253,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__(
@@ -397,9 +372,7 @@ def __init__(self, dictionary="", words=[]):
haystacks = []
if self.dictionary:
with open(self.dictionary) as dictionary:
- haystacks.extend(
- [smart_text(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)
@@ -410,7 +383,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"
@@ -452,7 +425,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(),
@@ -493,7 +466,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(),
@@ -535,7 +508,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(),
@@ -619,7 +592,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(),
@@ -691,7 +664,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(),
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 ""
diff --git a/password_policies/managers.py b/password_policies/managers.py
index d5aa7d6..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
@@ -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 3efd6a9..527b870 100644
--- a/password_policies/middleware.py
+++ b/password_policies/middleware.py
@@ -1,26 +1,20 @@
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.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
-from password_policies.utils import PasswordCheck, string_to_datetime, datetime_to_string
+from password_policies.utils import (
+ PasswordCheck,
+ datetime_to_string,
+ string_to_datetime,
+)
+
class PasswordChangeMiddleware(MiddlewareMixin):
"""
@@ -37,19 +31,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',
@@ -67,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
@@ -83,7 +66,9 @@ def _check_history(self, request):
else:
# TODO: This relies on request.user.date_joined which might not
# be available!!!
- request.session[self.last] = datetime_to_string(request.user.date_joined)
+ request.session[self.last] = datetime_to_string(
+ request.user.date_joined
+ )
date_last = string_to_datetime(request.session[self.last])
if date_last < self.expiry_datetime:
@@ -94,16 +79,14 @@ 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] = datetime_to_string(self.now)
# If the PASSWORD_CHECK_ONLY_AT_LOGIN is set, then only check at the beginning of session, which we can
# tell by self.now time having just been set.
- if (
- not settings.PASSWORD_CHECK_ONLY_AT_LOGIN
- or request.session.get(self.checked, None) == datetime_to_string(self.now)
- ):
+ if not settings.PASSWORD_CHECK_ONLY_AT_LOGIN or request.session.get(
+ self.checked, None
+ ) == datetime_to_string(self.now):
# If a password change is enforced we won't check
# the user's password history, thus reducing DB hits...
if PasswordChangeRequired.objects.filter(user=request.user).count():
@@ -129,28 +112,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 = u"/admin/logout/"
+ 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
@@ -163,7 +146,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 e9afa50..4b6a77e 100644
--- a/password_policies/migrations/0001_initial.py
+++ b/password_policies/migrations/0001_initial.py
@@ -1,12 +1,11 @@
# 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):
-
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..14b3849 100644
--- a/password_policies/migrations/0002_passwordprofile.py
+++ b/password_policies/migrations/0002_passwordprofile.py
@@ -1,31 +1,61 @@
# 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):
-
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..0f92c3d 100644
--- a/password_policies/models.py
+++ b/password_policies/models.py
@@ -3,11 +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
@@ -39,6 +35,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):
"""
@@ -73,6 +72,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):
"""
@@ -106,6 +108,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:
diff --git a/password_policies/receivers.py b/password_policies/receivers.py
index 95b56c4..3bdc551 100644
--- a/password_policies/receivers.py
+++ b/password_policies/receivers.py
@@ -1,13 +1,16 @@
import importlib
+
from django.core.signals import setting_changed
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 dc0620c..9fbf5a7 100644
--- a/password_policies/south_migrations/0001_initial.py
+++ b/password_policies/south_migrations/0001_initial.py
@@ -1,81 +1,171 @@
-# -*- coding: utf-8 -*-
-from south.utils import datetime_utils as datetime
+from django.contrib.auth import get_user_model
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()
+User = get_user_model()
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[f"{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[f"{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",
+ },
+ ),
+ },
+ "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"}),
},
- 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'})
+ f"{User._meta.app_label}.{User._meta.module_name}": {
+ "Meta": {
+ "object_name": User._meta.module_name,
+ "db_table": repr(User._meta.db_table),
+ },
},
- "%s.%s" % (User._meta.app_label, User._meta.module_name): {
- '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/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
-