Opened 16 years ago
Closed 4 years ago
#11715 closed Cleanup/optimization (fixed)
If change mutable list "inlines" in one admin options then it will change "inlines" for all admin options.
| Reported by: | Alexander Ivanov | Owned by: | Jacob Walls |
|---|---|---|---|
| Component: | contrib.admin | Version: | 1.1 |
| Severity: | Normal | Keywords: | inlines admin options |
| Cc: | summer.is.gone@…, Marc Tamlyn | Triage Stage: | Ready for checkin |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| 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)
Change History (9)
by , 16 years ago
| Attachment: | patch.diff added |
|---|
comment:1 by , 16 years ago
| Cc: | added |
|---|
comment:2 by , 16 years ago
| Needs tests: | set |
|---|---|
| Patch needs improvement: | set |
| Triage Stage: | Unreviewed → Accepted |
comment:3 by , 15 years ago
| Severity: | → Normal |
|---|---|
| Type: | → Bug |
comment:4 by , 14 years ago
| Cc: | 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?
comment:5 by , 4 years ago
| Owner: | changed from to |
|---|---|
| Patch needs improvement: | unset |
| Status: | new → assigned |
| Type: | Bug → Cleanup/optimization |
This issue predates the system check framework. Trying the original example (I created a new class in the admin_inlines test suite) now catches errors before failing at the db layer:
<class 'admin_inlines.admin.FootNoteAuditInline'>: (admin.E202) 'admin_inlines.FootNoteAudit' has no ForeignKey to 'admin_inlines.ShowInlineParent'.
What exactly is the issue with the current implementation?
I think if there is one, it's that folks might believe they can mutate the inlines on instances of ModelAdmin, but that's user error if they're not willing to accept it applying to all model admins.
PR clarifies the intended immutability of the inlines attribute of ModelAdmin by defaulting to (), but we could also just close as user error.
comment:6 by , 4 years ago
| Needs tests: | unset |
|---|
comment:7 by , 4 years ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
I'm not sure I agree with your solution, but the problem appears real.