diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 27680ceee4ed21b96769b184203850ac690e6c96..b563a7adefa666c297826487f05ac79a327b6580 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -10,6 +10,7 @@ Drupal 7.0, xxxx-xx-xx (development version)
       hashing and authentication schemes.
 - Usability:
     * Implemented drag-and-drop positioning for input format listings.
+    * Implemented drag-and-drop positioning for poll options.
     * Provided descriptions for user permissions.
 - Search:
     * Added support for language-aware searches.
diff --git a/modules/poll/poll.css b/modules/poll/poll.css
index 2aaf228641cddd499eee5c5a1bcbbb5e4eca47c3..bc8ddd73fa8588def0e09af806c6326d710a0fa8 100644
--- a/modules/poll/poll.css
+++ b/modules/poll/poll.css
@@ -33,6 +33,14 @@
 .node-form #edit-poll-more {
   margin: 0;
 }
+.node-form #poll-choice-table .form-text {
+  display: inline;
+  width: auto;
+}
+.node-form #poll-choice-table td.choice-flag {
+  white-space: nowrap;
+  width: 4em;
+}
 td.poll-chtext {
   width: 80%;
 }
diff --git a/modules/poll/poll.install b/modules/poll/poll.install
index 3d9b8b9ae8a094fdecd1049293799d4b4ddd3d42..bd4b031b82e6e39a84eebc716e20de4274106629 100644
--- a/modules/poll/poll.install
+++ b/modules/poll/poll.install
@@ -77,8 +77,9 @@ function poll_schema() {
         'default' => 0,
         'description' => t('The total number of votes this choice has received by all users.'),
       ),
