Skip to content

Commit

Permalink
Fixed #10271, #10281 -- Fixed the handling multiple inline models tha…
Browse files Browse the repository at this point in the history
…t 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
  • Loading branch information
freakboy3742 committed Mar 10, 2009
1 parent d0fff8c commit 3c8568a
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 52 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 25 additions & 4 deletions django/contrib/admin/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,10 +522,16 @@ def add_view(self, request, form_url='', extra_context=None):
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)
Expand All @@ -547,8 +553,13 @@ def add_view(self, request, form_url='', extra_context=None):
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)
Expand Down Expand Up @@ -608,9 +619,14 @@ def change_view(self, request, object_id, extra_context=None):
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:
Expand All @@ -625,8 +641,13 @@ def change_view(self, request, object_id, extra_context=None):

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)
Expand Down
12 changes: 10 additions & 2 deletions django/contrib/contenttypes/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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((
Expand All @@ -300,9 +300,17 @@ def __init__(self, data=None, files=None, instance=None, save_as_new=None):
))
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
Expand Down
13 changes: 9 additions & 4 deletions django/forms/formsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -62,7 +62,7 @@ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
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()

Expand All @@ -74,7 +74,7 @@ def _construct_forms(self):
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.
Expand Down Expand Up @@ -118,7 +118,7 @@ def _get_cleaned_data(self):

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:
Expand Down Expand Up @@ -176,6 +176,11 @@ def compare_ordering_values(x, y):
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
Expand Down
12 changes: 9 additions & 3 deletions django/forms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,8 @@ def validate_unique(self):
# 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
Expand Down Expand Up @@ -472,7 +472,7 @@ def __init__(self, data=None, files=None, instance=None,
# 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):
Expand All @@ -489,6 +489,12 @@ def _construct_form(self, i, **kwargs):
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}
Expand Down
5 changes: 5 additions & 0 deletions tests/modeltests/generic_relations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,4 +238,9 @@ def __unicode__(self):
<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>
"""}
56 changes: 51 additions & 5 deletions tests/regressiontests/admin_views/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -54,14 +54,14 @@ class Meta:

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
Expand All @@ -87,7 +87,7 @@ def changelist_view(self, request):
'extra_var': 'Hello!'
}
)

def modeladmin_year(self, obj):
return obj.date.year
modeladmin_year.admin_order_field = 'date'
Expand Down Expand Up @@ -121,7 +121,7 @@ def __unicode__(self):

class Color(models.Model):
value = models.CharField(max_length=10)
warm = models.BooleanField()
warm = models.BooleanField()
def __unicode__(self):
return self.value

Expand All @@ -134,12 +134,56 @@ def __unicode__(self):
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:
Expand All @@ -153,3 +197,5 @@ class ThingAdmin(admin.ModelAdmin):
admin.site.register(Book, inlines=[ChapterInline])
admin.site.register(Promo)
admin.site.register(ChapterXtra1)


Loading

0 comments on commit 3c8568a

Please sign in to comment.