Code

Ticket #16260: 16260-admin-popup-callback.diff

File 16260-admin-popup-callback.diff, 12.0 KB (added by julien, 2 years ago)
Line 
1diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
2index 4d23f8f..5e78bce 100644
3--- a/django/contrib/admin/options.py
4+++ b/django/contrib/admin/options.py
5@@ -776,14 +776,17 @@ class ModelAdmin(BaseModelAdmin):
6             self.message_user(request, msg + ' ' + _("You may edit it again below."))
7             if "_popup" in request.POST:
8                 post_url_continue += "?_popup=1"
9+                if '_callback' in request.REQUEST:
10+                    post_url_continue += '&_callback=%s' % request.REQUEST.get('_callback')
11             return HttpResponseRedirect(post_url_continue % pk_value)
12 
13         if "_popup" in request.POST:
14+            popup_callback = request.REQUEST.get('_callback', 'dismissAddAnotherPopup')
15             return HttpResponse(
16                 '<!DOCTYPE html><html><head><title></title></head><body>'
17-                '<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script></body></html>' % \
18+                '<script type="text/javascript">opener.%s(window, "%s", "%s");</script></body></html>' % \
19                 # escape() calls force_unicode.
20-                (escape(pk_value), escapejs(obj)))
21+                (popup_callback, escape(pk_value), escapejs(obj)))
22         elif "_addanother" in request.POST:
23             self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
24             return HttpResponseRedirect(request.path)
25@@ -1004,6 +1007,8 @@ class ModelAdmin(BaseModelAdmin):
26             'errors': helpers.AdminErrorList(form, formsets),
27             'app_label': opts.app_label,
28         }
29+        if '_callback' in request.REQUEST:
30+            context.update({'popup_callback': request.REQUEST.get('_callback')})
31         context.update(extra_context or {})
32         return self.render_change_form(request, context, form_url=form_url, add=True)
33 
34@@ -1234,6 +1239,7 @@ class ModelAdmin(BaseModelAdmin):
35             'selection_note_all': selection_note_all % {'total_count': cl.result_count},
36             'title': cl.title,
37             'is_popup': cl.is_popup,
38+            'popup_callback': cl.popup_callback,
39             'cl': cl,
40             'media': media,
41             'has_add_permission': self.has_add_permission(request),
42diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html
43index 82d7296..937f0c1 100644
44--- a/django/contrib/admin/templates/admin/change_form.html
45+++ b/django/contrib/admin/templates/admin/change_form.html
46@@ -37,7 +37,7 @@
47 {% endblock %}
48 <form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
49 <div>
50-{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
51+{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% if popup_callback %}<input type="hidden" name="_callback" value="{{ popup_callback }}" />{% endif %}{% endif %}
52 {% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %}
53 {% if errors %}
54     <p class="errornote">
55diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html
56index c72b663..c6b82ca 100644
57--- a/django/contrib/admin/templates/admin/change_list.html
58+++ b/django/contrib/admin/templates/admin/change_list.html
59@@ -54,7 +54,7 @@
60         <ul class="object-tools">
61           {% block object-tools-items %}
62             <li>
63-              <a href="{% url cl.opts|admin_urlname:'add' %}{% if is_popup %}?_popup=1{% endif %}" class="addlink">
64+              <a href="{% url cl.opts|admin_urlname:'add' %}{% if is_popup %}?_popup=1{% if popup_callback %}&_callback={{ popup_callback }}{% endif %}{% endif %}" class="addlink">
65                 {% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
66               </a>
67             </li>
68diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
69index 0f15781..92e4878 100644
70--- a/django/contrib/admin/templatetags/admin_list.py
71+++ b/django/contrib/admin/templatetags/admin_list.py
72@@ -228,8 +228,10 @@ def items_for_result(cl, result, form):
73                               table_tag,
74                               row_class,
75                               url,
76-                              format_html(' onclick="opener.dismissRelatedLookupPopup(window, {0}); return false;"', result_id)
77-                                if cl.is_popup else '',
78+                              format_html(
79+                                  ' onclick="opener.{0}(window, {1}); return false;"',
80+                                  cl.get_popup_callback(), result_id
81+                              ) if cl.is_popup else '',
82                               result_repr,
83                               table_tag)
84         else:
85diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
86index 85e03f3..ffd351b 100644
87--- a/django/contrib/admin/views/main.py
88+++ b/django/contrib/admin/views/main.py
89@@ -24,9 +24,12 @@ SEARCH_VAR = 'q'
90 TO_FIELD_VAR = 't'
91 IS_POPUP_VAR = 'pop'
92 ERROR_FLAG = 'e'
93+POPUP_CALLBACK_VAR = '_callback'
94+POPUP_CALLBACK_DEFAULT = 'dismissRelatedLookupPopup'
95 
96 IGNORED_PARAMS = (
97-    ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR, TO_FIELD_VAR)
98+    ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR, TO_FIELD_VAR,
99+    POPUP_CALLBACK_VAR)
100 
101 # Text to display within change-list table cells if the value is blank.
102 EMPTY_CHANGELIST_VALUE = ugettext_lazy('(None)')
103@@ -57,6 +60,7 @@ class ChangeList(object):
104             self.page_num = 0
105         self.show_all = ALL_VAR in request.GET
106         self.is_popup = IS_POPUP_VAR in request.GET
107+        self.popup_callback = request.REQUEST.get(POPUP_CALLBACK_VAR)
108         self.to_field = request.GET.get(TO_FIELD_VAR)
109         self.params = dict(request.GET.items())
110         if PAGE_VAR in self.params:
111@@ -377,3 +381,8 @@ class ChangeList(object):
112 
113     def url_for_result(self, result):
114         return "%s/" % quote(getattr(result, self.pk_attname))
115+
116+    def get_popup_callback(self):
117+        if self.popup_callback is not None:
118+            return self.popup_callback
119+        return POPUP_CALLBACK_DEFAULT
120\ No newline at end of file
121diff --git a/tests/regressiontests/admin_related_lookup_popup/__init__.py b/tests/regressiontests/admin_related_lookup_popup/__init__.py
122new file mode 100644
123index 0000000..e69de29
124diff --git a/tests/regressiontests/admin_related_lookup_popup/models.py b/tests/regressiontests/admin_related_lookup_popup/models.py
125new file mode 100644
126index 0000000..aa1e83e
127--- /dev/null
128+++ b/tests/regressiontests/admin_related_lookup_popup/models.py
129@@ -0,0 +1,22 @@
130+from django.db import models
131+from django.contrib import admin
132+
133+
134+__all__ = ['RelatedItem', 'AnotherRelatedItem']
135+
136+
137+class RelatedItem(models.Model):
138+    title = models.CharField(max_length=25, blank=True, null=True)
139+
140+    def __unicode__(self):
141+        return self.title
142+
143+class AnotherRelatedItem(models.Model):
144+    title = models.CharField(max_length=15, blank=True, null=True)
145+
146+    def __unicode__(self):
147+        return self.title
148+
149+
150+admin.site.register(RelatedItem, None)
151+admin.site.register(AnotherRelatedItem, None)
152diff --git a/tests/regressiontests/admin_related_lookup_popup/tests.py b/tests/regressiontests/admin_related_lookup_popup/tests.py
153new file mode 100644
154index 0000000..1cffd8e
155--- /dev/null
156+++ b/tests/regressiontests/admin_related_lookup_popup/tests.py
157@@ -0,0 +1,99 @@
158+"""
159+This file demonstrates writing tests using the unittest module. These will pass
160+when you run "manage.py test".
161+
162+Replace this with more appropriate tests for your application.
163+"""
164+
165+from django.contrib.admin.views.main import IS_POPUP_VAR, POPUP_CALLBACK_VAR
166+from django.contrib.auth.models import User
167+from django.core.urlresolvers import reverse
168+from django.test import TestCase
169+from models import *
170+
171+
172+class login(object):
173+    def __init__(self, testcase, user, password):
174+        self.testcase = testcase
175+        success = testcase.client.login(username=user, password=password)
176+        self.testcase.assertTrue(
177+            success,
178+            "login with username=%r, password=%r failed" % (user, password)
179+        )
180+
181+    def __enter__(self):
182+        pass
183+
184+    def __exit__(self, *args):
185+        self.testcase.client.logout()
186+
187+
188+class RelatedLookupPopupTest(TestCase):
189+
190+    def setUp(self):
191+        User.objects.create_superuser('root', 'root@example.com', '123')
192+        RelatedItem.objects.create(title='lorem')
193+        RelatedItem.objects.create(title='ipsum')
194+        RelatedItem.objects.create(title='dolorem')
195+
196+        AnotherRelatedItem.objects.create(title='lorem')
197+        AnotherRelatedItem.objects.create(title='ipsum')
198+        AnotherRelatedItem.objects.create(title='dolorem')
199+        AnotherRelatedItem.objects.create(title='lorem')
200+        AnotherRelatedItem.objects.create(title='ipsum')
201+
202+    def test_choose_from_changelist(self):
203+        with login(self, 'root', '123'):
204+            callbacks = {
205+                'dismissRelatedItemLookupPopup': (reverse('admin:admin_related_lookup_popup_relateditem_changelist'), 3),
206+                'dismissAnoterRelatedItemLookupPopup': (reverse('admin:admin_related_lookup_popup_anotherrelateditem_changelist'), 5),
207+            }
208+
209+            for callback_name, opts in callbacks.items():
210+                changelist_url, count = opts
211+
212+                response = self.client.get("%s?_callback=%s" % (changelist_url, callback_name))
213+                self.assertContains(response, '<a href="1/">', 1, 200)
214+                self.assertContains(response, '<a href="2/">', 1, 200)
215+                self.assertContains(response, '<a href="3/">', 1, 200)
216+                self.assertContains(response, 'onclick="opener.%s(' % callback_name, 0, 200)
217+
218+                response = self.client.get("%s?%s=1&_callback=%s" % \
219+                                           (changelist_url, IS_POPUP_VAR, callback_name))
220+                self.assertContains(response, '<a href="1/">', 0, 200)
221+                self.assertContains(response, '<a href="2/">', 0, 200)
222+                self.assertContains(response, '<a href="3/">', 0, 200)
223+                self.assertContains(response, 'onclick="opener.%s(' % callback_name, count, 200)
224+
225+    def test_add_new_related(self):
226+        with login(self, 'root', '123'):
227+            callback_name = 'dismissRelatedItemLookupPopup'
228+            add_url = reverse('admin:admin_related_lookup_popup_relateditem_add')
229+
230+            # check correct rendering form
231+            response = self.client.get("%s?_popup=1&_callback=%s" % (add_url, callback_name))
232+            self.assertEqual(response.status_code, 200)
233+            self.assertContains(response, '<input type="hidden" name="_popup" value="1" />', 1, 200)
234+            self.assertContains(response, '<input type="hidden" name="_callback" value="%s" />' % callback_name, 1, 200)
235+
236+            # check correct posting form
237+            response = self.client.post(add_url, {
238+                'title': 'zxc',
239+                '_popup': 1,
240+                '_callback': callback_name,
241+            })
242+            self.assertEqual(response.status_code, 200)
243+            self.assertContains(response, '<script')
244+            self.assertContains(response, callback_name)
245+
246+    def test_choose_new_from_changelist(self):
247+        with login(self, 'root', '123'):
248+            callback_name = 'dismissRelatedItemLookupPopup'
249+            changelist_url = reverse('admin:admin_related_lookup_popup_relateditem_changelist')
250+
251+            response = self.client.get("%s?%s=1" % (changelist_url, IS_POPUP_VAR))
252+            self.assertContains(response, 'add/?_popup=1', 1, 200)
253+
254+            response = self.client.get("%s?%s=1&%s=%s" % \
255+                        (changelist_url, IS_POPUP_VAR, POPUP_CALLBACK_VAR, callback_name))
256+            self.assertContains(response, 'add/?_popup=1&_callback=%s' % callback_name, 1, 200)