Kaydet (Commit) 3c8568a7 authored tarafından Russell Keith-Magee's avatar Russell Keith-Magee

Fixed #10271, #10281 -- Fixed the handling multiple inline models that share a…

Fixed #10271, #10281 --  Fixed the handling multiple inline models that share a common base class and have the link to the inline parent on the base class. Includes modifications that allow the equivalent handling for GenericFields. Thanks to Idan Gazit, Antti Kaihola (akaihola), and Alex Gaynor for their work on this patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@10017 bcc190cf-cafb-0310-a4f2-bffc1f526a37
üst d0fff8cc
......@@ -161,6 +161,7 @@ answer newbie questions, and generally made Django that much better:
Marc Garcia <marc.garcia@accopensys.com>
Alex Gaynor <alex.gaynor@gmail.com>
Andy Gayton <andy-django@thecablelounge.com>
Idan Gazit
Baishampayan Ghose
Dimitris Glezos <dimitris@glezos.com>
glin@seznam.cz
......
......@@ -522,10 +522,16 @@ class ModelAdmin(BaseModelAdmin):
else:
form_validated = False
new_object = self.model()
prefixes = {}
for FormSet in self.get_formsets(request):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(data=request.POST, files=request.FILES,
instance=new_object,
save_as_new=request.POST.has_key("_saveasnew"))
save_as_new=request.POST.has_key("_saveasnew"),
prefix=prefix)
formsets.append(formset)
if all_valid(formsets) and form_validated:
self.save_model(request, new_object, form, change=False)
......@@ -547,8 +553,13 @@ class ModelAdmin(BaseModelAdmin):
if isinstance(f, models.ManyToManyField):
initial[k] = initial[k].split(",")
form = ModelForm(initial=initial)
prefixes = {}
for FormSet in self.get_formsets(request):
formset = FormSet(instance=self.model())
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(instance=self.model(), prefix=prefix)
formsets.append(formset)
adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields)
......@@ -608,9 +619,14 @@ class ModelAdmin(BaseModelAdmin):
else:
form_validated = False
new_object = obj
prefixes = {}
for FormSet in self.get_formsets(request, new_object):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(request.POST, request.FILES,
instance=new_object)
instance=new_object, prefix=prefix)
formsets.append(formset)
if all_valid(formsets) and form_validated:
......@@ -625,8 +641,13 @@ class ModelAdmin(BaseModelAdmin):
else:
form = ModelForm(instance=obj)
prefixes = {}
for FormSet in self.get_formsets(request, obj):
formset = FormSet(instance=obj)
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(instance=obj, prefix=prefix)
formsets.append(formset)
adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
......
......@@ -291,7 +291,7 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
ct_field_name = "content_type"
ct_fk_field_name = "object_id"
def __init__(self, data=None, files=None, instance=None, save_as_new=None):
def __init__(self, data=None, files=None, instance=None, save_as_new=None, prefix=None):
opts = self.model._meta
self.instance = instance
self.rel_name = '-'.join((
......@@ -300,9 +300,17 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
))
super(BaseGenericInlineFormSet, self).__init__(
queryset=self.get_queryset(), data=data, files=files,
prefix=self.rel_name
prefix=prefix
)
#@classmethod
def get_default_prefix(cls):
opts = cls.model._meta
return '-'.join((opts.app_label, opts.object_name.lower(),
cls.ct_field.name, cls.ct_fk_field.name,
))
get_default_prefix = classmethod(get_default_prefix)
def get_queryset(self):
# Avoid a circular import.
from django.contrib.contenttypes.models import ContentType
......
......@@ -32,7 +32,7 @@ class BaseFormSet(StrAndUnicode):
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList):
self.is_bound = data is not None or files is not None
self.prefix = prefix or 'form'
self.prefix = prefix or self.get_default_prefix()
self.auto_id = auto_id
self.data = data
self.files = files
......@@ -62,7 +62,7 @@ class BaseFormSet(StrAndUnicode):
initial = {TOTAL_FORM_COUNT: self._total_form_count,
INITIAL_FORM_COUNT: self._initial_form_count}
self.management_form = ManagementForm(initial=initial, auto_id=self.auto_id, prefix=self.prefix)
# construct the forms in the formset
self._construct_forms()
......@@ -74,7 +74,7 @@ class BaseFormSet(StrAndUnicode):
self.forms = []
for i in xrange(self._total_form_count):
self.forms.append(self._construct_form(i))
def _construct_form(self, i, **kwargs):
"""
Instantiates and returns the i-th form instance in a formset.
......@@ -118,7 +118,7 @@ class BaseFormSet(StrAndUnicode):
def _get_deleted_forms(self):
"""
Returns a list of forms that have been marked for deletion. Raises an
Returns a list of forms that have been marked for deletion. Raises an
AttributeError if deletion is not allowed.
"""
if not self.is_valid() or not self.can_delete:
......@@ -176,6 +176,11 @@ class BaseFormSet(StrAndUnicode):
return [self.forms[i[0]] for i in self._ordering]
ordered_forms = property(_get_ordered_forms)
#@classmethod
def get_default_prefix(cls):
return 'form'
get_default_prefix = classmethod(get_default_prefix)
def non_form_errors(self):
"""
Returns an ErrorList of errors that aren't associated with a particular
......
......@@ -249,8 +249,8 @@ class BaseModelForm(BaseForm):
# This is an extra field that's not on the ModelForm, ignore it
continue
if not isinstance(f, ModelField):
# This is an extra field that happens to have a name that matches,
# for example, a related object accessor for this model. So
# This is an extra field that happens to have a name that matches,
# for example, a related object accessor for this model. So
# get_field_by_name found it, but it is not a Field so do not proceed
# to use it as if it were.
continue
......@@ -472,7 +472,7 @@ class BaseInlineFormSet(BaseModelFormSet):
# is there a better way to get the object descriptor?
self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name()
qs = self.model._default_manager.filter(**{self.fk.name: self.instance})
super(BaseInlineFormSet, self).__init__(data, files, prefix=prefix or self.rel_name,
super(BaseInlineFormSet, self).__init__(data, files, prefix=prefix,
queryset=qs)
def _construct_forms(self):
......@@ -489,6 +489,12 @@ class BaseInlineFormSet(BaseModelFormSet):
form.data[form.add_prefix(self._pk_field.name)] = None
return form
#@classmethod
def get_default_prefix(cls):
from django.db.models.fields.related import RelatedObject
return RelatedObject(cls.fk.rel.to, cls.model, cls.fk).get_accessor_name()
get_default_prefix = classmethod(get_default_prefix)
def save_new(self, form, commit=True):
fk_attname = self.fk.get_attname()
kwargs = {fk_attname: self.instance.pk}
......
......@@ -238,4 +238,9 @@ __test__ = {'API_TESTS':"""
<p><label for="id_generic_relations-taggeditem-content_type-object_id-1-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-1-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-1-tag" maxlength="50" /></p>
<p><label for="id_generic_relations-taggeditem-content_type-object_id-1-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-1-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-1-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-1-id" id="id_generic_relations-taggeditem-content_type-object_id-1-id" /></p>
>>> formset = GenericFormSet(instance=lion, prefix='x')
>>> for form in formset.forms:
... print form.as_p()
<p><label for="id_x-0-tag">Tag:</label> <input id="id_x-0-tag" type="text" name="x-0-tag" maxlength="50" /></p>
<p><label for="id_x-0-DELETE">Delete:</label> <input type="checkbox" name="x-0-DELETE" id="id_x-0-DELETE" /><input type="hidden" name="x-0-id" id="id_x-0-id" /></p>
"""}
......@@ -20,7 +20,7 @@ class Article(models.Model):
def __unicode__(self):
return self.title
def model_year(self):
return self.date.year
model_year.admin_order_field = 'date'
......@@ -54,14 +54,14 @@ class Chapter(models.Model):
class ChapterXtra1(models.Model):
chap = models.OneToOneField(Chapter, verbose_name=u'¿Chap?')
xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?')
xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?')
def __unicode__(self):
return u'¿Xtra1: %s' % self.xtra
class ChapterXtra2(models.Model):
chap = models.OneToOneField(Chapter, verbose_name=u'¿Chap?')
xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?')
xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?')
def __unicode__(self):
return u'¿Xtra2: %s' % self.xtra
......@@ -87,7 +87,7 @@ class ArticleAdmin(admin.ModelAdmin):
'extra_var': 'Hello!'
}
)
def modeladmin_year(self, obj):
return obj.date.year
modeladmin_year.admin_order_field = 'date'
......@@ -121,7 +121,7 @@ class ModelWithStringPrimaryKey(models.Model):
class Color(models.Model):
value = models.CharField(max_length=10)
warm = models.BooleanField()
warm = models.BooleanField()
def __unicode__(self):
return self.value
......@@ -134,12 +134,56 @@ class Thing(models.Model):
class ThingAdmin(admin.ModelAdmin):
list_filter = ('color',)
class Persona(models.Model):
"""
A simple persona associated with accounts, to test inlining of related
accounts which inherit from a common accounts class.
"""
name = models.CharField(blank=False, max_length=80)
def __unicode__(self):
return self.name
class Account(models.Model):
"""
A simple, generic account encapsulating the information shared by all
types of accounts.
"""
username = models.CharField(blank=False, max_length=80)
persona = models.ForeignKey(Persona, related_name="accounts")
servicename = u'generic service'
def __unicode__(self):
return "%s: %s" % (self.servicename, self.username)
class FooAccount(Account):
"""A service-specific account of type Foo."""
servicename = u'foo'
class BarAccount(Account):
"""A service-specific account of type Bar."""
servicename = u'bar'
class FooAccountAdmin(admin.StackedInline):
model = FooAccount
extra = 1
class BarAccountAdmin(admin.StackedInline):
model = BarAccount
extra = 1
class PersonaAdmin(admin.ModelAdmin):
inlines = (
FooAccountAdmin,
BarAccountAdmin
)
admin.site.register(Article, ArticleAdmin)
admin.site.register(CustomArticle, CustomArticleAdmin)
admin.site.register(Section, inlines=[ArticleInline])
admin.site.register(ModelWithStringPrimaryKey)
admin.site.register(Color)
admin.site.register(Thing, ThingAdmin)
admin.site.register(Persona, PersonaAdmin)
# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
# That way we cover all four cases:
......@@ -153,3 +197,5 @@ admin.site.register(Thing, ThingAdmin)
admin.site.register(Book, inlines=[ChapterInline])
admin.site.register(Promo)
admin.site.register(ChapterXtra1)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment