Repository: jazzband/django-axes
Pull request #1075 (2023-06-12 12:58:06)

chore(deps): bump pytest from 7.3.1 to 7.3.2
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.3.1 to 7.3.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/pytest-dev/pytest/releases">pytest's releases</a>.</em></p>
<blockquote>
<h2>7.3.2</h2>
<h1>pytest 7.3.2 (2023-06-10)</h1>
<h2>Bug Fixes</h2>
<ul>
<li><a href="https://redirect.github.com/pytest-dev/pytest/issues/10169">#10169</a>: Fix bug where very long option names could cause pytest to break with <code>OSError: [Errno 36] File name too long</code> on some systems.</li>
<li><a href="https://redirect.github.com/pytest-dev/pytest/issues/10894">#10894</a>: Support for Python 3.12 (beta at the time of writing).</li>
<li><a href="https://redirect.github.com/pytest-dev/pytest/issues/10987">#10987</a>: <code>testpaths</code>{.interpreted-text role=&quot;confval&quot;} is now honored to load root <code>conftests</code>.</li>
<li><a href="https://redirect.github.com/pytest-dev/pytest/issues/10999">#10999</a>: The [monkeypatch]{.title-ref} [setitem]{.title-ref}/[delitem]{.title-ref} type annotations now allow [TypedDict]{.title-ref} arguments.</li>
<li><a href="https://redirect.github.com/pytest-dev/pytest/issues/11028">#11028</a>: Fixed bug in assertion rewriting where a variable assigned with the walrus operator could not be used later in a function call.</li>
<li><a href="https://redirect.github.com/pytest-dev/pytest/issues/11054">#11054</a>: Fixed <code>--last-failed</code>'s &quot;(skipped N files)&quot; functionality for files inside of packages (directories with [__init__.py]{.title-ref} files).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/pytest-dev/pytest/commit/9d47a39bdd85e6bef40a5674e5f0eded54e54d7e"><code>9d47a39</code></a> Prepare release version 7.3.2</li>
<li><a href="https://github.com/pytest-dev/pytest/commit/d66697ed9a34200f120a36378dbebfb18f81957d"><code>d66697e</code></a> Merge pull request <a href="https://redirect.github.com/pytest-dev/pytest/issues/11094">#11094</a> from pytest-dev/backport-10894-to-7.3.x</li>
<li><a href="https://github.com/pytest-dev/pytest/commit/8e1bbe1a943ea103957728053ec4764a00364331"><code>8e1bbe1</code></a> [7.3.x] Python 3.12 support</li>
<li><a href="https://github.com/pytest-dev/pytest/commit/d054a689316dbab4fabd02140f0bca42ac7447bc"><code>d054a68</code></a> Merge pull request <a href="https://redirect.github.com/pytest-dev/pytest/issues/11058">#11058</a> from pytest-dev/backport-11055-to-7.3.x</li>
<li><a href="https://github.com/pytest-dev/pytest/commit/30a112583e27c7c2c06ccde5afd952a0306837ce"><code>30a1125</code></a> [7.3.x] cacheprovider: fix file-skipping feature for files in packages</li>
<li><a href="https://github.com/pytest-dev/pytest/commit/682fc81781400b8e1591d77ff41cb75b4b594fe8"><code>682fc81</code></a> Merge pull request <a href="https://redirect.github.com/pytest-dev/pytest/issues/11057">#11057</a> from pytest-dev/backport-11041-to-7.3.x</li>
<li><a href="https://github.com/pytest-dev/pytest/commit/331bc1be4634cf95fdb33021d4355c35723b73fe"><code>331bc1b</code></a> [7.3.x] 11028 - Fix warlus operator behavior when called by a function</li>
<li><a href="https://github.com/pytest-dev/pytest/commit/69689c6eb55188980d055e280ceca060a9be634b"><code>69689c6</code></a> [7.3.x] nonpython example now repr all exceptions (<a href="https://redirect.github.com/pytest-dev/pytest/issues/11034">#11034</a>)</li>
<li><a href="https://github.com/pytest-dev/pytest/commit/6c95cb607d87f493d308b98ad21a28ac9821105e"><code>6c95cb6</code></a> [7.3.x] Spelling and grammar fixes (<a href="https://redirect.github.com/pytest-dev/pytest/issues/11015">#11015</a>)</li>
<li><a href="https://github.com/pytest-dev/pytest/commit/efcb81c4927573ef624c0fb723643f10b69cbff9"><code>efcb81c</code></a> [7.3.x] Reference &quot;Status of Python Versions&quot; in backwards-compatibility poli...</li>
<li>Additional commits viewable in <a href="https://github.com/pytest-dev/pytest/compare/7.3.1...7.3.2">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=pytest&package-manager=pip&previous-version=7.3.1&new-version=7.3.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/1075?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jazzband) Report
> Merging [#1075](https://app.codecov.io/gh/jazzband/django-axes/pull/1075?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jazzband) (b8a1664) 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    #1075   +/-   ##
=======================================
  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"]]``


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, [])