diff --git a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js index 668c56a89d..284d44ad62 100644 --- a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js +++ b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js @@ -125,7 +125,7 @@ this.textContent = newRepr; this.value = newId; } - }); + }).trigger('change'); selects.next().find('.select2-selection__rendered').each(function() { // The element can have a clear button as a child. // Use the lastChild to modify only the displayed value. @@ -178,7 +178,7 @@ event.preventDefault(); opener.dismissRelatedLookupPopup(window, $(this).data("popup-opener")); }); - $('body').on('click', '.related-widget-wrapper-link', function(e) { + $('body').on('click', '.related-widget-wrapper-link[data-popup="yes"]', function(e) { e.preventDefault(); if (this.href) { const event = $.Event('django:show-related', {href: this.href}); diff --git a/django/contrib/admin/templates/admin/widgets/related_widget_wrapper.html b/django/contrib/admin/templates/admin/widgets/related_widget_wrapper.html index a97bec16e3..9f7e586003 100644 --- a/django/contrib/admin/templates/admin/widgets/related_widget_wrapper.html +++ b/django/contrib/admin/templates/admin/widgets/related_widget_wrapper.html @@ -7,12 +7,14 @@ {% if can_change_related %} {% translate 'Change' %} {% endif %} {% if can_add_related %} {% translate 'Add' %} @@ -21,10 +23,18 @@ {% if can_delete_related %} {% translate 'Delete' %} {% endif %} + {% if can_view_related %} + + {% translate 'View' %} + + {% endif %} {% endif %} {% endspaceless %} {% endblock %} diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index c64c5d14f3..a361968b27 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -300,10 +300,11 @@ class RelatedFieldWidgetWrapper(forms.Widget): rel_opts = self.rel.model._meta info = (rel_opts.app_label, rel_opts.model_name) self.widget.choices = self.choices + related_field_name = self.rel.get_related_field().name url_params = "&".join( "%s=%s" % param for param in [ - (TO_FIELD_VAR, self.rel.get_related_field().name), + (TO_FIELD_VAR, related_field_name), (IS_POPUP_VAR, 1), ] ) @@ -325,6 +326,7 @@ class RelatedFieldWidgetWrapper(forms.Widget): info, "delete", "__fk__" ) if self.can_view_related or self.can_change_related: + context["view_related_url_params"] = f"{TO_FIELD_VAR}={related_field_name}" context["change_related_template_url"] = self.get_related_url( info, "change", "__fk__" ) diff --git a/docs/intro/_images/admin09.png b/docs/intro/_images/admin09.png index bfaeb3b99f..7b649dc1d3 100644 Binary files a/docs/intro/_images/admin09.png and b/docs/intro/_images/admin09.png differ diff --git a/docs/releases/4.1.txt b/docs/releases/4.1.txt index 46d0cf3540..e3112ad0f5 100644 --- a/docs/releases/4.1.txt +++ b/docs/releases/4.1.txt @@ -61,6 +61,8 @@ Minor features * The admin :meth:`history view ` is now paginated. +* Related widget wrappers now have a link to object's change form. + :mod:`django.contrib.admindocs` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index e291801cae..db724b4196 100644 --- a/tests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -1675,6 +1675,7 @@ class AdminRawIdWidgetSeleniumTests(AdminWidgetSeleniumTestCase): class RelatedFieldWidgetSeleniumTests(AdminWidgetSeleniumTestCase): def test_ForeignKey_using_to_field(self): from selenium.webdriver.common.by import By + from selenium.webdriver.support.ui import Select self.admin_login(username="super", password="secret", login_url="/") self.selenium.get( @@ -1698,6 +1699,12 @@ class RelatedFieldWidgetSeleniumTests(AdminWidgetSeleniumTestCase): # The field now contains the new user self.selenium.find_element(By.CSS_SELECTOR, "#id_user option[value=newuser]") + self.selenium.find_element(By.ID, "view_id_user").click() + self.wait_for_value("#id_username", "newuser") + self.selenium.back() + + select = Select(self.selenium.find_element(By.ID, "id_user")) + select.select_by_value("newuser") # Click the Change User button to change it self.selenium.find_element(By.ID, "change_id_user").click() self.wait_for_and_switch_to_popup() @@ -1714,6 +1721,12 @@ class RelatedFieldWidgetSeleniumTests(AdminWidgetSeleniumTestCase): By.CSS_SELECTOR, "#id_user option[value=changednewuser]" ) + self.selenium.find_element(By.ID, "view_id_user").click() + self.wait_for_value("#id_username", "changednewuser") + self.selenium.back() + + select = Select(self.selenium.find_element(By.ID, "id_user")) + select.select_by_value("changednewuser") # Go ahead and submit the form to make sure it works self.selenium.find_element(By.CSS_SELECTOR, save_button_css_selector).click() self.wait_for_text(