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:
parent
cfd0cb0064
commit
f0c2228709
|
@ -27,14 +27,14 @@
|
|||
var count = i + 1;
|
||||
$(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
|
||||
});
|
||||
}
|
||||
};
|
||||
var reinitDateTimeShortCuts = function() {
|
||||
// Reinitialize the calendar and clock widgets by force, yuck.
|
||||
if (typeof DateTimeShortcuts != "undefined") {
|
||||
$(".datetimeshortcuts").remove();
|
||||
DateTimeShortcuts.init();
|
||||
}
|
||||
}
|
||||
};
|
||||
var updateSelectFilter = function() {
|
||||
// If any SelectFilter widgets were added, instantiate a new instance.
|
||||
if (typeof SelectFilter != "undefined"){
|
||||
|
@ -47,7 +47,7 @@
|
|||
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static "admin/" %}");
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
var initPrepopulatedFields = function(row) {
|
||||
row.find('.prepopulated_field').each(function() {
|
||||
var field = $(this);
|
||||
|
@ -55,13 +55,13 @@
|
|||
var dependency_list = input.data('dependency_list') || [];
|
||||
var dependencies = [];
|
||||
$.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) {
|
||||
input.prepopulate(dependencies, input.attr('maxlength'));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
$(rows).formset({
|
||||
prefix: "{{ inline_admin_formset.formset.prefix }}",
|
||||
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}",
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
{% 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>
|
||||
{% 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 %}">
|
||||
<td class="original">
|
||||
{% if inline_admin_form.original or inline_admin_form.show_url %}<p>
|
||||
|
@ -103,7 +103,7 @@
|
|||
var dependency_list = input.data('dependency_list') || [];
|
||||
var dependencies = [];
|
||||
$.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) {
|
||||
input.prepopulate(dependencies, input.attr('maxlength'));
|
||||
|
|
|
@ -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 %}">
|
||||
{% if line.fields|length_is:'1' %}{{ line.errors }}{% endif %}
|
||||
{% 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 field.is_checkbox %}
|
||||
{{ field.field }}{{ field.label_tag }}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% load l10n %}
|
||||
<script type="text/javascript">
|
||||
(function($) {
|
||||
var field = null;
|
||||
var field;
|
||||
|
||||
{% for field in prepopulated_fields %}
|
||||
field = {
|
||||
|
@ -13,10 +13,13 @@
|
|||
|
||||
{% for dependency in field.dependencies %}
|
||||
field['dependency_ids'].push('#{{ dependency.auto_id }}');
|
||||
field['dependency_list'].push('.{{ dependency.name }}');
|
||||
field['dependency_list'].push('{{ dependency.name }}');
|
||||
{% 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'])
|
||||
.prepopulate(field['dependency_ids'], field.maxLength);
|
||||
{% endfor %}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import sys
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
|
||||
from django.test import LiveServerTestCase
|
||||
from django.utils.importlib import import_module
|
||||
|
@ -71,4 +72,16 @@ class AdminSeleniumWebDriverTestCase(LiveServerTestCase):
|
|||
with Django.
|
||||
"""
|
||||
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))
|
|
@ -26,7 +26,7 @@ from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
|
|||
CoverLetter, Story, OtherStory, Book, Promo, ChapterXtra1, Pizza, Topping,
|
||||
Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug,
|
||||
AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
|
||||
AdminOrderedCallable, Report, Color2)
|
||||
AdminOrderedCallable, Report, Color2, MainPrepopulated, RelatedPrepopulated)
|
||||
|
||||
|
||||
def callable_year(dt_value):
|
||||
|
@ -532,6 +532,38 @@ class CustomTemplateBooleanFieldListFilter(BooleanFieldListFilter):
|
|||
class CustomTemplateFilterColorAdmin(admin.ModelAdmin):
|
||||
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.register(Article, ArticleAdmin)
|
||||
site.register(CustomArticle, CustomArticleAdmin)
|
||||
|
@ -576,6 +608,7 @@ site.register(CoverLetter, CoverLetterAdmin)
|
|||
site.register(Story, StoryAdmin)
|
||||
site.register(OtherStory, OtherStoryAdmin)
|
||||
site.register(Report, ReportAdmin)
|
||||
site.register(MainPrepopulated, MainPrepopulatedAdmin)
|
||||
|
||||
# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
|
||||
# That way we cover all four cases:
|
||||
|
|
|
@ -575,3 +575,25 @@ class Report(models.Model):
|
|||
|
||||
def __unicode__(self):
|
||||
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)
|
|
@ -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.util import quote
|
||||
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.contenttypes.models import ContentType
|
||||
from django.forms.util import ErrorList
|
||||
|
@ -40,7 +41,7 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount,
|
|||
FoodDelivery, RowLevelChangePermissionModel, Paper, CoverLetter, Story,
|
||||
OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField,
|
||||
AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable,
|
||||
Report)
|
||||
Report, MainPrepopulated, RelatedPrepopulated)
|
||||
|
||||
|
||||
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/')
|
||||
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):
|
||||
urls = "regressiontests.admin_views.urls"
|
||||
fixtures = ['admin-views-users.xml']
|
||||
|
|
Loading…
Reference in New Issue