Repository: browniebroke/django-codemod

Issue:
Django 3.0: update super call to InlineModelAdmin.has_add_permissions()
Currently, the transformer for `InlineModelAdmin.has_add_permissions()` updates method definition in subclasses of `InlineModelAdmin`, however if the implementation calls the base method, the call isn't updated.

**Example of input code:**

```python
from django.contrib import admin


class MyInlineInline(admin.TabularInline):
	def has_add_permission(self, request):
		if condition:
			return False
		return super().has_add_permission(request)
```

**Current output:**

```python
from django.contrib import admin


class MyInlineInline(admin.TabularInline):
	def has_add_permission(self, request, obj=None):
		if condition:
			return False
		return super().has_add_permission(request)
```

**Expected output:** 

```python
from django.contrib import admin


class MyInlineInline(admin.TabularInline):
	def has_add_permission(self, request, obj=None):
		if condition:
			return False
		return super().has_add_permission(request, obj=obj)
```

Notice the last line in each snippet, should add the keyword argument `obj=obj`.


Discussion:

Resolving patch:
diff --git a/django_codemod/visitors/admin.py b/django_codemod/visitors/admin.py
--- a/django_codemod/visitors/admin.py
+++ b/django_codemod/visitors/admin.py
@@ -1,8 +1,11 @@
 from typing import Union
 
 from libcst import (
+    Arg,
+    BaseExpression,
     BaseSmallStatement,
     BaseStatement,
+    Call,
     ClassDef,
     FunctionDef,
     ImportFrom,
@@ -115,3 +118,24 @@ def leave_FunctionDef(
             )
             return updated_node.with_changes(params=updated_params)
         return super().leave_FunctionDef(original_node, updated_node)
+
+    def leave_Call(self, original_node: Call, updated_node: Call) -> BaseExpression:
+        if (
+            self.is_visiting_subclass
+            and m.matches(
+                updated_node,
+                m.Call(
+                    func=m.Attribute(
+                        attr=m.Name("has_add_permission"),
+                        value=m.Call(func=m.Name("super")),
+                    )
+                ),
+            )
+            and len(updated_node.args) < 2
+        ):
+            updated_args = (
+                *updated_node.args,
+                Arg(keyword=Name("obj"), value=Name("obj")),
+            )
+            return updated_node.with_changes(args=updated_args)
+        return super().leave_Call(original_node, updated_node)


Test patch:
diff --git a/tests/visitors/test_admin.py b/tests/visitors/test_admin.py
--- a/tests/visitors/test_admin.py
+++ b/tests/visitors/test_admin.py
@@ -16,7 +16,9 @@ def test_model_admin_base_class(self) -> None:
             class MyAdmin(admin.ModelAdmin):
 
                 def has_add_permission(self, request):
-                    return False
+                    if somethings:
+                        return False
+                    return super().has_add_permission(request)
         """
         after = """
             from django.contrib import admin
@@ -24,7 +26,9 @@ def has_add_permission(self, request):
             class MyAdmin(admin.ModelAdmin):
 
                 def has_add_permission(self, request):
-                    return False
+                    if somethings:
+                        return False
+                    return super().has_add_permission(request)
         """
         self.assertCodemod(before, after)
 
@@ -36,7 +40,9 @@ def test_no_base_class(self) -> None:
             class MyCustomStuff:
 
                 def has_add_permission(self, request):
-                    return False
+                    if somethings:
+                        return False
+                    return super().has_add_permission(request)
         """
         after = """
             from django.contrib import admin
@@ -44,7 +50,9 @@ def has_add_permission(self, request):
             class MyCustomStuff:
 
                 def has_add_permission(self, request):
-                    return False
+                    if somethings:
+                        return False
+                    return super().has_add_permission(request)
         """
         self.assertCodemod(before, after)
 
@@ -64,7 +72,9 @@ def test_simple_substitution(self, import_, base_class) -> None:
             class MyInlineInline({base_class}):
 
                 def has_add_permission(self, request):
-                    return False
+                    if somethings:
+                        return False
+                    return super().has_add_permission(request)
         """
         after = f"""
             from django.contrib{import_}
@@ -72,7 +82,9 @@ def has_add_permission(self, request):
             class MyInlineInline({base_class}):
 
                 def has_add_permission(self, request, obj = None):
-                    return False
+                    if somethings:
+                        return False
+                    return super().has_add_permission(request, obj = obj)
         """
         self.assertCodemod(before, after)
 
@@ -92,7 +104,9 @@ def test_multiple_base_classes_last(self, import_, base_class) -> None:
             class MyInlineInline(InlineMixin, {base_class}):
 
                 def has_add_permission(self, request):
-                    return False
+                    if somethings:
+                        return False
+                    return super().has_add_permission(request)
         """
         after = f"""
             from django.contrib{import_}
@@ -100,7 +114,9 @@ def has_add_permission(self, request):
             class MyInlineInline(InlineMixin, {base_class}):
 
                 def has_add_permission(self, request, obj = None):
-                    return False
+                    if somethings:
+                        return False
+                    return super().has_add_permission(request, obj = obj)
         """
         self.assertCodemod(before, after)
 
@@ -120,7 +136,9 @@ def test_multiple_base_classes_first(self, import_, base_class) -> None:
             class MyInlineInline({base_class}, OtherBase):
 
                 def has_add_permission(self, request):
-                    return False
+                    if somethings:
+                        return False
+                    return super().has_add_permission(request)
         """
         after = f"""
             from django.contrib{import_}
@@ -128,19 +146,24 @@ def has_add_permission(self, request):
             class MyInlineInline({base_class}, OtherBase):
 
                 def has_add_permission(self, request, obj = None):
-                    return False
+                    if somethings:
+                        return False
+                    return super().has_add_permission(request, obj = obj)
         """
         self.assertCodemod(before, after)
 
-    def test_context_cleared(self) -> None:
+    @parameterized.expand([("admin.TabularInline"), ("admin.StackedInline")])
+    def test_context_cleared(self, base_class) -> None:
         """Test that context is cleared and doesn't leak to other classes."""
-        before = """
+        before = f"""
             from django.contrib import admin
 
-            class MyInlineInline(admin.TabularInline):
+            class MyInlineInline({base_class}):
 
                 def has_add_permission(self, request):
-                    return False
+                    if somethings:
+                        return False
+                    return super().has_add_permission(request)
 
 
             class MyAdmin(admin.ModelAdmin):
@@ -148,13 +171,15 @@ class MyAdmin(admin.ModelAdmin):
                 def has_add_permission(self, request):
                     return False
         """
-        after = """
+        after = f"""
             from django.contrib import admin
 
-            class MyInlineInline(admin.TabularInline):
+            class MyInlineInline({base_class}):
 
                 def has_add_permission(self, request, obj = None):
-                    return False
+                    if somethings:
+                        return False
+                    return super().has_add_permission(request, obj = obj)
 
 
             class MyAdmin(admin.ModelAdmin):