Skip to content

Commit

Permalink
Merge pull request #45 from browniebroke/inline-has-add-permissions
Browse files Browse the repository at this point in the history
Add the `obj` argument to `InlineModelAdmin.has_add_permission()`
  • Loading branch information
browniebroke authored May 14, 2020
2 parents b6c5220 + 2832a22 commit 474005c
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 12 deletions.
18 changes: 10 additions & 8 deletions django_codemod/commands/django_codemod.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from .base import BaseCodemodCommand
from ..visitors.django_30 import RenderToResponseToRenderTransformer
from ..visitors.django_30 import (
RenderToResponseToRenderTransformer,
InlineHasAddPermissionsTransformer,
)
from ..visitors.django_40 import (
ForceTextToForceStrTransformer,
SmartTextToForceStrTransformer,
Expand All @@ -14,24 +17,23 @@

class Django30Command(BaseCodemodCommand):
"""
Resolve deprecations for removals in Django 3.0.
Combines all the other commands in this module, to fix these deprecations:
Resolve following deprecations:
- ``django.shortcuts.render_to_response``
- Replaces ``render_to_response()`` by ``render()`` and add ``request=None``
as the first argument of ``render()``.
- Add the ``obj`` argument to ``InlineModelAdmin.has_add_permission()``.
"""

DESCRIPTION: str = "Resolve deprecations for removals in Django 3.0."
transformers = [
RenderToResponseToRenderTransformer,
InlineHasAddPermissionsTransformer,
]


class Django40Command(BaseCodemodCommand):
"""
Resolve deprecations for removals in Django 4.0.
Combines all the other commands in this module, to fix these deprecations:
Resolve following deprecations:
- ``django.utils.encoding.force_text``
- ``django.utils.encoding.smart_text``
Expand Down
74 changes: 72 additions & 2 deletions django_codemod/visitors/django_30.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
# This is expected to cover most of the things listed in this section:
# https://docs.djangoproject.com/en/dev/internals/deprecation/#deprecation-removed-in-3-0
from typing import Sequence
from typing import Sequence, Union

from libcst import Call, Name, Arg
from libcst import (
Call,
Name,
Arg,
RemovalSentinel,
matchers as m,
ClassDef,
BaseStatement,
FunctionDef,
Param,
)
from libcst.codemod import ContextAwareTransformer

from .base import BaseSimpleFuncRenameTransformer

Expand All @@ -20,3 +31,62 @@ class RenderToResponseToRenderTransformer(BaseSimpleFuncRenameTransformer):

def update_call_args(self, node: Call) -> Sequence[Arg]:
return (Arg(value=Name("None")), *node.args)


class InlineHasAddPermissionsTransformer(ContextAwareTransformer):
"""Add the ``obj`` argument to ``InlineModelAdmin.has_add_permission()``."""

context_key = "InlineHasAddPermissionsTransformer"

def visit_ClassDef_bases(self, node: ClassDef) -> None:
if m.matches(
node,
m.ClassDef(
bases=(
m.OneOf(
m.Arg(
m.Attribute(
value=m.Name("admin"), attr=m.Name("TabularInline")
)
),
m.Arg(m.Name("TabularInline")),
m.Arg(
m.Attribute(
value=m.Name("admin"), attr=m.Name("StackedInline")
)
),
m.Arg(m.Name("StackedInline")),
),
)
),
):
self.context.scratch[self.context_key] = True
super().visit_ClassDef_bases(node)

def leave_ClassDef(
self, original_node: ClassDef, updated_node: ClassDef
) -> Union[BaseStatement, RemovalSentinel]:
self.context.scratch.pop(self.context_key, None)
return super().leave_ClassDef(original_node, updated_node)

@property
def _is_context_right(self):
return self.context.scratch.get(self.context_key, False)

def leave_FunctionDef(
self, original_node: FunctionDef, updated_node: FunctionDef
) -> Union[BaseStatement, RemovalSentinel]:
if (
m.matches(updated_node, m.FunctionDef(name=m.Name("has_add_permission")))
and self._is_context_right
):
if len(updated_node.params.params) == 2:
old_params = updated_node.params
updated_params = old_params.with_changes(
params=(
*old_params.params,
Param(name=Name("obj"), default=Name("None")),
)
)
return updated_node.with_changes(params=updated_params)
return super().leave_FunctionDef(original_node, updated_node)
2 changes: 1 addition & 1 deletion docs/codemods.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ This command should fix things `removed in Django 3.0`_.
Django 4.0
----------

These command should fix things `removed in Django 4.0`_.
This command should fix things `removed in Django 4.0`_.

.. _removed in Django 4.0: https://docs.djangoproject.com/en/dev/internals/deprecation/#deprecation-removed-in-4-0

Expand Down
118 changes: 117 additions & 1 deletion tests/visitors/test_django_30.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from django_codemod.visitors.django_30 import RenderToResponseToRenderTransformer
from django_codemod.visitors.django_30 import (
RenderToResponseToRenderTransformer,
InlineHasAddPermissionsTransformer,
)
from .base import BaseVisitorTest


Expand Down Expand Up @@ -48,3 +51,116 @@ def test_already_imported_substitution(self) -> None:
result = render(None, "index.html", context={})
"""
self.assertCodemod(before, after)


class TestInlineHasAddPermissionsTransformer(BaseVisitorTest):

transformer = InlineHasAddPermissionsTransformer

def test_noop(self) -> None:
"""Test no modification when base class doesn't match."""
before = """
class MyAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
return False
"""
after = """
class MyAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
return False
"""
self.assertCodemod(before, after)

def test_tabular_inline(self) -> None:
"""Test modification when base class is admin.TabularInline."""
before = """
class MyInlineInline(TabularInline):
def has_add_permission(self, request):
return False
"""
after = """
class MyInlineInline(TabularInline):
def has_add_permission(self, request, obj = None):
return False
"""
self.assertCodemod(before, after)

def test_admin_tabular_inline(self) -> None:
"""Test modification when base class is admin.TabularInline."""
before = """
class MyInlineInline(admin.TabularInline):
def has_add_permission(self, request):
return False
"""
after = """
class MyInlineInline(admin.TabularInline):
def has_add_permission(self, request, obj = None):
return False
"""
self.assertCodemod(before, after)

def test_stacked_inline(self) -> None:
"""Test modification when base class is StackedInline."""
before = """
class MyInlineInline(StackedInline):
def has_add_permission(self, request):
return False
"""
after = """
class MyInlineInline(StackedInline):
def has_add_permission(self, request, obj = None):
return False
"""
self.assertCodemod(before, after)

def test_admin_stacked_inline(self) -> None:
"""Test modification when base class is admin.StackedInline."""
before = """
class MyInlineInline(admin.StackedInline):
def has_add_permission(self, request):
return False
"""
after = """
class MyInlineInline(admin.StackedInline):
def has_add_permission(self, request, obj = None):
return False
"""
self.assertCodemod(before, after)

def test_context_cleared(self) -> None:
"""Test that context is cleared and doesn't leak to other classes."""
before = """
class MyInlineInline(admin.TabularInline):
def has_add_permission(self, request):
return False
class MyAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
return False
"""
after = """
class MyInlineInline(admin.TabularInline):
def has_add_permission(self, request, obj = None):
return False
class MyAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
return False
"""
self.assertCodemod(before, after)

0 comments on commit 474005c

Please sign in to comment.