Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #13068 (again) -- Corrected the admin stacked inline template t…

…o 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
  • Loading branch information...
commit f0c2228709b0481eac09c860d7a4a30d3987cc19 1 parent cfd0cb0
Julien Phalip authored February 19, 2012
10  django/contrib/admin/templates/admin/edit_inline/stacked.html
@@ -27,14 +27,14 @@
27 27
                 var count = i + 1;
28 28
                 $(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
29 29
             });
30  
-        }
  30
+        };
31 31
         var reinitDateTimeShortCuts = function() {
32 32
             // Reinitialize the calendar and clock widgets by force, yuck.
33 33
             if (typeof DateTimeShortcuts != "undefined") {
34 34
                 $(".datetimeshortcuts").remove();
35 35
                 DateTimeShortcuts.init();
36 36
             }
37  
-        }
  37
+        };
38 38
         var updateSelectFilter = function() {
39 39
             // If any SelectFilter widgets were added, instantiate a new instance.
40 40
             if (typeof SelectFilter != "undefined"){
@@ -47,7 +47,7 @@
47 47
                   SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static "admin/" %}");
48 48
                 });
49 49
             }
50  
-        }
  50
+        };
51 51
         var initPrepopulatedFields = function(row) {
52 52
             row.find('.prepopulated_field').each(function() {
53 53
                 var field = $(this);
@@ -55,13 +55,13 @@
55 55
                 var dependency_list = input.data('dependency_list') || [];
56 56
                 var dependencies = [];
57 57
                 $.each(dependency_list, function(i, field_name) {
58  
-                  dependencies.push('#' + row.find(field_name).find('input, select, textarea').attr('id'));
  58
+                  dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id'));
59 59
                 });
60 60
                 if (dependencies.length) {
61 61
                     input.prepopulate(dependencies, input.attr('maxlength'));
62 62
                 }
63 63
             });
64  
-        }
  64
+        };
65 65
         $(rows).formset({
66 66
             prefix: "{{ inline_admin_formset.formset.prefix }}",
67 67
             addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}",
4  django/contrib/admin/templates/admin/edit_inline/tabular.html
@@ -22,7 +22,7 @@
22 22
         {% if inline_admin_form.form.non_field_errors %}
23 23
         <tr><td colspan="{{ inline_admin_form|cell_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr>
24 24
         {% endif %}
25  
-        <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 %}"
  25
+        <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 %}"
26 26
              id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
27 27
         <td class="original">
28 28
           {% if inline_admin_form.original or inline_admin_form.show_url %}<p>
@@ -103,7 +103,7 @@
103 103
                 var dependency_list = input.data('dependency_list') || [];
104 104
                 var dependencies = [];
105 105
                 $.each(dependency_list, function(i, field_name) {
106  
-                  dependencies.push('#' + row.find(field_name).find('input, select, textarea').attr('id'));
  106
+                  dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id'));
107 107
                 });
108 108
                 if (dependencies.length) {
109 109
                     input.prepopulate(dependencies, input.attr('maxlength'));
2  django/contrib/admin/templates/admin/includes/fieldset.html
@@ -7,7 +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 %}">
8 8
             {% if line.fields|length_is:'1' %}{{ line.errors }}{% endif %}
9 9
             {% for field in line %}
10  
-                <div{% if not line.fields|length_is:'1' %} class="field-box{% if not field.is_readonly and field.errors %} errors{% endif %}"{% endif %}>
  10
+                <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 %}>
11 11
                     {% if not line.fields|length_is:'1' and not field.is_readonly %}{{ field.errors }}{% endif %}
12 12
                     {% if field.is_checkbox %}
13 13
                         {{ field.field }}{{ field.label_tag }}
9  django/contrib/admin/templates/admin/prepopulated_fields_js.html
... ...
@@ -1,7 +1,7 @@
1 1
 {% load l10n %}
2 2
 <script type="text/javascript">
