Repository: jazzband/django-axes
Pull request #1062 (2023-05-25 12:57:16)

chore(deps): bump prospector from 1.10.0 to 1.10.2
Bumps [prospector](https://github.com/PyCQA/prospector) from 1.10.0 to 1.10.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/PyCQA/prospector/releases">prospector's releases</a>.</em></p>
<blockquote>
<h2>1.10.2</h2>
<p>1.10.1 was released to fix a packaging and installation problem (see <a href="https://redirect.github.com/PyCQA/pylint-plugin-utils/issues/29">this issue</a>)
for a description of the problem).</p>
<p>However this caused a separate regression in packaging and installation <a href="https://redirect.github.com/PyCQA/prospector/issues/618">#618</a></p>
<p>1.10.2 fixes both issues</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/PyCQA/prospector/blob/master/CHANGELOG.rst">prospector's changelog</a>.</em></p>
<blockquote>
<h2>Version 1.10.1 and 1.10.2</h2>
<p><strong>Bugfixes</strong>:</p>
<p>1.10.1 was released to fix a packaging and installation problem (see <code>this issue &lt;https://github.com/PyCQA/pylint-plugin-utils/issues/29</code>_
for a description of the problem).</p>
<p>However this caused a separate regression in packaging and installation <code>[#618](https://github.com/PyCQA/prospector/issues/618) &lt;https://github.com/PyCQA/prospector/issues/618&gt;</code>_</p>
<p>1.10.2 fixes both issues</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/PyCQA/prospector/commit/81efade2699199fe28c26e8fd1a8b6fc119b019a"><code>81efade</code></a> <a href="https://redirect.github.com/PyCQA/prospector/issues/618">#618</a> Fixing optional extras naming to use underscores again</li>
<li><a href="https://github.com/PyCQA/prospector/commit/dcf91ad90d0e41517d4d394b459bb767275b62a6"><code>dcf91ad</code></a> Bump requests from 2.30.0 to 2.31.0 (<a href="https://redirect.github.com/PyCQA/prospector/issues/617">#617</a>)</li>
<li><a href="https://github.com/PyCQA/prospector/commit/415633984daed2c8d3f6818f8c3545d5bcafff04"><code>4156339</code></a> [pre-commit.ci] pre-commit autoupdate (<a href="https://redirect.github.com/PyCQA/prospector/issues/616">#616</a>)</li>
<li><a href="https://github.com/PyCQA/prospector/commit/7ac60a5ea7c9b516b10a31162cdadd0919b13172"><code>7ac60a5</code></a> Bumping verison for bugfix release to stop polluting site-packages (see https...</li>
<li><a href="https://github.com/PyCQA/prospector/commit/f5988d1c53a5e0dbfaaa6bc93a7270d63929cd13"><code>f5988d1</code></a> Removing LICENSE from includes, as it will get installed into the site-packag...</li>
<li><a href="https://github.com/PyCQA/prospector/commit/ef733e5380900105f28d82b10fd6510bd41b2bd1"><code>ef733e5</code></a> Updating pre-commit docs to use latest version</li>
<li><a href="https://github.com/PyCQA/prospector/commit/53f07522c0e53de33c51464840d135f163b8f7dc"><code>53f0752</code></a> [pre-commit.ci] pre-commit autoupdate (<a href="https://redirect.github.com/PyCQA/prospector/issues/615">#615</a>)</li>
<li>See full diff in <a href="https://github.com/PyCQA/prospector/compare/1.10.0...1.10.2">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=prospector&package-manager=pip&previous-version=1.10.0&new-version=1.10.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/jazzband/django-axes/pull/1062?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jazzband) Report
> Merging [#1062](https://app.codecov.io/gh/jazzband/django-axes/pull/1062?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jazzband) (62fba7d) into [master](https://app.codecov.io/gh/jazzband/django-axes/commit/fcdeb4ae3f11724b99da54dfa61df90abcf54f7b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jazzband) (fcdeb4a) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##           master    #1062   +/-   ##
=======================================
  Coverage   94.52%   94.52%           
=======================================
  Files          34       34           
  Lines        1114     1114           
  Branches      203      203           
=======================================
  Hits         1053     1053           
  Misses         47       47           
  Partials       14       14           
```



:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jazzband)


Patch:
diff --git a/.github/ISSUE_TEMPLATES/bug_report.md b/.github/ISSUE_TEMPLATES/bug_report.md
new file mode 100644
index 0000000..261a642
--- /dev/null
+++ b/.github/ISSUE_TEMPLATES/bug_report.md
@@ -0,0 +1,32 @@
+---
+name: Bug report
+about: Create a report to help us improve django-axes
+title: 'BUG: Short description of the problem'
+labels: 'bug'
+assignees: ''
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. 
+2. 
+3. 
+4.
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Your environment**
+python version:
+django version:
+django-axes version:
+Operating system:
+
+**Additional context**
+Add any other context about the problem here.
+
+**Possible implementation**
+Not obligatory, but suggest an idea for implementing addition or change
diff --git a/.github/ISSUE_TEMPLATES/feature_request.md b/.github/ISSUE_TEMPLATES/feature_request.md
new file mode 100644
index 0000000..77f1005
--- /dev/null
+++ b/.github/ISSUE_TEMPLATES/feature_request.md
@@ -0,0 +1,19 @@
+---
+name: Feature request
+about: Suggest an idea for django-axes
+title: 'FEATURE REQUEST: Short description of requested feature'
+labels: 'feature request'
+assignees: ''
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..94a1759
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,19 @@
+# What does this PR do?
+
+<!--
+Congratulations! You've made it this far! You're not quite done yet though.
+
+Please replace this with a description of the change and which issue is fixed (if applicable). Please also include relevant motivation and context. List any dependencies (if any) that are required for this change.
+
+Once you're done, someone will review your PR shortly. They may suggest changes to make the code even better.
+-->
+
+<!-- Remove if not applicable -->
+
+Fixes # (issue)
+
+
+## Before submitting
+- [ ] This PR fixes a typo or improves the docs (you can dismiss the other checks if that's the case).
+- [ ] Did you make sure to update the documentation with your changes?
+- [ ] Did you write any new necessary tests?
diff --git a/axes/checks.py b/axes/checks.py
index 65fb79f..8b6b0f4 100644
--- a/axes/checks.py
+++ b/axes/checks.py
@@ -21,6 +21,7 @@ class Messages:
     )
     BACKEND_INVALID = "You do not have 'axes.backends.AxesStandaloneBackend' or a subclass in your settings.AUTHENTICATION_BACKENDS."
     SETTING_DEPRECATED = "You have a deprecated setting {deprecated_setting} configured in your project settings"
+    CALLABLE_INVALID = "{callable_setting} is not a valid callable."
 
 
 class Hints:
@@ -28,6 +29,7 @@ class Hints:
     MIDDLEWARE_INVALID = None
     BACKEND_INVALID = "AxesModelBackend was renamed to AxesStandaloneBackend in django-axes version 5.0."
     SETTING_DEPRECATED = None
+    CALLABLE_INVALID = None
 
 
 class Codes:
@@ -35,6 +37,7 @@ class Codes:
     MIDDLEWARE_INVALID = "axes.W002"
     BACKEND_INVALID = "axes.W003"
     SETTING_DEPRECATED = "axes.W004"
+    CALLABLE_INVALID = "axes.W005"
 
 
 @register(Tags.security, Tags.caches, Tags.compatibility)
@@ -153,3 +156,49 @@ def axes_deprecation_check(app_configs, **kwargs):  # pylint: disable=unused-arg
             pass
 
     return warnings
+
+
+@register
+def axes_conf_check(app_configs, **kwargs):  # pylint: disable=unused-argument
+    warnings = []
+
+    callable_settings = [
+        "AXES_CLIENT_IP_CALLABLE",
+        "AXES_CLIENT_STR_CALLABLE",
+        "AXES_LOCKOUT_CALLABLE",
+        "AXES_USERNAME_CALLABLE",
+        "AXES_WHITELIST_CALLABLE",
+        "AXES_COOLOFF_TIME",
+        "AXES_LOCKOUT_PARAMETERS",
+    ]
+
+    for callable_setting in callable_settings:
+        value = getattr(settings, callable_setting)
+        if not is_valid_callable(value):
+            warnings.append(
+                Warning(
+                    msg=Messages.CALLABLE_INVALID.format(
+                        callable_setting=callable_setting
+                    ),
+                    hint=Hints.CALLABLE_INVALID,
+                    id=Codes.CALLABLE_INVALID,
+                )
+            )
+
+    return warnings
+
+
+def is_valid_callable(value) -> bool:
+    if value is None:
+        return True
+
+    if callable(value):
+        return True
+
+    if isinstance(value, str):
+        try:
+            import_string(value)
+        except ImportError:
+            return False
+
+    return True
diff --git a/docs/2_installation.rst b/docs/2_installation.rst
index 21dc0dc..e1d6a15 100644
--- a/docs/2_installation.rst
+++ b/docs/2_installation.rst
@@ -120,7 +120,7 @@ To keep old behaviour with the new flag, configure the following:
 #. If you used ``settings.AXES_LOCK_OUT_BY_USER_OR_IP``, use ``settings.AXES_LOCKOUT_PARAMETERS = ["username", "ip_address"]``, and
 #. If you used ``settings.AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP``, use ``settings.AXES_LOCKOUT_PARAMETERS = [["username", "ip_address"]]``,
 #. If you used ``settings.AXES_USE_USER_AGENT``, add ``"user_agent"`` to your list(s) of lockout parameters.
-    #. ``settings.AXES_USE_USER_AGENT`` would become ``settings.AXES_LOCKOUT_PARAMETERS = ["ip_address", "user_agent"]``
+    #. ``settings.AXES_USE_USER_AGENT`` would become ``settings.AXES_LOCKOUT_PARAMETERS = [["ip_address", "user_agent"]]``
     #. ``settings.AXES_USE_USER_AGENT`` with ``settings.AXES_ONLY_USER_FAILURES`` would become ``settings.AXES_LOCKOUT_PARAMETERS = [["username", "user_agent"]]``
     #. ``settings.AXES_USE_USER_AGENT`` with ``settings.AXES_LOCK_OUT_BY_USER_OR_IP`` would become ``settings.AXES_LOCKOUT_PARAMETERS = [["ip_address", "user_agent"], "username"]``
     #. ``settings.AXES_USE_USER_AGENT`` with ``settings.AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP`` would become ``settings.AXES_LOCKOUT_PARAMETERS = [["ip_address", "user_agent", "username"]]``
diff --git a/requirements-qa.txt b/requirements-qa.txt
index 6db02a9..d4b73dc 100644
--- a/requirements-qa.txt
+++ b/requirements-qa.txt
@@ -1,4 +1,4 @@
 black==23.3.0
 mypy==1.3.0
-prospector==1.10.0
+prospector==1.10.2
 types-pkg_resources  # Type stub
diff --git a/requirements.txt b/requirements.txt
index 7c4c941..7e59721 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
 -e .
 -r requirements-qa.txt
 -r requirements-test.txt
-sphinx_rtd_theme==1.2.0
+sphinx_rtd_theme==1.2.2
 tox==4.5.1


Test patch:
diff --git a/requirements-test.txt b/requirements-test.txt
index fc04b11..085cc91 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -1,7 +1,7 @@
 -e .
 django-ipware>=3
 coverage==7.2.5
-pytest==7.3.1
+pytest==7.3.2
 pytest-cov==4.0.0
 pytest-django==4.5.2
 pytest-subtests==0.11.0
diff --git a/tests/test_checks.py b/tests/test_checks.py
index 1ccad7c..b9ed5f6 100644
--- a/tests/test_checks.py
+++ b/tests/test_checks.py
@@ -110,3 +110,22 @@ class DeprecatedSettingsTestCase(AxesTestCase):
     def test_deprecated_success_access_log_flag(self):
         warnings = run_checks()
         self.assertEqual(warnings, [self.disable_success_access_log_warning])
+
+
+class ConfCheckTestCase(AxesTestCase):
+    @override_settings(AXES_USERNAME_CALLABLE="module.not_defined")
+    def test_invalid_import_path(self):
+        warnings = run_checks()
+        warning = Warning(
+            msg=Messages.CALLABLE_INVALID.format(
+                callable_setting="AXES_USERNAME_CALLABLE"
+            ),
+            hint=Hints.CALLABLE_INVALID,
+            id=Codes.CALLABLE_INVALID,
+        )
+        self.assertEqual(warnings, [warning])
+
+    @override_settings(AXES_COOLOFF_TIME=lambda: 1)
+    def test_valid_callable(self):
+        warnings = run_checks()
+        self.assertEqual(warnings, [])