1 | From 117e99511e0985701780ed1bcd3afd456e244ae3 |
---|
2 | Added assertXML[Not]Equal assertions |
---|
3 | |
---|
4 | To 015b1c15f25f2f72a40a9bfce9d91ec91ddff2a2 |
---|
5 | Tests + revert firebug |
---|
6 | |
---|
7 | ----------------------- django/contrib/admin/options.py ----------------------- |
---|
8 | index f4205f2..58c55b0 100644 |
---|
9 | @@ -715,11 +715,16 @@ class ModelAdmin(BaseModelAdmin): |
---|
10 | """ |
---|
11 | obj.delete() |
---|
12 | |
---|
13 | - def save_formset(self, request, form, formset, change): |
---|
14 | + def save_formset(self, request, formset, change): |
---|
15 | """ |
---|
16 | Given an inline formset save it to the database. |
---|
17 | """ |
---|
18 | formset.save() |
---|
19 | + for form in formset.forms: |
---|
20 | + if hasattr(form, 'nested_formsets'): |
---|
21 | + for nested_formset in form.nested_formsets: |
---|
22 | + self.save_formset(request, nested_formset, change) |
---|
23 | + |
---|
24 | |
---|
25 | def save_related(self, request, form, formsets, change): |
---|
26 | """ |
---|
27 | @@ -731,7 +736,7 @@ class ModelAdmin(BaseModelAdmin): |
---|
28 | """ |
---|
29 | form.save_m2m() |
---|
30 | for formset in formsets: |
---|
31 | - self.save_formset(request, form, formset, change=change) |
---|
32 | + self.save_formset(request, formset, change=change) |
---|
33 | |
---|
34 | def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): |
---|
35 | opts = self.model._meta |
---|
36 | @@ -920,6 +925,73 @@ class ModelAdmin(BaseModelAdmin): |
---|
37 | self.message_user(request, msg) |
---|
38 | return None |
---|
39 | |
---|
40 | + |
---|
41 | + |
---|
42 | + def add_nested_inline_formsets(self, request, inline, formset, depth=0): |
---|
43 | + if depth > 5: |
---|
44 | + raise Exception("Maximum nesting depth reached (5)") |
---|
45 | + for form in formset.forms: |
---|
46 | + nested_formsets = [] |
---|
47 | + for nested_inline in inline.get_inline_instances(request): |
---|
48 | + InlineFormSet = nested_inline.get_formset(request, form.instance) |
---|
49 | + prefix = "%s-%s" % (form.prefix, InlineFormSet.get_default_prefix()) |
---|
50 | + if request.method == 'POST': |
---|
51 | + nested_formset = InlineFormSet(request.POST, request.FILES, |
---|
52 | + instance=form.instance, |
---|
53 | + prefix=prefix, queryset=nested_inline.queryset(request)) |
---|
54 | + else: |
---|
55 | + nested_formset = InlineFormSet(instance=form.instance, |
---|
56 | + prefix=prefix, queryset=nested_inline.queryset(request)) |
---|
57 | + nested_formsets.append(nested_formset) |
---|
58 | + if nested_inline.inlines: |
---|
59 | + self.add_nested_inline_formsets(request, nested_inline, nested_formset, depth=depth+1) |
---|
60 | + form.nested_formsets = nested_formsets |
---|
61 | + |
---|
62 | + def wrap_nested_inline_formsets(self, request, inline, formset): |
---|
63 | + media = None |
---|
64 | + def get_media(extra_media): |
---|
65 | + if media: |
---|
66 | + return media + extra_media |
---|
67 | + else: |
---|
68 | + return extra_media |
---|
69 | + |
---|
70 | + for form in formset.forms: |
---|
71 | + wrapped_nested_formsets = [] |
---|
72 | + for nested_inline, nested_formset in zip(inline.get_inline_instances(request), form.nested_formsets): |
---|
73 | + if form.instance.pk: |
---|
74 | + instance = form.instance |
---|
75 | + else: |
---|
76 | + instance = None |
---|
77 | + fieldsets = list(nested_inline.get_fieldsets(request)) |
---|
78 | + readonly = list(nested_inline.get_readonly_fields(request)) |
---|
79 | + prepopulated = dict(nested_inline.get_prepopulated_fields(request)) |
---|
80 | + wrapped_nested_formset = helpers.InlineAdminFormSet(nested_inline, nested_formset, |
---|
81 | + fieldsets, prepopulated, readonly, model_admin=self) |
---|
82 | + wrapped_nested_formsets.append(wrapped_nested_formset) |
---|
83 | + media = get_media(wrapped_nested_formset.media) |
---|
84 | + if nested_inline.inlines: |
---|
85 | + media = get_media(self.wrap_nested_inline_formsets(request, nested_inline, nested_formset)) |
---|
86 | + form.nested_formsets = wrapped_nested_formsets |
---|
87 | + return media |
---|
88 | + |
---|
89 | + def all_valid_with_nesting(self, formsets): |
---|
90 | + "Recursively validate all nested formsets" |
---|
91 | + if not all_valid(formsets): |
---|
92 | + return False |
---|
93 | + for formset in formsets: |
---|
94 | + if not formset.is_bound: |
---|
95 | + pass |
---|
96 | + for form in formset: |
---|
97 | + if hasattr(form, 'nested_formsets'): |
---|
98 | + if not self.all_valid_with_nesting(form.nested_formsets): |
---|
99 | + return False |
---|
100 | + # Here be dragons :( |
---|
101 | + if not form.cleaned_data: |
---|
102 | + form._errors["__all__"] = form.error_class([u"Parent object must be created when creating nested inlines."]) |
---|
103 | + return False |
---|
104 | + return True |
---|
105 | + |
---|
106 | + |
---|
107 | @csrf_protect_m |
---|
108 | @transaction.commit_on_success |
---|
109 | def add_view(self, request, form_url='', extra_context=None): |
---|
110 | @@ -952,7 +1024,9 @@ class ModelAdmin(BaseModelAdmin): |
---|
111 | save_as_new="_saveasnew" in request.POST, |
---|
112 | prefix=prefix, queryset=inline.queryset(request)) |
---|
113 | formsets.append(formset) |
---|
114 | - if all_valid(formsets) and form_validated: |
---|
115 | + if inline.inlines: |
---|
116 | + self.add_nested_inline_formsets(request, inline, formset) |
---|
117 | + if self.all_valid_with_nesting(formsets) and form_validated: |
---|
118 | self.save_model(request, new_object, form, False) |
---|
119 | self.save_related(request, form, formsets, False) |
---|
120 | self.log_addition(request, new_object) |
---|
121 | @@ -978,6 +1052,8 @@ class ModelAdmin(BaseModelAdmin): |
---|
122 | formset = FormSet(instance=self.model(), prefix=prefix, |
---|
123 | queryset=inline.queryset(request)) |
---|
124 | formsets.append(formset) |
---|
125 | + if inline.inlines: |
---|
126 | + self.add_nested_inline_formsets(request, inline, formset) |
---|
127 | |
---|
128 | adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), |
---|
129 | self.get_prepopulated_fields(request), |
---|
130 | @@ -994,6 +1070,8 @@ class ModelAdmin(BaseModelAdmin): |
---|
131 | fieldsets, prepopulated, readonly, model_admin=self) |
---|
132 | inline_admin_formsets.append(inline_admin_formset) |
---|
133 | media = media + inline_admin_formset.media |
---|
134 | + if inline.inlines: |
---|
135 | + media = media + self.wrap_nested_inline_formsets(request, inline, formset) |
---|
136 | |
---|
137 | context = { |
---|
138 | 'title': _('Add %s') % force_text(opts.verbose_name), |
---|
139 | @@ -1047,10 +1125,11 @@ class ModelAdmin(BaseModelAdmin): |
---|
140 | formset = FormSet(request.POST, request.FILES, |
---|
141 | instance=new_object, prefix=prefix, |
---|
142 | queryset=inline.queryset(request)) |
---|
143 | - |
---|
144 | formsets.append(formset) |
---|
145 | + if inline.inlines: |
---|
146 | + self.add_nested_inline_formsets(request, inline, formset) |
---|
147 | |
---|
148 | - if all_valid(formsets) and form_validated: |
---|
149 | + if self.all_valid_with_nesting(formsets) and form_validated: |
---|
150 | self.save_model(request, new_object, form, True) |
---|
151 | self.save_related(request, form, formsets, True) |
---|
152 | change_message = self.construct_change_message(request, form, formsets) |
---|
153 | @@ -1068,6 +1147,8 @@ class ModelAdmin(BaseModelAdmin): |
---|
154 | formset = FormSet(instance=obj, prefix=prefix, |
---|
155 | queryset=inline.queryset(request)) |
---|
156 | formsets.append(formset) |
---|
157 | + if inline.inlines: |
---|
158 | + self.add_nested_inline_formsets(request, inline, formset) |
---|
159 | |
---|
160 | adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), |
---|
161 | self.get_prepopulated_fields(request, obj), |
---|
162 | @@ -1084,6 +1165,8 @@ class ModelAdmin(BaseModelAdmin): |
---|
163 | fieldsets, prepopulated, readonly, model_admin=self) |
---|
164 | inline_admin_formsets.append(inline_admin_formset) |
---|
165 | media = media + inline_admin_formset.media |
---|
166 | + if inline.inlines: |
---|
167 | + media = media + self.wrap_nested_inline_formsets(request, inline, formset) |
---|
168 | |
---|
169 | context = { |
---|
170 | 'title': _('Change %s') % force_text(opts.verbose_name), |
---|
171 | @@ -1358,6 +1441,7 @@ class InlineModelAdmin(BaseModelAdmin): |
---|
172 | verbose_name = None |
---|
173 | verbose_name_plural = None |
---|
174 | can_delete = True |
---|
175 | + inlines = [] |
---|
176 | |
---|
177 | def __init__(self, parent_model, admin_site): |
---|
178 | self.admin_site = admin_site |
---|
179 | @@ -1369,9 +1453,25 @@ class InlineModelAdmin(BaseModelAdmin): |
---|
180 | if self.verbose_name_plural is None: |
---|
181 | self.verbose_name_plural = self.model._meta.verbose_name_plural |
---|
182 | |
---|
183 | + def get_inline_instances(self, request): |
---|
184 | + inline_instances = [] |
---|
185 | + for inline_class in self.inlines: |
---|
186 | + inline = inline_class(self.model, self.admin_site) |
---|
187 | + if request: |
---|
188 | + if not (inline.has_add_permission(request) or |
---|
189 | + inline.has_change_permission(request) or |
---|
190 | + inline.has_delete_permission(request)): |
---|
191 | + continue |
---|
192 | + if not inline.has_add_permission(request): |
---|
193 | + inline.max_num = 0 |
---|
194 | + inline_instances.append(inline) |
---|
195 | + return inline_instances |
---|
196 | + |
---|
197 | @property |
---|
198 | def media(self): |
---|
199 | - extra = '' if settings.DEBUG else '.min' |
---|
200 | + # FIXME: Development Handieness |
---|
201 | + # extra = '' if settings.DEBUG else '.min' |
---|
202 | + extra = '' |
---|
203 | js = ['jquery%s.js' % extra, 'jquery.init.js', 'inlines%s.js' % extra] |
---|
204 | if self.prepopulated_fields: |
---|
205 | js.extend(['urlify.js', 'prepopulate%s.js' % extra]) |
---|
206 | |
---|
207 | --------------- django/contrib/admin/static/admin/js/inlines.js --------------- |
---|
208 | index 4dc9459..2c4fa6e 100644 |
---|
209 | @@ -32,15 +32,16 @@ |
---|
210 | el.name = el.name.replace(id_regex, replacement); |
---|
211 | } |
---|
212 | }; |
---|
213 | - var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off"); |
---|
214 | - var nextIndex = parseInt(totalForms.val(), 10); |
---|
215 | - var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off"); |
---|
216 | - // only show the add button if we are allowed to add more items, |
---|
217 | - // note that max_num = None translates to a blank string. |
---|
218 | - var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0; |
---|
219 | + var nextIndex = get_no_forms(options.prefix); |
---|
220 | + |
---|
221 | + // Add form classes for dynamic behaviour |
---|
222 | $this.each(function(i) { |
---|
223 | $(this).not("." + options.emptyCssClass).addClass(options.formCssClass); |
---|
224 | }); |
---|
225 | + |
---|
226 | + // Only show the add button if we are allowed to add more items, |
---|
227 | + // note that max_num = None translates to a blank string. |
---|
228 | + var showAddButton = get_max_forms(options.prefix) === '' || (get_max_forms(options.prefix)-get_no_forms(options.prefix)) > 0; |
---|
229 | if ($this.length && showAddButton) { |
---|
230 | var addButton; |
---|
231 | if ($this.attr("tagName") == "TR") { |
---|
232 | @@ -56,7 +57,7 @@ |
---|
233 | } |
---|
234 | addButton.click(function(e) { |
---|
235 | e.preventDefault(); |
---|
236 | - var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS"); |
---|
237 | + var nextIndex = get_no_forms(options.prefix); |
---|
238 | var template = $("#" + options.prefix + "-empty"); |
---|
239 | var row = template.clone(true); |
---|
240 | row.removeClass(options.emptyCssClass) |
---|
241 | @@ -76,49 +77,55 @@ |
---|
242 | row.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></span>"); |
---|
243 | } |
---|
244 | row.find("*").each(function() { |
---|
245 | - updateElementIndex(this, options.prefix, totalForms.val()); |
---|
246 | + updateElementIndex(this, options.prefix, nextIndex); |
---|
247 | }); |
---|
248 | + // when adding something from a cloned formset the id is the same |
---|
249 | + |
---|
250 | // Insert the new form when it has been fully edited |
---|
251 | row.insertBefore($(template)); |
---|
252 | + |
---|
253 | + // Insert the nested formsets into the new form |
---|
254 | + if (row.is("tr")) { |
---|
255 | + // If the forms are laid out in table rows, insert |
---|
256 | + // the remove button into the last table cell: |
---|
257 | + nested_formsets = create_nested_formset(options.prefix, nextIndex, options); |
---|
258 | + nested_formsets.each(function() { |
---|
259 | + ($('<tr class="nested-inline-row">').html(($('<td>', {colspan: '100%'}).html($(this))))).insertBefore($(template)); |
---|
260 | + }); |
---|
261 | + } else { |
---|
262 | + // stacked |
---|
263 | + nested_formsets = create_nested_formset(options.prefix, nextIndex, options); |
---|
264 | + nested_formsets.each(function() { |
---|
265 | + row.append($(this)); |
---|
266 | + }); |
---|
267 | + } |
---|
268 | + |
---|
269 | // Update number of total forms |
---|
270 | - $(totalForms).val(parseInt(totalForms.val(), 10) + 1); |
---|
271 | - nextIndex += 1; |
---|
272 | + change_no_forms(options.prefix, true); |
---|
273 | + |
---|
274 | // Hide add button in case we've hit the max, except we want to add infinitely |
---|
275 | - if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) { |
---|
276 | + if ((get_max_forms(options.prefix) !== '') && (get_max_forms(options.prefix)-get_no_forms(options.prefix)) <= 0) { |
---|
277 | addButton.parent().hide(); |
---|
278 | } |
---|
279 | + |
---|
280 | // The delete button of each row triggers a bunch of other things |
---|
281 | row.find("a." + options.deleteCssClass).click(function(e) { |
---|
282 | e.preventDefault(); |
---|
283 | // Remove the parent form containing this button: |
---|
284 | var row = $(this).parents("." + options.formCssClass); |
---|
285 | + var formset_to_update = row.parent(); |
---|
286 | row.remove(); |
---|
287 | - nextIndex -= 1; |
---|
288 | + change_no_forms(options.prefix, false); |
---|
289 | // If a post-delete callback was provided, call it with the deleted form: |
---|
290 | if (options.removed) { |
---|
291 | - options.removed(row); |
---|
292 | - } |
---|
293 | - // Update the TOTAL_FORMS form count. |
---|
294 | - var forms = $("." + options.formCssClass); |
---|
295 | - $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); |
---|
296 | - // Show add button again once we drop below max |
---|
297 | - if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) { |
---|
298 | - addButton.parent().show(); |
---|
299 | - } |
---|
300 | - // Also, update names and ids for all remaining form controls |
---|
301 | - // so they remain in sequence: |
---|
302 | - for (var i=0, formCount=forms.length; i<formCount; i++) |
---|
303 | - { |
---|
304 | - updateElementIndex($(forms).get(i), options.prefix, i); |
---|
305 | - $(forms.get(i)).find("*").each(function() { |
---|
306 | - updateElementIndex(this, options.prefix, i); |
---|
307 | - }); |
---|
308 | + options.removed(formset_to_update); |
---|
309 | } |
---|
310 | }); |
---|
311 | // If a post-add callback was supplied, call it with the added form: |
---|
312 | if (options.added) { |
---|
313 | options.added(row); |
---|
314 | } |
---|
315 | + nextIndex = nextIndex + 1; |
---|
316 | }); |
---|
317 | } |
---|
318 | return this; |
---|
319 | @@ -212,6 +219,13 @@ |
---|
320 | var count = i + 1; |
---|
321 | $(this).html($(this).html().replace(/(#\d+)/g, "#" + count)); |
---|
322 | }); |
---|
323 | + }; |
---|
324 | + |
---|
325 | + var update_inline_labels = function(formset_to_update) { |
---|
326 | + formset_to_update.children('.inline-related').not('.empty-form').children('h3').find('.inline_label').each(function(i) { |
---|
327 | + var count = i + 1; |
---|
328 | + $(this).html($(this).html().replace(/(#\d+)/g, "#" + count)); |
---|
329 | + }); |
---|
330 | }; |
---|
331 | |
---|
332 | var reinitDateTimeShortCuts = function() { |
---|
333 | @@ -258,15 +272,164 @@ |
---|
334 | deleteCssClass: "inline-deletelink", |
---|
335 | deleteText: options.deleteText, |
---|
336 | emptyCssClass: "empty-form", |
---|
337 | - removed: updateInlineLabel, |
---|
338 | + removed: update_inline_labels, |
---|
339 | added: (function(row) { |
---|
340 | initPrepopulatedFields(row); |
---|
341 | reinitDateTimeShortCuts(); |
---|
342 | updateSelectFilter(); |
---|
343 | - updateInlineLabel(row); |
---|
344 | + update_inline_labels(row.parent()); |
---|
345 | }) |
---|
346 | }); |
---|
347 | |
---|
348 | return $rows; |
---|
349 | }; |
---|
350 | + |
---|
351 | + function create_nested_formset(parent_formset_prefix, next_form_id, options) { |
---|
352 | + var formsets = $(false); |
---|
353 | + // Normalize prefix to something we can rely on |
---|
354 | + var normalized_parent_formset_prefix = parent_formset_prefix.replace(/[-][0-9][-]/g,"-0-"); |
---|
355 | + // Check if the form should have nested formsets |
---|
356 | + var nested_inlines = $('#' + normalized_parent_formset_prefix + "-group ." + normalized_parent_formset_prefix + "-nested-inline").not('.cloned'); |
---|
357 | + nested_inlines.each(function() { |
---|
358 | + // prefixes for the nested formset |
---|
359 | + var normalized_formset_prefix = $(this).attr('id').split('-group')[0]; // = "parent_formset_prefix"-0-"nested_inline_name"_set |
---|
360 | + var formset_prefix = normalized_formset_prefix.replace(normalized_parent_formset_prefix + "-0", parent_formset_prefix + "-" + next_form_id); // = "parent_formset_prefix"-"next_form_id"-"nested_inline_name"_set |
---|
361 | + // Create the nested formset |
---|
362 | + var nested_formsets = create_nested_formset(formset_prefix, 0, options); |
---|
363 | + // Find the normalized formset and clone it |
---|
364 | + var template = $("#" + normalized_formset_prefix + "-group").clone(); |
---|
365 | + template.addClass('cloned'); |
---|
366 | + if (template.children().first().hasClass('tabular')) { |
---|
367 | + // Template is tabular |
---|
368 | + template.find(".form-row").not(".empty-form").remove(); |
---|
369 | + template.find(".nested-inline-row").remove(); |
---|
370 | + // Make a new form |
---|
371 | + template_form = template.find("#" + normalized_formset_prefix + "-empty") |
---|
372 | + new_form = template_form.clone() |
---|
373 | + .removeClass(options.emptyCssClass) |
---|
374 | + .addClass(options.formCssClass); |
---|
375 | + new_form.insertBefore(template_form); |
---|
376 | + // Update Form Properties |
---|
377 | + template.find('#id_' + formset_prefix + '-TOTAL_FORMS').val(1); |
---|
378 | + update_props(template, normalized_formset_prefix, formset_prefix); |
---|
379 | + var add_text = template.find('.add-row').text(); |
---|
380 | + template.find('.add-row').remove(); |
---|
381 | + template.find('.tabular.inline-related tbody tr.' + formset_prefix + '-not-nested').tabularFormset({ |
---|
382 | + prefix: formset_prefix, |
---|
383 | + adminStaticPrefix: options.adminStaticPrefix, |
---|
384 | + addText: add_text, |
---|
385 | + deleteText: options.deleteText |
---|
386 | + }); |
---|
387 | + // Insert nested formsets |
---|
388 | + nested_formsets.each(function() { |
---|
389 | + template.find("#" + formset_prefix + "-empty").before(($('<tr class="nested-inline-row">').html(($('<td>', {colspan: '100%'}).html($(this)))))); |
---|
390 | + }); |
---|
391 | + } else { |
---|
392 | + // Template is stacked |
---|
393 | + template.find(".inline-related").not(".empty-form").remove(); |
---|
394 | + // Make a new form |
---|
395 | + template_form = template.find("#" + normalized_formset_prefix + "-empty") |
---|
396 | + new_form = template_form.clone() |
---|
397 | + .removeClass(options.emptyCssClass) |
---|
398 | + .addClass(options.formCssClass); |
---|
399 | + new_form.insertBefore(template_form); |
---|
400 | + // Update Form Properties |
---|
401 | + console.log('looking for: ' + '#id_' + formset_prefix + '-TOTAL_FORMS'); |
---|
402 | + console.log(template.html()); |
---|
403 | + template.find('#id_' + normalized_formset_prefix + '-TOTAL_FORMS').val(1); |
---|
404 | + new_form.find('.inline_label').text('#1'); |
---|
405 | + update_props(template, normalized_formset_prefix, formset_prefix); |
---|
406 | + var add_text = template.find('.add-row').text(); |
---|
407 | + template.find('.add-row').remove(); |
---|
408 | + template.find(".inline-related").stackedFormset({ |
---|
409 | + prefix: formset_prefix, |
---|
410 | + adminStaticPrefix: options.adminStaticPrefix, |
---|
411 | + addText: add_text, |
---|
412 | + deleteText: options.deleteText |
---|
413 | + }); |
---|
414 | + nested_formsets.each(function() { |
---|
415 | + new_form.append($(this)); |
---|
416 | + }); |
---|
417 | + } |
---|
418 | + if (formsets.length) { |
---|
419 | + formsets = formsets.add(template); |
---|
420 | + } else { |
---|
421 | + formsets = template; |
---|
422 | + } |
---|
423 | + }); |
---|
424 | + return formsets; |
---|
425 | + }; |
---|
426 | + |
---|
427 | + function update_props(template, normalized_formset_prefix, formset_prefix) { |
---|
428 | + // Fix template id |
---|
429 | + template.attr('id',template.attr('id').replace(normalized_formset_prefix, formset_prefix)); |
---|
430 | + template.find('*').each(function() { |
---|
431 | + if ($(this).attr("for")) { |
---|
432 | + $(this).attr("for", $(this).attr("for").replace(normalized_formset_prefix, formset_prefix)); |
---|
433 | + } |
---|
434 | + if ($(this).attr("class")) { |
---|
435 | + $(this).attr("class", $(this).attr("class").replace(normalized_formset_prefix, formset_prefix)); |
---|
436 | + } |
---|
437 | + if (this.id) { |
---|
438 | + this.id = this.id.replace(normalized_formset_prefix, formset_prefix); |
---|
439 | + } |
---|
440 | + if (this.name) { |
---|
441 | + this.name = this.name.replace(normalized_formset_prefix, formset_prefix); |
---|
442 | + } |
---|
443 | + }); |
---|
444 | + // fix __prefix__ where needed |
---|
445 | + prefix_fix = template.find(".inline-related").first(); |
---|
446 | + nextIndex = get_no_forms(formset_prefix); |
---|
447 | + if (prefix_fix.hasClass('tabular')) { |
---|
448 | + // tabular |
---|
449 | + prefix_fix = prefix_fix.find('.form-row').first(); |
---|
450 | + prefix_fix.attr('id',prefix_fix.attr('id').replace('-empty','-' + nextIndex)); |
---|
451 | + } else { |
---|
452 | + // stacked |
---|
453 | + prefix_fix.attr('id',prefix_fix.attr('id').replace('-empty','-' + nextIndex)); |
---|
454 | + } |
---|
455 | + prefix_fix.find('*').each(function() { |
---|
456 | + if ($(this).attr("for")) { |
---|
457 | + $(this).attr("for", $(this).attr("for").replace('__prefix__', '0')); |
---|
458 | + } |
---|
459 | + if ($(this).attr("class")) { |
---|
460 | + $(this).attr("class", $(this).attr("class").replace('__prefix__', '0')); |
---|
461 | + } |
---|
462 | + if (this.id) { |
---|
463 | + this.id = this.id.replace('__prefix__', '0'); |
---|
464 | + } |
---|
465 | + if (this.name) { |
---|
466 | + this.name = this.name.replace('__prefix__', '0'); |
---|
467 | + } |
---|
468 | + }); |
---|
469 | + }; |
---|
470 | + |
---|
471 | + // This returns the amount of forms in the given formset |
---|
472 | + function get_no_forms(formset_prefix) { |
---|
473 | + formset_prop = $("#id_" + formset_prefix + "-TOTAL_FORMS") |
---|
474 | + if (!formset_prop.length) { |
---|
475 | + return 0; |
---|
476 | + } |
---|
477 | + return parseInt(formset_prop.attr("autocomplete", "off").val()); |
---|
478 | + } |
---|
479 | + |
---|
480 | + function change_no_forms(formset_prefix, increase) { |
---|
481 | + var no_forms = get_no_forms(formset_prefix); |
---|
482 | + if (increase) { |
---|
483 | + $("#id_" + formset_prefix + "-TOTAL_FORMS").attr("autocomplete", "off").val(parseInt(no_forms) + 1); |
---|
484 | + } else { |
---|
485 | + $("#id_" + formset_prefix + "-TOTAL_FORMS").attr("autocomplete", "off").val(parseInt(no_forms) - 1); |
---|
486 | + } |
---|
487 | + }; |
---|
488 | + |
---|
489 | + // This return the maximum amount of forms in the given formset |
---|
490 | + function get_max_forms(formset_prefix) { |
---|
491 | + var max_forms = $("#id_" + formset_prefix + "-MAX_FORMS").attr("autocomplete", "off").val(); |
---|
492 | + if (typeof max_forms == 'undefined') { |
---|
493 | + return ''; |
---|
494 | + } |
---|
495 | + return parseInt(max_forms); |
---|
496 | + |
---|
497 | + }; |
---|
498 | + |
---|
499 | })(django.jQuery); |
---|
500 | |
---|
501 | -------- django/contrib/admin/templates/admin/edit_inline/stacked.html -------- |
---|
502 | index 2025dd8..91eb4b6 100644 |
---|
503 | @@ -1,13 +1,14 @@ |
---|
504 | {% load i18n admin_static %} |
---|
505 | -<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group"> |
---|
506 | - <h2>{{ inline_admin_formset.opts.verbose_name_plural|title }}</h2> |
---|
507 | -{{ inline_admin_formset.formset.management_form }} |
---|
508 | -{{ inline_admin_formset.formset.non_form_errors }} |
---|
509 | +{% with recursive_formset=inline_admin_formset stacked_template='admin/edit_inline/stacked.html' tabular_template='admin/edit_inline/tabular.html'%} |
---|
510 | +<div class="inline-group {{ prev_prefix|default:"Root" }}-nested-inline" id="{{ recursive_formset.formset.prefix }}-group" style="margin-left: {{ indent|default:0 }}px"> |
---|
511 | + <h2>{{ recursive_formset.opts.verbose_name_plural|title }}</h2> |
---|
512 | +{{ recursive_formset.formset.management_form }} |
---|
513 | +{{ recursive_formset.formset.non_form_errors }} |
---|
514 | |
---|
515 | -{% for inline_admin_form in inline_admin_formset %}<div class="inline-related{% if forloop.last %} empty-form last-related{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}"> |
---|
516 | - <h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b> <span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %}#{{ forloop.counter }}{% endif %}</span> |
---|
517 | +{% for inline_admin_form in recursive_formset %}<div class="inline-related{% if forloop.last %} empty-form last-related{% endif %}" id="{{ recursive_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}"> |
---|
518 | + <h3><b>{{ recursive_formset.opts.verbose_name|title }}:</b> <span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %}#{{ forloop.counter }}{% endif %}</span> |
---|
519 | {% if inline_admin_form.show_url %}<a href="{% url 'admin:view_on_site' inline_admin_form.original_content_type_id inline_admin_form.original.pk %}">{% trans "View on site" %}</a>{% endif %} |
---|
520 | - {% 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 %} |
---|
521 | + {% if recursive_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 %} |
---|
522 | </h3> |
---|
523 | {% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %} |
---|
524 | {% for fieldset in inline_admin_form %} |
---|
525 | @@ -15,16 +16,26 @@ |
---|
526 | {% endfor %} |
---|
527 | {% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %} |
---|
528 | {{ inline_admin_form.fk_field.field }} |
---|
529 | + {% if inline_admin_form.form.nested_formsets %} |
---|
530 | + {% for inline_admin_formset in inline_admin_form.form.nested_formsets %} |
---|
531 | + {% if inline_admin_formset.opts.template == stacked_template %} |
---|
532 | + {% include stacked_template with indent=10 prev_prefix=recursive_formset.formset.prefix%} |
---|
533 | + {% else %} |
---|
534 | + {% include tabular_template with indent=10 prev_prefix=recursive_formset.formset.prefix%} |
---|
535 | + {% endif %} |
---|
536 | + {% endfor %} |
---|
537 | + {% endif %} |
---|
538 | </div>{% endfor %} |
---|
539 | </div> |
---|
540 | |
---|
541 | <script type="text/javascript"> |
---|
542 | (function($) { |
---|
543 | - $("#{{ inline_admin_formset.formset.prefix }}-group .inline-related").stackedFormset({ |
---|
544 | - prefix: '{{ inline_admin_formset.formset.prefix }}', |
---|
545 | + $("#{{ recursive_formset.formset.prefix }}-group .inline-related").stackedFormset({ |
---|
546 | + prefix: '{{ recursive_formset.formset.prefix }}', |
---|
547 | adminStaticPrefix: '{% static "admin/" %}', |
---|
548 | - deleteText: "{% trans "Remove" %}", |
---|
549 | - addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}" |
---|
550 | + addText: "{% blocktrans with verbose_name=recursive_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}", |
---|
551 | + deleteText: "{% trans "Remove" %}" |
---|
552 | }); |
---|
553 | })(django.jQuery); |
---|
554 | </script> |
---|
555 | +{% endwith %} |
---|
556 | |
---|
557 | -------- django/contrib/admin/templates/admin/edit_inline/tabular.html -------- |
---|
558 | index f2757ed..b1cb7f6 100644 |
---|
559 | @@ -1,29 +1,30 @@ |
---|
560 | {% load i18n admin_static admin_modify %} |
---|
561 | -<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group"> |
---|
562 | +{% with recursive_formset=inline_admin_formset stacked_template='admin/edit_inline/stacked.html' tabular_template='admin/edit_inline/tabular.html'%} |
---|
563 | +<div class="inline-group {{ prev_prefix|default:"Root" }}-nested-inline" id="{{ recursive_formset.formset.prefix }}-group" style="margin-left: {{ indent|default:0 }}px"> |
---|
564 | <div class="tabular inline-related {% if forloop.last %}last-related{% endif %}"> |
---|
565 | -{{ inline_admin_formset.formset.management_form }} |
---|
566 | +{{ recursive_formset.formset.management_form }} |
---|
567 | <fieldset class="module"> |
---|
568 | - <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2> |
---|
569 | - {{ inline_admin_formset.formset.non_form_errors }} |
---|
570 | + <h2>{{ recursive_formset.opts.verbose_name_plural|capfirst }}</h2> |
---|
571 | + {{ recursive_formset.formset.non_form_errors }} |
---|
572 | <table> |
---|
573 | <thead><tr> |
---|
574 | - {% for field in inline_admin_formset.fields %} |
---|
575 | + {% for field in recursive_formset.fields %} |
---|
576 | {% if not field.widget.is_hidden %} |
---|
577 | <th{% if forloop.first %} colspan="2"{% endif %}{% if field.required %} class="required"{% endif %}>{{ field.label|capfirst }} |
---|
578 | {% if field.help_text %} <img src="{% static "admin/img/icon-unknown.gif" %}" class="help help-tooltip" width="10" height="10" alt="({{ field.help_text|striptags }})" title="{{ field.help_text|striptags }}" />{% endif %} |
---|
579 | </th> |
---|
580 | {% endif %} |
---|
581 | {% endfor %} |
---|
582 | - {% if inline_admin_formset.formset.can_delete %}<th>{% trans "Delete?" %}</th>{% endif %} |
---|
583 | + {% if recursive_formset.formset.can_delete %}<th>{% trans "Delete?" %}</th>{% endif %} |
---|
584 | </tr></thead> |
---|
585 | |
---|
586 | <tbody> |
---|
587 | - {% for inline_admin_form in inline_admin_formset %} |
---|
588 | + {% for inline_admin_form in recursive_formset %} |
---|
589 | {% if inline_admin_form.form.non_field_errors %} |
---|
590 | <tr><td colspan="{{ inline_admin_form|cell_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr> |
---|
591 | {% endif %} |
---|
592 | - <tr class="form-row {% cycle "row1" "row2" %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}{% if forloop.last %} empty-form{% endif %}" |
---|
593 | - id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}"> |
---|
594 | + <tr class="form-row {% cycle "row1" "row2" %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}{% if forloop.last %} empty-form{% endif %} {{ recursive_formset.formset.prefix }}-not-nested" |
---|
595 | + id="{{ recursive_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}"> |
---|
596 | <td class="original"> |
---|
597 | {% if inline_admin_form.original or inline_admin_form.show_url %}<p> |
---|
598 | {% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %} |
---|
599 | @@ -55,10 +56,23 @@ |
---|
600 | {% endfor %} |
---|
601 | {% endfor %} |
---|
602 | {% endfor %} |
---|
603 | - {% if inline_admin_formset.formset.can_delete %} |
---|
604 | + {% if recursive_formset.formset.can_delete %} |
---|
605 | <td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td> |
---|
606 | {% endif %} |
---|
607 | </tr> |
---|
608 | + {% if inline_admin_form.form.nested_formsets %} |
---|
609 | + {% for inline_admin_formset in inline_admin_form.form.nested_formsets %} |
---|
610 | + <tr class="nested-inline-row"> |
---|
611 | + <td colspan="100%" style="border-top:none"> |
---|
612 | + {% if inline_admin_formset.opts.template == stacked_template %} |
---|
613 | + {% include stacked_template with indent=0 prev_prefix=recursive_formset.formset.prefix %} |
---|
614 | + {% else %} |
---|
615 | + {% include tabular_template with indent=0 prev_prefix=recursive_formset.formset.prefix %} |
---|
616 | + {% endif %} |
---|
617 | + </td> |
---|
618 | + </tr> |
---|
619 | + {% endfor %} |
---|
620 | + {% endif %} |
---|
621 | {% endfor %} |
---|
622 | </tbody> |
---|
623 | </table> |
---|
624 | @@ -67,13 +81,13 @@ |
---|
625 | </div> |
---|
626 | |
---|
627 | <script type="text/javascript"> |
---|
628 | - |
---|
629 | (function($) { |
---|
630 | - $("#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr").tabularFormset({ |
---|
631 | - prefix: "{{ inline_admin_formset.formset.prefix }}", |
---|
632 | + $("#{{ recursive_formset.formset.prefix }}-group .tabular.inline-related tbody tr.{{ recursive_formset.formset.prefix }}-not-nested").tabularFormset({ |
---|
633 | + prefix: "{{ recursive_formset.formset.prefix }}", |
---|
634 | adminStaticPrefix: '{% static "admin/" %}', |
---|
635 | - addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}", |
---|
636 | + addText: "{% blocktrans with recursive_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}", |
---|
637 | deleteText: "{% trans 'Remove' %}" |
---|
638 | }); |
---|
639 | })(django.jQuery); |
---|
640 | </script> |
---|
641 | +{% endwith %} |
---|
642 | |
---|
643 | ------------------------ django/contrib/admin/tests.py ------------------------ |
---|
644 | index 7c62c1a..6fe6216 100644 |
---|
645 | @@ -2,6 +2,7 @@ from django.test import LiveServerTestCase |
---|
646 | from django.utils.importlib import import_module |
---|
647 | from django.utils.unittest import SkipTest |
---|
648 | from django.utils.translation import ugettext as _ |
---|
649 | +from selenium import webdriver |
---|
650 | |
---|
651 | class AdminSeleniumWebDriverTestCase(LiveServerTestCase): |
---|
652 | webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver' |
---|
653 | @@ -13,6 +14,7 @@ class AdminSeleniumWebDriverTestCase(LiveServerTestCase): |
---|
654 | module, attr = cls.webdriver_class.rsplit('.', 1) |
---|
655 | mod = import_module(module) |
---|
656 | WebDriver = getattr(mod, attr) |
---|
657 | + #Avoid startup screen |
---|
658 | cls.selenium = WebDriver() |
---|
659 | except Exception as e: |
---|
660 | raise SkipTest('Selenium webdriver "%s" not installed or not ' |
---|
661 | |
---|
662 | ----------------- tests/regressiontests/admin_inlines/admin.py ----------------- |
---|
663 | index cf51fa4..3f2d067 100644 |
---|
664 | @@ -123,6 +123,34 @@ class ChildModel1Inline(admin.TabularInline): |
---|
665 | |
---|
666 | class ChildModel2Inline(admin.StackedInline): |
---|
667 | model = ChildModel2 |
---|
668 | + |
---|
669 | +class FurnitureInline(admin.StackedInline): |
---|
670 | + model = Furniture |
---|
671 | + extra = 1 |
---|
672 | + |
---|
673 | +class InhabitantInline(admin.StackedInline): |
---|
674 | + model = Inhabitant |
---|
675 | + extra = 1 |
---|
676 | + inlines = [ FurnitureInline, ] |
---|
677 | + |
---|
678 | +class AppartementInline(admin.TabularInline): |
---|
679 | + model = Appartement |
---|
680 | + extra = 1 |
---|
681 | + inlines = [ InhabitantInline, ] |
---|
682 | + |
---|
683 | +class MonumentInline(admin.StackedInline): |
---|
684 | + model = Monument |
---|
685 | + extra = 1 |
---|
686 | + |
---|
687 | +class BuildingInline(admin.TabularInline): |
---|
688 | + model = Building |
---|
689 | + extra = 1 |
---|
690 | + inlines = [ AppartementInline, ] |
---|
691 | + |
---|
692 | +class CityInline(admin.StackedInline): |
---|
693 | + model = City |
---|
694 | + extra = 1 |
---|
695 | + inlines = [BuildingInline, MonumentInline, ] |
---|
696 | |
---|
697 | |
---|
698 | site.register(TitleCollection, inlines=[TitleInline]) |
---|
699 | @@ -141,4 +169,11 @@ site.register(Holder4, Holder4Admin) |
---|
700 | site.register(Author, AuthorAdmin) |
---|
701 | site.register(CapoFamiglia, inlines=[ConsigliereInline, SottoCapoInline]) |
---|
702 | site.register(ProfileCollection, inlines=[ProfileInline]) |
---|
703 | -site.register(ParentModelWithCustomPk, inlines=[ChildModel1Inline, ChildModel2Inline]) |
---|
704 | \ No newline at end of file |
---|
705 | +site.register(ParentModelWithCustomPk, inlines=[ChildModel1Inline, ChildModel2Inline]) |
---|
706 | +site.register(Country, inlines=[CityInline]) |
---|
707 | +site.register(City) |
---|
708 | +site.register(Building) |
---|
709 | +site.register(Monument) |
---|
710 | +site.register(Appartement) |
---|
711 | +site.register(Inhabitant) |
---|
712 | +site.register(Furniture) |
---|
713 | \ No newline at end of file |
---|
714 | |
---|
715 | ---------------- tests/regressiontests/admin_inlines/models.py ---------------- |
---|
716 | index b004d5f..c51af58 100644 |
---|
717 | @@ -180,3 +180,51 @@ class Profile(models.Model): |
---|
718 | collection = models.ForeignKey(ProfileCollection, blank=True, null=True) |
---|
719 | first_name = models.CharField(max_length=100) |
---|
720 | last_name = models.CharField(max_length=100) |
---|
721 | + |
---|
722 | +class Country(models.Model): |
---|
723 | + name = models.CharField(max_length=100) |
---|
724 | + |
---|
725 | + def __unicode__(self): |
---|
726 | + return self.name |
---|
727 | + |
---|
728 | +class City(models.Model): |
---|
729 | + name = models.CharField(max_length=100) |
---|
730 | + country = models.ForeignKey(Country) |
---|
731 | + |
---|
732 | + def __unicode__(self): |
---|
733 | + return self.name |
---|
734 | + |
---|
735 | +class Building(models.Model): |
---|
736 | + name = models.CharField(max_length=100) |
---|
737 | + city = models.ForeignKey(City) |
---|
738 | + |
---|
739 | + def __unicode__(self): |
---|
740 | + return self.name |
---|
741 | + |
---|
742 | +class Appartement(models.Model): |
---|
743 | + name = models.CharField(max_length=100) |
---|
744 | + building = models.ForeignKey(Building) |
---|
745 | + |
---|
746 | + def __unicode__(self): |
---|
747 | + return self.name |
---|
748 | + |
---|
749 | +class Inhabitant(models.Model): |
---|
750 | + name = models.CharField(max_length=100) |
---|
751 | + appartement = models.ForeignKey(Appartement) |
---|
752 | + |
---|
753 | + def __unicode__(self): |
---|
754 | + return self.name |
---|
755 | + |
---|
756 | +class Furniture(models.Model): |
---|
757 | + name = models.CharField(max_length=100) |
---|
758 | + inhabitant = models.ForeignKey(Inhabitant) |
---|
759 | + |
---|
760 | + def __unicode__(self): |
---|
761 | + return self.name |
---|
762 | + |
---|
763 | +class Monument(models.Model): |
---|
764 | + name = models.CharField(max_length=100) |
---|
765 | + city = models.ForeignKey(City) |
---|
766 | + |
---|
767 | + def __unicode__(self): |
---|
768 | + return self.name |
---|
769 | \ No newline at end of file |
---|
770 | |
---|
771 | ----------------- tests/regressiontests/admin_inlines/tests.py ----------------- |
---|
772 | index 5bb6077..a6ef27a0 100644 |
---|
773 | @@ -584,6 +584,59 @@ class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase): |
---|
774 | "%s.row1" % row_selector)), 2, msg="Expect two row1 styled rows") |
---|
775 | self.assertEqual(len(self.selenium.find_elements_by_css_selector( |
---|
776 | "%s.row2" % row_selector)), 1, msg="Expect one row2 styled row") |
---|
777 | + |
---|
778 | + def test_nested_add_links(self): |
---|
779 | + self.admin_login(username='super', password='secret') |
---|
780 | + self.selenium.get('%s%s' % (self.live_server_url, |
---|
781 | + '/admin/admin_inlines/country/add/')) |
---|
782 | + self.assertEqual(len(self.selenium.find_elements_by_css_selector( |
---|
783 | + 'tr.add-row')), 2) |
---|
784 | + |
---|
785 | + def test_nested_adding_deleting(self): |
---|
786 | + self.admin_login(username='super', password='secret') |
---|
787 | + self.selenium.get('%s%s' % (self.live_server_url, |
---|
788 | + '/admin/admin_inlines/country/add/')) |
---|
789 | + |
---|
790 | + # Add some forms |
---|
791 | + self.selenium.find_element_by_link_text('Add another City').click() |
---|
792 | + self.selenium.find_element_by_link_text('Add another City').click() |
---|
793 | + # Add monument in first city |
---|
794 | + self.selenium.find_element_by_xpath('/html/body/div/div[3]/div/form/div[2]/div/div/div[2]/div[3]/a').click() |
---|
795 | + # Add building in second city |
---|
796 | + self.selenium.find_element_by_xpath('/html/body/div/div[3]/div/form/div[2]/div/div[2]/div/div/fieldset/table/tbody/tr[4]/td/a').click() |
---|
797 | + # Add apartement in second building of second city |
---|
798 | + self.selenium.find_element_by_xpath('/html/body/div/div[3]/div/form/div[2]/div/div[2]/div/div/fieldset/table/tbody/tr[4]/td/div/div/fieldset/table/tbody/tr[4]/td/a').click() |
---|
799 | + # Add inhabitants in third city |
---|
800 | + self.selenium.find_element_by_xpath('/html/body/div/div[3]/div/form/div[2]/div/div[3]/div/div/fieldset/table/tbody/tr[2]/td/div/div/fieldset/table/tbody/tr[2]/td/div/div[3]/a').click() |
---|
801 | + self.selenium.find_element_by_xpath('/html/body/div/div[3]/div/form/div[2]/div/div[3]/div/div/fieldset/table/tbody/tr[2]/td/div/div/fieldset/table/tbody/tr[2]/td/div/div[4]/a').click() |
---|
802 | + # Add furniture in first city |
---|
803 | + self.selenium.find_element_by_xpath('/html/body/div/div[3]/div/form/div[2]/div/div/div/div/fieldset/table/tbody/tr[2]/td/div/div/fieldset/table/tbody/tr[2]/td/div/div/div/div[3]/a').click() |
---|
804 | + # Check if everything went ok |
---|
805 | + pass |
---|
806 | + # Delete some stuff |
---|
807 | + pass |
---|
808 | + # Chek if everythin was deleted |
---|
809 | + pass |
---|
810 | + |
---|
811 | + def test_nested_inlines(self): |
---|
812 | + self.admin_login(username='super', password='secret') |
---|
813 | + self.selenium.get('%s%s' % (self.live_server_url, |
---|
814 | + '/admin/admin_inlines/country/add/')) |
---|
815 | + |
---|
816 | + # Enter some data |
---|
817 | + self.selenium.find_element_by_css_selector('#id_name').send_keys('Belgium') |
---|
818 | + self.selenium.find_element_by_css_selector('#id_city_set-0-name').send_keys('1') |
---|
819 | + self.selenium.find_element_by_css_selector('#id_city_set-0-building_set-0-name').send_keys('1.1') |
---|
820 | + self.selenium.find_element_by_css_selector('#id_city_set-0-building_set-0-appartement_set-0-name').send_keys('1.1.1') |
---|
821 | + self.selenium.find_element_by_css_selector('#id_city_set-0-building_set-0-appartement_set-0-inhabitant_set-0-name').send_keys('1.1.1.1') |
---|
822 | + self.selenium.find_element_by_css_selector('#id_city_set-0-building_set-0-appartement_set-0-inhabitant_set-0-furniture_set-0-name').send_keys('1.1.1.1.1') |
---|
823 | + self.selenium.find_element_by_css_selector('#id_city_set-0-monument_set-0-name').send_keys('Monument') |
---|
824 | + |
---|
825 | + # Save |
---|
826 | + self.selenium.find_element_by_xpath('/html/body/div/div[3]/div/form/div[2]/div[2]/input').click() |
---|
827 | + |
---|
828 | + |
---|
829 | + self.fail("ok") |
---|
830 | |
---|
831 | |
---|
832 | class SeleniumChromeTests(SeleniumFirefoxTests): |
---|