models.py:
{{{
from django.db import models
class Entry(models.Model):
slug = models.SlugField(max_length=80, unique=True)
title = models.CharField(max_length=255, blank=True, null=True)
class Meta:
verbose_name_plural = 'Entries'
class EntryDetail(models.Model):
entry = models.OneToOneField(
Entry,
to_field='slug',
primary_key=True,
on_delete=models.CASCADE
)
description = models.TextField(blank=True, null=True)
class AnotherEntryDetail(models.Model):
entry = models.OneToOneField(
Entry,
primary_key=True,
on_delete=models.CASCADE
)
otherdescription = models.TextField(blank=True, null=True)
}}}
admin.py
{{{
from django.contrib import admin
from .models import Entry, EntryDetail, AnotherEntryDetail
class EntryDetailInline(admin.StackedInline):
model = EntryDetail
class AnotherEntryDetailInline(admin.StackedInline):
model = AnotherEntryDetail
@admin.register(Entry)
class EntryAdmin(admin.ModelAdmin):
inlines = [
EntryDetailInline,
AnotherEntryDetailInline
]
def get_readonly_fields(self, request, obj=None):
# Even with EntryDetail.entry_id ON UPDATE: CASCADE set on DB-
level
# changing Entry.slug through the admin-change-form with
EntryDetail-inline fails.
# Set slug readonly as workaround. Don't know if this is worth
fixing.
readonly_fields = super().get_readonly_fields(request, obj)
if hasattr(obj, 'entrydetail') and 'slug' not in readonly_fields:
readonly_fields += ('slug',)
return readonly_fields
}}}
**Expected behavior:**
In the admin:
* add an Entry
* fill slug and "Another entry detail" description
* save
* (change "Another entry detail" description)
* **save**
**Steps to reproduce the bug:**
In the admin:
* add an Entry
* fill slug and "Entry detail" description
* save
* (change "Entry detail" description)
* **save**
* Error message in admin: "Please correct the error below."
**Workaround:**
in models.py replace
{{{
class EntryDetailInline(admin.StackedInline):
model = EntryDetail
}}}
with:
{{{
from django.forms import BaseInlineFormSet
class EntryDetailFormSet(BaseInlineFormSet):
def add_fields(self, form, index):
super().add_fields(form, index)
related_name = self._pk_field.remote_field.related_name or
self._pk_field.remote_field.name
if hasattr(self.instance, related_name):
form.fields[self._pk_field.name].to_field =
self._pk_field.remote_field.field_name
class EntryDetailInline(admin.StackedInline):
model = EntryDetail
formset = EntryDetailFormSet
}}}
''Sorry for the confusing ticket-title, i couldn't come up with something
better.''
--
Ticket URL: <https://code.djangoproject.com/ticket/29981>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* stage: Unreviewed => Accepted
Old description:
New description:
**Code sample:**
models.py:
{{{
from django.db import models
class Entry(models.Model):
slug = models.SlugField(max_length=80, unique=True)
title = models.CharField(max_length=255, blank=True, null=True)
class EntryDetail(models.Model):
entry = models.OneToOneField(
Entry,
to_field='slug',
primary_key=True,
on_delete=models.CASCADE
)
description = models.TextField(blank=True, null=True)
}}}
admin.py
{{{
from django.contrib import admin
from .models import Entry, EntryDetail
class EntryDetailInline(admin.StackedInline):
model = EntryDetail
@admin.register(Entry)
class EntryAdmin(admin.ModelAdmin):
inlines = [EntryDetailInline]
def get_readonly_fields(self, request, obj=None):
# Even with EntryDetail.entry_id ON UPDATE: CASCADE set on DB-
level
# changing Entry.slug through the admin-change-form with
EntryDetail-inline fails.
# Set slug readonly as workaround. Don't know if this is worth
fixing.
readonly_fields = super().get_readonly_fields(request, obj)
if hasattr(obj, 'entrydetail') and 'slug' not in readonly_fields:
readonly_fields += ('slug',)
return readonly_fields
}}}
**Steps to reproduce the bug:**
In the admin:
* add an Entry
* fill slug, title, and "Entry detail" description
* save and continue editing
* change "Entry detail" description
* save
* Error message in admin: "Please correct the error below." (with no
errors listed). `formset[0].errors` is `[{'entry': ['The inline value did
not match the parent instance.']}]` which comes from
`InlineForeignKeyField` value is `aaa` and `orig` is `1`.
**Workaround:**
in models.py replace
{{{
class EntryDetailInline(admin.StackedInline):
model = EntryDetail
}}}
with:
{{{
from django.forms import BaseInlineFormSet
class EntryDetailFormSet(BaseInlineFormSet):
def add_fields(self, form, index):
super().add_fields(form, index)
related_name = self._pk_field.remote_field.related_name or
self._pk_field.remote_field.name
if hasattr(self.instance, related_name):
form.fields[self._pk_field.name].to_field =
self._pk_field.remote_field.field_name
class EntryDetailInline(admin.StackedInline):
model = EntryDetail
formset = EntryDetailFormSet
}}}
--
Comment:
I reproduced the issue on master
(78fc64578a8715b9812075bbebc829c1251c07fa). I removed
`AnotherEntryDetailInline` from the description as it doesn't seem
required to reproduce the issue.
--
Ticket URL: <https://code.djangoproject.com/ticket/29981#comment:1>
* cc: Sergey Fedoseev (added)
--
Ticket URL: <https://code.djangoproject.com/ticket/29981#comment:2>
* owner: nobody => Patrik Sletmo
* status: new => assigned
Comment:
I've managed to reproduce the issue and will start to look for a solution.
--
Ticket URL: <https://code.djangoproject.com/ticket/29981#comment:3>
Comment (by Bernie):
Replying to [comment:3 Patrik Sletmo]:
> I've managed to reproduce the issue and will start to look for a
solution.
Start looking at django.forms.models.BaseInlineFormSet.add_fields:
{{{
form.fields[name] = InlineForeignKeyField(self.instance, **kwargs)
}}}
When there is an existing inline-instance, InlineForeignKeyField needs to
know about the to_field.
--
Ticket URL: <https://code.djangoproject.com/ticket/29981#comment:4>
Comment (by Patrik Sletmo):
Thanks Bernie!
There is currently some code for correctly setting the to_field
[https://github.com/django/django/blob/ef87b38ef7b07a5a9b4ee424a04a9811836dee39/django/forms/models.py#L961
here], but it appears that this only works for foreign key fields when
they are not also the primary key of the contained model. The code linked
above was introduced 10 years ago in
[https://github.com/django/django/commit/a00be663112bc674f71492717f7bbbb35b922e42
this commit] and I'm beginning to wonder if the placement was incorrect
back then.
I've tried moving the linked if-statement outside of its surrounding
block, and this seems to fix the issue while not breaking any existing
test cases. I'll look through the code a bit more to see if this change
introduces any issue or if there exist a better way of doing this. If this
turns out to be a reasonable solution I'll make sure to submit a patch
tomorrow.
--
Ticket URL: <https://code.djangoproject.com/ticket/29981#comment:5>
* has_patch: 0 => 1
Comment:
It has been a busy week so I haven't really been able to spend any time on
this until now. I have submitted a patch with a fix for the issue together
with a regression test.
--
Ticket URL: <https://code.djangoproject.com/ticket/29981#comment:6>
* status: assigned => closed
* resolution: => fixed
Comment:
In [changeset:"14e2b1b065085c1d2d3e94ebaeefe25e12595a00" 14e2b1b0]:
{{{
#!CommitTicketReference repository=""
revision="14e2b1b065085c1d2d3e94ebaeefe25e12595a00"
Fixed #29981 -- Fixed inline formsets with a OnetoOneField primary key
that uses to_field.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/29981#comment:7>