diff --git a/django/contrib/admin/media/css/base.css b/django/contrib/admin/media/css/base.css
index d7fc72cb5f..13e5fe1b1d 100644
--- a/django/contrib/admin/media/css/base.css
+++ b/django/contrib/admin/media/css/base.css
@@ -350,6 +350,16 @@ table.orderable-initalized .order-cell, body>tr>td.order-cell {
background-color: #F6F6F6;
}
+.dragHandle {
+ height: 20px;
+ width: 20px;
+ float: right;
+ cursor: move;
+ background-color: #fff;
+ border: 1px solid #f00;
+ display: block;
+}
+
.empty_form {
display: none;
}
diff --git a/django/contrib/admin/media/css/forms.css b/django/contrib/admin/media/css/forms.css
index 8bd062662f..836848cf46 100644
--- a/django/contrib/admin/media/css/forms.css
+++ b/django/contrib/admin/media/css/forms.css
@@ -233,10 +233,6 @@ fieldset.monospace textarea {
width: 8em;
}
-.inline-related {
- position: relative;
-}
-
.inline-related h3 {
margin: 0;
color: #666;
@@ -247,10 +243,7 @@ fieldset.monospace textarea {
}
.inline-related h3 span.delete {
- padding-left: 20px;
- position: absolute;
- top: 2px;
- right: 10px;
+ float: right;
}
.inline-related h3 span.delete label {
@@ -329,24 +322,26 @@ fieldset.monospace textarea {
display: none;
}
-.inline-selector {
+ul.inline-selector {
float: left;
width: 19%;
+ padding: 0;
}
.inline-selector a, .inline-selector a:visited {
color: #000;
}
-.inline-selector-item {
+li.inline-selector-item {
background-color: #F6F6F6;
border: 1px solid #E7E7E7;
padding: 5px;
margin: 7px 0px 7px 5px;
font-size: 0.9em;
+ list-style-type: none;
}
-.inline-selector-item:hover {
+li.inline-selector-item:hover {
background-color: #C1DBFD;
}
@@ -360,7 +355,7 @@ fieldset.monospace textarea {
float: right;
}
-.inline-selected {
+li.inline-selected {
background-color: #9EB7D5;
}
diff --git a/django/contrib/admin/media/js/jquery-disable.text.select.pack.js b/django/contrib/admin/media/js/jquery-disable.text.select.pack.js
new file mode 100755
index 0000000000..30d391c440
--- /dev/null
+++ b/django/contrib/admin/media/js/jquery-disable.text.select.pack.js
@@ -0,0 +1,19 @@
+/**
+ * .disableTextSelect - Disable Text Select Plugin
+ *
+ * Version: 1.1
+ * Updated: 2007-11-28
+ *
+ * Used to stop users from selecting text
+ *
+ * Copyright (c) 2007 James Dempster (letssurf@gmail.com, http://www.jdempster.com/category/jquery/disabletextselect/)
+ *
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ **/
+
+/**
+ * Requirements:
+ * - jQuery (John Resig, http://www.jquery.com/)
+ **/
+(function($){if($.browser.mozilla){$.fn.disableTextSelect=function(){return this.each(function(){$(this).css({"MozUserSelect":"none"})})};$.fn.enableTextSelect=function(){return this.each(function(){$(this).css({"MozUserSelect":""})})}}else{if($.browser.msie){$.fn.disableTextSelect=function(){return this.each(function(){$(this).bind("selectstart.disableTextSelect",function(){return false})})};$.fn.enableTextSelect=function(){return this.each(function(){$(this).unbind("selectstart.disableTextSelect")})}}else{$.fn.disableTextSelect=function(){return this.each(function(){$(this).bind("mousedown.disableTextSelect",function(){return false})})};$.fn.enableTextSelect=function(){return this.each(function(){$(this).unbind("mousedown.disableTextSelect")})}}}})(jQuery)
diff --git a/django/contrib/admin/media/js/jquery-listreorder.js b/django/contrib/admin/media/js/jquery-listreorder.js
new file mode 100755
index 0000000000..fb6128592b
--- /dev/null
+++ b/django/contrib/admin/media/js/jquery-listreorder.js
@@ -0,0 +1,289 @@
+/*
+Copyright (c) 2009 Jordan Bach, http://www.utdallas.edu/~jrb048000/
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+/*
+List Reorder
+
+Enables drag-and-drop reordering of list items for any simple ordered
or unordered - list.
+
+Author: Jordan Bach
+Version: 0.1
+Created: January 1, 2009
+License: MIT License
+
+Constructor
+ $(expr).ListOrder(options)
+
+Methods:
+ makeDefaultOrder - sets the current list order to [0, 1, 2, ...]
+ restoreOrder - returns the list to its original order
+
+Events:
+ listorderchanged - Fired after a list item is dropped. The 2nd argument
+ is a jQuery object representing the list that fired the event.
+ The 3rd argument is an array where the values represent
+ the original index of each list item.
+
+Options:
+ itemHoverClass : 'itemHover',
+ dragTargetClass : 'dragTarget',
+ dropTargetClass : 'dropTarget',
+ dragHandleClass : 'dragHandle'
+
+*/
+
+(function($){
+
+$.fn.ListReorder = function (options) {
+
+ $.fn.ListReorder.defaults = {
+ itemHoverClass : 'itemHover',
+ dragTargetClass : 'dragTarget',
+ dropTargetClass : 'dropTarget',
+ dragHandleClass : 'dragHandle',
+ useDefaultDragHandle : false
+ };
+
+ var opts = $.extend({}, $.fn.ListReorder.defaults, options);
+
+ return this.each(function() {
+ var theList = $(this), // The list (
|)
+ theItems = $('li', theList), // All - elements in the list
+ dragActive = false, // Are we currently dragging an item?
+ dropTarget = null, // The list placeholder
+ dragTarget = null, // The element currently being dragged
+ dropIndex = -1, // The list index of the dropTarget
+ offset = {}, // Positions the mouse in the dragTarget
+ listOrder = [], // Keeps track of order relative to original order
+ ref = this;
+
+ theList.mouseout(ul_mouseout);
+
+ // Create the drag target
+ dragTarget = $('');
+ dragTarget.insertAfter(theList);
+ dragTarget.hide();
+ dragTarget.css('position', 'absolute');
+ dragTarget.addClass(opts.dragTargetClass);
+
+ for (var i = 0; i < theItems.length; i++)
+ listOrder.push(i);
+
+ resetList();
+
+ function resetList() {
+ theItems = $('li', theList),
+
+ // For each
- in the list
+ theItems.each(function() {
+ var li = $(this);
+
+ var dragHandle = $('');
+ dragHandle.addClass(opts.dragHandleClass)
+ .mouseover(li_mouseover)
+ .mousedown(dragHandle_mousedown);
+
+ if (opts.useDefaultDragHandle)
+ dragHandle.css({
+ 'display' : 'block',
+ 'float' : 'right',
+ 'width' : '10px',
+ 'height' : '10px',
+ 'border' : '2px solid #333',
+ 'background' : '#ccc',
+ 'cursor' : 'move'
+ });
+
+ $('.' + opts.dragHandleClass, li).remove();
+ li.prepend(dragHandle);
+ });
+
+ clearListItemStyles();
+ }
+
+ // Return all list items to their default state
+ function clearListItemStyles() {
+ theItems.each(function() {
+ var li = $(this);
+ li.removeClass(opts.itemHoverClass);
+ li.removeClass(opts.dropTargetClass);
+ });
+ }
+
+ // Handle any cleanup when the mouse leaves the list
+ function ul_mouseout() {
+ if (!dragActive)
+ clearListItemStyles();
+ }
+
+ // Add a hover class to a list item on mouseover
+ function li_mouseover() {
+ if (!dragActive) {
+ clearListItemStyles();
+ $(this).parent().addClass(opts.itemHoverClass);
+ }
+ }
+
+ // Prepare the list for dragging an item
+ function dragHandle_mousedown(e) {
+ var li = $(this).parent();
+
+ dragActive = true;
+ dropIndex = theItems.index(li);
+
+ // Show the drag target
+ dragTarget.html(li.html());
+ dragTarget.css('display', 'block');
+ offset.top = e.pageY - li.offset().top;
+ offset.left = e.pageX - li.offset().left;
+ updateDragTargetPos(e);
+
+ // Insert the placeholder
+ dropTarget = li;
+ dropTarget.html('');
+ dropTarget.css('height', dragTarget.css('height'));
+ dragTarget.css('width', dropTarget.width() + 'px');
+ dropTarget.addClass(opts.dropTargetClass);
+
+ // Disable Text and DOM selection
+ $(document).disableTextSelect();
+
+ $(document).mouseup(dragHandle_mouseup);
+ $(document).mousemove(document_mousemove);
+ }
+
+ // If this were on the element, we could lose the drag on the element
+ // if we move the mouse too fast
+ function document_mousemove(e) {
+ if (dragActive) {
+ // drag target follows mouse cursor
+ updateDragTargetPos(e);
+
+ // Don't do mess with drop index if we are above or below the list
+ if (y_mid(dragTarget) > y_bot(theList)
+ || y_mid(dragTarget) < y_top(theList)) {
+ return;
+ }
+
+ // detect position of drag target relative to list items
+ // and swap drop target and neighboring item if necessary
+ if (y_mid(dragTarget) + 5 < y_top(dropTarget)) {
+ swapListItems(dropIndex, --dropIndex);
+ } else if (y_mid(dragTarget) - 5 > y_bot(dropTarget)) {
+ swapListItems(dropIndex, ++dropIndex);
+ }
+ }
+ }
+
+ function dragHandle_mouseup() {
+ // Restore the drop target
+ dropTarget.html(dragTarget.html());
+ dropTarget.removeClass(opts.dragTargetClass);
+ dropTarget = null;
+
+ // Hide the drag target
+ dragTarget.css('display', 'none');
+
+ dragActive = false;
+ dragTarget.unbind('mouseup', dragHandle_mouseup);
+ $(document).unbind('mousemove', document_mousemove);
+ resetList();
+
+ theList.trigger('listorderchanged', [theList, listOrder]);
+
+ // Re-enable text selection
+ $(document).enableTextSelect();
+ $(document).unbind('mouseup', dragHandle_mouseup);
+ }
+
+ function updateDragTargetPos(e) {
+ dragTarget.css({
+ 'top' : e.pageY - offset.top + 'px',
+ 'left' : e.pageX - offset.left + 'px'
+ });
+ }
+
+ // Change the order of two list items
+ function swapListItems(oldDropIndex, newDropIndex) {
+ // keep indices in bounds
+ if (dropIndex < 0) {
+ dropIndex = 0;
+ return;
+ } else if (dropIndex >= theItems.length) {
+ dropIndex = theItems.length - 1;
+ return;
+ }
+
+ var t = listOrder[oldDropIndex];
+ listOrder[oldDropIndex] = listOrder[newDropIndex];
+ listOrder[newDropIndex] = t;
+
+ // swap list items
+ var oldDropTarget = theItems.get(oldDropIndex),
+ newDropTarget = theItems.get(newDropIndex),
+ temp1 = $(oldDropTarget).clone(true);
+ temp2 = $(newDropTarget).clone(true);
+
+ $(oldDropTarget).replaceWith(temp2)
+ .mouseover(li_mouseover)
+ .mousedown(dragHandle_mousedown);
+ $(newDropTarget).replaceWith(temp1)
+ .mouseover(li_mouseover)
+ .mousedown(dragHandle_mousedown);
+
+ // reset so it is valid on next use
+ theItems = $('li', theList);
+ dropTarget = $(theItems.get(newDropIndex));
+ }
+
+ function y_top(jq) {
+ return jq.offset().top;
+ }
+
+ function y_mid(jq) {
+ return (y_top(jq) + y_bot(jq)) / 2
+ }
+
+ function y_bot(jq) {
+ return jq.offset().top + jq.outerHeight();
+ }
+
+ this.makeDefaultOrder = function() {
+ for (var i = 0; i < listOrder.length; i++)
+ listOrder[i] = i;
+ }
+
+ this.restoreOrder = function() {
+ for (var i = 0; i < theItems.length; i++) {
+ if (i != listOrder[i]) {
+ var k = 0;
+ for (; k < listOrder.length; k++)
+ if (listOrder[k] == i)
+ break;
+ swapListItems(i, k);
+ }
+ }
+ theList.trigger('listorderchanged', [theList, listOrder]);
+ }
+ });
+}
+})(jQuery);
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index c97ef588fd..9d8c862615 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -1129,7 +1129,8 @@ class InlineModelAdmin(BaseModelAdmin):
if self.filter_vertical or self.filter_horizontal:
js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
if self.order_field:
- js.append('js/jquery-tablednd.js')
+ js.extend(['js/jquery-tablednd.js', 'js/jquery-disable.text.select.pack.js',
+ 'js/jquery-listreorder.js'])
return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
media = property(_media)
diff --git a/django/contrib/admin/templates/admin/edit_inline/selector.html b/django/contrib/admin/templates/admin/edit_inline/selector.html
index 5da586ff1d..82fa4bbc1f 100644
--- a/django/contrib/admin/templates/admin/edit_inline/selector.html
+++ b/django/contrib/admin/templates/admin/edit_inline/selector.html
@@ -1,25 +1,26 @@
{% load i18n %}
-