3 3
 (function($) {
4  
-    var field = null;
  4
+    var field;
5 5
 
6 6
 {% for field in prepopulated_fields %}
7 7
     field = {
@@ -13,10 +13,13 @@
13 13
 
14 14
     {% for dependency in field.dependencies %}
15 15
     field['dependency_ids'].push('#{{ dependency.auto_id }}');
16  
-    field['dependency_list'].push('.{{ dependency.name }}');
  16
+    field['dependency_list'].push('{{ dependency.name }}');
17 17
     {% endfor %}
18 18
 
19  
-    $('.empty-form .{{ field.field.name }}').addClass('prepopulated_field');
  19
+    {% comment %}
  20
+    Mark prepopulated fields in the main form and stacked inlines (.empty-form .form-row) and in tabular inlines (.empty-form.form-row)
  21
+    {% endcomment %}
  22
+    $('.empty-form .form-row .field-{{ field.field.name }}, .empty-form.form-row .field-{{ field.field.name }}').addClass('prepopulated_field');
20 23
     $(field.id).data('dependency_list', field['dependency_list'])
21 24
                .prepopulate(field['dependency_ids'], field.maxLength);
22 25
 {% endfor %}
15  django/contrib/admin/tests.py
... ...
@@ -1,4 +1,5 @@
1 1
 import sys
  2
+from selenium.common.exceptions import NoSuchElementException
2 3
 
3 4
 from django.test import LiveServerTestCase
4 5
 from django.utils.importlib import import_module
@@ -71,4 +72,16 @@ def get_css_value(self, selector, attribute):
71 72
         with Django.
72 73
         """
73 74
         return self.selenium.execute_script(
74  
-            'return django.jQuery("%s").css("%s")' % (selector, attribute))
  75
+            'return django.jQuery("%s").css("%s")' % (selector, attribute))
  76
+
  77
+    def select_option(self, selector, value):
  78
+        """
  79
+        Helper function to select the <OPTION> that has the value `value` and
  80
+        that is in the <SELECT> widget identified by the CSS selector `selector`.
  81
+        """
  82
+        options = self.selenium.find_elements_by_css_selector('%s option' % selector)
  83
+        for option in options:
  84
+            if option.get_attribute('value') == value:
  85
+                option.click()
  86
+                return
  87
+        raise NoSuchElementException('Option "%s" not found in "%s"' % (value, selector))
35  tests/regressiontests/admin_views/admin.py
@@ -26,7 +26,7 @@
26 26
     CoverLetter, Story, OtherStory, Book, Promo, ChapterXtra1, Pizza, Topping,
27 27
     Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug,
28 28
     AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
29  
-    AdminOrderedCallable, Report, Color2)
  29
+    AdminOrderedCallable, Report, Color2, MainPrepopulated, RelatedPrepopulated)
30 30
 
31 31
 
32 32
 def callable_year(dt_value):
@@ -532,6 +532,38 @@ class CustomTemplateBooleanFieldListFilter(BooleanFieldListFilter):
532 532
 class CustomTemplateFilterColorAdmin(admin.ModelAdmin):
533 533
     list_filter = (('warm', CustomTemplateBooleanFieldListFilter),)
534 534
 
  535
+
  536
+# For Selenium Prepopulated tests -------------------------------------
  537
+class RelatedPrepopulatedInline1(admin.StackedInline):
  538
+    fieldsets = (
  539
+        (None, {
  540
+            'fields': (('pubdate', 'status'), ('name', 'slug1', 'slug2',),)
  541
+        }),
  542
+    )
  543
+    model = RelatedPrepopulated
  544
+    extra = 1
  545
+    prepopulated_fields = {'slug1': ['name', 'pubdate'],
  546
+                           'slug2': ['status', 'name']}
  547
+
  548
+class RelatedPrepopulatedInline2(admin.TabularInline):
  549
+    model = RelatedPrepopulated
  550
+    extra = 1
  551
+    prepopulated_fields = {'slug1': ['name', 'pubdate'],
  552
+                           'slug2': ['status', 'name']}
  553
+
  554
+class MainPrepopulatedAdmin(admin.ModelAdmin):
  555
+    inlines = [RelatedPrepopulatedInline1, RelatedPrepopulatedInline2]
  556
+    fieldsets = (
  557
+        (None, {
  558
+            'fields': (('pubdate', 'status'), ('name', 'slug1', 'slug2',),)
  559
+        }),
  560
+    )
  561
+    prepopulated_fields = {'slug1': ['name', 'pubdate'],
  562
+                           'slug2': ['status', 'name']}
  563
+
  564
+
  565
+
  566
+
535 567
 site = admin.AdminSite(name="admin")
536 568
 site.register(Article, ArticleAdmin)
537 569
 site.register(CustomArticle, CustomArticleAdmin)
@@ -576,6 +608,7 @@ class CustomTemplateFilterColorAdmin(admin.ModelAdmin):
576 608
 site.register(Story, StoryAdmin)
577 609
 site.register(OtherStory, OtherStoryAdmin)
578 610
 site.register(Report, ReportAdmin)
  611
+site.register(MainPrepopulated, MainPrepopulatedAdmin)
579 612
 
580 613
 # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
581 614
 # That way we cover all four cases:
22  tests/regressiontests/admin_views/models.py
@@ -575,3 +575,25 @@ class Report(models.Model):
575 575
 
576 576
     def __unicode__(self):
577 577
         return self.title
  578
+
  579
+
  580
+class MainPrepopulated(models.Model):
  581
+    name = models.CharField(max_length=100)
  582
+    pubdate = models.DateField()
  583
+    status = models.CharField(
  584
+        max_length=20,
  585
+        choices=(('option one', 'Option One'),
  586
+                 ('option two', 'Option Two')))
  587
+    slug1 = models.SlugField()
  588
+    slug2 = models.SlugField()
  589
+
  590
+class RelatedPrepopulated(models.Model):
  591
+    parent = models.ForeignKey(MainPrepopulated)
  592
+    name = models.CharField(max_length=75)
  593
+    pubdate = models.DateField()
  594
+    status = models.CharField(
  595
+        max_length=20,
  596
+        choices=(('option one', 'Option One'),
  597
+                 ('option two', 'Option Two')))
  598
+    slug1 = models.SlugField(max_length=50)
  599
+    slug2 = models.SlugField(max_length=60)
109  tests/regressiontests/admin_views/tests.py
@@ -17,7 +17,8 @@
17 17
 from django.contrib.admin.sites import LOGIN_FORM_KEY
18 18
 from django.contrib.admin.util import quote
19 19
 from django.contrib.admin.views.main import IS_POPUP_VAR
20  
-from django.contrib.auth import REDIRECT_FIELD_NAME, admin
  20
+from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
  21
+from django.contrib.auth import REDIRECT_FIELD_NAME
21 22
 from django.contrib.auth.models import Group, User, Permission, UNUSABLE_PASSWORD
22 23
 from django.contrib.contenttypes.models import ContentType
23 24
 from django.forms.util import ErrorList
@@ -40,7 +41,7 @@
40 41
     FoodDelivery, RowLevelChangePermissionModel, Paper, CoverLetter, Story,
41 42
     OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField,
42 43
     AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable,
43  
-    Report)
  44
+    Report, MainPrepopulated, RelatedPrepopulated)
44 45
 
45 46
 
46 47
 ERROR_MESSAGE = "Please enter the correct username and password \
@@ -2892,6 +2893,110 @@ def test_prepopulated_maxlength_localized(self):
2892 2893
         response = self.client.get('/test_admin/admin/admin_views/prepopulatedpostlargeslug/add/')
2893 2894
         self.assertContains(response, "maxLength: 1000") # instead of 1,000
2894 2895
 
  2896
+
  2897
+class SeleniumPrePopulatedTests(AdminSeleniumWebDriverTestCase):
  2898
+    urls = "regressiontests.admin_views.urls"
  2899
+    fixtures = ['admin-views-users.xml']
  2900
+
  2901
+    def test_basic(self):
  2902
+        """
  2903
+        Ensure that the Javascript-automated prepopulated fields work with the
  2904
+        main form and with stacked and tabular inlines.
  2905
+        Refs #13068, #9264, #9983, #9784.
  2906
+        """
  2907
+        self.admin_login(username='super', password='secret', login_url='/test_admin/admin/')
  2908
+        self.selenium.get('%s%s' % (self.live_server_url,
  2909
+            '/test_admin/admin/admin_views/mainprepopulated/add/'))
  2910
+
  2911
+        # Main form ----------------------------------------------------------
  2912
+        self.selenium.find_element_by_css_selector('#id_pubdate').send_keys('2012-02-18')
  2913
+        self.select_option('#id_status', 'option two')
  2914
+        self.selenium.find_element_by_css_selector('#id_name').send_keys(u' this is the mAin nÀMë and it\'s awεšome')
  2915
+        slug1 = self.selenium.find_element_by_css_selector('#id_slug1').get_attribute('value')
  2916
+        slug2 = self.selenium.find_element_by_css_selector('#id_slug2').get_attribute('value')
  2917
+        self.assertEqual(slug1, 'main-name-and-its-awesome-2012-02-18')
  2918
+        self.assertEqual(slug2, 'option-two-main-name-and-its-awesome')
  2919
+
  2920
+        # Stacked inlines ----------------------------------------------------
  2921
+        # Initial inline
  2922
+        self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-0-pubdate').send_keys('2011-12-17')
  2923
+        self.select_option('#id_relatedprepopulated_set-0-status', 'option one')
  2924
+        self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-0-name').send_keys(u' here is a sŤāÇkeð   inline !  ')
  2925
+        slug1 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-0-slug1').get_attribute('value')
  2926
+        slug2 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-0-slug2').get_attribute('value')
  2927
+        self.assertEqual(slug1, 'here-stacked-inline-2011-12-17')
  2928
+        self.assertEqual(slug2, 'option-one-here-stacked-inline')
  2929
+
  2930
+        # Add an inline
  2931
+        self.selenium.find_element_by_css_selector('#relatedprepopulated_set-group .add-row a').click()
  2932
+        self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-1-pubdate').send_keys('1999-01-25')
  2933
+        self.select_option('#id_relatedprepopulated_set-1-status', 'option two')
  2934
+        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... ')
  2935
+        slug1 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-1-slug1').get_attribute('value')
  2936
+        slug2 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-1-slug2').get_attribute('value')
  2937
+        self.assertEqual(slug1, 'now-you-have-another-stacked-inline-very-loooooooo') # 50 characters maximum for slug1 field
  2938
+        self.assertEqual(slug2, 'option-two-now-you-have-another-stacked-inline-very-looooooo') # 60 characters maximum for slug2 field
  2939
+
  2940
+        # Tabular inlines ----------------------------------------------------
  2941
+        # Initial inline
  2942
+        self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-0-pubdate').send_keys('1234-12-07')
  2943
+        self.select_option('#id_relatedprepopulated_set-2-0-status', 'option two')
  2944
+        self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-0-name').send_keys(u'And now, with a tÃbűlaŘ inline !!!')
  2945
+        slug1 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-0-slug1').get_attribute('value')
  2946
+        slug2 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-0-slug2').get_attribute('value')
  2947
+        self.assertEqual(slug1, 'and-now-tabular-inline-1234-12-07')
  2948
+        self.assertEqual(slug2, 'option-two-and-now-tabular-inline')
  2949
+
  2950
+        # Add an inline
  2951
+        self.selenium.find_element_by_css_selector('#relatedprepopulated_set-2-group .add-row a').click()
  2952
+        self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-1-pubdate').send_keys('1981-08-22')
  2953
+        self.select_option('#id_relatedprepopulated_set-2-1-status', 'option one')
  2954
+        self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-1-name').send_keys(u'a tÃbűlaŘ inline with ignored ;"&*^\%$#@-/`~ characters')
  2955
+        slug1 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-1-slug1').get_attribute('value')
  2956
+        slug2 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-1-slug2').get_attribute('value')
  2957
+        self.assertEqual(slug1, 'tabular-inline-ignored-characters-1981-08-22')
  2958
+        self.assertEqual(slug2, 'option-one-tabular-inline-ignored-characters')
  2959
+
  2960
+        # Save and check that everything is properly stored in the database
  2961
+        self.selenium.find_element_by_xpath('//input[@value="Save"]').click()
  2962
+        self.assertEqual(MainPrepopulated.objects.all().count(), 1)
  2963
+        MainPrepopulated.objects.get(
  2964
+            name=u' this is the mAin nÀMë and it\'s awεšome',
  2965
+            pubdate='2012-02-18',
  2966
+            status='option two',
  2967
+            slug1='main-name-and-its-awesome-2012-02-18',
  2968
+            slug2='option-two-main-name-and-its-awesome',
  2969
+        )
  2970
+        self.assertEqual(RelatedPrepopulated.objects.all().count(), 4)
  2971
+        RelatedPrepopulated.objects.get(
  2972
+            name=u' here is a sŤāÇkeð   inline !  ',
  2973
+            pubdate='2011-12-17',
  2974
+            status='option one',
  2975
+            slug1='here-stacked-inline-2011-12-17',
  2976
+            slug2='option-one-here-stacked-inline',
  2977
+        )
  2978
+        RelatedPrepopulated.objects.get(
  2979
+            name=u' now you haVe anöther   sŤāÇkeð  inline with a very ... loooooooooooooooooo', # 75 characters in name field
  2980
+            pubdate='1999-01-25',
  2981
+            status='option two',
  2982
+            slug1='now-you-have-another-stacked-inline-very-loooooooo',
  2983
+            slug2='option-two-now-you-have-another-stacked-inline-very-looooooo',
  2984
+        )
  2985
+        RelatedPrepopulated.objects.get(
  2986
+            name=u'And now, with a tÃbűlaŘ inline !!!',
  2987
+            pubdate='1234-12-07',
  2988
+            status='option two',
  2989
+            slug1='and-now-tabular-inline-1234-12-07',
  2990
+            slug2='option-two-and-now-tabular-inline',
  2991
+        )
  2992
+        RelatedPrepopulated.objects.get(
  2993
+            name=u'a tÃbűlaŘ inline with ignored ;"&*^\%$#@-/`~ characters',
  2994
+            pubdate='1981-08-22',
  2995
+            status='option one',
  2996
+            slug1='tabular-inline-ignored-characters-1981-08-22',
  2997
+            slug2='option-one-tabular-inline-ignored-characters',
  2998
+        )
  2999
+
2895 3000
 class ReadonlyTest(TestCase):
2896 3001
     urls = "regressiontests.admin_views.urls"
2897 3002
     fixtures = ['admin-views-users.xml']

0 notes on commit f0c2228

Please sign in to comment.
Something went wrong with that request. Please try again.