Code

Opened 5 years ago

Last modified 2 years ago

#11715 new Bug

If change mutable list "inlines" in one admin options then it will change "inlines" for all admin options.

Reported by: aiv Owned by: nobody
Component: contrib.admin Version: 1.1
Severity: Normal Keywords: inlines admin options
Cc: summer.is.gone@…, mjtamlyn Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: yes Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description

Now attribute "inlines" is mutable and declared in the body of ModelAdmin class:

class ModelAdmin(BaseModelAdmin):
    ...
    inlines = []
    ...

When we change this attribute in one sub-class, python will change it in all other sub-classes:

from django.db import models

class Author(models.Model):
   name = models.CharField(max_length=100)

class Book(models.Model):
   author = models.ForeignKey(Author)
   title = models.CharField(max_length=100)
from django.contrib import admin
from models import *

class BookInline(admin.TabularInline):
    model = Book

class AuthorAdmin(admin.ModelAdmin):
    def __init__(self, *args, **kwds):
        self.inlines.append(BookInline)
        return super(AuthorAdmin, self).__init__(*args, **kwds)

class BookAdmin(admin.ModelAdmin):
   pass

admin.site.register(Author, AuthorAdmin)
admin.site.register(Book, BookAdmin)

This code will add BookInline to BookAdmin, too!

So, when we will go to admin and try to change Book, we will see exception:

<class 'test.models.Book'> has no ForeignKey to <class 'test.models.Book'>

We can fix it like this:

--- django/contrib/admin/options.py
+++ django/contrib/admin/options.py
@@ -187,7 +187,7 @@ class ModelAdmin(BaseModelAdmin):
     save_as = False
     save_on_top = False
     ordering = None
-    inlines = []
+    inlines = None

     # Custom templates (designed to be over-ridden in subclasses)
     change_form_template = None
@@ -206,6 +206,8 @@ class ModelAdmin(BaseModelAdmin):
         self.opts = model._meta
         self.admin_site = admin_site
         self.inline_instances = []
+        if self.inlines is None:
+            self.inlines = []
         for inline_class in self.inlines:
             inline_instance = inline_class(self.model, self.admin_site)
             self.inline_instances.append(inline_instance)

--- django/contrib/admin/validation.py
+++ django/contrib/admin/validation.py
@@ -133,7 +133,7 @@ def validate(cls, model):


     # inlines = []
-    if hasattr(cls, 'inlines'):
+    if hasattr(cls, 'inlines') and cls.inlines is not None:
         check_isseq(cls, 'inlines', cls.inlines)
         for idx, inline in enumerate(cls.inlines):
             if not issubclass(inline, BaseModelAdmin):

But then our sample will raise:

'NoneType' object has no attribute 'append'

I think, we can do it like this:

--- django/contrib/admin/options.py
+++ django/contrib/admin/options.py
@@ -187,7 +187,7 @@ class ModelAdmin(BaseModelAdmin):
     save_as = False
     save_on_top = False
     ordering = None
-    inlines = []
+    inlines = ()

     # Custom templates (designed to be over-ridden in subclasses)
     change_form_template = None

It will fix logic with mutable attribute, but users will have to write:

 from django.contrib import admin
 from models import *

 class BookInline(admin.TabularInline):
     model = Book

 class AuthorAdmin(admin.ModelAdmin):
+    inlines = []
     def __init__(self, *args, **kwds):
         self.inlines.append(BookInline)
         return super(AuthorAdmin, self).__init__(*args, **kwds)

 class BookAdmin(admin.ModelAdmin):
    pass

 admin.site.register(Author, AuthorAdmin)
 admin.site.register(Book, BookAdmin)

I believe, this will be more correct variant.

Attachments (1)

patch.diff (330 bytes) - added by aiv 5 years ago.

Download all attachments as: .zip

Change History (5)

Changed 5 years ago by aiv

comment:1 Changed 5 years ago by summerisgone

  • Cc summer.is.gone@… added
  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset

comment:2 Changed 4 years ago by russellm

  • Needs tests set
  • Patch needs improvement set
  • Triage Stage changed from Unreviewed to Accepted

I'm not sure I agree with your solution, but the problem appears real.

comment:3 Changed 3 years ago by julien

  • Severity set to Normal
  • Type set to Bug

comment:4 Changed 2 years ago by mjtamlyn

  • Cc mjtamlyn added
  • Easy pickings unset
  • UI/UX unset

I agree with this being an issue - and a weird one to debug too!

I'd extend it to actions as well which would suffer from the same issue. Whilst it would potentially break some code to change it to a tuple, I would say this is more correct, an also consistent with the other attributes such as list_filter.

What exactly is the issue with the current implementation?

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as new
The owner will be changed from nobody to anonymous. Next status will be 'assigned'
as The resolution will be set. Next status will be 'closed'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.