Index: src/wp-admin/includes/ajax-actions.php
===================================================================
--- src/wp-admin/includes/ajax-actions.php	(revision 31615)
+++ src/wp-admin/includes/ajax-actions.php	(working copy)
@@ -2267,6 +2267,9 @@
 	if ( 'attachment' != $post['post_type'] )
 		wp_send_json_error();
 
+	if ( isset( $changes['parent'] ) )
+		$post['post_parent'] = $changes['parent'];
+
 	if ( isset( $changes['title'] ) )
 		$post['post_title'] = $changes['title'];
 
Index: src/wp-admin/includes/class-wp-media-list-table.php
===================================================================
--- src/wp-admin/includes/class-wp-media-list-table.php	(revision 31615)
+++ src/wp-admin/includes/class-wp-media-list-table.php	(working copy)
@@ -139,6 +139,9 @@
 		if ( isset( $_REQUEST['found_post_id'] ) && isset( $_REQUEST['media'] ) )
 			return 'attach';
 
+		if ( isset( $_REQUEST['parent_post_id'] ) && isset( $_REQUEST['media'] ) )
+			return 'detach';
+
 		if ( isset( $_REQUEST['delete_all'] ) || isset( $_REQUEST['delete_all2'] ) )
 			return 'delete_all';
 
@@ -406,7 +409,16 @@
 				} else {
 					echo $title;
 				} ?></strong>,
-				<?php echo get_the_time( __( 'Y/m/d' ) ); ?>
+				<?php echo get_the_time( __( 'Y/m/d' ) ); ?><br />
+				<?php
+				if ( $user_can_edit ):
+					$detach_url = add_query_arg( array(
+						'parent_post_id' => $post->post_parent,
+						'media[]' => $post->ID,
+						'_wpnonce' => wp_create_nonce( 'bulk-' . $this->_args['plural'] )
+					), 'upload.php' ); ?>
+				<a class="hide-if-no-js detach-from-parent" href="<?php echo $detach_url ?>"><?php _e( 'Detach' ); ?></a>
+				<?php endif; ?>
 			</td>
 <?php
 		} else {
Index: src/wp-admin/includes/media.php
===================================================================
--- src/wp-admin/includes/media.php	(revision 31615)
+++ src/wp-admin/includes/media.php	(working copy)
@@ -3010,3 +3010,61 @@
 
 	return $metadata;
 }
+
+/**
+ * Encapsulate logic for Attach/Detach actions
+ *
+ * @since 4.2.0
+ *
+ * @global wpdb $wpdb
+ * @param int    $parent_id
+ * @param string $action
+ */
+function wp_media_attach_action( $parent_id, $action = 'attach' ) {
+	global $wpdb;
+
+	if ( ! $parent_id ) {
+		return;
+	}
+
+	if ( ! current_user_can( 'edit_post', $parent_id ) ) {
+		wp_die( __( 'You are not allowed to edit this post.' ) );
+	}
+	$ids = array();
+	foreach ( (array) $_REQUEST['media'] as $att_id ) {
+		$att_id = (int) $att_id;
+
+		if ( ! current_user_can( 'edit_post', $att_id ) ) {
+			continue;
+		}
+
+		$ids[] = $att_id;
+	}
+
+	if ( ! empty( $ids ) ) {
+		$ids_string = implode( ',', $ids );
+		if ( 'attach' === $action ) {
+			$result = $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET post_parent = %d WHERE post_type = 'attachment' AND ID IN ( $ids_string )", $parent_id ) );
+		} else {
+			$result = $wpdb->query( "UPDATE $wpdb->posts SET post_parent = 0 WHERE post_type = 'attachment' AND ID IN ( $ids_string )" );
+		}
+
+		foreach ( $ids as $att_id ) {
+			clean_attachment_cache( $att_id );
+		}
+	}
+
+	if ( isset( $result ) ) {
+		$location = 'upload.php';
+		if ( $referer = wp_get_referer() ) {
+			if ( false !== strpos( $referer, 'upload.php' ) ) {
+				$location = $referer;
+			}
+		}
+
+		$key = 'attach' === $action ? 'attached' : 'detached';
+		$location = add_query_arg( array( $key => $result ) , $location );
+		wp_redirect( $location );
+		exit;
+	}
+}
\ No newline at end of file
Index: src/wp-admin/upload.php
===================================================================
--- src/wp-admin/upload.php	(revision 31615)
+++ src/wp-admin/upload.php	(working copy)
@@ -113,45 +113,14 @@
 	}
 
 	switch ( $doaction ) {
+		case 'detach':
+			wp_media_attach_action( $_REQUEST['parent_post_id'], 'detach' );
+			break;
+
 		case 'attach':
-			$parent_id = (int) $_REQUEST['found_post_id'];
-			if ( !$parent_id )
-				return;
+			wp_media_attach_action( $_REQUEST['found_post_id'] );
+			break;
 
-			$parent = get_post( $parent_id );
-			if ( !current_user_can( 'edit_post', $parent_id ) )
-				wp_die( __( 'You are not allowed to edit this post.' ) );
-
-			$attach = array();
-			foreach ( (array) $_REQUEST['media'] as $att_id ) {
-				$att_id = (int) $att_id;
-
-				if ( !current_user_can( 'edit_post', $att_id ) )
-					continue;
-
-				$attach[] = $att_id;
-			}
-
-			if ( ! empty( $attach ) ) {
-				$attach_string = implode( ',', $attach );
-				$attached = $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET post_parent = %d WHERE post_type = 'attachment' AND ID IN ( $attach_string )", $parent_id ) );
-				foreach ( $attach as $att_id ) {
-					clean_attachment_cache( $att_id );
-				}
-			}
-
-			if ( isset( $attached ) ) {
-				$location = 'upload.php';
-				if ( $referer = wp_get_referer() ) {
-					if ( false !== strpos( $referer, 'upload.php' ) )
-						$location = $referer;
-				}
-
-				$location = add_query_arg( array( 'attached' => $attached ) , $location );
-				wp_redirect( $location );
-				exit;
-			}
-			break;
 		case 'trash':
 			if ( !isset( $post_ids ) )
 				break;
@@ -256,9 +225,14 @@
 
 if ( ! empty( $_GET['attached'] ) && $attached = absint( $_GET['attached'] ) ) {
 	$message = sprintf( _n('Reattached %d attachment.', 'Reattached %d attachments.', $attached), $attached );
-	$_SERVER['REQUEST_URI'] = remove_query_arg(array('attached'), $_SERVER['REQUEST_URI']);
+	$_SERVER['REQUEST_URI'] = remove_query_arg( array( 'detached', 'attached' ), $_SERVER['REQUEST_URI'] );
 }
 
+if ( ! empty( $_GET['detached'] ) && $detached = absint( $_GET['detached'] ) ) {
+	$message = sprintf( _n( 'Detached %d attachment.', 'Detached %d attachments.', $detached ), $detached );
+	$_SERVER['REQUEST_URI'] = remove_query_arg( array( 'detached', 'attached' ), $_SERVER['REQUEST_URI'] );
+}
+
 if ( ! empty( $_GET['deleted'] ) && $deleted = absint( $_GET['deleted'] ) ) {
 	$message = sprintf( _n( 'Media attachment permanently deleted.', '%d media attachments permanently deleted.', $deleted ), number_format_i18n( $_GET['deleted'] ) );
 	$_SERVER['REQUEST_URI'] = remove_query_arg(array('deleted'), $_SERVER['REQUEST_URI']);
Index: src/wp-includes/js/media/views/attachment/details.js
===================================================================
--- src/wp-includes/js/media/views/attachment/details.js	(revision 31615)
+++ src/wp-includes/js/media/views/attachment/details.js	(working copy)
@@ -35,7 +35,8 @@
 		'click .untrash-attachment':      'untrashAttachment',
 		'click .edit-attachment':         'editAttachment',
 		'click .refresh-attachment':      'refreshAttachment',
-		'keydown':                        'toggleSelectionHandler'
+		'keydown':                        'toggleSelectionHandler',
+		'click .detach-from-parent':      'detachFromParent'
 	},
 
 	initialize: function() {
@@ -134,6 +135,20 @@
 			this.controller.trigger( 'attachment:keydown:arrow', event );
 			return;
 		}
+	},
+
+	/**
+	 * @param {Object} event
+	 */
+	detachFromParent: function( event ) {
+		event.preventDefault();
+
+		this.model.save({
+			'parent' : 0,
+			'uploadedTo' : 0,
+			'uploadedToLink' : '',
+			'uploadedToTitle' : ''
+		});
 	}
 });
 
