diff --git a/AUTHORS b/AUTHORS
index 55211a6..e11415c 100644
a
|
b
|
answer newbie questions, and generally made Django that much better:
|
80 | 80 | Matías Bordese |
81 | 81 | Sean Brant |
82 | 82 | Andrew Brehaut <http://brehaut.net/blog> |
| 83 | Marco Beri <marcoberi@gmail.com> |
83 | 84 | brut.alll@gmail.com |
84 | 85 | btoll@bestweb.net |
85 | 86 | Jonathan Buchanan <jonathan.buchanan@gmail.com> |
diff --git a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js
index 1bc78f8..7ff0e83 100644
a
|
b
|
function html_unescape(text) {
|
11 | 11 | return text; |
12 | 12 | } |
13 | 13 | |
| 14 | function html_escape(text) { |
| 15 | text = text.replace(/&/g, '&'); |
| 16 | text = text.replace(/</g, '<'); |
| 17 | text = text.replace(/>/g, '>'); |
| 18 | text = text.replace(/"/g, '"'); |
| 19 | text = text.replace(/'/g, '''); |
| 20 | return text; |
| 21 | } |
| 22 | |
14 | 23 | // IE doesn't accept periods or dashes in the window name, but the element IDs |
15 | 24 | // we use to generate popup window names may contain them, therefore we map them |
16 | | // to allowed characters in a reversible way so that we can locate the correct |
| 25 | // to allowed characters in a reversible way so that we can locate the correct |
17 | 26 | // element when the popup window is dismissed. |
18 | 27 | function id_to_windowname(text) { |
19 | 28 | text = text.replace(/\./g, '__dot__'); |
… |
… |
function windowname_to_id(text) {
|
27 | 36 | return text; |
28 | 37 | } |
29 | 38 | |
| 39 | function showRelatedObjectPopup(triggeringLink) { |
| 40 | // TODO: use proper id in name to update the label on change |
| 41 | // see admin/options.py#641 |
| 42 | return openPopupWindow(triggeringLink.href, '_popup', 'showrelatedobject'); |
| 43 | } |
| 44 | |
30 | 45 | function showRelatedObjectLookupPopup(triggeringLink) { |
31 | 46 | var name = triggeringLink.id.replace(/^lookup_/, ''); |
32 | 47 | name = id_to_windowname(name); |
33 | | var href; |
34 | | if (triggeringLink.href.search(/\?/) >= 0) { |
35 | | href = triggeringLink.href + '&pop=1'; |
36 | | } else { |
37 | | href = triggeringLink.href + '?pop=1'; |
38 | | } |
39 | | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); |
40 | | win.focus(); |
41 | | return false; |
| 48 | return openPopupWindow(triggeringLink.href, 'pop', name); |
42 | 49 | } |
43 | 50 | |
44 | | function dismissRelatedLookupPopup(win, chosenId) { |
| 51 | function dismissRelatedLookupPopup(win, chosenId, chosenIdHref, chosenName) { |
45 | 52 | var name = windowname_to_id(win.name); |
46 | 53 | var elem = document.getElementById(name); |
| 54 | var nameElem = document.getElementById("view_lookup_" + name); |
| 55 | |
47 | 56 | if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { |
48 | 57 | elem.value += ',' + chosenId; |
49 | 58 | } else { |
50 | 59 | document.getElementById(name).value = chosenId; |
51 | 60 | } |
| 61 | |
| 62 | if (nameElem) { |
| 63 | nameElem.innerHTML = '<a href="' + chosenIdHref + '" ' + |
| 64 | 'onclick="return showRelatedObjectPopup(this);">' + |
| 65 | html_escape(chosenName) + '</a>'; |
| 66 | } |
| 67 | |
52 | 68 | win.close(); |
53 | 69 | } |
54 | 70 | |
55 | 71 | function showAddAnotherPopup(triggeringLink) { |
56 | 72 | var name = triggeringLink.id.replace(/^add_/, ''); |
57 | 73 | name = id_to_windowname(name); |
58 | | href = triggeringLink.href |
59 | | if (href.indexOf('?') == -1) { |
60 | | href += '?_popup=1'; |
61 | | } else { |
62 | | href += '&_popup=1'; |
63 | | } |
64 | | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); |
65 | | win.focus(); |
66 | | return false; |
| 74 | return openPopupWindow(triggeringLink.href, '_popup', name); |
67 | 75 | } |
68 | 76 | |
69 | 77 | function dismissAddAnotherPopup(win, newId, newRepr) { |
70 | 78 | // newId and newRepr are expected to have previously been escaped by |
71 | 79 | // django.utils.html.escape. |
72 | 80 | newId = html_unescape(newId); |
| 81 | var newRepr_escaped = newRepr; |
73 | 82 | newRepr = html_unescape(newRepr); |
74 | 83 | var name = windowname_to_id(win.name); |
75 | 84 | var elem = document.getElementById(name); |
… |
… |
function dismissAddAnotherPopup(win, newId, newRepr) {
|
84 | 93 | } else { |
85 | 94 | elem.value = newId; |
86 | 95 | } |
| 96 | |
| 97 | var nameElem = document.getElementById("view_lookup_" + name); |
| 98 | if (nameElem) { |
| 99 | var chosenIdHref = win.location.href.replace(/\/add\/[^\/]*$/, |
| 100 | '/' + newId + '/'); |
| 101 | nameElem.innerHTML = '<a href="' + chosenIdHref + '" ' + |
| 102 | 'onclick="return showRelatedObjectPopup(this);">' + |
| 103 | newRepr_escaped + '</a>'; |
| 104 | } |
87 | 105 | } |
88 | 106 | } else { |
89 | 107 | var toId = name + "_to"; |
… |
… |
function dismissAddAnotherPopup(win, newId, newRepr) {
|
94 | 112 | } |
95 | 113 | win.close(); |
96 | 114 | } |
| 115 | |
| 116 | function openPopupWindow(href, popup_var, name) { |
| 117 | if (href.indexOf('?') == -1) { |
| 118 | href += '?'; |
| 119 | } else { |
| 120 | href += '&'; |
| 121 | } |
| 122 | href += popup_var + '=1'; |
| 123 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); |
| 124 | win.focus(); |
| 125 | return false; |
| 126 | } |
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 8f68eb1..a3aaf4c 100644
a
|
b
|
class ModelAdmin(BaseModelAdmin):
|
674 | 674 | return HttpResponseRedirect(request.path + "?_popup=1") |
675 | 675 | else: |
676 | 676 | return HttpResponseRedirect(request.path) |
| 677 | elif request.POST.has_key("_popup"): |
| 678 | # object changed via raw id link popup |
| 679 | return HttpResponse('<script type="text/javascript">window.close();</script>') |
| 680 | # TODO: this should update the object label in the href tag |
| 681 | # see admin/media/js/admin/RelatedObjectLookups.js#39 |
| 682 | # return HttpResponse('<script type="text/javascript"> |
| 683 | # opener.dismissRelatedObjectPopup(window, "%s", "%s");</script>' % \ |
| 684 | # escape() calls force_unicode. |
| 685 | # (escape(pk_value), escape(obj))) |
677 | 686 | elif request.POST.has_key("_saveasnew"): |
678 | 687 | msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': obj} |
679 | 688 | self.message_user(request, msg) |
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index 7cd3f0a..bf8fba2 100644
a
|
b
|
from django.conf import settings
|
4 | 4 | from django.contrib.admin.util import lookup_field, display_for_field, label_for_field |
5 | 5 | from django.contrib.admin.views.main import ALL_VAR, EMPTY_CHANGELIST_VALUE |
6 | 6 | from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR |
| 7 | from django.contrib.admin.util import obj_label, get_related_url |
7 | 8 | from django.core.exceptions import ObjectDoesNotExist |
8 | 9 | from django.db import models |
9 | 10 | from django.forms.forms import pretty_name |
… |
… |
def items_for_result(cl, result, form):
|
166 | 167 | attr = pk |
167 | 168 | value = result.serializable_value(attr) |
168 | 169 | result_id = repr(force_unicode(value))[1:] |
169 | | yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % \ |
170 | | (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag)) |
| 170 | result_name = obj_label(result) |
| 171 | result_url = get_related_url(result, result.pk) |
| 172 | yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % |
| 173 | (table_tag, row_class, url, |
| 174 | (cl.is_popup |
| 175 | and ' onclick="opener.dismissRelatedLookupPopup(' |
| 176 | "window, %s, '%s', '%s'); return false;\"" % |
| 177 | (result_id, result_url, |
| 178 | result_name.replace("'", r"\'")) |
| 179 | or ''), |
| 180 | conditional_escape(result_repr), table_tag)) |
171 | 181 | else: |
172 | 182 | # By default the fields come from ModelAdmin.list_editable, but if we pull |
173 | 183 | # the fields out of the form instead of list_editable custom admins |
diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py
index d252a80..dbaea60 100644
a
|
b
|
from django.db import models
|
3 | 3 | from django.utils import formats |
4 | 4 | from django.utils.html import escape |
5 | 5 | from django.utils.safestring import mark_safe |
6 | | from django.utils.text import capfirst |
| 6 | from django.utils.text import capfirst, truncate_words |
7 | 7 | from django.utils.encoding import force_unicode, smart_unicode, smart_str |
8 | 8 | from django.utils.translation import ungettext, ugettext as _ |
9 | 9 | from django.core.urlresolvers import reverse, NoReverseMatch |
… |
… |
def display_for_field(value, field):
|
305 | 305 | return formats.number_format(value) |
306 | 306 | else: |
307 | 307 | return smart_unicode(value) |
| 308 | |
| 309 | def get_related_url(rel_to, action=None, admin_site=None): |
| 310 | reverse_path = 'admin:%s_%s' |
| 311 | rel_path = '%s%s/%s/' |
| 312 | params = [rel_to._meta.app_label, rel_to._meta.object_name.lower()] |
| 313 | |
| 314 | if action: |
| 315 | reverse_path += '_%s' |
| 316 | rel_path += '%s/' |
| 317 | params.append(action) |
| 318 | |
| 319 | if admin_site: |
| 320 | try: |
| 321 | return reverse(reverse_path % tuple(params), |
| 322 | current_app=admin_site.name) |
| 323 | except NoReverseMatch: |
| 324 | params.insert(0, admin_site.root_path) |
| 325 | return rel_path % tuple(params) |
| 326 | |
| 327 | params.insert(0, '../../../') |
| 328 | return rel_path % tuple(params) |
| 329 | |
| 330 | def obj_label(obj): |
| 331 | return escape(truncate_words(obj, 7)) |
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index 120df94..911e40c 100644
a
|
b
|
import django.utils.copycompat as copy
|
7 | 7 | from django import forms |
8 | 8 | from django.forms.widgets import RadioFieldRenderer |
9 | 9 | from django.forms.util import flatatt |
10 | | from django.utils.html import escape |
11 | | from django.utils.text import truncate_words |
12 | 10 | from django.utils.translation import ugettext as _ |
13 | 11 | from django.utils.safestring import mark_safe |
14 | 12 | from django.utils.encoding import force_unicode |
15 | 13 | from django.conf import settings |
16 | | from django.core.urlresolvers import reverse, NoReverseMatch |
| 14 | from django.contrib.admin.util import get_related_url, obj_label |
17 | 15 | |
18 | 16 | class FilteredSelectMultiple(forms.SelectMultiple): |
19 | 17 | """ |
… |
… |
class ForeignKeyRawIdWidget(forms.TextInput):
|
110 | 108 | def render(self, name, value, attrs=None): |
111 | 109 | if attrs is None: |
112 | 110 | attrs = {} |
113 | | related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower()) |
| 111 | # it would make sense to bind admin_site to ForeignKeyRawIdWidget as |
| 112 | # well (see RelatedFieldWidgetWrapper.__init__) for proper URL reversing |
| 113 | related_url = get_related_url(self.rel.to) |
114 | 114 | params = self.url_parameters() |
115 | 115 | if params: |
116 | 116 | url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.items()]) |
… |
… |
class ForeignKeyRawIdWidget(forms.TextInput):
|
124 | 124 | output.append('<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \ |
125 | 125 | (related_url, url, name)) |
126 | 126 | output.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" /></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup'))) |
127 | | if value: |
128 | | output.append(self.label_for_value(value)) |
| 127 | output.append(self.label_for_value(value, 'view_lookup_id_%s' % name)) |
129 | 128 | return mark_safe(u''.join(output)) |
130 | 129 | |
131 | 130 | def base_url_parameters(self): |
… |
… |
class ForeignKeyRawIdWidget(forms.TextInput):
|
147 | 146 | params.update({TO_FIELD_VAR: self.rel.get_related_field().name}) |
148 | 147 | return params |
149 | 148 | |
150 | | def label_for_value(self, value): |
151 | | key = self.rel.get_related_field().name |
152 | | obj = self.rel.to._default_manager.using(self.db).get(**{key: value}) |
153 | | return ' <strong>%s</strong>' % escape(truncate_words(obj, 14)) |
| 149 | def label_for_value(self, value, name): |
| 150 | if value: |
| 151 | key = self.rel.get_related_field().name |
| 152 | obj = self.rel.to._default_manager.using( |
| 153 | self.db).get(**{key: value}) |
| 154 | related_url = get_related_url(obj, obj.pk) |
| 155 | return (' <strong id="%s"><a href="%s" ' |
| 156 | 'onclick="return showRelatedObjectPopup(this);">%s</a>' |
| 157 | '</strong>' % (name, related_url, obj_label(obj))) |
| 158 | else: |
| 159 | # a placeholder that will be filled in |
| 160 | # JavaScript dismissRelatedLookupPopup() |
| 161 | return ' <strong id="%s"></strong>' % name |
154 | 162 | |
155 | 163 | class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): |
156 | 164 | """ |
… |
… |
class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
|
171 | 179 | def url_parameters(self): |
172 | 180 | return self.base_url_parameters() |
173 | 181 | |
174 | | def label_for_value(self, value): |
| 182 | def label_for_value(self, value, name): |
175 | 183 | return '' |
176 | 184 | |
177 | 185 | def value_from_datadict(self, data, files, name): |
… |
… |
class RelatedFieldWidgetWrapper(forms.Widget):
|
221 | 229 | media = property(_media) |
222 | 230 | |
223 | 231 | def render(self, name, value, *args, **kwargs): |
224 | | rel_to = self.rel.to |
225 | | info = (rel_to._meta.app_label, rel_to._meta.object_name.lower()) |
226 | | try: |
227 | | related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name) |
228 | | except NoReverseMatch: |
229 | | info = (self.admin_site.root_path, rel_to._meta.app_label, rel_to._meta.object_name.lower()) |
230 | | related_url = '%s%s/%s/add/' % info |
231 | 232 | self.widget.choices = self.choices |
232 | 233 | output = [self.widget.render(name, value, *args, **kwargs)] |
| 234 | rel_to = self.rel.to |
233 | 235 | if rel_to in self.admin_site._registry: # If the related object has an admin interface: |
| 236 | related_url = get_related_url(rel_to, 'add', self.admin_site) |
234 | 237 | # TODO: "id_" is hard-coded here. This should instead use the correct |
235 | 238 | # API to determine the ID dynamically. |
236 | 239 | output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \ |
diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py
index b9fe67c..e3e47ec 100644
a
|
b
|
from django.db import models
|
4 | 4 | from django.core.files.storage import default_storage |
5 | 5 | from django.contrib.auth.models import User |
6 | 6 | |
7 | | class MyFileField(models.FileField): |
8 | | pass |
| 7 | class MyFileField(models.FileField): |
| 8 | pass |
9 | 9 | |
10 | 10 | class Member(models.Model): |
11 | 11 | name = models.CharField(max_length=100) |
… |
… |
Currently: <a target="_blank" href="%(STORAGE_URL)salbums/hybrid_theory.jpg">alb
|
116 | 116 | >>> rel = Album._meta.get_field('band').rel |
117 | 117 | >>> w = ForeignKeyRawIdWidget(rel) |
118 | 118 | >>> print conditional_escape(w.render('test', band.pk, attrs={})) |
119 | | <input type="text" name="test" value="1" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/band/?t=id" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <strong>Linkin Park</strong> |
| 119 | <input type="text" name="test" value="1" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/band/?t=id" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <strong id="view_lookup_id_test"><a href="../../../admin_widgets/band/1/" onclick="return showRelatedObjectPopup(this);">Linkin Park</a></strong> |
120 | 120 | |
121 | 121 | >>> m1 = Member.objects.create(pk=1, name='Chester') |
122 | 122 | >>> m2 = Member.objects.create(pk=2, name='Mike') |
… |
… |
True
|
147 | 147 | >>> rel = Inventory._meta.get_field('parent').rel |
148 | 148 | >>> w = ForeignKeyRawIdWidget(rel) |
149 | 149 | >>> print w.render('test', core.parent_id, attrs={}) |
150 | | <input type="text" name="test" value="86" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <strong>Apple</strong> |
| 150 | <input type="text" name="test" value="86" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <strong id="view_lookup_id_test"><a href="../../../admin_widgets/inventory/1/" onclick="return showRelatedObjectPopup(this);">Apple</a></strong> |
151 | 151 | |
152 | 152 | # see #9258 |
153 | 153 | >>> hidden = Inventory.objects.create(barcode=93, name='Hidden', hidden=True) |
154 | 154 | >>> child_of_hidden = Inventory.objects.create(barcode=94, name='Child of hidden', parent=hidden) |
155 | 155 | >>> print w.render('test', child_of_hidden.parent_id, attrs={}) |
156 | | <input type="text" name="test" value="93" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <strong>Hidden</strong> |
| 156 | <input type="text" name="test" value="93" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <strong id="view_lookup_id_test"><a href="../../../admin_widgets/inventory/4/" onclick="return showRelatedObjectPopup(this);">Hidden</a></strong> |
| 157 | |
| 158 | >>> apostrophe = Inventory.objects.create(barcode=88, name="'Apostrophe', and <javascript> and a really really` really really really really really long one!") |
| 159 | >>> apostrophic_child = Inventory.objects.create(barcode=89, name="Apostrophe's child", parent=apostrophe) |
| 160 | >>> print w.render('test', apostrophic_child.parent_id, attrs={}) |
| 161 | <input type="text" name="test" value="88" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <strong id="view_lookup_id_test"><a href="../../../admin_widgets/inventory/6/" onclick="return showRelatedObjectPopup(this);">'Apostrophe', and <javascript> and a really really` ...</a></strong> |
157 | 162 | """ % { |
158 | 163 | 'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX, |
159 | 164 | 'STORAGE_URL': default_storage.url(''), |