diff --git a/AUTHORS b/AUTHORS index 05be5d870c4..1c5a1fee6b2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -73,6 +73,7 @@ answer newbie questions, and generally made Django that much better: Andrew Pinkham Andrews Medina Andriy Sokolovskiy + Andy Chosak Andy Dustman Andy Gayton andy@jadedplanet.net @@ -803,6 +804,7 @@ answer newbie questions, and generally made Django that much better: schwank@gmail.com Scot Hacker Scott Barr + Scott Cranfill Scott Fitsimones Scott Pashley scott@staplefish.com diff --git a/django/contrib/admin/static/admin/js/urlify.js b/django/contrib/admin/static/admin/js/urlify.js index cf79777e785..61dedb23e98 100644 --- a/django/contrib/admin/static/admin/js/urlify.js +++ b/django/contrib/admin/static/admin/js/urlify.js @@ -148,23 +148,9 @@ function URLify(s, num_chars, allowUnicode) { // changes, e.g., "Petty theft" to "petty-theft" - // remove all these words from the string before urlifying if (!allowUnicode) { s = downcode(s); } - const hasUnicodeChars = /[^\u0000-\u007f]/.test(s); - // Remove English words only if the string contains ASCII (English) - // characters. - if (!hasUnicodeChars) { - const removeList = [ - "a", "an", "as", "at", "before", "but", "by", "for", "from", - "is", "in", "into", "like", "of", "off", "on", "onto", "per", - "since", "than", "the", "this", "that", "to", "up", "via", - "with" - ]; - const r = new RegExp('\\b(' + removeList.join('|') + ')\\b', 'gi'); - s = s.replace(r, ''); - } s = s.toLowerCase(); // convert to lowercase // if downcode doesn't hit, the char will be stripped here if (allowUnicode) { diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 085beb45186..b85711d510f 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1096,8 +1096,7 @@ subclass:: automatically generate the value for ``SlugField`` fields from one or more other fields. The generated value is produced by concatenating the values of the source fields, and then by transforming that result into a valid - slug (e.g. substituting dashes for spaces; lowercasing ASCII letters; and - removing various English stop words such as 'a', 'an', 'as', and similar). + slug (e.g. substituting dashes for spaces and lowercasing ASCII letters). Prepopulated fields aren't modified by JavaScript after a value has been saved. It's usually undesired that slugs change (which would cause an @@ -1106,6 +1105,11 @@ subclass:: ``prepopulated_fields`` doesn't accept ``DateTimeField``, ``ForeignKey``, ``OneToOneField``, and ``ManyToManyField`` fields. + .. versionchanged:: 3.2 + + In older versions, various English stop words are removed from + generated values. + .. attribute:: ModelAdmin.preserve_filters The admin now preserves filters on the list view after creating, editing diff --git a/docs/releases/3.2.txt b/docs/releases/3.2.txt index bd8db085a99..1ba636c4a6f 100644 --- a/docs/releases/3.2.txt +++ b/docs/releases/3.2.txt @@ -275,6 +275,9 @@ Miscellaneous external build tool. The minified vendored JavaScript files packaged with the admin (e.g. :ref:`jquery.min.js `) are still included. +* :attr:`.ModelAdmin.prepopulated_fields` no longer strips English stop words, + such as ``'a'`` or ``'an'``. + .. _deprecated-features-3.2: Features deprecated in 3.2 diff --git a/js_tests/admin/URLify.test.js b/js_tests/admin/URLify.test.js index 4ff706be37f..471f2fbf050 100644 --- a/js_tests/admin/URLify.test.js +++ b/js_tests/admin/URLify.test.js @@ -7,8 +7,8 @@ QUnit.test('empty string', function(assert) { assert.strictEqual(URLify('', 8, true), ''); }); -QUnit.test('strip nonessential words', function(assert) { - assert.strictEqual(URLify('the D is silent', 8, true), 'd-silent'); +QUnit.test('preserve nonessential words', function(assert) { + assert.strictEqual(URLify('the D is silent', 15, true), 'the-d-is-silent'); }); QUnit.test('strip non-URL characters', function(assert) { @@ -23,7 +23,6 @@ QUnit.test('trim trailing hyphens', function(assert) { assert.strictEqual(URLify('D silent always', 9, true), 'd-silent'); }); -QUnit.test('do not remove English words if the string contains non-ASCII', function(assert) { - // If removing English words wasn't skipped, the last 'a' would be removed. +QUnit.test('non-ASCII string', function(assert) { assert.strictEqual(URLify('Kaupa-miða', 255, true), 'kaupa-miða'); }); diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index e9723a9a7c1..81743c3b3b2 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -4453,13 +4453,13 @@ class SeleniumTests(AdminSeleniumTestCase): # Main form ---------------------------------------------------------- self.selenium.find_element_by_id('id_pubdate').send_keys('2012-02-18') self.select_option('#id_status', 'option two') - self.selenium.find_element_by_id('id_name').send_keys(' this is the mAin nÀMë and it\'s awεšomeıııİ') + self.selenium.find_element_by_id('id_name').send_keys(' the mAin nÀMë and it\'s awεšomeıııİ') slug1 = self.selenium.find_element_by_id('id_slug1').get_attribute('value') slug2 = self.selenium.find_element_by_id('id_slug2').get_attribute('value') slug3 = self.selenium.find_element_by_id('id_slug3').get_attribute('value') - self.assertEqual(slug1, 'main-name-and-its-awesomeiiii-2012-02-18') - self.assertEqual(slug2, 'option-two-main-name-and-its-awesomeiiii') - self.assertEqual(slug3, 'this-is-the-main-n\xe0m\xeb-and-its-aw\u03b5\u0161ome\u0131\u0131\u0131i') + self.assertEqual(slug1, 'the-main-name-and-its-awesomeiiii-2012-02-18') + self.assertEqual(slug2, 'option-two-the-main-name-and-its-awesomeiiii') + self.assertEqual(slug3, 'the-main-n\xe0m\xeb-and-its-aw\u03b5\u0161ome\u0131\u0131\u0131i') # Stacked inlines ---------------------------------------------------- # Initial inline @@ -4470,8 +4470,8 @@ class SeleniumTests(AdminSeleniumTestCase): ) slug1 = self.selenium.find_element_by_id('id_relatedprepopulated_set-0-slug1').get_attribute('value') slug2 = self.selenium.find_element_by_id('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') + self.assertEqual(slug1, 'here-is-a-stacked-inline-2011-12-17') + self.assertEqual(slug2, 'option-one-here-is-a-stacked-inline') initial_select2_inputs = self.selenium.find_elements_by_class_name('select2-selection') # Inline formsets have empty/invisible forms. # Only the 4 visible select2 inputs are initialized. @@ -4493,9 +4493,9 @@ class SeleniumTests(AdminSeleniumTestCase): slug1 = self.selenium.find_element_by_id('id_relatedprepopulated_set-1-slug1').get_attribute('value') slug2 = self.selenium.find_element_by_id('id_relatedprepopulated_set-1-slug2').get_attribute('value') # 50 characters maximum for slug1 field - self.assertEqual(slug1, 'now-you-have-another-stacked-inline-very-loooooooo') + self.assertEqual(slug1, 'now-you-have-another-stacked-inline-with-a-very-lo') # 60 characters maximum for slug2 field - self.assertEqual(slug2, 'option-two-now-you-have-another-stacked-inline-very-looooooo') + self.assertEqual(slug2, 'option-two-now-you-have-another-stacked-inline-with-a-very-l') # Tabular inlines ---------------------------------------------------- # Initial inline @@ -4506,8 +4506,8 @@ class SeleniumTests(AdminSeleniumTestCase): ) slug1 = self.selenium.find_element_by_id('id_relatedprepopulated_set-2-0-slug1').get_attribute('value') slug2 = self.selenium.find_element_by_id('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') + self.assertEqual(slug1, 'and-now-with-a-tabular-inline-1234-12-07') + self.assertEqual(slug2, 'option-two-and-now-with-a-tabular-inline') # Add an inline # Button may be outside the browser frame. @@ -4521,12 +4521,12 @@ class SeleniumTests(AdminSeleniumTestCase): self.selenium.find_element_by_id('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_id('id_relatedprepopulated_set-2-1-name').send_keys( - r'a tÃbűlaŘ inline with ignored ;"&*^\%$#@-/`~ characters' + r'tÃbűlaŘ inline with ignored ;"&*^\%$#@-/`~ characters' ) slug1 = self.selenium.find_element_by_id('id_relatedprepopulated_set-2-1-slug1').get_attribute('value') slug2 = self.selenium.find_element_by_id('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') + self.assertEqual(slug1, 'tabular-inline-with-ignored-characters-1981-08-22') + self.assertEqual(slug2, 'option-one-tabular-inline-with-ignored-characters') # Add an inline without an initial inline. # The button is outside of the browser frame. self.selenium.execute_script("window.scrollTo(0, document.body.scrollHeight);") @@ -4540,42 +4540,42 @@ class SeleniumTests(AdminSeleniumTestCase): self.selenium.find_element_by_xpath('//input[@value="Save"]').click() self.assertEqual(MainPrepopulated.objects.all().count(), 1) MainPrepopulated.objects.get( - name=' this is the mAin nÀMë and it\'s awεšomeıııİ', + name=' the mAin nÀMë and it\'s awεšomeıııİ', pubdate='2012-02-18', status='option two', - slug1='main-name-and-its-awesomeiiii-2012-02-18', - slug2='option-two-main-name-and-its-awesomeiiii', - slug3='this-is-the-main-nàmë-and-its-awεšomeıııi', + slug1='the-main-name-and-its-awesomeiiii-2012-02-18', + slug2='option-two-the-main-name-and-its-awesomeiiii', + slug3='the-main-nàmë-and-its-awεšomeıııi', ) self.assertEqual(RelatedPrepopulated.objects.all().count(), 4) RelatedPrepopulated.objects.get( name=' 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', + slug1='here-is-a-stacked-inline-2011-12-17', + slug2='option-one-here-is-a-stacked-inline', ) RelatedPrepopulated.objects.get( # 75 characters in name field name=' now you haVe anöther sŤāÇkeð inline with a very ... loooooooooooooooooo', 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', + slug1='now-you-have-another-stacked-inline-with-a-very-lo', + slug2='option-two-now-you-have-another-stacked-inline-with-a-very-l', ) RelatedPrepopulated.objects.get( name='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', + slug1='and-now-with-a-tabular-inline-1234-12-07', + slug2='option-two-and-now-with-a-tabular-inline', ) RelatedPrepopulated.objects.get( - name=r'a tÃbűlaŘ inline with ignored ;"&*^\%$#@-/`~ characters', + name=r'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', + slug1='tabular-inline-with-ignored-characters-1981-08-22', + slug2='option-one-tabular-inline-with-ignored-characters', ) def test_populate_existing_object(self): @@ -4601,8 +4601,8 @@ class SeleniumTests(AdminSeleniumTestCase): # The slugs got prepopulated since they were originally empty slug1 = self.selenium.find_element_by_id('id_slug1').get_attribute('value') slug2 = self.selenium.find_element_by_id('id_slug2').get_attribute('value') - self.assertEqual(slug1, 'main-name-best-2012-02-18') - self.assertEqual(slug2, 'option-two-main-name-best') + self.assertEqual(slug1, 'this-is-the-main-name-the-best-2012-02-18') + self.assertEqual(slug2, 'option-two-this-is-the-main-name-the-best') # Save the object with self.wait_page_loaded(): @@ -4614,8 +4614,8 @@ class SeleniumTests(AdminSeleniumTestCase): # The slugs got prepopulated didn't change since they were originally not empty slug1 = self.selenium.find_element_by_id('id_slug1').get_attribute('value') slug2 = self.selenium.find_element_by_id('id_slug2').get_attribute('value') - self.assertEqual(slug1, 'main-name-best-2012-02-18') - self.assertEqual(slug2, 'option-two-main-name-best') + self.assertEqual(slug1, 'this-is-the-main-name-the-best-2012-02-18') + self.assertEqual(slug2, 'option-two-this-is-the-main-name-the-best') def test_collapsible_fieldset(self): """