diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 2071792..443880c 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -6,7 +6,7 @@ from django.forms.models import (modelform_factory, modelformset_factory,
inlineformset_factory, BaseInlineFormSet)
from django.contrib.contenttypes.models import ContentType
from django.contrib.admin import widgets, helpers
-from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
+from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict, obj_label
from django.contrib.admin.templatetags.admin_static import static
from django.contrib import messages
from django.views.decorators.csrf import csrf_protect
@@ -826,6 +826,13 @@ class ModelAdmin(BaseModelAdmin):
return HttpResponseRedirect(request.path + "?_popup=1")
else:
return HttpResponseRedirect(request.path)
+ elif "_popup" in request.POST:
+ # object changed via raw id link popup
+ obj_id = repr(force_unicode(obj._get_pk_val()))[1:]
+ obj_url = reverse('admin:%s_%s_change' % (opts.app_label, opts.object_name.lower()), args=(obj.pk,), current_app=self.admin_site.name)
+ label = obj_label(obj).replace("'", r"\'")
+ return HttpResponse('" % (obj_id, obj_url, label))
elif "_saveasnew" in request.POST:
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(verbose_name), 'obj': obj}
self.message_user(request, msg)
diff --git a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js
index ce54fa5..77ea7ee 100644
--- a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js
+++ b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js
@@ -11,6 +11,15 @@ function html_unescape(text) {
return text;
}
+function html_escape(text) {
+ text = text.replace(/&/g, '&');
+ text = text.replace(//g, '>');
+ text = text.replace(/"/g, '"');
+ text = text.replace(/'/g, ''');
+ return text;
+}
+
// IE doesn't accept periods or dashes in the window name, but the element IDs
// we use to generate popup window names may contain them, therefore we map them
// to allowed characters in a reversible way so that we can locate the correct
@@ -27,49 +36,66 @@ function windowname_to_id(text) {
return text;
}
+function getAdminMediaPrefix() {
+ // Copy-paste from DateTimeShortcuts.js.
+ if (window.__admin_media_prefix__ != undefined) {
+ return window.__admin_media_prefix__;
+ } else {
+ return '/missing-admin-media-prefix/';
+ }
+}
+
+var CLEAR_RAW_ID = '' +
+'';
+
+// FIXME: the following produce 'gettext is not defined' errors in FireBug.
+// Needs to be tracked down.
+// (jsi18n is generally included before this in admin templates)
+//
+// 'width="10" height="10" alt="' + gettext('Clear') + '" title="' +
+// gettext('Clear') + '" />';
+
+function showRelatedObjectPopup(triggeringLink) {
+ var name = triggeringLink.parentNode.id.replace(/^view_lookup_/, '');
+ name = id_to_windowname(name);
+ return openPopupWindow(triggeringLink.href, '_popup', name);
+}
+
function showRelatedObjectLookupPopup(triggeringLink) {
var name = triggeringLink.id.replace(/^lookup_/, '');
name = id_to_windowname(name);
- var href;
- if (triggeringLink.href.search(/\?/) >= 0) {
- href = triggeringLink.href + '&pop=1';
- } else {
- href = triggeringLink.href + '?pop=1';
- }
- var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
- win.focus();
- return false;
+ return openPopupWindow(triggeringLink.href, 'pop', name);
}
-function dismissRelatedLookupPopup(win, chosenId) {
+function dismissRelatedLookupPopup(win, chosenId, chosenIdHref, chosenName) {
var name = windowname_to_id(win.name);
var elem = document.getElementById(name);
+ var nameElem = document.getElementById("view_lookup_" + name);
if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) {
elem.value += ',' + chosenId;
} else {
document.getElementById(name).value = chosenId;
}
+ if (nameElem) {
+ nameElem.innerHTML = '' +
+ html_escape(chosenName) + ' ' + CLEAR_RAW_ID;
+ }
win.close();
}
function showAddAnotherPopup(triggeringLink) {
var name = triggeringLink.id.replace(/^add_/, '');
name = id_to_windowname(name);
- href = triggeringLink.href
- if (href.indexOf('?') == -1) {
- href += '?_popup=1';
- } else {
- href += '&_popup=1';
- }
- var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
- win.focus();
- return false;
+ return openPopupWindow(triggeringLink.href, '_popup', name);
}
function dismissAddAnotherPopup(win, newId, newRepr) {
// newId and newRepr are expected to have previously been escaped by
// django.utils.html.escape.
newId = html_unescape(newId);
+ var newRepr_escaped = newRepr;
newRepr = html_unescape(newRepr);
var name = windowname_to_id(win.name);
var elem = document.getElementById(name);
@@ -85,6 +111,14 @@ function dismissAddAnotherPopup(win, newId, newRepr) {
} else {
elem.value = newId;
}
+ var nameElem = document.getElementById("view_lookup_" + name);
+ if (nameElem) {
+ var chosenIdHref = win.location.href.replace(/\/add\/[^\/]*$/,
+ '/' + newId + '/');
+ nameElem.innerHTML = '' +
+ newRepr_escaped + ' ' + CLEAR_RAW_ID;
+ }
}
} else {
var toId = name + "_to";
@@ -95,3 +129,21 @@ function dismissAddAnotherPopup(win, newId, newRepr) {
}
win.close();
}
+
+function clearRawId(triggeringLink) {
+ triggeringLink.parentNode.previousSibling.previousSibling.previousSibling.previousSibling.value = '';
+ triggeringLink.parentNode.innerHTML = '';
+ return false;
+}
+
+function openPopupWindow(href, popup_var, name) {
+ if (href.indexOf('?') == -1) {
+ href += '?';
+ } else {
+ href += '&';
+ }
+ href += popup_var + '=1';
+ var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
+ win.focus();
+ return false;
+}
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index e778429..036c0e0 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -3,8 +3,10 @@ import datetime
from django.contrib.admin.util import lookup_field, display_for_field, label_for_field
from django.contrib.admin.views.main import (ALL_VAR, EMPTY_CHANGELIST_VALUE,
ORDER_VAR, PAGE_VAR, SEARCH_VAR)
+from django.contrib.admin.util import obj_label
from django.contrib.admin.templatetags.admin_static import static
from django.core.exceptions import ObjectDoesNotExist
+from django.core.urlresolvers import reverse
from django.db import models
from django.utils import formats
from django.utils.html import escape, conditional_escape
@@ -221,8 +223,12 @@ def items_for_result(cl, result, form):
attr = pk
value = result.serializable_value(attr)
result_id = repr(force_unicode(value))[1:]
- yield mark_safe(u'<%s%s>%s%s>' % \
- (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))
+ result_name = obj_label(result)
+ result_url = reverse('admin:%s_%s_change' % (result._meta.app_label, result._meta.object_name.lower()),
+ args=(result.pk,),
+ current_app=cl.model_admin.admin_site.name)
+ yield mark_safe(u'<%s%s>%s%s>' %
+ (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(' "window, %s, '%s', '%s'); return false;\"" % (result_id, result_url, result_name.replace("'", r"\'")) or ''), conditional_escape(result_repr), table_tag))
else:
# By default the fields come from ModelAdmin.list_editable, but if we pull
# 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 61182a6..16b276c 100644
--- a/django/contrib/admin/util.py
+++ b/django/contrib/admin/util.py
@@ -6,7 +6,7 @@ from django.forms.forms import pretty_name
from django.utils import formats
from django.utils.html import escape
from django.utils.safestring import mark_safe
-from django.utils.text import capfirst
+from django.utils.text import capfirst, truncate_words
from django.utils import timezone
from django.utils.encoding import force_unicode, smart_unicode, smart_str
from django.utils.translation import ungettext
@@ -332,6 +332,9 @@ def display_for_field(value, field):
else:
return smart_unicode(value)
+def obj_label(obj):
+ return escape(truncate_words(obj, 7))
+
class NotRelationField(Exception):
pass
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index 29958b2..442228e 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -9,10 +9,11 @@ from django.core.urlresolvers import reverse
from django.forms.widgets import RadioFieldRenderer
from django.forms.util import flatatt
from django.utils.html import escape
-from django.utils.text import Truncator
+from django.utils.text import Truncator, truncate_words
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
from django.utils.encoding import force_unicode
+from django.contrib.admin.util import obj_label
class FilteredSelectMultiple(forms.SelectMultiple):
@@ -160,8 +161,7 @@ class ForeignKeyRawIdWidget(forms.TextInput):
extra.append(u''
% (static('admin/img/selector-search.gif'), _('Lookup')))
output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)] + extra
- if value:
- output.append(self.label_for_value(value))
+ output.append(self.label_for_value(value, 'view_lookup_id_%s' % name))
return mark_safe(u''.join(output))
def base_url_parameters(self):
@@ -173,13 +173,44 @@ class ForeignKeyRawIdWidget(forms.TextInput):
params.update({TO_FIELD_VAR: self.rel.get_related_field().name})
return params
- def label_for_value(self, value):
- key = self.rel.get_related_field().name
- try:
- obj = self.rel.to._default_manager.using(self.db).get(**{key: value})
- return ' %s' % escape(Truncator(obj).words(14, truncate='...'))
- except (ValueError, self.rel.to.DoesNotExist):
- return ''
+ def label_for_value(self, value, name):
+ if value:
+ rel_to = self.rel.to
+ label, related_url = '', ''
+ key = self.rel.get_related_field().name
+ try:
+ obj = rel_to._default_manager.using(
+ self.db).get(**{key: value})
+ except:
+ pass
+ else:
+ label = obj_label(obj)
+ if rel_to in self.admin_site._registry:
+ try:
+ related_url = reverse('admin:%s_%s_change' %
+ (obj._meta.app_label,
+ obj._meta.module_name),
+ args=(obj.pk,),
+ current_app=self.admin_site.name)
+ except NoReverseMatch:
+ raise
+ if label and related_url:
+ label = '%s' % (related_url, label)
+
+ return (' %(label)s'
+ ' '
+ ''
+ '' % {'name': name,
+ 'label': label,
+ 'img_src': static('admin/img/icon_deletelink.gif'),
+ 'clear': _("Clear")}
+ )
+ else:
+ # a placeholder that will be filled in
+ # JavaScript dismissRelatedLookupPopup()
+ return ' ' % name
+
class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
"""
@@ -201,8 +232,10 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
def url_parameters(self):
return self.base_url_parameters()
- def label_for_value(self, value):
- return ''
+ def label_for_value(self, value, name):
+ value = [int(v) for v in value.split(',')]
+ objs = self.rel.to._default_manager.filter(pk__in=value)
+ return ' %s' % ', '.join(truncate_words(obj, 14) for obj in objs)
def value_from_datadict(self, data, files, name):
value = data.get(name)
diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py
index 87e0309..786970b 100644
--- a/tests/regressiontests/admin_widgets/tests.py
+++ b/tests/regressiontests/admin_widgets/tests.py
@@ -292,7 +292,7 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase):
w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
conditional_escape(w.render('test', band.pk, attrs={})),
- ' Linkin Park' % dict(admin_media_prefix(), bandpk=band.pk)
+ ' Linkin Park ' % dict(admin_media_prefix(), bandpk=band.pk)
)
def test_relations_to_non_primary_key(self):
@@ -307,7 +307,7 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase):
w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
w.render('test', core.parent_id, attrs={}),
- ' Apple' % admin_media_prefix()
+ u' Apple ' % admin_media_prefix()
)
def test_fk_related_model_not_in_admin(self):
@@ -320,7 +320,7 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase):
w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
conditional_escape(w.render('honeycomb_widget', big_honeycomb.pk, attrs={})),
- ' Honeycomb object' % {'hcombpk': big_honeycomb.pk}
+ ' Honeycomb object ' % {'hcombpk': big_honeycomb.pk}
)
def test_fk_to_self_model_not_in_admin(self):
@@ -333,7 +333,7 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase):
w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
conditional_escape(w.render('individual_widget', subject1.pk, attrs={})),
- ' Individual object' % {'subj1pk': subject1.pk}
+ ' Individual object ' % {'subj1pk': subject1.pk}
)
def test_proper_manager_for_label_lookup(self):
@@ -349,7 +349,7 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase):
)
self.assertHTMLEqual(
w.render('test', child_of_hidden.parent_id, attrs={}),
- ' Hidden' % admin_media_prefix()
+ ' Hidden ' % admin_media_prefix()
)
@@ -365,12 +365,12 @@ class ManyToManyRawIdWidgetTest(DjangoTestCase):
w = widgets.ManyToManyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
conditional_escape(w.render('test', [m1.pk, m2.pk], attrs={})),
- ' ' % dict(admin_media_prefix(), m1pk=m1.pk, m2pk=m2.pk)
+ ' Chester, Mike' % dict(admin_media_prefix(), m1pk=m1.pk, m2pk=m2.pk)
)
self.assertHTMLEqual(
conditional_escape(w.render('test', [m1.pk])),
- ' ' % dict(admin_media_prefix(), m1pk=m1.pk)
+ ' Chester' % dict(admin_media_prefix(), m1pk=m1.pk)
)
self.assertEqual(w._has_changed(None, None), False)
@@ -393,12 +393,12 @@ class ManyToManyRawIdWidgetTest(DjangoTestCase):
w = widgets.ManyToManyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
conditional_escape(w.render('company_widget1', [c1.pk, c2.pk], attrs={})),
- '' % {'c1pk': c1.pk, 'c2pk': c2.pk}
+ ' Company object, Company object' % {'c1pk': c1.pk, 'c2pk': c2.pk}
)
self.assertHTMLEqual(
conditional_escape(w.render('company_widget2', [c1.pk])),
- '' % {'c1pk': c1.pk}
+ ' Company object' % {'c1pk': c1.pk}
)
class RelatedFieldWidgetWrapperTests(DjangoTestCase):
@@ -691,4 +691,4 @@ class HorizontalVerticalFilterSeleniumChromeTests(HorizontalVerticalFilterSeleni
webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'
class HorizontalVerticalFilterSeleniumIETests(HorizontalVerticalFilterSeleniumFirefoxTests):
- webdriver_class = 'selenium.webdriver.ie.webdriver.WebDriver'
\ No newline at end of file
+ webdriver_class = 'selenium.webdriver.ie.webdriver.WebDriver'