﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
11715	"If change mutable list ""inlines"" in one admin options then it will change ""inlines"" for all admin options."	Alexander Ivanov	Jacob Walls	"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.
"	Cleanup/optimization	closed	contrib.admin	1.1	Normal	fixed	inlines admin options	summer.is.gone@… Marc Tamlyn	Ready for checkin	1	0	0	0	0	0
