1
0
mirror of https://github.com/django/django.git synced 2025-03-12 02:12:38 +00:00

Fixed #13068 (again) -- Corrected the admin stacked inline template to allow prepopulated fields to work (Thanks Stanislas Guerra for the report). Also fixed a regression introduced in [16953] where prepopulated fields wouldn't be recognized any more due to the additional "field-" CSS class name prefix.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17562 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Julien Phalip 2012-02-19 16:42:12 +00:00
parent cfd0cb0064
commit f0c2228709
8 changed files with 191 additions and 15 deletions

View File

@ -27,14 +27,14 @@
var count = i + 1; var count = i + 1;
$(this).html($(this).html().replace(/(#\d+)/g, "#" + count)); $(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
}); });
} };
var reinitDateTimeShortCuts = function() { var reinitDateTimeShortCuts = function() {
// Reinitialize the calendar and clock widgets by force, yuck. // Reinitialize the calendar and clock widgets by force, yuck.
if (typeof DateTimeShortcuts != "undefined") { if (typeof DateTimeShortcuts != "undefined") {
$(".datetimeshortcuts").remove(); $(".datetimeshortcuts").remove();
DateTimeShortcuts.init(); DateTimeShortcuts.init();
} }
} };
var updateSelectFilter = function() { var updateSelectFilter = function() {
// If any SelectFilter widgets were added, instantiate a new instance. // If any SelectFilter widgets were added, instantiate a new instance.
if (typeof SelectFilter != "undefined"){ if (typeof SelectFilter != "undefined"){
@ -47,7 +47,7 @@
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static "admin/" %}"); SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static "admin/" %}");
}); });
} }
} };
var initPrepopulatedFields = function(row) { var initPrepopulatedFields = function(row) {
row.find('.prepopulated_field').each(function() { row.find('.prepopulated_field').each(function() {
var field = $(this); var field = $(this);
@ -55,13 +55,13 @@
var dependency_list = input.data('dependency_list') || []; var dependency_list = input.data('dependency_list') || [];
var dependencies = []; var dependencies = [];
$.each(dependency_list, function(i, field_name) { $.each(dependency_list, function(i, field_name) {
dependencies.push('#' + row.find(field_name).find('input, select, textarea').attr('id')); dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id'));
}); });
if (dependencies.length) { if (dependencies.length) {
input.prepopulate(dependencies, input.attr('maxlength')); input.prepopulate(dependencies, input.attr('maxlength'));
} }
}); });
} };
$(rows).formset({ $(rows).formset({
prefix: "{{ inline_admin_formset.formset.prefix }}", prefix: "{{ inline_admin_formset.formset.prefix }}",
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}", addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}",

View File

@ -22,7 +22,7 @@
{% if inline_admin_form.form.non_field_errors %} {% if inline_admin_form.form.non_field_errors %}
<tr><td colspan="{{ inline_admin_form|cell_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr> <tr><td colspan="{{ inline_admin_form|cell_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr>
{% endif %} {% endif %}
<tr class="{% cycle "row1" "row2" %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}{% if forloop.last %} empty-form{% endif %}" <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 %}"
id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}"> id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
<td class="original"> <td class="original">
{% if inline_admin_form.original or inline_admin_form.show_url %}<p> {% if inline_admin_form.original or inline_admin_form.show_url %}<p>
@ -103,7 +103,7 @@
var dependency_list = input.data('dependency_list') || []; var dependency_list = input.data('dependency_list') || [];
var dependencies = []; var dependencies = [];
$.each(dependency_list, function(i, field_name) { $.each(dependency_list, function(i, field_name) {
dependencies.push('#' + row.find(field_name).find('input, select, textarea').attr('id')); dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id'));
}); });
if (dependencies.length) { if (dependencies.length) {
input.prepopulate(dependencies, input.attr('maxlength')); input.prepopulate(dependencies, input.attr('maxlength'));

View File

@ -7,7 +7,7 @@
<div class="form-row{% if line.fields|length_is:'1' and line.errors %} errors{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}"> <div class="form-row{% if line.fields|length_is:'1' and line.errors %} errors{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
{% if line.fields|length_is:'1' %}{{ line.errors }}{% endif %} {% if line.fields|length_is:'1' %}{{ line.errors }}{% endif %}
{% for field in line %} {% for field in line %}
<div{% if not line.fields|length_is:'1' %} class="field-box{% if not field.is_readonly and field.errors %} errors{% endif %}"{% endif %}> <div{% if not line.fields|length_is:'1' %} class="field-box{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}"{% endif %}>
{% if not line.fields|length_is:'1' and not field.is_readonly %}{{ field.errors }}{% endif %} {% if not line.fields|length_is:'1' and not field.is_readonly %}{{ field.errors }}{% endif %}
{% if field.is_checkbox %} {% if field.is_checkbox %}
{{ field.field }}{{ field.label_tag }} {{ field.field }}{{ field.label_tag }}

View File

@ -1,7 +1,7 @@
{% load l10n %} {% load l10n %}
<script type="text/javascript"> <script type="text/javascript">
(function($) { (function($) {
var field = null; var field;
{% for field in prepopulated_fields %} {% for field in prepopulated_fields %}
field = { field = {
@ -13,10 +13,13 @@
{% for dependency in field.dependencies %} {% for dependency in field.dependencies %}
field['dependency_ids'].push('#{{ dependency.auto_id }}'); field['dependency_ids'].push('#{{ dependency.auto_id }}');
field['dependency_list'].push('.{{ dependency.name }}'); field['dependency_list'].push('{{ dependency.name }}');
{% endfor %} {% endfor %}
$('.empty-form .{{ field.field.name }}').addClass('prepopulated_field'); {% comment %}
Mark prepopulated fields in the main form and stacked inlines (.empty-form .form-row) and in tabular inlines (.empty-form.form-row)
{% endcomment %}
$('.empty-form .form-row .field-{{ field.field.name }}, .empty-form.form-row .field-{{ field.field.name }}').addClass('prepopulated_field');
$(field.id).data('dependency_list', field['dependency_list']) $(field.id).data('dependency_list', field['dependency_list'])
.prepopulate(field['dependency_ids'], field.maxLength); .prepopulate(field['dependency_ids'], field.maxLength);
{% endfor %} {% endfor %}

View File

@ -1,4 +1,5 @@
import sys import sys
from selenium.common.exceptions import NoSuchElementException
from django.test import LiveServerTestCase from django.test import LiveServerTestCase
from django.utils.importlib import import_module from django.utils.importlib import import_module
@ -71,4 +72,16 @@ class AdminSeleniumWebDriverTestCase(LiveServerTestCase):
with Django. with Django.
""" """
return self.selenium.execute_script( return self.selenium.execute_script(
'return django.jQuery("%s").css("%s")' % (selector, attribute)) 'return django.jQuery("%s").css("%s")' % (selector, attribute))
def select_option(self, selector, value):
"""
Helper function to select the <OPTION> that has the value `value` and
that is in the <SELECT> widget identified by the CSS selector `selector`.
"""
options = self.selenium.find_elements_by_css_selector('%s option' % selector)
for option in options:
if option.get_attribute('value') == value:
option.click()
return
raise NoSuchElementException('Option "%s" not found in "%s"' % (value, selector))

View File

@ -26,7 +26,7 @@ from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
CoverLetter, Story, OtherStory, Book, Promo, ChapterXtra1, Pizza, Topping, CoverLetter, Story, OtherStory, Book, Promo, ChapterXtra1, Pizza, Topping,
Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug, Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug,
AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
AdminOrderedCallable, Report, Color2) AdminOrderedCallable, Report, Color2, MainPrepopulated, RelatedPrepopulated)
def callable_year(dt_value): def callable_year(dt_value):
@ -532,6 +532,38 @@ class CustomTemplateBooleanFieldListFilter(BooleanFieldListFilter):
class CustomTemplateFilterColorAdmin(admin.ModelAdmin): class CustomTemplateFilterColorAdmin(admin.ModelAdmin):
list_filter = (('warm', CustomTemplateBooleanFieldListFilter),) list_filter = (('warm', CustomTemplateBooleanFieldListFilter),)
# For Selenium Prepopulated tests -------------------------------------
class RelatedPrepopulatedInline1(admin.StackedInline):
fieldsets = (
(None, {
'fields': (('pubdate', 'status'), ('name', 'slug1', 'slug2',),)
}),
)
model = RelatedPrepopulated
extra = 1
prepopulated_fields = {'slug1': ['name', 'pubdate'],
'slug2': ['status', 'name']}
class RelatedPrepopulatedInline2(admin.TabularInline):
model = RelatedPrepopulated
extra = 1
prepopulated_fields = {'slug1': ['name', 'pubdate'],
'slug2': ['status', 'name']}
class MainPrepopulatedAdmin(admin.ModelAdmin):
inlines = [RelatedPrepopulatedInline1, RelatedPrepopulatedInline2]
fieldsets = (
(None, {
'fields': (('pubdate', 'status'), ('name', 'slug1', 'slug2',),)
}),
)
prepopulated_fields = {'slug1': ['name', 'pubdate'],
'slug2': ['status', 'name']}
site = admin.AdminSite(name="admin") site = admin.AdminSite(name="admin")
site.register(Article, ArticleAdmin) site.register(Article, ArticleAdmin)
site.register(CustomArticle, CustomArticleAdmin) site.register(CustomArticle, CustomArticleAdmin)
@ -576,6 +608,7 @@ site.register(CoverLetter, CoverLetterAdmin)
site.register(Story, StoryAdmin) site.register(Story, StoryAdmin)
site.register(OtherStory, OtherStoryAdmin) site.register(OtherStory, OtherStoryAdmin)
site.register(Report, ReportAdmin) site.register(Report, ReportAdmin)
site.register(MainPrepopulated, MainPrepopulatedAdmin)
# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
# That way we cover all four cases: # That way we cover all four cases:

View File

@ -575,3 +575,25 @@ class Report(models.Model):
def __unicode__(self): def __unicode__(self):
return self.title return self.title
class MainPrepopulated(models.Model):
name = models.CharField(max_length=100)
pubdate = models.DateField()
status = models.CharField(
max_length=20,
choices=(('option one', 'Option One'),
('option two', 'Option Two')))
slug1 = models.SlugField()
slug2 = models.SlugField()
class RelatedPrepopulated(models.Model):
parent = models.ForeignKey(MainPrepopulated)
name = models.CharField(max_length=75)
pubdate = models.DateField()
status = models.CharField(
max_length=20,
choices=(('option one', 'Option One'),
('option two', 'Option Two')))
slug1 = models.SlugField(max_length=50)
slug2 = models.SlugField(max_length=60)

View File

@ -17,7 +17,8 @@ from django.contrib.admin.models import LogEntry, DELETION
from django.contrib.admin.sites import LOGIN_FORM_KEY from django.contrib.admin.sites import LOGIN_FORM_KEY
from django.contrib.admin.util import quote from django.contrib.admin.util import quote
from django.contrib.admin.views.main import IS_POPUP_VAR from django.contrib.admin.views.main import IS_POPUP_VAR
from django.contrib.auth import REDIRECT_FIELD_NAME, admin from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.models import Group, User, Permission, UNUSABLE_PASSWORD from django.contrib.auth.models import Group, User, Permission, UNUSABLE_PASSWORD
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.forms.util import ErrorList from django.forms.util import ErrorList
@ -40,7 +41,7 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount,
FoodDelivery, RowLevelChangePermissionModel, Paper, CoverLetter, Story, FoodDelivery, RowLevelChangePermissionModel, Paper, CoverLetter, Story,
OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField, OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField,
AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable, AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable,
Report) Report, MainPrepopulated, RelatedPrepopulated)
ERROR_MESSAGE = "Please enter the correct username and password \ ERROR_MESSAGE = "Please enter the correct username and password \
@ -2892,6 +2893,110 @@ class PrePopulatedTest(TestCase):
response = self.client.get('/test_admin/admin/admin_views/prepopulatedpostlargeslug/add/') response = self.client.get('/test_admin/admin/admin_views/prepopulatedpostlargeslug/add/')
self.assertContains(response, "maxLength: 1000") # instead of 1,000 self.assertContains(response, "maxLength: 1000") # instead of 1,000
class SeleniumPrePopulatedTests(AdminSeleniumWebDriverTestCase):
urls = "regressiontests.admin_views.urls"
fixtures = ['admin-views-users.xml']
def test_basic(self):
"""
Ensure that the Javascript-automated prepopulated fields work with the
main form and with stacked and tabular inlines.
Refs #13068, #9264, #9983, #9784.
"""
self.admin_login(username='super', password='secret', login_url='/test_admin/admin/')
self.selenium.get('%s%s' % (self.live_server_url,
'/test_admin/admin/admin_views/mainprepopulated/add/'))
# Main form ----------------------------------------------------------
self.selenium.find_element_by_css_selector('#id_pubdate').send_keys('2012-02-18')
self.select_option('#id_status', 'option two')
self.selenium.find_element_by_css_selector('#id_name').send_keys(u' this is the mAin nÀMë and it\'s awεšome')
slug1 = self.selenium.find_element_by_css_selector('#id_slug1').get_attribute('value')
slug2 = self.selenium.find_element_by_css_selector('#id_slug2').get_attribute('value')
self.assertEqual(slug1, 'main-name-and-its-awesome-2012-02-18')
self.assertEqual(slug2, 'option-two-main-name-and-its-awesome')
# Stacked inlines ----------------------------------------------------
# Initial inline
self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-0-pubdate').send_keys('2011-12-17')
self.select_option('#id_relatedprepopulated_set-0-status', 'option one')
self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-0-name').send_keys(u' here is a sŤāÇkeð inline ! ')
slug1 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-0-slug1').get_attribute('value')
slug2 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-0-slug2').get_attribute('value')
self.assertEqual(slug1, 'here-stacked-inline-2011-12-17')
self.assertEqual(slug2, 'option-one-here-stacked-inline')
# Add an inline
self.selenium.find_element_by_css_selector('#relatedprepopulated_set-group .add-row a').click()
self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-1-pubdate').send_keys('1999-01-25')
self.select_option('#id_relatedprepopulated_set-1-status', 'option two')
self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-1-name').send_keys(u' now you haVe anöther sŤāÇkeð inline with a very ... loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog text... ')
slug1 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-1-slug1').get_attribute('value')
slug2 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-1-slug2').get_attribute('value')
self.assertEqual(slug1, 'now-you-have-another-stacked-inline-very-loooooooo') # 50 characters maximum for slug1 field
self.assertEqual(slug2, 'option-two-now-you-have-another-stacked-inline-very-looooooo') # 60 characters maximum for slug2 field
# Tabular inlines ----------------------------------------------------
# Initial inline
self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-0-pubdate').send_keys('1234-12-07')
self.select_option('#id_relatedprepopulated_set-2-0-status', 'option two')
self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-0-name').send_keys(u'And now, with a tÃbűlaŘ inline !!!')
slug1 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-0-slug1').get_attribute('value')
slug2 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-0-slug2').get_attribute('value')
self.assertEqual(slug1, 'and-now-tabular-inline-1234-12-07')
self.assertEqual(slug2, 'option-two-and-now-tabular-inline')
# Add an inline
self.selenium.find_element_by_css_selector('#relatedprepopulated_set-2-group .add-row a').click()
self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-1-pubdate').send_keys('1981-08-22')
self.select_option('#id_relatedprepopulated_set-2-1-status', 'option one')
self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-1-name').send_keys(u'a tÃbűlaŘ inline with ignored ;"&*^\%$#@-/`~ characters')
slug1 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-1-slug1').get_attribute('value')
slug2 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-1-slug2').get_attribute('value')
self.assertEqual(slug1, 'tabular-inline-ignored-characters-1981-08-22')
self.assertEqual(slug2, 'option-one-tabular-inline-ignored-characters')
# Save and check that everything is properly stored in the database
self.selenium.find_element_by_xpath('//input[@value="Save"]').click()
self.assertEqual(MainPrepopulated.objects.all().count(), 1)
MainPrepopulated.objects.get(
name=u' this is the mAin nÀMë and it\'s awεšome',
pubdate='2012-02-18',
status='option two',
slug1='main-name-and-its-awesome-2012-02-18',
slug2='option-two-main-name-and-its-awesome',
)
self.assertEqual(RelatedPrepopulated.objects.all().count(), 4)
RelatedPrepopulated.objects.get(
name=u' here is a sŤāÇkeð inline ! ',
pubdate='2011-12-17',
status='option one',
slug1='here-stacked-inline-2011-12-17',
slug2='option-one-here-stacked-inline',
)
RelatedPrepopulated.objects.get(
name=u' now you haVe anöther sŤāÇkeð inline with a very ... loooooooooooooooooo', # 75 characters in name field
pubdate='1999-01-25',
status='option two',
slug1='now-you-have-another-stacked-inline-very-loooooooo',
slug2='option-two-now-you-have-another-stacked-inline-very-looooooo',
)
RelatedPrepopulated.objects.get(
name=u'And now, with a tÃbűlaŘ inline !!!',
pubdate='1234-12-07',
status='option two',
slug1='and-now-tabular-inline-1234-12-07',
slug2='option-two-and-now-tabular-inline',
)
RelatedPrepopulated.objects.get(
name=u'a tÃbűlaŘ inline with ignored ;"&*^\%$#@-/`~ characters',
pubdate='1981-08-22',
status='option one',
slug1='tabular-inline-ignored-characters-1981-08-22',
slug2='option-one-tabular-inline-ignored-characters',
)
class ReadonlyTest(TestCase): class ReadonlyTest(TestCase):
urls = "regressiontests.admin_views.urls" urls = "regressiontests.admin_views.urls"
fixtures = ['admin-views-users.xml'] fixtures = ['admin-views-users.xml']