-      'chorder' => array(
+      'weight' => array(
         'type' => 'int',
+        'size' => 'tiny',
         'not null' => TRUE,
         'default' => 0,
         'description' => t('The sort order of this choice among all choices for the same node.'),
@@ -93,6 +94,12 @@ function poll_schema() {
   $schema['poll_votes'] = array(
     'description' => t('Stores per-{users} votes for each {poll}.'),
     'fields' => array(
+      'chid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => t("The {users}'s vote for this poll."),
+      ),
       'nid' => array(
         'type' => 'int',
         'unsigned' => TRUE,
@@ -106,12 +113,6 @@ function poll_schema() {
         'default' => 0,
         'description' => t('The {users}.uid this vote is from unless the voter was anonymous.'),
       ),
-      'chorder' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => -1,
-        'description' => t("The {users}'s vote for this poll."),
-      ),
       'hostname' => array(
         'type' => 'varchar',
         'length' => 128,
@@ -122,6 +123,7 @@ function poll_schema() {
     ),
     'primary key' => array('nid', 'uid', 'hostname'),
     'indexes' => array(
+      'chid'     => array('chid'),
       'hostname' => array('hostname'),
       'uid' => array('uid'),
     ),
diff --git a/modules/poll/poll.module b/modules/poll/poll.module
index 2a1ab86a23760739ede992134364695bf37560ea..d1d21cc938f371bbcdc6a38e1c7dce526070840f 100644
--- a/modules/poll/poll.module
+++ b/modules/poll/poll.module
@@ -227,11 +227,23 @@ function poll_form(&$node, $form_state) {
   );
 
   // Add the current choices to the form.
-  for ($delta = 0; $delta < $choice_count; $delta++) {
-    $text = isset($node->choice[$delta]['chtext']) ? $node->choice[$delta]['chtext'] : '';
-    $votes = isset($node->choice[$delta]['chvotes']) ? $node->choice[$delta]['chvotes'] : 0;
+  $delta = 0;
+  $weight = 0;
+  if (isset($node->choice)) {
+    $delta = count($node->choice);
+    $weight = -$delta;
+    foreach ($node->choice as $chid => $choice) {
+      $key = 'chid:'. $chid;
+      $form['choice_wrapper']['choice'][$key] = _poll_choice_form($key, $choice['chid'], $choice['chtext'], $choice['chvotes'], $choice['weight'], $choice_count);
+      $weight = ($choice['weight'] > $weight) ? $choice['weight'] : $weight;
+    }
+  }
 
-    $form['choice_wrapper']['choice'][$delta] = _poll_choice_form($delta, $text, $votes);
+  // Add initial or additional choices.
+  $existing_delta = $delta;
+  for ($delta; $delta < $choice_count; $delta++) {
+    $key = 'new:'. ($delta - $existing_delta);
+    $form['choice_wrapper']['choice'][$key] = _poll_choice_form($key, NULL, '', 0, $weight, $choice_count);
   }
 
   // We name our button 'poll_more' to avoid conflicts with other modules using
@@ -251,30 +263,30 @@ function poll_form(&$node, $form_state) {
   );
 
   // Poll attributes
-  $_duration = array(0 => t('Unlimited')) + drupal_map_assoc(array(86400, 172800, 345600, 604800, 1209600, 2419200, 4838400, 9676800, 31536000), "format_interval");
-  $_active = array(0 => t('Closed'), 1 => t('Active'));
-
-  if ($admin) {
-    $form['settings'] = array(
-      '#type' => 'fieldset',
-      '#collapsible' => TRUE,
-      '#title' => t('Poll settings'),
-      '#weight' => -3,
-    );
+  $duration = array(0 => t('Unlimited')) + drupal_map_assoc(array(86400, 172800, 345600, 604800, 1209600, 2419200, 4838400, 9676800, 31536000), "format_interval");
+  $active = array(0 => t('Closed'), 1 => t('Active'));
+
+  $form['settings'] = array(
+    '#type' => 'fieldset',
+    '#collapsible' => TRUE,
+    '#title' => t('Poll settings'),
+    '#weight' => -3,
+    '#access' => $admin,
+  );
 
-    $form['settings']['active'] = array(
-      '#type' => 'radios',
-      '#title' => t('Poll status'),
-      '#default_value' => isset($node->active) ? $node->active : 1,
-      '#options' => $_active,
-      '#description' => t('When a poll is closed, visitors can no longer vote for it.')
-    );
-  }
+  $form['settings']['active'] = array(
+    '#type' => 'radios',
+    '#title' => t('Poll status'),
+    '#default_value' => isset($node->active) ? $node->active : 1,
+    '#options' => $active,
+    '#description' => t('When a poll is closed, visitors can no longer vote for it.'),
+    '#access' => $admin,
+  );
   $form['settings']['runtime'] = array(
     '#type' => 'select',
     '#title' => t('Poll duration'),
     '#default_value' => isset($node->runtime) ? $node->runtime : 0,
-    '#options' => $_duration,
+    '#options' => $duration,
     '#description' => t('After this period, the poll will be closed automatically.'),
   );
 
@@ -296,7 +308,7 @@ function poll_more_choices_submit($form, &$form_state) {
   }
 }
 
-function _poll_choice_form($delta, $value = '', $votes = 0) {
+function _poll_choice_form($key, $chid = NULL, $value = '', $votes = 0, $weight = 0, $size = 10) {
   $admin = user_access('administer nodes');
 
   $form = array(
@@ -305,23 +317,33 @@ function _poll_choice_form($delta, $value = '', $votes = 0) {
 
   // We'll manually set the #parents property of these fields so that
   // their values appear in the $form_state['values']['choice'] array.
+  $form['chid'] = array(
+    '#type' => 'value',
+    '#value' => $chid,
+    '#parents' => array('choice', $key, 'chid'),
+  );
+
   $form['chtext'] = array(
     '#type' => 'textfield',
-    '#title' => t('Choice @n', array('@n' => ($delta + 1))),
     '#default_value' => $value,
-    '#parents' => array('choice', $delta, 'chtext'),
+    '#parents' => array('choice', $key, 'chtext'),
   );
 
-  if ($admin) {
-    $form['chvotes'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Votes for choice @n', array('@n' => ($delta + 1))),
-      '#default_value' => $votes,
-      '#size' => 5,
-      '#maxlength' => 7,
-      '#parents' => array('choice', $delta, 'chvotes'),
-    );
-  }
+  $form['chvotes'] = array(
+    '#type' => 'textfield',
+    '#default_value' => $votes,
+    '#size' => 5,
+    '#maxlength' => 7,
+    '#parents' => array('choice', $key, 'chvotes'),
+    '#access' => user_access('administer nodes'),
+  );
+
+  $form['weight'] = array(
+    '#type' => 'weight',
+    '#default_value' => $weight,
+    '#delta' => $size,
+    '#parents' => array('choice', $key, 'weight'),
+  );
 
   return $form;
 }
@@ -330,20 +352,39 @@ function _poll_choice_form($delta, $value = '', $votes = 0) {
  * Menu callback for AHAH additions.
  */
 function poll_choice_js() {
+  // Add the new element to the stored form state. Without adding the element
+  // to the form, Drupal is not aware of this new elements existence and will
+  // not process it. We retreive the cached form, add the element, and resave.
+  $form_build_id = $_POST['form_build_id'];
+  $form_state = array('submitted' => FALSE);
+  $form = form_get_cache($form_build_id, $form_state);
+
   $delta = count($_POST['choice']);
+  $key = isset($form['#node']->choice) ? 'new:'. ($delta - count($form['#node']->choice)) : 'new:'. $delta;
+
+  // Match the new choice at the current greatest weight.
+  $weight = 0;
+  foreach ($_POST['choice'] as $choice) {
+    $weight = $choice['weight'] > $weight ? $choice['weight'] : $weight;
+  }
 
   // Build our new form element.
-  $form_element = _poll_choice_form($delta);
+  $form_element = _poll_choice_form($key, NULL, NULL, NULL, $weight, $delta + 1);
   drupal_alter('form', $form_element, array(), 'poll_choice_js');
 
-  // Build the new form.
-  $form_state = array('submitted' => FALSE);
-  $form_build_id = $_POST['form_build_id'];
-  // Add the new element to the stored form. Without adding the element to the
-  // form, Drupal is not aware of this new elements existence and will not
-  // process it. We retreive the cached form, add the element, and resave.
-  $form = form_get_cache($form_build_id, $form_state);
-  $form['choice_wrapper']['choice'][$delta] = $form_element;
+  // Dynamically increase the delta of the weight field for every field added.
+  foreach(element_children($form['choice_wrapper']['choice']) as $n) {
+    $form['choice_wrapper']['choice'][$n]['weight']['#delta'] = $delta + 1;
+  }
+
+  // Add the new poll choice.
+  $form['choice_wrapper']['choice'][$key] = $form_element;
+
+  // Reorder the form to use the same order as post.
+  $order = array_flip(array_keys($_POST['choice']));
+  $form['choice_wrapper']['choice'] = array_merge($order, $form['choice_wrapper']['choice']);
+
+  // Resave the cache.
   form_set_cache($form_build_id, $form, $form_state);
   $form += array(
     '#post' => $_POST,
@@ -356,8 +397,8 @@ function poll_choice_js() {
   // Render the new output.
   $choice_form = $form['choice_wrapper']['choice'];
   unset($choice_form['#prefix'], $choice_form['#suffix']); // Prevent duplicate wrappers.
-  $choice_form[$delta]['#attributes']['class'] = empty($choice_form[$delta]['#attributes']['class']) ? 'ahah-new-content' : $choice_form[$delta]['#attributes']['class'] . ' ahah-new-content';
-  $choice_form[$delta]['chvotes']['#value'] = 0;
+  $choice_form[$key]['#attributes']['class'] = empty($choice_form[$key]['#attributes']['class']) ? 'ahah-new-content' : $choice_form[$key]['#attributes']['class'] .' ahah-new-content';
+  $choice_form[$key]['chvotes']['#value'] = 0;
   $output = theme('status_messages') . drupal_render($choice_form);
 
   drupal_json(array('status' => TRUE, 'data' => $output));
@@ -405,22 +446,22 @@ function poll_load($node) {
   $poll = db_fetch_object(db_query("SELECT runtime, active FROM {poll} WHERE nid = %d", $node->nid));
 
   // Load the appropriate choices into the $poll object.
-  $result = db_query("SELECT chtext, chvotes, chorder FROM {poll_choices} WHERE nid = %d ORDER BY chorder", $node->nid);
+  $result = db_query("SELECT chid, chtext, chvotes, weight FROM {poll_choices} WHERE nid = %d ORDER BY weight", $node->nid);
   while ($choice = db_fetch_array($result)) {
-    $poll->choice[$choice['chorder']] = $choice;
+    $poll->choice[$choice['chid']] = $choice;
   }
 
   // Determine whether or not this user is allowed to vote.
   $poll->allowvotes = FALSE;
   if (user_access('vote on polls') && $poll->active) {
     if ($user->uid) {
-      $result = db_fetch_object(db_query('SELECT chorder FROM {poll_votes} WHERE nid = %d AND uid = %d', $node->nid, $user->uid));
+      $result = db_fetch_object(db_query('SELECT chid FROM {poll_votes} WHERE nid = %d AND uid = %d', $node->nid, $user->uid));
     }
     else {
-      $result = db_fetch_object(db_query("SELECT chorder FROM {poll_votes} WHERE nid = %d AND hostname = '%s'", $node->nid, ip_address()));
+      $result = db_fetch_object(db_query("SELECT chid FROM {poll_votes} WHERE nid = %d AND hostname = '%s'", $node->nid, ip_address()));
     }
-    if (isset($result->chorder)) {
-      $poll->vote = $result->chorder;
+    if (isset($result->chid)) {
+      $poll->vote = $result->chid;
     }
     else {
       $poll->vote = -1;
@@ -444,10 +485,9 @@ function poll_insert($node) {
 
   db_query("INSERT INTO {poll} (nid, runtime, active) VALUES (%d, %d, %d)", $node->nid, $node->runtime, $node->active);
 
-  $i = 0;
   foreach ($node->choice as $choice) {
     if ($choice['chtext'] != '') {
-      db_query("INSERT INTO {poll_choices} (nid, chtext, chvotes, chorder) VALUES (%d, '%s', %d, %d)", $node->nid, $choice['chtext'], $choice['chvotes'], $i++);
+      db_query("INSERT INTO {poll_choices} (nid, chtext, chvotes, weight) VALUES (%d, '%s', %d, %d)", $node->nid, $choice['chtext'], $choice['chvotes'], $choice['weight']);
     }
   }
 }
@@ -459,29 +499,19 @@ function poll_update($node) {
   // Update poll settings.
   db_query('UPDATE {poll} SET runtime = %d, active = %d WHERE nid = %d', $node->runtime, $node->active, $node->nid);
 
-  // Clean poll choices.
-  db_query('DELETE FROM {poll_choices} WHERE nid = %d', $node->nid);
-
-  // Poll choices come in the same order with the same numbers as they are in
-  // the database, but some might have an empty title, which signifies that
-  // they should be removed. We remove all votes to the removed options, so
-  // people who voted on them can vote again.
-  $new_chorder = 0;
-  foreach ($node->choice as $old_chorder => $choice) {
-    $chvotes = isset($choice['chvotes']) ? (int)$choice['chvotes'] : 0;
-    $chtext = $choice['chtext'];
-
-    if (!empty($chtext)) {
-      db_query("INSERT INTO {poll_choices} (nid, chtext, chvotes, chorder) VALUES (%d, '%s', %d, %d)", $node->nid, $chtext, $chvotes, $new_chorder);
-      if ($new_chorder != $old_chorder) {
-        // We can only remove items in the middle, not add, so
-        // new_chorder is always <= old_chorder, making this safe.
-        db_query("UPDATE {poll_votes} SET chorder = %d WHERE nid = %d AND chorder = %d", $new_chorder, $node->nid, $old_chorder);
+  // Poll choices with empty titles signifies removal. We remove all votes to
+  // the removed options, so people who voted on them can vote again.
+  foreach ($node->choice as $key => $choice) {
+    if (!empty($choice['chtext'])) {
+      if (isset($choice['chid'])) {
+        db_query("UPDATE {poll_choices} SET chtext = '%s', chvotes = %d, weight = %d WHERE chid = %d", $choice['chtext'], (int)$choice['chvotes'], $choice['weight'], $choice['chid']);
+      }
+      else {
+        db_query("INSERT INTO {poll_choices} (nid, chtext, chvotes, weight) VALUES (%d, '%s', %d, %d)", $node->nid, $choice['chtext'], (int)$choice['chvotes'], $choice['weight']);
       }
-      $new_chorder++;
     }
     else {
-      db_query("DELETE FROM {poll_votes} WHERE nid = %d AND chorder = %d", $node->nid, $old_chorder);
+      db_query("DELETE FROM {poll_votes} WHERE nid = %d AND chid = %d", $node->nid, $key);
     }
   }
 }
@@ -607,14 +637,14 @@ function poll_vote($form, &$form_state) {
 
   global $user;
   if ($user->uid) {
-    db_query('INSERT INTO {poll_votes} (nid, chorder, uid) VALUES (%d, %d, %d)', $node->nid, $choice, $user->uid);
+    db_query('INSERT INTO {poll_votes} (nid, chid, uid) VALUES (%d, %d, %d)', $node->nid, $choice, $user->uid);
   }
   else {
-    db_query("INSERT INTO {poll_votes} (nid, chorder, hostname) VALUES (%d, %d, '%s')", $node->nid, $choice, ip_address());
+    db_query("INSERT INTO {poll_votes} (nid, chid, hostname) VALUES (%d, %d, '%s')", $node->nid, $choice, ip_address());
   }
 
   // Add one to the votes.
-  db_query("UPDATE {poll_choices} SET chvotes = chvotes + 1 WHERE nid = %d AND chorder = %d", $node->nid, $choice);
+  db_query("UPDATE {poll_choices} SET chvotes = chvotes + 1 WHERE chid = %d", $choice);
 
   cache_clear_all();
   drupal_set_message(t('Your vote was recorded.'));
@@ -672,35 +702,40 @@ function poll_view_results(&$node, $teaser, $page, $block) {
  * @ingroup themeable
  */
 function theme_poll_choices($form) {
-  // Change the button title to reflect the behavior when using JavaScript.
-  drupal_add_js('if (Drupal.jsEnabled) { $(document).ready(function() { $("#edit-poll-more").val("' . t('Add another choice') . '"); }); }', 'inline');
+  drupal_add_tabledrag('poll-choice-table', 'order', 'sibling', 'poll-weight');
 
+  $delta = 0;
   $rows = array();
   $headers = array(
+    '',
     t('Choice'),
     t('Vote count'),
+    t('Weight'),
   );
 
   foreach (element_children($form) as $key) {
-    // No need to print the field title every time.
-    unset($form[$key]['chtext']['#title'], $form[$key]['chvotes']['#title']);
+    $delta++;
+    // Set special classes for drag and drop updating.
+    $form[$key]['weight']['#attributes']['class'] = 'poll-weight';
 
     // Build the table row.
     $row = array(
       'data' => array(
-        array('data' => drupal_render($form[$key]['chtext']), 'class' => 'poll-chtext'),
-        array('data' => drupal_render($form[$key]['chvotes']), 'class' => 'poll-chvotes'),
+        array('class' => 'choice-flag'),
+        drupal_render($form[$key]['chtext']),
+        drupal_render($form[$key]['chvotes']),
+        drupal_render($form[$key]['weight']),
       ),
+      'class' => 'draggable',
     );
 
-    // Add additional attributes to the row, such as a class for this row.
-    if (isset($form[$key]['#attributes'])) {
-      $row = array_merge($row, $form[$key]['#attributes']);
-    }
+    // Add any additional classes set on the row.
+    $row['class'] .= isset($form[$key]['#attributes']['class']) ? ' '. $form[$key]['#attributes']['class'] : '';
+
     $rows[] = $row;
   }
 
-  $output = theme('table', $headers, $rows);
+  $output = theme('table', $headers, $rows, array('id' => 'poll-choice-table'));
   $output .= drupal_render($form);
   return $output;
 }
@@ -784,7 +819,7 @@ function poll_cancel($form, &$form_state) {
   }
 
   // Subtract from the votes.
-  db_query("UPDATE {poll_choices} SET chvotes = chvotes - 1 WHERE nid = %d AND chorder = %d", $node->nid, $node->vote);
+  db_query("UPDATE {poll_choices} SET chvotes = chvotes - 1 WHERE chid = %d", $node->vote);
 }
 
 /**
diff --git a/modules/poll/poll.pages.inc b/modules/poll/poll.pages.inc
index c5895f902e88eb366e27760e95b43e5f7117715d..39d4563983ba2e9359bec87c3e87955a59615e32 100644
--- a/modules/poll/poll.pages.inc
+++ b/modules/poll/poll.pages.inc
@@ -33,13 +33,14 @@ function poll_votes($node) {
 
   $header[] = array('data' => t('Visitor'), 'field' => 'u.name');
   $header[] = array('data' => t('Vote'), 'field' => 'pv.chorder');
+  $header[] = array('data' => t('Vote'), 'field' => 'pc.weight');
 
-  $result = pager_query("SELECT pv.chorder, pv.uid, pv.hostname, u.name FROM {poll_votes} pv LEFT JOIN {users} u ON pv.uid = u.uid WHERE pv.nid = %d" . tablesort_sql($header), 20, 0, NULL, $node->nid);
+  $result = pager_query("SELECT pv.chid, pv.uid, pv.hostname, u.name FROM {poll_votes} pv INNER JOIN {poll_choices} pc ON pv.chid = pc.chid LEFT JOIN {users} u ON pv.uid = u.uid WHERE pv.nid = %d". tablesort_sql($header), 20, 0, NULL, $node->nid);
   $rows = array();
   while ($vote = db_fetch_object($result)) {
     $rows[] = array(
       $vote->name ? theme('username', $vote) : check_plain($vote->hostname),
-      check_plain($node->choice[$vote->chorder]['chtext']));
+      check_plain($node->choice[$vote->chid]['chtext']));
   }
   $output .= theme('table', $header, $rows);
   $output .= theme('pager', NULL, 20, 0);
diff --git a/modules/system/system.install b/modules/system/system.install
index 89c2afde288423455096feb00de6475bd4671693..7e2d240b78bf2cbf0d8592914116caafb2d8dad7 100644
--- a/modules/system/system.install
+++ b/modules/system/system.install
@@ -2996,6 +2996,27 @@ function system_update_7007() {
 }
 
 
+/**
+ * Use the poll_choice primary key to record votes in poll_votes rather than
+ * the choice order. Rename chorder to weight.
+ */
+function system_update_7008() {
+  $ret = array();
+  if (db_table_exists('poll_votes')) {
+    // Add chid column and convert existing votes.
+    db_add_field($ret, 'poll_votes', 'chid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0));
+    db_add_index($ret, 'poll_votes', 'chid', array('chid'));
+    $ret[] = update_sql("UPDATE {poll_votes} v SET chid = (SELECT chid FROM {poll_choices} c WHERE v.chorder = c.chorder AND v.nid = c.nid)");
+    // Remove old chorder column.
+    db_drop_field($ret, 'poll_votes', 'chorder');
+  }
+  if (db_table_exists('poll_choices')) {
+    // Change the chorder column to weight in poll_choices.
+    db_change_field($ret, 'poll_choices', 'chorder', 'weight', array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny'));
+  }
+  return $ret;
+}
+
 /**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.