Ticket #27487: ticket_27487.diff

File ticket_27487.diff, 39.7 KB (added by Adonys Alea Boffill, 7 years ago)

New patch with the corrections related to template-based widget rendering.

  • django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js

    diff --git a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js
    index 3fb1e52..9f68bc1 100644
    a b  
    8686                } else {
    8787                    elem.value = newId;
    8888                }
     89            } else if (elemName === 'UL') {
     90                var numberOfItems = elem.children.length,
     91                    // If the input field is wrapped into a label.
     92                    wrapLabel = (String(elem.getAttribute('data-wrap-label')).toLowerCase() == "true"),
     93                    // Multiple selection or not.
     94                    multiple = (String(elem.getAttribute('data-multiple')).toLowerCase() == "true");
     95
     96                if (numberOfItems > 0) {
     97                    if (!multiple) {
     98                        // Remove previous input checked, the new item will marked as checked.
     99                        elem.querySelector('input:checked').checked = false;
     100                    }
     101                    // Clone an item and include this as the final item of the UL list.
     102                    // The clone will be the new item and their information will be
     103                    // replaced with the received data.
     104                    elem.appendChild(elem.lastElementChild.cloneNode(true));
     105                    // newItem's var is the new copy, is necessary to get a new instance to work
     106                    // with it in order to avoid to modify the cloned item.
     107                    var newItem = elem.lastElementChild,
     108                        newItemInput = newItem.getElementsByTagName('input')[0];
     109
     110                    newItemInput.value = newId;
     111                    newItemInput.setAttribute('id', elem.id + '_' + numberOfItems);
     112                    // Mark the new item's input as checked.
     113                    newItemInput.checked = true;
     114
     115                    if (wrapLabel) {
     116                        var newItemLabel = newItem.getElementsByTagName('label')[0];
     117                        newItemLabel.lastChild.textContent = ' ' + newRepr;
     118                        newItemLabel.setAttribute('for', elem.id + '_' + numberOfItems);
     119                    }
     120                } else {
     121                    // The UL list is empty, then is necessary to build the new item.
     122                    var newItem = document.createElement('li'),
     123                        newItemInput = document.createElement('input');
     124
     125                    newItemInput.setAttribute('type', (multiple) ? 'checkbox' : 'radio');
     126                    // The elem.id is composed by 'id_' and the name of the related field.
     127                    // Example: 'id_books', is only necessary to remove 'id_' to get the
     128                    // name of the field.
     129                    newItemInput.setAttribute('name', elem.id.replace('id_', ''));
     130                    newItemInput.setAttribute('id', elem.id + '_0');
     131                    // Mark the new item's input as checked.
     132                    newItemInput.checked = true;
     133                    newItemInput.value = newId;
     134
     135                    if (wrapLabel) {
     136                        var newItemLabel = document.createElement('label');
     137                        newItemLabel.textContent = ' ' + newRepr;
     138                        newItemLabel.setAttribute('for', elem.id + '_0');
     139                        // Insert input into label.
     140                        newItemLabel.insertBefore(newItemInput, newItemLabel.firstChild);
     141                        // Append input wrapped into new item.
     142                        newItem.appendChild(newItemLabel);
     143                    } else {
     144                        // Append input no wrapped into new item.
     145                        newItem.appendChild(newItemInput);
     146                    }
     147                    // Append new item into UL list.
     148                    elem.appendChild(newItem);
     149                }
    89150            }
    90151            // Trigger a change event to update related links if required.
    91152            $(elem).trigger('change');
  • django/forms/jinja2/django/forms/widgets/multiple_input.html

    diff --git a/django/forms/jinja2/django/forms/widgets/multiple_input.html b/django/forms/jinja2/django/forms/widgets/multiple_input.html
    index be3d449..324f495 100644
    a b  
    1 {% set id = widget.attrs.id %}<ul{% if id %} id="{{ id }}"{% endif %}>{% for group, options, index in widget.optgroups %}{% if group %}
     1{% set id = widget.attrs.id %}<ul{% if id %} id="{{ id }}"{% endif %} data-wrap-label="{{ wrap_label|lower }}" data-multiple="{{ multiple|lower }}">{% for group, options, index in widget.optgroups %}{% if group %}
    22  <li>{{ group }}<ul{% if id %} id="{{ id }}_{{ index }}{% endif %}">{% endif %}{% for widget in options %}
    33    <li>{% include widget.template_name %}</li>{% endfor %}{% if group %}
    44  </ul></li>{% endif %}{% endfor %}
  • django/forms/templates/django/forms/widgets/multiple_input.html

    diff --git a/django/forms/templates/django/forms/widgets/multiple_input.html b/django/forms/templates/django/forms/widgets/multiple_input.html
    index 60282ff..55881ba 100644
    a b  
    1 {% with id=widget.attrs.id %}<ul{% if id %} id="{{ id }}"{% endif %}>{% for group, options, index in widget.optgroups %}{% if group %}
     1{% with id=widget.attrs.id %}<ul{% if id %} id="{{ id }}"{% endif %} data-wrap-label="{{ wrap_label|lower }}" data-multiple="{{ multiple|lower }}">{% for group, options, index in widget.optgroups %}{% if group %}
    22  <li>{{ group }}<ul{% if id %} id="{{ id }}_{{ index }}{% endif %}">{% endif %}{% for option in options %}
    33    <li>{% include option.template_name with widget=option %}</li>{% endfor %}{% if group %}
    44  </ul></li>{% endif %}{% endfor %}
  • django/forms/widgets.py

    diff --git a/django/forms/widgets.py b/django/forms/widgets.py
    index 4f028d3..5aed91a 100644
    a b class ChoiceWidget(Widget):  
    626626        context = super(ChoiceWidget, self).get_context(name, value, attrs)
    627627        context['widget']['optgroups'] = self.optgroups(name, context['widget']['value'], attrs)
    628628        context['wrap_label'] = True
     629        context['multiple'] = self.allow_multiple_selected
    629630        return context
    630631
    631632    def id_for_label(self, id_, index='0'):
  • js_tests/admin/RelatedObjectLookups.test.js

    diff --git a/js_tests/admin/RelatedObjectLookups.test.js b/js_tests/admin/RelatedObjectLookups.test.js
    index 9ec1eed..04ad8bf 100644
    a b  
    33/* eslint global-strict: 0, strict: 0 */
    44'use strict';
    55
    6 QUnit.module('admin.RelatedObjectLookups');
     6QUnit.module('admin.RelatedObjectLookups', {
     7    beforeEach: function() {
     8        this.win = {name: 'id_teacher', close: function() {}};
     9    }
     10});
    711
    812QUnit.test('id_to_windowname', function(assert) {
    913    assert.equal(id_to_windowname('.test'), '__dot__test');
    QUnit.test('windowname_to_id', function(assert) {  
    1418    assert.equal(windowname_to_id('__dot__test'), '.test');
    1519    assert.equal(windowname_to_id('misc__dash__test'), 'misc-test');
    1620});
     21
     22QUnit.test('dismissAddRelatedObjectPopup/Select', function(assert) {
     23    var $ = django.jQuery;
     24    $('<div class="related-widget-wrapper"></div>').appendTo('#qunit-fixture');
     25    $('<select id="id_teacher" name="teacher"></select>').appendTo('div.related-widget-wrapper');
     26    $('<option value="" selected="">---------</option>').appendTo('#id_teacher');
     27
     28    dismissAddRelatedObjectPopup(this.win, '1', 'Alex');
     29    assert.equal($('#id_teacher').find('option').length, 2);
     30    assert.equal($('#id_teacher').find('option').last().text(), 'Alex');
     31    assert.ok($('#id_teacher').find('option').last()[0].selected);
     32
     33    dismissAddRelatedObjectPopup(this.win, '1', 'Susan');
     34    assert.equal($('#id_teacher').find('option').length, 3);
     35    assert.equal($('#id_teacher').find('option').last().text(), 'Susan');
     36    // Only the last new item is marked as checked.
     37    assert.equal($('#id_teacher').find('option:selected').length, 1);
     38    assert.ok($('#id_teacher').find('option').last()[0].selected);
     39});
     40
     41QUnit.test('dismissAddRelatedObjectPopup/SelectMultiple', function(assert) {
     42    var $ = django.jQuery;
     43    $('<div class="related-widget-wrapper"></div>').appendTo('#qunit-fixture');
     44    $('<select multiple="multiple" id="id_teacher" name="teacher"></select>').appendTo('div.related-widget-wrapper');
     45
     46    dismissAddRelatedObjectPopup(this.win, '1', 'Alex');
     47    assert.equal($('#id_teacher').find('option').length, 1);
     48    assert.equal($('#id_teacher').find('option').first().text(), 'Alex');
     49    assert.ok($('#id_teacher').find('option').first()[0].selected);
     50
     51    dismissAddRelatedObjectPopup(this.win, '2', 'Susan');
     52    assert.equal($('#id_teacher').find('option').length, 2);
     53    assert.equal($('#id_teacher').find('option').last().text(), 'Susan');
     54    // All new items are marked as checked.
     55    assert.equal($('#id_teacher').find('option:selected').length, 2);
     56    assert.ok($('#id_teacher').find('option').first()[0].selected);
     57    assert.ok($('#id_teacher').find('option').last()[0].selected);
     58});
     59
     60QUnit.test('dismissAddRelatedObjectPopup/TextInput', function(assert) {
     61    var $ = django.jQuery;
     62    $('<div class="related-widget-wrapper"></div>').appendTo('#qunit-fixture');
     63    $('<input id="id_teacher" name="teacher" type="text" value="">').appendTo('div.related-widget-wrapper');
     64
     65    dismissAddRelatedObjectPopup(this.win, '1', 'Alex');
     66    assert.equal($('#id_teacher').val(), '1');
     67
     68    dismissAddRelatedObjectPopup(this.win, '2', 'Susan');
     69    assert.equal($('#id_teacher').val(), '2');
     70});
     71
     72QUnit.test('dismissAddRelatedObjectPopup/TextInput.vManyToManyRawIdAdminField', function(assert) {
     73    var $ = django.jQuery;
     74    $('<div class="related-widget-wrapper"></div>').appendTo('#qunit-fixture');
     75    $('<input id="id_teacher" name="teacher" type="text" value="" class="vManyToManyRawIdAdminField">').appendTo(
     76        'div.related-widget-wrapper'
     77    );
     78
     79    dismissAddRelatedObjectPopup(this.win, '1', 'Alex');
     80    assert.equal($('#id_teacher').val(), '1');
     81
     82    dismissAddRelatedObjectPopup(this.win, '2', 'Susan');
     83    assert.equal($('#id_teacher').val(), '1,2');
     84});
     85
     86QUnit.test('dismissAddRelatedObjectPopup/RadioSelect', function(assert) {
     87    var $ = django.jQuery;
     88    $('<div class="related-widget-wrapper"></div>').appendTo('#qunit-fixture');
     89    $('<ul id="id_teacher" data-wrap-label="true" data-multiple="false"></ul>').appendTo('div.related-widget-wrapper');
     90
     91    // Add the first item.
     92    dismissAddRelatedObjectPopup(this.win, '1', 'Alex');
     93    assert.equal($('#id_teacher').find('li').length, 1);
     94    assert.equal($('input#id_teacher_0').length, 1);
     95    assert.equal($('input#id_teacher_0').val(), 1);
     96    assert.equal($('#id_teacher').find('li').last().find('label').text(), ' Alex');
     97    assert.equal($('#id_teacher').find('input').last()[0].type, "radio");
     98    assert.ok($('#id_teacher').find('input').last()[0].checked);
     99
     100    // Add the other item.
     101    dismissAddRelatedObjectPopup(this.win, '2', 'Susan');
     102    assert.equal($('#id_teacher').find('li').length, 2);
     103    assert.equal($('input#id_teacher_1').length, 1);
     104    assert.equal($('input#id_teacher_1').val(), 2);
     105    assert.equal($('#id_teacher').find('li').last().find('label').text(), ' Susan');
     106    assert.equal($('#id_teacher').find('input').last()[0].type, "radio");
     107    // Only the last new item is marked as checked like the select behavior.
     108    assert.equal($('#id_teacher').find('input:checked').length, 1);
     109    assert.notOk($('#id_teacher').find('input').first()[0].checked);
     110    assert.ok($('#id_teacher').find('input').last()[0].checked);
     111});
     112
     113QUnit.test('dismissAddRelatedObjectPopup/CheckboxSelectMultiple', function(assert) {
     114    var $ = django.jQuery;
     115    $('<div class="related-widget-wrapper"></div>').appendTo('#qunit-fixture');
     116    $('<ul id="id_teacher" data-wrap-label="true" data-multiple="true"></ul>').appendTo('div.related-widget-wrapper');
     117
     118    // Add the first item.
     119    dismissAddRelatedObjectPopup(this.win, '1', 'Alex');
     120    assert.equal($('#id_teacher').find('li').length, 1);
     121    assert.equal($('input#id_teacher_0').length, 1);
     122    assert.equal($('input#id_teacher_0').val(), 1);
     123    assert.equal($('#id_teacher').find('li').first().find('label').text(), ' Alex');
     124    assert.equal($('#id_teacher').find('input').first()[0].type, "checkbox");
     125    assert.ok($('#id_teacher').find('input').first()[0].checked);
     126
     127    // Add the other item.
     128    dismissAddRelatedObjectPopup(this.win, '2', 'Susan');
     129    assert.equal($('#id_teacher').find('li').length, 2);
     130    assert.equal($('input#id_teacher_1').length, 1);
     131    assert.equal($('input#id_teacher_1').val(), 2);
     132    assert.equal($('#id_teacher').find('li').last().find('label').text(), ' Susan');
     133    assert.equal($('#id_teacher').find('input').last()[0].type, "checkbox");
     134    // All new items are marked as checked like the select multiple behavior.
     135    assert.equal($('#id_teacher').find('input:checked').length, 2);
     136    assert.ok($('#id_teacher').find('input').first()[0].checked);
     137    assert.ok($('#id_teacher').find('input').last()[0].checked);
     138});
     139
  • tests/admin_widgets/models.py

    diff --git a/tests/admin_widgets/models.py b/tests/admin_widgets/models.py
    index 274c36e..9b3ed2b 100644
    a b class Profile(models.Model):  
    163163
    164164    def __str__(self):
    165165        return self.user.username
     166
     167
     168@python_2_unicode_compatible
     169class Manager(models.Model):
     170    name = models.CharField(max_length=255)
     171
     172    def __str__(self):
     173        return self.name
     174
     175
     176@python_2_unicode_compatible
     177class Team(models.Model):
     178    name = models.CharField(max_length=255)
     179    team_manager = models.ForeignKey(Manager, models.CASCADE, null=True, blank=True)
     180
     181    def __str__(self):
     182        return self.name
     183
     184
     185@python_2_unicode_compatible
     186class City(models.Model):
     187    name = models.CharField(max_length=255)
     188    teams = models.ManyToManyField(Team)
     189
     190    def __str__(self):
     191        return self.name
     192
     193
     194@python_2_unicode_compatible
     195class Player(models.Model):
     196    """
     197    This model will be used to test ForeignKey widget relation field as
     198    RadioSelect in RelatedFieldWidgetSeleniumTests, the default widget
     199    will be overwritten in the test admin file.
     200    """
     201    name = models.CharField(max_length=255)
     202    team = models.ForeignKey(Team, models.CASCADE, null=True, blank=True)
     203
     204    def __str__(self):
     205        return self.name
     206
     207
     208@python_2_unicode_compatible
     209class Fan(models.Model):
     210    """
     211    This model will be used to test ManyToMany widget relation field as
     212    CheckboxSelectMultiple in RelatedFieldWidgetSeleniumTests, the default
     213    widget will be overwritten in the test admin file.
     214    """
     215    name = models.CharField(max_length=255)
     216    teams = models.ManyToManyField(Team)
     217
     218    def __str__(self):
     219        return self.name
  • tests/admin_widgets/tests.py

    diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py
    index b74df4e..c54189f 100644
    a b from django.urls import reverse  
    2323from django.utils import six, translation
    2424
    2525from .models import (
    26     Advisor, Album, Band, Bee, Car, Company, Event, Honeycomb, Individual,
    27     Inventory, Member, MyFileField, Profile, School, Student,
     26    Advisor, Album, Band, Bee, Car, City, Company, Event, Fan, Honeycomb, Individual,
     27    Inventory, Manager, Member, MyFileField, Player, Profile, School, Student, Team,
    2828)
    2929from .widgetadmin import site as widget_admin_site
    3030
    class RelatedFieldWidgetSeleniumTests(AdminWidgetSeleniumTestCase):  
    12941294        profiles = Profile.objects.all()
    12951295        self.assertEqual(len(profiles), 1)
    12961296        self.assertEqual(profiles[0].user.username, username_value)
     1297
     1298    def test_ForeignKey_as_Select(self):
     1299        self.admin_login(username='super', password='secret', login_url='/')
     1300        self.selenium.get(self.live_server_url + reverse('admin:admin_widgets_team_add'))
     1301
     1302        main_window = self.selenium.current_window_handle
     1303        # Only an empty option in the Select
     1304        self.assertEqual(len(self.selenium.find_elements_by_css_selector('#id_team_manager option')), 1)
     1305
     1306        # Click the Add Manager button to add new
     1307        self.selenium.find_element_by_id('add_id_team_manager').click()
     1308        self.wait_for_popup()
     1309        self.selenium.switch_to.window('id_team_manager')
     1310
     1311        manager_name_field = self.selenium.find_element_by_id('id_name')
     1312        manager_name_field.send_keys('Martin')
     1313
     1314        save_button_css_selector = '.submit-row > input[type=submit]'
     1315        self.selenium.find_element_by_css_selector(save_button_css_selector).click()
     1316        self.selenium.switch_to.window(main_window)
     1317
     1318        self.assertEqual(len(self.selenium.find_elements_by_css_selector('#id_team_manager option')), 2)
     1319        new_manager = Manager.objects.get(pk=1)
     1320        self.assertEqual(new_manager.name, 'Martin')
     1321        new_option_selector = '#id_team_manager option[value="%d"]' % new_manager.pk
     1322        self.assertTrue(self.selenium.find_element_by_css_selector(new_option_selector).get_attribute('selected'))
     1323
     1324        # Click the Add Manager button to add other
     1325        self.selenium.find_element_by_id('add_id_team_manager').click()
     1326        self.wait_for_popup()
     1327        self.selenium.switch_to.window('id_team_manager')
     1328
     1329        manager_name_field = self.selenium.find_element_by_id('id_name')
     1330        manager_name_field.send_keys('Joe')
     1331
     1332        self.selenium.find_element_by_css_selector(save_button_css_selector).click()
     1333        self.selenium.switch_to.window(main_window)
     1334
     1335        self.assertEqual(len(self.selenium.find_elements_by_css_selector('#id_team_manager option')), 3)
     1336        new_manager = Manager.objects.get(pk=2)
     1337        self.assertEqual(new_manager.name, 'Joe')
     1338        new_option_selector = '#id_team_manager option[value="%d"]' % new_manager.pk
     1339        self.assertTrue(self.selenium.find_element_by_css_selector(new_option_selector).get_attribute('selected'))
     1340
     1341        # Go ahead and submit the form to make sure it works
     1342        team_name_field = self.selenium.find_element_by_id('id_name')
     1343        team_name_field.send_keys('Blue Jays')
     1344        self.selenium.find_element_by_css_selector(save_button_css_selector).click()
     1345        self.wait_for_text('li.success', 'The team "Blue Jays" was added successfully.')
     1346        teams = Team.objects.all()
     1347        self.assertEqual(len(teams), 1)
     1348        # 'Joe' match because was the last selected Manager
     1349        self.assertEqual(teams[0].team_manager.name, 'Joe')
     1350
     1351    def test_ManyToMany_as_SelectMultiple(self):
     1352        self.admin_login(username='super', password='secret', login_url='/')
     1353        self.selenium.get(self.live_server_url + reverse('admin:admin_widgets_city_add'))
     1354
     1355        main_window = self.selenium.current_window_handle
     1356
     1357        # Click the Add Team button to add new
     1358        self.selenium.find_element_by_id('add_id_teams').click()
     1359        self.wait_for_popup()
     1360        self.selenium.switch_to.window('id_teams')
     1361
     1362        team_name_field = self.selenium.find_element_by_id('id_name')
     1363        team_name_field.send_keys('Cubs')
     1364
     1365        save_button_css_selector = '.submit-row > input[type=submit]'
     1366        self.selenium.find_element_by_css_selector(save_button_css_selector).click()
     1367        self.selenium.switch_to.window(main_window)
     1368
     1369        self.assertEqual(len(self.selenium.find_elements_by_css_selector('#id_teams option')), 1)
     1370        new_team = Team.objects.get(name='Cubs')
     1371        new_option_selector = '#id_teams option[value="%d"]' % new_team.pk
     1372        self.assertTrue(self.selenium.find_element_by_css_selector(new_option_selector).get_attribute('selected'))
     1373
     1374        # Click the Add Team button to add other
     1375        self.selenium.find_element_by_id('add_id_teams').click()
     1376        self.wait_for_popup()
     1377        self.selenium.switch_to.window('id_teams')
     1378
     1379        team_name_field = self.selenium.find_element_by_id('id_name')
     1380        team_name_field.send_keys('White Sox')
     1381
     1382        self.selenium.find_element_by_css_selector(save_button_css_selector).click()
     1383        self.selenium.switch_to.window(main_window)
     1384
     1385        self.assertEqual(len(self.selenium.find_elements_by_css_selector('#id_teams option')), 2)
     1386        new_team = Team.objects.get(name='White Sox')
     1387        new_option_selector = '#id_teams option[value="%d"]' % new_team.pk
     1388        self.assertTrue(self.selenium.find_element_by_css_selector(new_option_selector).get_attribute('selected'))
     1389
     1390        # Go ahead and submit the form to make sure it works
     1391        city_name_field = self.selenium.find_element_by_id('id_name')
     1392        city_name_field.send_keys('Chicago')
     1393        self.selenium.find_element_by_css_selector(save_button_css_selector).click()
     1394        self.wait_for_text('li.success', 'The city "Chicago" was added successfully.')
     1395        cities = City.objects.all()
     1396        self.assertEqual(len(cities), 1)
     1397        self.assertEqual(cities[0].name, 'Chicago')
     1398        teams = cities[0].teams.all()
     1399        self.assertEqual(len(teams), 2)
     1400        self.assertListEqual([t.name for t in teams], ['Cubs', 'White Sox'])
     1401
     1402    def test_ForeignKey_as_RadioSelect(self):
     1403        self.admin_login(username='super', password='secret', login_url='/')
     1404        self.selenium.get(self.live_server_url + reverse('admin:admin_widgets_player_add'))
     1405
     1406        main_window = self.selenium.current_window_handle
     1407        # Only an empty option in the RadioSelect
     1408        self.assertEqual(len(self.selenium.find_elements_by_css_selector('#id_team li')), 1)
     1409
     1410        # Click the Add Team button to add new
     1411        self.selenium.find_element_by_id('add_id_team').click()
     1412        self.wait_for_popup()
     1413        self.selenium.switch_to.window('id_team')
     1414
     1415        team_name_field = self.selenium.find_element_by_id('id_name')
     1416        team_name_field.send_keys('Cardinals')
     1417
     1418        save_button_css_selector = '.submit-row > input[type=submit]'
     1419        self.selenium.find_element_by_css_selector(save_button_css_selector).click()
     1420        self.selenium.switch_to.window(main_window)
     1421
     1422        self.assertEqual(len(self.selenium.find_elements_by_css_selector('#id_team li')), 2)
     1423        new_team = Team.objects.get(name='Cardinals')
     1424        new_input_selector = '#id_team li input[value="%d"]' % new_team.pk
     1425        self.assertTrue(self.selenium.find_element_by_css_selector(new_input_selector).get_attribute('checked'))
     1426
     1427        # Click the Add Team button to add other
     1428        self.selenium.find_element_by_id('add_id_team').click()
     1429        self.wait_for_popup()
     1430        self.selenium.switch_to.window('id_team')
     1431
     1432        team_name_field = self.selenium.find_element_by_id('id_name')
     1433        team_name_field.send_keys('Red Sox')
     1434
     1435        save_button_css_selector = '.submit-row > input[type=submit]'
     1436        self.selenium.find_element_by_css_selector(save_button_css_selector).click()
     1437        self.selenium.switch_to.window(main_window)
     1438
     1439        self.assertEqual(len(self.selenium.find_elements_by_css_selector('#id_team li')), 3)
     1440        new_team = Team.objects.get(name='Red Sox')
     1441        new_input_selector = '#id_team li input[value="%d"]' % new_team.pk
     1442        self.assertTrue(self.selenium.find_element_by_css_selector(new_input_selector).get_attribute('checked'))
     1443
     1444        # Go ahead and submit the form to make sure it works
     1445        player_name_field = self.selenium.find_element_by_id('id_name')
     1446        player_name_field.send_keys('David')
     1447        self.selenium.find_element_by_css_selector(save_button_css_selector).click()
     1448        self.wait_for_text('li.success', 'The player "David" was added successfully.')
     1449        player = Player.objects.all()
     1450        self.assertEqual(len(player), 1)
     1451        # 'Red Sox' match because was the last selected Team
     1452        self.assertEqual(player[0].team.name, 'Red Sox')
     1453
     1454    def test_ManyToMany_as_CheckboxSelectMultiple(self):
     1455        self.admin_login(username='super', password='secret', login_url='/')
     1456        self.selenium.get(self.live_server_url + reverse('admin:admin_widgets_fan_add'))
     1457
     1458        main_window = self.selenium.current_window_handle
     1459
     1460        # Click the Add Team button to add new
     1461        self.selenium.find_element_by_id('add_id_teams').click()
     1462        self.wait_for_popup()
     1463        self.selenium.switch_to.window('id_teams')
     1464
     1465        team_name_field = self.selenium.find_element_by_id('id_name')
     1466        team_name_field.send_keys('Cubs')
     1467
     1468        save_button_css_selector = '.submit-row > input[type=submit]'
     1469        self.selenium.find_element_by_css_selector(save_button_css_selector).click()
     1470        self.selenium.switch_to.window(main_window)
     1471
     1472        self.assertEqual(len(self.selenium.find_elements_by_css_selector('#id_teams li')), 1)
     1473        new_team = Team.objects.get(name='Cubs')
     1474        new_input_selector = '#id_teams li input[value="%d"]' % new_team.pk
     1475        self.assertTrue(self.selenium.find_element_by_css_selector(new_input_selector).get_attribute('checked'))
     1476
     1477        # Click the Add Team button to add other
     1478        self.selenium.find_element_by_id('add_id_teams').click()
     1479        self.wait_for_popup()
     1480        self.selenium.switch_to.window('id_teams')
     1481
     1482        team_name_field = self.selenium.find_element_by_id('id_name')
     1483        team_name_field.send_keys('White Sox')
     1484
     1485        self.selenium.find_element_by_css_selector(save_button_css_selector).click()
     1486        self.selenium.switch_to.window(main_window)
     1487
     1488        self.assertEqual(len(self.selenium.find_elements_by_css_selector('#id_teams li')), 2)
     1489        new_team = Team.objects.get(name='White Sox')
     1490        new_input_selector = '#id_teams li input[value="%d"]' % new_team.pk
     1491        self.assertTrue(self.selenium.find_element_by_css_selector(new_input_selector).get_attribute('checked'))
     1492
     1493        # Go ahead and submit the form to make sure it works
     1494        fan_name_field = self.selenium.find_element_by_id('id_name')
     1495        fan_name_field.send_keys('John')
     1496        self.selenium.find_element_by_css_selector(save_button_css_selector).click()
     1497        self.wait_for_text('li.success', 'The fan "John" was added successfully.')
     1498        fans = Fan.objects.all()
     1499        self.assertEqual(len(fans), 1)
     1500        self.assertEqual(fans[0].name, 'John')
     1501        teams = fans[0].teams.all()
     1502        self.assertEqual(len(teams), 2)
     1503        self.assertListEqual([t.name for t in teams], ['Cubs', 'White Sox'])
  • tests/admin_widgets/widgetadmin.py

    diff --git a/tests/admin_widgets/widgetadmin.py b/tests/admin_widgets/widgetadmin.py
    index a03e044..235140f 100644
    a b  
     1from django import forms
    12from django.contrib import admin
     3from django.forms import CheckboxSelectMultiple, RadioSelect
    24
    35from .models import (
    4     Advisor, Album, Band, Bee, Car, CarTire, Event, Inventory, Member, Profile,
    5     School, User,
     6    Advisor, Album, Band, Bee, Car, CarTire, City, Event, Fan, Inventory,
     7    Manager, Member, Player, Profile, School, Team, User,
    68)
    79
    810
    class SchoolAdmin(admin.ModelAdmin):  
    3739    filter_horizontal = ('alumni',)
    3840
    3941
     42class PlayerForm(forms.ModelForm):
     43
     44    class Meta:
     45        model = Player
     46        fields = '__all__'
     47        widgets = {'team': RadioSelect}
     48
     49
     50class PlayerAdmin(admin.ModelAdmin):
     51    form = PlayerForm
     52
     53
     54class FanForm(forms.ModelForm):
     55
     56    class Meta:
     57        model = Fan
     58        fields = '__all__'
     59        widgets = {'teams': CheckboxSelectMultiple}
     60
     61
     62class FanAdmin(admin.ModelAdmin):
     63    form = FanForm
     64
     65
    4066site = WidgetAdmin(name='widget-admin')
    4167
    4268site.register(User)
    site.register(Advisor)  
    5783site.register(School, SchoolAdmin)
    5884
    5985site.register(Profile)
     86
     87site.register(Team)
     88site.register(Manager)
     89site.register(City)
     90site.register(Player, PlayerAdmin)
     91site.register(Fan, FanAdmin)
  • tests/forms_tests/tests/test_forms.py

    diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py
    index 2b93575..bcfe74a 100644
    a b class FormsTestCase(SimpleTestCase):  
    583583            language = ChoiceField(choices=[('P', 'Python'), ('J', 'Java')], widget=RadioSelect)
    584584
    585585        f = FrameworkForm(auto_id=False)
    586         self.assertHTMLEqual(str(f['language']), """<ul>
     586        self.assertHTMLEqual(str(f['language']), """<ul data-wrap-label="true" data-multiple="false">
    587587<li><label><input type="radio" name="language" value="P" required /> Python</label></li>
    588588<li><label><input type="radio" name="language" value="J" required /> Java</label></li>
    589589</ul>""")
    590590        self.assertHTMLEqual(f.as_table(), """<tr><th>Name:</th><td><input type="text" name="name" required /></td></tr>
    591 <tr><th>Language:</th><td><ul>
     591<tr><th>Language:</th><td><ul data-wrap-label="true" data-multiple="false">
    592592<li><label><input type="radio" name="language" value="P" required /> Python</label></li>
    593593<li><label><input type="radio" name="language" value="J" required /> Java</label></li>
    594594</ul></td></tr>""")
    595595        self.assertHTMLEqual(f.as_ul(), """<li>Name: <input type="text" name="name" required /></li>
    596 <li>Language: <ul>
     596<li>Language: <ul data-wrap-label="true" data-multiple="false">
    597597<li><label><input type="radio" name="language" value="P" required /> Python</label></li>
    598598<li><label><input type="radio" name="language" value="J" required /> Java</label></li>
    599599</ul></li>""")
    class FormsTestCase(SimpleTestCase):  
    604604        f = FrameworkForm(auto_id='id_%s')
    605605        self.assertHTMLEqual(
    606606            str(f['language']),
    607             """<ul id="id_language">
     607            """<ul id="id_language" data-wrap-label="true" data-multiple="false">
    608608<li><label for="id_language_0"><input type="radio" id="id_language_0" value="P" name="language" required />
    609609Python</label></li>
    610610<li><label for="id_language_1"><input type="radio" id="id_language_1" value="J" name="language" required />
    Java</label></li>  
    618618        self.assertHTMLEqual(
    619619            f.as_table(),
    620620            """<tr><th><label for="id_name">Name:</label></th><td><input type="text" name="name" id="id_name" required /></td></tr>
    621 <tr><th><label for="id_language_0">Language:</label></th><td><ul id="id_language">
     621<tr><th><label for="id_language_0">Language:</label></th><td><ul id="id_language" data-wrap-label="true" data-multiple="false">
    622622<li><label for="id_language_0"><input type="radio" id="id_language_0" value="P" name="language" required />
    623623Python</label></li>
    624624<li><label for="id_language_1"><input type="radio" id="id_language_1" value="J" name="language" required />
    Java</label></li>  
    628628        self.assertHTMLEqual(
    629629            f.as_ul(),
    630630            """<li><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" required /></li>
    631 <li><label for="id_language_0">Language:</label> <ul id="id_language">
     631<li><label for="id_language_0">Language:</label> <ul id="id_language" data-wrap-label="true" data-multiple="false">
    632632<li><label for="id_language_0"><input type="radio" id="id_language_0" value="P" name="language" required />
    633633Python</label></li>
    634634<li><label for="id_language_1"><input type="radio" id="id_language_1" value="J" name="language" required />
    Java</label></li>  
    638638        self.assertHTMLEqual(
    639639            f.as_p(),
    640640            """<p><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" required /></p>
    641 <p><label for="id_language_0">Language:</label> <ul id="id_language">
     641<p><label for="id_language_0">Language:</label> <ul id="id_language" data-wrap-label="true" data-multiple="false">
    642642<li><label for="id_language_0"><input type="radio" id="id_language_0" value="P" name="language" required />
    643643Python</label></li>
    644644<li><label for="id_language_1"><input type="radio" id="id_language_1" value="J" name="language" required />
    Java</label></li>  
    851851            )
    852852
    853853        f = SongForm(auto_id=False)
    854         self.assertHTMLEqual(str(f['composers']), """<ul>
     854        self.assertHTMLEqual(str(f['composers']), """<ul data-wrap-label="true" data-multiple="true">
    855855<li><label><input type="checkbox" name="composers" value="J" /> John Lennon</label></li>
    856856<li><label><input type="checkbox" name="composers" value="P" /> Paul McCartney</label></li>
    857857</ul>""")
    858858        f = SongForm({'composers': ['J']}, auto_id=False)
    859         self.assertHTMLEqual(str(f['composers']), """<ul>
     859        self.assertHTMLEqual(str(f['composers']), """<ul data-wrap-label="true" data-multiple="true">
    860860<li><label><input checked type="checkbox" name="composers" value="J" /> John Lennon</label></li>
    861861<li><label><input type="checkbox" name="composers" value="P" /> Paul McCartney</label></li>
    862862</ul>""")
    863863        f = SongForm({'composers': ['J', 'P']}, auto_id=False)
    864         self.assertHTMLEqual(str(f['composers']), """<ul>
     864        self.assertHTMLEqual(str(f['composers']), """<ul data-wrap-label="true" data-multiple="true">
    865865<li><label><input checked type="checkbox" name="composers" value="J" /> John Lennon</label></li>
    866866<li><label><input checked type="checkbox" name="composers" value="P" /> Paul McCartney</label></li>
    867867</ul>""")
    Java</label></li>  
    886886        f = SongForm(auto_id='%s_id')
    887887        self.assertHTMLEqual(
    888888            str(f['composers']),
    889             """<ul id="composers_id">
     889            """<ul id="composers_id" data-wrap-label="true" data-multiple="true">
    890890<li><label for="composers_id_0">
    891891<input type="checkbox" name="composers" value="J" id="composers_id_0" /> John Lennon</label></li>
    892892<li><label for="composers_id_1">
  • tests/forms_tests/tests/test_i18n.py

    diff --git a/tests/forms_tests/tests/test_i18n.py b/tests/forms_tests/tests/test_i18n.py
    index 56225e0..9f83295 100644
    a b class FormsI18nTests(SimpleTestCase):  
    5959        self.assertHTMLEqual(
    6060            f.as_p(),
    6161            '<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label>'
    62             '<ul id="id_somechoice">\n'
     62            '<ul id="id_somechoice" data-wrap-label="true" data-multiple="false">\n'
    6363            '<li><label for="id_somechoice_0">'
    6464            '<input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" required /> '
    6565            'En tied\xe4</label></li>\n'
    class FormsI18nTests(SimpleTestCase):  
    7979                '\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c'
    8080                '\u043d\u043e\u0435 \u043f\u043e\u043b\u0435.</li></ul>\n'
    8181                '<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label>'
    82                 ' <ul id="id_somechoice">\n<li><label for="id_somechoice_0">'
     82                ' <ul id="id_somechoice" data-wrap-label="true" data-multiple="false">\n'
     83                '<li><label for="id_somechoice_0">'
    8384                '<input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" required /> '
    8485                'En tied\xe4</label></li>\n'
    8586                '<li><label for="id_somechoice_1">'
  • tests/forms_tests/widget_tests/test_checkboxselectmultiple.py

    diff --git a/tests/forms_tests/widget_tests/test_checkboxselectmultiple.py b/tests/forms_tests/widget_tests/test_checkboxselectmultiple.py
    index a92e553..db42521 100644
    a b class CheckboxSelectMultipleTest(WidgetTest):  
    99
    1010    def test_render_value(self):
    1111        self.check_html(self.widget(choices=self.beatles), 'beatles', ['J'], html=(
    12             """<ul>
     12            """<ul data-wrap-label="true" data-multiple="true">
    1313            <li><label><input checked type="checkbox" name="beatles" value="J" /> John</label></li>
    1414            <li><label><input type="checkbox" name="beatles" value="P" /> Paul</label></li>
    1515            <li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
    class CheckboxSelectMultipleTest(WidgetTest):  
    1919
    2020    def test_render_value_multiple(self):
    2121        self.check_html(self.widget(choices=self.beatles), 'beatles', ['J', 'P'], html=(
    22             """<ul>
     22            """<ul data-wrap-label="true" data-multiple="true">
    2323            <li><label><input checked type="checkbox" name="beatles" value="J" /> John</label></li>
    2424            <li><label><input checked type="checkbox" name="beatles" value="P" /> Paul</label></li>
    2525            <li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
    class CheckboxSelectMultipleTest(WidgetTest):  
    3232        If the value is None, none of the options are selected.
    3333        """
    3434        self.check_html(self.widget(choices=self.beatles), 'beatles', None, html=(
    35             """<ul>
     35            """<ul data-wrap-label="true" data-multiple="true">
    3636            <li><label><input type="checkbox" name="beatles" value="J" /> John</label></li>
    3737            <li><label><input type="checkbox" name="beatles" value="P" /> Paul</label></li>
    3838            <li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
    class CheckboxSelectMultipleTest(WidgetTest):  
    4747            ('Video', (('vhs', 'VHS'), ('dvd', 'DVD'))),
    4848        )
    4949        html = """
    50         <ul id="media">
     50        <ul id="media" data-wrap-label="true" data-multiple="true">
    5151        <li>
    5252        <label for="media_0"><input id="media_0" name="nestchoice" type="checkbox" value="unknown" /> Unknown</label>
    5353        </li>
    class CheckboxSelectMultipleTest(WidgetTest):  
    8484        """
    8585        choices = [('a', 'A'), ('b', 'B'), ('c', 'C')]
    8686        html = """
    87         <ul id="abc">
     87        <ul id="abc" data-wrap-label="true" data-multiple="true">
    8888        <li>
    8989        <label for="abc_0"><input checked type="checkbox" name="letters" value="a" id="abc_0" /> A</label>
    9090        </li>
    class CheckboxSelectMultipleTest(WidgetTest):  
    102102        """
    103103        widget = CheckboxSelectMultiple(attrs={'id': 'abc'}, choices=[('a', 'A'), ('b', 'B'), ('c', 'C')])
    104104        html = """
    105         <ul id="abc">
     105        <ul id="abc" data-wrap-label="true" data-multiple="true">
    106106        <li>
    107107        <label for="abc_0"><input checked type="checkbox" name="letters" value="a" id="abc_0" /> A</label>
    108108        </li>
  • tests/forms_tests/widget_tests/test_radioselect.py

    diff --git a/tests/forms_tests/widget_tests/test_radioselect.py b/tests/forms_tests/widget_tests/test_radioselect.py
    index fea26ce..df1cff5 100644
    a b class RadioSelectTest(WidgetTest):  
    88
    99    def test_render(self):
    1010        self.check_html(self.widget(choices=self.beatles), 'beatle', 'J', html=(
    11             """<ul>
     11            """<ul data-wrap-label="true" data-multiple="false">
    1212            <li><label><input checked type="radio" name="beatle" value="J" /> John</label></li>
    1313            <li><label><input type="radio" name="beatle" value="P" /> Paul</label></li>
    1414            <li><label><input type="radio" name="beatle" value="G" /> George</label></li>
    class RadioSelectTest(WidgetTest):  
    2323            ('Video', (('vhs', 'VHS'), ('dvd', 'DVD'))),
    2424        )
    2525        html = """
    26         <ul id="media">
     26        <ul id="media" data-wrap-label="true" data-multiple="false">
    2727        <li>
    2828        <label for="media_0"><input id="media_0" name="nestchoice" type="radio" value="unknown" /> Unknown</label>
    2929        </li>
    class RadioSelectTest(WidgetTest):  
    5555        """
    5656        widget = RadioSelect(attrs={'id': 'foo'}, choices=self.beatles)
    5757        html = """
    58         <ul id="foo">
     58        <ul id="foo" data-wrap-label="true" data-multiple="false">
    5959        <li>
    6060        <label for="foo_0"><input checked type="radio" id="foo_0" value="J" name="beatle" /> John</label>
    6161        </li>
    class RadioSelectTest(WidgetTest):  
    7272        inputs.
    7373        """
    7474        html = """
    75         <ul id="bar">
     75        <ul id="bar" data-wrap-label="true" data-multiple="false">
    7676        <li>
    7777        <label for="bar_0"><input checked type="radio" id="bar_0" value="J" name="beatle" /> John</label>
    7878        </li>
Back to Top