diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cf8acc98..36740558 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,10 @@ Changelog ========= +* Add Django 1.10+ fixer to rewrite ``request.user`` functions that changed to boolean attributes: ``is_authenticated`` and ``is_anonymous``. + + Thanks to Alessandro Ferrini in `PR #423 `__. + 1.15.0 (2023-09-24) ------------------- diff --git a/README.rst b/README.rst index 5fe30408..224f8cfc 100644 --- a/README.rst +++ b/README.rst @@ -306,6 +306,19 @@ Django 1.10 `Release Notes `__ +``request.user`` boolean attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Rewrites calls to ``request.user.is_authenticated()`` and ``request.user.is_anonymous()`` to remove the parentheses, per `the deprecation `__. + +.. code-block:: diff + + -request.user.is_authenticated() + +request.user.is_authenticated + + -self.request.user.is_anonymous() + +self.request.user.is_anonymous + Compatibility imports ~~~~~~~~~~~~~~~~~~~~~ @@ -321,7 +334,6 @@ Whilst mentioned in the `Django 2.1 release notes Iterable[tuple[Offset, TokenFunc]]: + if ( + isinstance(node.func, ast.Attribute) + and node.func.attr in ("is_anonymous", "is_authenticated") + and len(node.args) == 0 + and is_request_or_self_request_user(node.func.value) + ): + yield ( + ast_start_offset(node), + partial(rewrite_user_attribute, attr=node.func.attr), + ) + + +def is_request_or_self_request_user(node: ast.AST) -> bool: + return ( + isinstance(node, ast.Attribute) + and node.attr == "user" + and ( + (isinstance(node.value, ast.Name) and node.value.id == "request") + or ( + isinstance(node.value, ast.Attribute) + and isinstance(node.value.value, ast.Name) + and node.value.value.id == "self" + and node.value.attr == "request" + ) + ) + ) + + +def rewrite_user_attribute(tokens: list[Token], i: int, *, attr: str) -> None: + j = find(tokens, i, name=NAME, src=attr) + y = find(tokens, j, name=OP, src="(") + z = find(tokens, y, name=OP, src=")") + del tokens[y : z + 1] diff --git a/tests/fixers/test_request_user_attributes.py b/tests/fixers/test_request_user_attributes.py new file mode 100644 index 00000000..adfca96a --- /dev/null +++ b/tests/fixers/test_request_user_attributes.py @@ -0,0 +1,213 @@ +from __future__ import annotations + +from django_upgrade.data import Settings +from tests.fixers.tools import check_noop +from tests.fixers.tools import check_transformed + +settings = Settings(target_version=(1, 10)) + + +def test_not_request(): + check_noop( + """\ + user.is_authenticated() + """, + settings, + ) + + +def test_not_self_request(): + check_noop( + """\ + self.user.is_authenticated() + """, + settings, + ) + + +def test_not_user(): + check_noop( + """\ + request.is_authenticated() + """, + settings, + ) + + +def test_not_self_user(): + check_noop( + """\ + self.request.is_authenticated() + """, + settings, + ) + + +def test_request_user_is_anonymous_simple(): + check_transformed( + """\ + request.user.is_anonymous() + """, + """\ + request.user.is_anonymous + """, + settings, + ) + + +def test_request_user_is_authenticated_simple(): + check_transformed( + """\ + request.user.is_authenticated() + """, + """\ + request.user.is_authenticated + """, + settings, + ) + + +def test_self_request_user_is_anonymous_simple(): + check_transformed( + """\ + self.request.user.is_anonymous() + """, + """\ + self.request.user.is_anonymous + """, + settings, + ) + + +def test_self_request_user_is_authenticated_simple(): + check_transformed( + """\ + self.request.user.is_authenticated() + """, + """\ + self.request.user.is_authenticated + """, + settings, + ) + + +def test_if_request_user_is_anonymous(): + check_transformed( + """\ + if request.user.is_anonymous(): + ... + """, + """\ + if request.user.is_anonymous: + ... + """, + settings, + ) + + +def test_if_request_user_is_authenticated(): + check_transformed( + """\ + if request.user.is_authenticated(): + ... + """, + """\ + if request.user.is_authenticated: + ... + """, + settings, + ) + + +def test_if_self_request_user_is_anonymous(): + check_transformed( + """\ + if self.request.user.is_anonymous(): + ... + """, + """\ + if self.request.user.is_anonymous: + ... + """, + settings, + ) + + +def test_if_self_request_user_is_authenticated(): + check_transformed( + """\ + if self.request.user.is_authenticated(): + ... + """, + """\ + if self.request.user.is_authenticated: + ... + """, + settings, + ) + + +def test_spaces_between_noop(): + check_noop( + "request . user . is_authenticated ", + settings, + ) + + +def test_spaces_between(): + check_transformed( + "request . user . is_authenticated ( )", + "request . user . is_authenticated ", + settings, + ) + + +def test_comment_between(): + check_transformed( + """\ + request.user.is_anonymous( # something + ) + """, + """\ + request.user.is_anonymous + """, + settings, + ) + + +def test_spaces_and_comments_noop(): + check_noop( + """\ + if ( + request + .user + .is_authenticated # bla + ): + ... + """, + settings, + ) + + +def test_spaces_and_comments(): + check_transformed( + """\ + if ( + request + .user + .is_authenticated # bla + () # bla + ): + ... + """, + """\ + if ( + request + .user + .is_authenticated # bla + # bla + ): + ... + """, + settings, + )