Index: src/wp-includes/js/media/views/attachment.js
===================================================================
--- src/wp-includes/js/media/views/attachment.js	(revision 31615)
+++ src/wp-includes/js/media/views/attachment.js	(working copy)
@@ -52,6 +52,7 @@
 			this.listenTo( this.model, 'change', this.render );
 		} else {
 			this.listenTo( this.model, 'change:percent', this.progress );
+			this.listenTo( this.model, 'change:parent', this.render );
 		}
 		this.listenTo( this.model, 'change:title', this._syncTitle );
 		this.listenTo( this.model, 'change:caption', this._syncCaption );
Index: src/wp-includes/js/media/views.js
===================================================================
--- src/wp-includes/js/media/views.js	(revision 31618)
+++ src/wp-includes/js/media/views.js	(working copy)
@@ -2656,6 +2656,7 @@
 			this.listenTo( this.model, 'change', this.render );
 		} else {
 			this.listenTo( this.model, 'change:percent', this.progress );
+			this.listenTo( this.model, 'change:parent', this.render );
 		}
 		this.listenTo( this.model, 'change:title', this._syncTitle );
 		this.listenTo( this.model, 'change:caption', this._syncCaption );
@@ -3195,7 +3196,8 @@
 		'click .untrash-attachment':      'untrashAttachment',
 		'click .edit-attachment':         'editAttachment',
 		'click .refresh-attachment':      'refreshAttachment',
-		'keydown':                        'toggleSelectionHandler'
+		'keydown':                        'toggleSelectionHandler',
+		'click .detach-from-parent':      'detachFromParent'
 	},
 
 	initialize: function() {
@@ -3294,6 +3296,20 @@
 			this.controller.trigger( 'attachment:keydown:arrow', event );
 			return;
 		}
+	},
+
+	/**
+	 * @param {Object} event
+	 */
+	detachFromParent: function( event ) {
+		event.preventDefault();
+
+		this.model.save({
+			'parent' : 0,
+			'uploadedTo' : 0,
+			'uploadedToLink' : '',
+			'uploadedToTitle' : ''
+		});
 	}
 });
 
Index: src/wp-includes/media-template.php
===================================================================
--- src/wp-includes/media-template.php	(revision 31615)
+++ src/wp-includes/media-template.php	(working copy)
@@ -418,6 +418,9 @@
 						<# } else { #>
 							<span class="value">{{ data.uploadedToTitle }}</span>
 						<# } #>
+						<# if ( data.nonces.edit ) { #>
+						<a class="detach-from-parent" data-id="{{ data.id }}" href="#">(<?php _e( 'Detach' ); ?>)</a>
+						<# } #>
 					</label>
 				<# } #>
 				<div class="attachment-compat"></div>
