Skip to content

Commit d2d6301

Browse files
committed
[soc2009/admin-ui] A far better way to implement adding inlines using javascript. Instead of handling all the prefix incrementing in Javascript and losing any default values, added a way to generate a template form that can be cloned every time a new inline is added.
This is for stacked inlines. Support for tabular and selector inlines coming next. git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/admin-ui@11152 bcc190cf-cafb-0310-a4f2-bffc1f526a37
1 parent f5bb5b5 commit d2d6301

File tree

5 files changed

+55
-45
lines changed

5 files changed

+55
-45
lines changed

django/contrib/admin/helpers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ def __iter__(self):
114114
yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, original)
115115
for form in self.formset.extra_forms:
116116
yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, None)
117+
118+
yield InlineAdminForm(self.formset, self.formset.empty_form, self.fieldsets, self.opts.prepopulated_fields, None)
117119

118120
def fields(self):
119121
fk = getattr(self.formset, "fk", None)

django/contrib/admin/media/css/base.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,10 @@ table.orderable-initalized .order-cell, body>tr>td.order-cell {
350350
background-color: #F6F6F6;
351351
}
352352

353+
.empty_form {
354+
display: none;
355+
}
356+
353357
/* FORM DEFAULTS */
354358

355359
input, textarea, select {

django/contrib/admin/templates/admin/edit_inline/stacked.html

Lines changed: 27 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ <h2>{{ inline_admin_formset.opts.verbose_name_plural|title }}</h2>
55
{{ inline_admin_formset.formset.non_form_errors }}
66

77
{% for inline_admin_form in inline_admin_formset %}
8-
<div class="inline-related{% if forloop.last %} last-related{% endif %}" id="{{ inline_admin_formset.opts.verbose_name}}{{ forloop.counter }}">
8+
<div class="inline-related{% if forloop.last %} empty_form{% endif %}" id="{{ inline_admin_formset.opts.verbose_name}}{% if not forloop.last %}{{ forloop.counter }}{% else %}-empty{% endif %}">
99
<h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>&nbsp;<span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %} #{{ forloop.counter }}{% endif %}</span>
1010
{% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
1111
</h3>
@@ -22,62 +22,48 @@ <h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>&nbsp;<span class=
2222
</div>
2323
{% endfor %}
2424

25-
<ul class="tools add_inline">
25+
<ul class="tools add_inline" id="{{ inline_admin_formset.opts.verbose_name}}-addinline">
2626
<li><a id="{{ inline_admin_formset.opts.verbose_name }}-add" class="add" href="#">Add a {{ inline_admin_formset.opts.verbose_name }}</a></li>
2727
</ul>
2828

2929
</div>
3030

3131
<script type="text/javascript">
32-
function increment_fields(el) {
33-
el.attr('id', el.attr('id').replace(/-(\d+)-/, function (num) {
34-
var newnum = parseInt(num.replace(/-/g,''))+1;
35-
return '-' + newnum + '-';
36-
}));
37-
38-
el.attr('name', el.attr('name').replace(/-(\d+)-/, function (num) {
39-
var newnum = parseInt(num.replace(/-/g,''))+1;
40-
return '-' + newnum + '-';
41-
}));
42-
}
43-
4432
$(function() {
45-
var id_prefix = "{{ inline_admin_formset.opts.verbose_name }}";
46-
var total_forms = $('#' + id_prefix + '-group input[id$="TOTAL_FORMS"]');
47-
var initial_forms = $('#' + id_prefix + '-group').find('input[id$="INITIAL_FORMS"]');
33+
var prefix = "{{ inline_admin_formset.opts.verbose_name }}";
34+
var id_prefix = "#" + prefix;
35+
var total_forms = $(id_prefix + '-group input[id$="TOTAL_FORMS"]');
36+
var initial_forms = $(id_prefix + '-group').find('input[id$="INITIAL_FORMS"]');
4837

49-
// since javascript is turned on, unhide the "add new <inline>" link and hide the extras
38+
// since javascript is turned on, unhide the "add new <inline>" link
5039
$('.add_inline').show();
5140

52-
if (parseInt(initial_forms.val()) > 0) {
53-
$('#' + id_prefix + '-group .inline-related:gt(' + (initial_forms.val() - 1) + ')').remove();
54-
}
55-
else {
56-
$('#' + id_prefix + '-group .inline-related:gt(0)').remove();
57-
$('#' + id_prefix + '-group .inline-related:first').hide();
58-
}
59-
60-
total_forms.val(parseInt(initial_forms.val()));
61-
62-
// clicking on the "add" link will add a blank form to add a new inline object
63-
$('#' + id_prefix + "-add").click(function() {
64-
if (parseInt(total_forms.val()) == 0) {
65-
$('#' + id_prefix + '-group .inline-related:first').fadeIn('normal');
66-
total_forms.val(parseInt(total_forms.val()) + 1);
67-
return false;
41+
// hide the extras, but only if there were no form errors
42+
if (!$('.errornote').html()) {
43+
if (parseInt(initial_forms.val()) > 0) {
44+
$(id_prefix + '-group .inline-related:gt(' + (initial_forms.val() - 1) + ')')
45+
.not('.empty_form').remove();
46+
}
47+
else {
48+
$(id_prefix + '-group .inline-related').not('.empty_form').remove();
6849
}
6950

70-
var last_inline = $('#' + id_prefix + '-group .inline-related:last');
71-
var new_inline = last_inline.clone(true).hide().insertAfter(last_inline).fadeIn('normal');
51+
total_forms.val(parseInt(initial_forms.val()));
52+
}
53+
54+
$(id_prefix + "-add").click(function() {
55+
var new_inline = $(id_prefix + '-empty').clone(true)
56+
.insertBefore(id_prefix + '-addinline').fadeIn('normal');
7257

73-
new_inline.find('input, select').each(function(i) {
74-
increment_fields($(this));
75-
$(this).val("");
76-
});
58+
var inline_template = $(new_inline).html();
59+
var new_inline_html = inline_template.replace(/__prefix__/g, total_forms.val().toString());
7760

7861
total_forms.val(parseInt(total_forms.val()) + 1);
7962

80-
new_inline.find(".inline_label").text('#' + total_forms.val());
63+
$(new_inline).html(new_inline_html);
64+
$(new_inline).attr('id', prefix + total_forms.val().toString());
65+
$(new_inline).find('.inline_label').html('#' + total_forms.val().toString());
66+
$(new_inline).removeClass('empty_form');
8167

8268
return false;
8369
});

django/forms/formsets.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,21 @@ def _get_extra_forms(self):
118118
return self.forms[self.initial_form_count():]
119119
extra_forms = property(_get_extra_forms)
120120

121+
def _get_empty_form(self, **kwargs):
122+
defaults = {'auto_id': self.auto_id, 'prefix': self.add_prefix("__prefix__")}
123+
if self.data or self.files:
124+
defaults['data'] = self.data
125+
defaults['files'] = self.files
126+
127+
defaults['empty_permitted'] = True
128+
defaults.update(kwargs)
129+
130+
form = self.form(**defaults)
131+
self.add_fields(form, None)
132+
133+
return form
134+
empty_form = property(_get_empty_form)
135+
121136
# Maybe this should just go away?
122137
def _get_cleaned_data(self):
123138
"""
@@ -133,7 +148,7 @@ def _get_deleted_forms(self):
133148
Returns a list of forms that have been marked for deletion. Raises an
134149
AttributeError if deletion is not allowed.
135150
"""
136-
if not self.can_delete:
151+
if not self.is_valid or not self.can_delete:
137152
raise AttributeError("'%s' object has no attribute 'deleted_forms'" % self.__class__.__name__)
138153
# construct _deleted_form_indexes which is just a list of form indexes
139154
# that have had their deletion widget set to True
@@ -154,7 +169,7 @@ def _get_ordered_forms(self):
154169
Returns a list of form in the order specified by the incoming data.
155170
Raises an AttributeError if ordering is not allowed.
156171
"""
157-
if not self.can_order:
172+
if not self.is_valid or not self.can_order:
158173
raise AttributeError("'%s' object has no attribute 'ordered_forms'" % self.__class__.__name__)
159174
# Construct _ordering, which is a list of (form_index, order_field_value)
160175
# tuples. After constructing this list, we'll sort it by order_field_value
@@ -267,7 +282,7 @@ def add_fields(self, form, index):
267282
"""A hook for adding extra fields on to each form instance."""
268283
if self.can_order:
269284
# Only pre-fill the ordering field for initial forms.
270-
if index < self.initial_form_count():
285+
if index != None and index < self.initial_form_count():
271286
form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), initial=index+1, required=False)
272287
else:
273288
form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), required=False)

django/forms/models.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,10 @@ def pk_is_not_editable(pk):
664664
or (pk.rel and pk.rel.parent_link and pk_is_not_editable(pk.rel.to._meta.pk)))
665665
if pk_is_not_editable(pk) or pk.name not in form.fields:
666666
try:
667-
pk_value = self.get_queryset()[index].pk
667+
if index:
668+
pk_value = self.get_queryset()[index].pk
669+
else:
670+
pk_value = None
668671
except IndexError:
669672
pk_value = None
670673
if isinstance(pk, OneToOneField) or isinstance(pk, ForeignKey):

0 commit comments

Comments
 (0)