diff --git a/includes/party.data.inc b/includes/party.data.inc
index 76790ce..43f4b4d 100644
--- a/includes/party.data.inc
+++ b/includes/party.data.inc
@@ -38,6 +38,12 @@ class PartyDefaultDataSet {
   protected $party;
 
   /**
+   * var array $actions
+   *    An array of action objects for this data set.
+   */
+  protected $actions = array();
+
+  /**
    * Alters entity info for entities requesting CRM integration.
    *
    * @param array $entity_info
@@ -229,7 +235,7 @@ class PartyDefaultDataSet {
       // can be stored against forms etc.
       if ($create) {
         $entity = $this->createEntity();
-        $this->attachEntity($entity, $delta);
+        $this->attachEntity($entity, 'insert', FALSE, $delta);
       }
       else {
         // Nothing found and not creating; return false.
@@ -282,6 +288,28 @@ class PartyDefaultDataSet {
   }
 
   /**
+   * Get a PartyDataSetAction class for an action on this data set.
+   *
+   * @param $action
+   *   The action name, as given in the data set info.
+   */
+  public function getAction($action) {
+    // If we've already built this action controller don't do so again.
+    if (!empty($this->actions[$action])) {
+      return $this->actions[$action];
+    }
+
+    // Check the action is legit.
+    $info = party_get_data_set_info($this->data_set);
+    if (empty($info['actions'][$action])) {
+      throw new Exception(t('Action: @action does not exist for data set @data_set', array('@action' => $action, '@data_set' => $this->data_set)));
+    }
+
+    $this->actions[$action] = new $info['actions'][$action]['controller']($this, $action);
+    return $this->actions[$action];
+  }
+
+  /**
    * Get actions for the attached entity. Check party access in each case.
    *
    * @return
@@ -315,7 +343,8 @@ class PartyDefaultDataSet {
   }
 
   /**
-   *  Return the renderable array for one of our attached entities.
+   * DEPRECATED: This method is deprecated and pending removal. It is better to
+   *   use $this->getAction('view')->build($party, $this, array($delta));
    *
    * @param int $delta
    *  The delta of the entity to render.
@@ -327,6 +356,9 @@ class PartyDefaultDataSet {
    *  A renderable array.
    */
   public function display($delta, $view_mode = NULL) {
+    // Let sys admins know that this is being used.
+    watchdog('Party', 'Use of deprecated method "display" on data set controller');
+
     $entity = $this->getEntity($delta);
 
     if (!isset($view_mode)) {
diff --git a/includes/party.data_ui.inc b/includes/party.data_ui.inc
index ab86488..3402e5c 100644
--- a/includes/party.data_ui.inc
+++ b/includes/party.data_ui.inc
@@ -5,9 +5,7 @@
  */
 
 /**
- * The interface for action UI controllers.
- *
- * @see party_attached_entity_action_form()
+ * Interface for all Data Set actions (even those that do not include forms).
  */
 interface PartyDataSetActionInterface {
 
@@ -24,13 +22,106 @@ interface PartyDataSetActionInterface {
    *   We don't actually need this loaded, but we need a menu loader to convert
    *   the path-style string to the machine name, and a menu loader that doesn't
    *   load would be weird too.
-   * @param $eid
-   *   The id of the entity, if relevant to this action.
+   * @param $deltas
+   *   An array of data set deltas to build this for. Defaults to FALSE, in
+   *   which case all deltas that exist should be catered for.
    *
    * @return
    *  A localized string.
    */
-  public function get_page_title($name, $var);
+  public function actionTitle($party, $data_set, $deltas = FALSE);
+
+  /**
+   * Build the page render array.
+   *
+   * @param $party
+   *   A loaded party object.
+   * @param $data_set
+   *   A loaded data set controller object
+   * @param $deltas
+   *   An array of data set deltas to build this for. Defaults to FALSE, in
+   *   which case all deltas that exist should be catered for.
+   * @param $settings
+   *   An optional array of extra settings for the action. These are defined in
+   *   the data set info along with the actions available. If no values are set
+   *   then the defaults here are used.
+   */
+  public function build($party, $data_set, $deltas = FALSE, $settings = array());
+}
+
+abstract class PartyDataSetAction implements PartyDataSetActionInterface {
+
+  /**
+   * @var $action
+   *
+   * The name of this action.
+   */
+  public $action;
+
+  /**
+   * @var $data_set
+   *
+   * The data set this Action controller is on.
+   */
+  public $data_set;
+
+  /**
+   * Constructor for the Action class.
+   */
+  public function __construct($data_set, $action) {
+    $data_set_info = party_get_data_set_info($data_set->getDataInfo('name'));
+
+    $this->data_set = $data_set;
+    $this->action = $action;
+    $this->action_info = $data_set_info['actions'][$action];
+  }
+
+  /**
+   * @implements PartyDataSetActionInterface::build();
+   */
+  public function build($party, $data_set, $deltas = FALSE, $settings = array()) {
+    return array();
+  }
+}
+
+/**
+ * The abstract class for all data ui actions that require a form.
+ *
+ * @see party_data_set_actionForm()
+ */
+abstract class PartyDataSetActionForm extends PartyDataSetAction {
+
+  /**
+   * Build the page render array.
+   *
+   * @param $party
+   *   A loaded party object.
+   * @param $data_set
+   *   A loaded data set.
+   *   We don't actually need this loaded, but we need a menu loader to convert
+   *   the path-style string to the machine name, and a menu loader that doesn't
+   *   load would be weird too.
+   * @param $deltas
+   *   An array of data set deltas to build this for. Defaults to FALSE, in
+   *   which case all deltas that exist should be catered for.
+   */
+  function build($party, $data_set, $deltas = FALSE, $settings = array()) {
+    module_load_include('inc', 'party', 'party.pages');
+
+    // We use two different form ids, one for when we only have the one delta
+    // and another for dealing with multiple entities.
+    // @todo: Extend form_ids so that difference attached entity forms can be on one
+    //        page. Utilize hook_forms.
+    if (is_array($deltas) && count($deltas) == 1) {
+      $deltas = reset($deltas);
+      $form_id = 'party_data_set_action_form_single__' . $data_set->getDataInfo('name');
+    }
+    else {
+      $form_id = 'party_data_set_action_form__' . $data_set->getDataInfo('name');
+    }
+
+    return drupal_get_form($form_id, $this->action, $party, $data_set, $deltas);
+  }
 
   /**
    * Provides the action form for adding a new entity.
@@ -48,17 +139,17 @@ interface PartyDataSetActionInterface {
    * @return
    *  A FormAPI array.
    */
-  function action_form($form, &$form_state, $party, $data_set, $eid = NULL);
+  public abstract function actionForm($form, &$form_state, $party, $data_set, $delta);
 
   /**
    * Form validation for the action form.
    */
-  function action_form_validate($form, &$form_state);
+  public abstract function actionFormValidate($form, &$form_state, $party, $data_set, $delta);
 
   /**
    * Form submission for the action form.
    */
-  function action_form_submit($form, &$form_state);
+  public abstract function actionFormSubmit($form, &$form_state, $party, $data_set, $delta);
 
 }
 
@@ -67,15 +158,14 @@ interface PartyDataSetActionInterface {
  *
  * @todo: not yet in use. See todo in party_menu().
  */
-class PartyDefaultDataSetUIAdd implements PartyDataSetActionInterface {
+class PartyDefaultDataSetUIAdd extends PartyDataSetActionForm {
 
   /**
    * The page title for the action form.
    */
-  function get_page_title($party, $data_set, $eid = NULL) {
-    $data_set_controller = party_get_crm_controller($party, $data_set['set_name']);
+  function actionTitle($party, $data_set, $deltas = FALSE) {
     return t("Add new @data-set-label to @party-label", array(
-      '@data-set-label' => $data_set_controller->getDataInfo('label'),
+      '@data-set-label' => $data_set->getDataInfo('label'),
       '@party-label' => $party->label,
     ));
   }
@@ -86,51 +176,24 @@ class PartyDefaultDataSetUIAdd implements PartyDataSetActionInterface {
    * @param $party
    *   A loaded party object.
    * @param $data_set
-   *   A loaded data set.
-   *   We don't actually need this loaded, but we need a menu loader to convert
-   *   the path-style string to the machine name, and a menu loader that doesn't
-   *   load would be weird too.
+   *   A loaded data set controller
    * @param $eid
    *   The id of the entity, if relevant to this action.
    */
-  function action_form($form, &$form_state, $party, $data_set, $eid = NULL) {
-    $form = array();
-
-    $controller = party_get_crm_controller($party, $data_set['set_name']);
-    $form_state['#attached_entity'] = $controller->createEntity();
-
-    field_attach_form($data_set['entity type'], $form_state['#attached_entity'], $form, $form_state);
-
-    $form['actions'] = array(
-      '#type' => 'actions',
-      'submit' => array(
-        '#type' => 'submit',
-        '#value' => t('Create new @label', array(
-          '@label' => $data_set['label'],
-        )),
-      ),
-    );
-
-    return $form;
+  function actionForm($form, &$form_state, $party, $data_set, $delta) {
+    $action_form = array();
+    $form['#entity'] = $data_set->createEntity();
+    field_attach_form($data_set->getDataInfo('entity type'), $form['#entity'], $action_form, $form_state);
+    return $action_form;
   }
 
-  function action_form_validate($form, &$form_state) {
-    // Get the original form parameters.
-    list ($party, $data_set, $action, $eid) = $form_state['build_info']['args'];
-
-    field_attach_form_validate($data_set['entity type'], $form_state['#attached_entity'], $form, $form_state);
+  function actionFormValidate($form, &$form_state, $party, $data_set, $delta) {
+    field_attach_form_validate($data_set->getDataInfo('entity type'), $form['#entity'], $form, $form_state);
   }
 
-  function action_form_submit($form, &$form_state) {
-    // Get the original form parameters.
-    list ($party, $data_set, $action, $eid) = $form_state['build_info']['args'];
-
-    field_attach_submit($data_set['entity type'], $form_state['#attached_entity'], $form, $form_state);
-
-    // Attach the entity and save the entity and controller.
-    $controller = party_get_crm_controller($party, $data_set['set_name']);
-    $controller->attachEntity($form_state['#attached_entity']);
-    $controller->save(TRUE);
+  function actionFormSubmit($form, &$form_state, $party, $data_set, $delta) {
+    field_attach_submit($data_set->getDataInfo('entity type'), $form['#entity'], $form, $form_state);
+    $data_set->attachEntity($form['#entity'])->save(TRUE);
   }
 
 }
@@ -138,12 +201,12 @@ class PartyDefaultDataSetUIAdd implements PartyDataSetActionInterface {
 /**
  * The 'attach' action: attach an existing entity.
  */
-class PartyDefaultDataSetUIAttach implements PartyDataSetActionInterface {
+class PartyDefaultDataSetUIAttach extends PartyDataSetActionForm {
 
   /**
    * The page title for the action form.
    */
-  function get_page_title($party, $data_set, $eid = NULL) {
+  function actionTitle($party, $data_set, $eid = NULL) {
     $data_set_controller = party_get_crm_controller($party, $data_set['set_name']);
     return t("Attach existing @data-set-label to @party-label", array(
       '@data-set-label' => $data_set_controller->getDataInfo('label'),
@@ -164,7 +227,7 @@ class PartyDefaultDataSetUIAttach implements PartyDataSetActionInterface {
    * @param $eid
    *   The id of the entity, if relevant to this action.
    */
-  function action_form($form, &$form_state, $party, $data_set, $eid = NULL) {
+  function actionForm($form, &$form_state, $party, $data_set, $deltas = FALSE) {
     $form = array();
 
     drupal_set_message("This doesn't work for general entities yet!", 'error');
@@ -172,12 +235,151 @@ class PartyDefaultDataSetUIAttach implements PartyDataSetActionInterface {
     return $form;
   }
 
-  function action_form_validate($form, &$form_state) {
+  function actionFormValidate($form, &$form_state, $party, $data_set, $delta) {
+
+  }
+
+  function actionFormSubmit($form, &$form_state, $party, $data_set, $delta) {
 
   }
+}
 
-  function action_form_submit($form, &$form_state) {
+/**
+ * The 'view' action: view an existing attached entity.
+ */
+class PartyDefaultDataSetUIView extends PartyDataSetAction {
 
+  /**
+   * @implements PartyDataSetActionInterface::actionTitle().
+   */
+  public function actionTitle($party, $data_set, $deltas = FALSE) {
+    return t('View @data_set', array('@data_set' => $data_set->getDataInfo('label')));
   }
 
+  /**
+   * @overrides PartyDataSetAction::build().
+   */
+  public function build($party, $data_set, $deltas = FALSE, $settings = array()) {
+    // Get default settings.
+    $settings += $this->action_info['settings'];
+
+    if ($deltas === FALSE) {
+      $entities = $data_set->getEntities();
+    }
+    else {
+      foreach($deltas as $delta) {
+        $entities[$delta] = $data_set->getEntity($delta);
+      }
+    }
+
+    $build = entity_view($data_set->getDataInfo('entity type'), $entities, $settings['view_mode']);
+
+    return $build;
+  }
 }
+
+/**
+ * The 'edit' action: edit an existing attahced entity.
+ */
+class PartyDefaultDataSetUIEdit extends PartyDataSetActionForm {
+
+  /**
+   * @implements PartyDataSetActionInterface::actionTitle().
+   */
+  public function actionTitle($party, $data_set, $deltas = FALSE) {
+    return t('Edit @data_set', array('@data_set' => $data_set->getDataInfo('label')));
+  }
+
+  /**
+   * Provides the action form for adding a new entity.
+   *
+   * @param $party
+   *   A loaded party object.
+   * @param $data_set
+   *   A loaded data set.
+   *   We don't actually need this loaded, but we need a menu loader to convert
+   *   the path-style string to the machine name, and a menu loader that doesn't
+   *   load would be weird too.
+   * @param $delta
+   *   The delta of the entity to build the form for this time.
+   */
+  function actionForm($form, &$form_state, $party, $data_set, $delta) {
+    $action_form = array();
+    field_attach_form($data_set->getDataInfo('entity type'), $data_set->getEntity($delta), $action_form, $form_state);
+    return $action_form;
+  }
+
+  function actionFormValidate($form, &$form_state, $party, $data_set, $delta) {
+    field_attach_form_validate($data_set->getDataInfo('entity type'), $data_set->getEntity($delta), $form, $form_state);
+  }
+
+  function actionFormSubmit($form, &$form_state, $party, $data_set, $delta) {
+    // Submit fields.
+    field_attach_submit($data_set->getDataInfo('entity type'), $data_set->getEntity($delta), $form, $form_state);
+    entity_save($data_set->getDataInfo('entity type'), $data_set->getEntity($delta));
+  }
+}
+
+/**
+ * The 'detach' action: detach an existing attached entity.
+ */
+class PartyDefaultDataSetUIDetach extends PartyDataSetActionForm {
+
+  /**
+   * @overrides PartyDataSetAction::build().
+   *
+   * This operation only supports using the single form.
+   */
+  function build($party, $data_set, $deltas = FALSE, $settings = array()) {
+    // We use two different form ids, one for when we only have the one delta
+    // and another for dealing with multiple entities.
+    if (!(is_array($deltas) && count($deltas) == 1)) {
+      throw new Exception('Remove operation only works with one entity at a time, multiple deltas given.');
+    }
+
+    return parent::build($party, $data_set, $deltas, $settings);
+  }
+
+  /**
+   * @implements PartyDataSetActionInterface::actionTitle().
+   */
+  public function actionTitle($party, $data_set, $deltas = FALSE) {
+    return t('Edit @data_set', array('@data_set' => $data_set->getDataInfo('label')));
+  }
+
+  /**
+   * Provides the action form for adding a new entity.
+   *
+   * @param $party
+   *   A loaded party object.
+   * @param $data_set
+   *   A loaded data set.
+   *   We don't actually need this loaded, but we need a menu loader to convert
+   *   the path-style string to the machine name, and a menu loader that doesn't
+   *   load would be weird too.
+   * @param $delta
+   *   The delta of the entity to build the form for this time.
+   */
+  function actionForm($form, &$form_state, $party, $data_set, $delta) {
+    return confirm_form($form,
+      t('Are you sure you want to remove %attached from %party?', array(
+        '%attached' => $data_set->getDataInfo('label'),
+        '%party' =>  $party->label,
+      )),
+      'party/' . $party->pid, // @todo: go back to the dataset page.
+      t('Are you sure you want to remove this data set from this party?') . ' ' . t('This action cannot be undone.'),
+      t('Remove'),
+      t('Cancel')
+    );
+  }
+
+  function actionFormValidate($form, &$form_state, $party, $data_set, $delta) { }
+
+  function actionFormSubmit($form, &$form_state, $party, $data_set, $delta) {
+    // Submit fields.
+    $data_set->detachEntityByDelta($delta)->save();
+
+    // Set a sensible redirect
+    $form_state['redirect'] = 'party/'.$party->pid;
+  }
+}
\ No newline at end of file
diff --git a/modules/party_profile/party_profile.module b/modules/party_profile/party_profile.module
index 0d77510..70581a2 100644
--- a/modules/party_profile/party_profile.module
+++ b/modules/party_profile/party_profile.module
@@ -51,13 +51,21 @@ function party_profile_party_data_set_info() {
           'controller' => 'PartyDefaultDataSetUIAdd',
           'action label' => 'Add @data-set',
         ),
-        /*
-        // Does attach make sense for profiles?
-        'attach' => array(
-          'controller' => 'PartyDefaultDataSetUIAttach',
-          'action label' => 'Attach existing @data-set',
+        'view' => array(
+          'controller' => 'PartyDefaultDataSetUIView',
+          'action label' => 'View @data-set',
+          'settings' => array(
+            'view_mode' => 'party',
+          ),
+        ),
+        'edit' => array(
+          'controller' => 'PartyDefaultDataSetUIEdit',
+          'action label' => 'Edit @data-set',
+        ),
+        'detach' => array(
+          'controller' => 'PartyDefaultDataSetUIDetach',
+          'action label' => 'Detach @data-set',
         ),
-        */
       ),
     );
   }
diff --git a/modules/party_user/includes/party_user.data_ui.inc b/modules/party_user/includes/party_user.data_ui.inc
index e0d3f6f..9057c0b 100644
--- a/modules/party_user/includes/party_user.data_ui.inc
+++ b/modules/party_user/includes/party_user.data_ui.inc
@@ -22,7 +22,7 @@ class PartyUserDataSetUIAttach extends PartyDefaultDataSetUIAttach {
    * @param $eid
    *   The id of the entity, if relevant to this action.
    */
-  function action_form($form, &$form_state, $party, $data_set, $eid = NULL) {
+  function actionForm($form, &$form_state, $party, $data_set, $eid = NULL) {
     $form = array();
 
     $form['user'] = array(
@@ -42,7 +42,7 @@ class PartyUserDataSetUIAttach extends PartyDefaultDataSetUIAttach {
     return $form;
   }
 
-  function action_form_validate($form, &$form_state) {
+  function actionFormValidate($form, &$form_state) {
     // Validate the user name.
     $account = user_load_by_name($form_state['values']['user']);
     if (!$account) {
@@ -50,7 +50,7 @@ class PartyUserDataSetUIAttach extends PartyDefaultDataSetUIAttach {
     }
   }
 
-  function action_form_submit($form, &$form_state) {
+  function actionFormSubmit($form, &$form_state) {
     // Get the original form parameters.
     list ($party, $data_set, $action, $eid) = $form_state['build_info']['args'];
 
@@ -83,7 +83,7 @@ class PartyUserDataSetUIAdd extends PartyDefaultDataSetUIAdd {
    * @param $eid
    *   The id of the entity, if relevant to this action.
    */
-  function action_form($form, &$form_state, $party, $data_set, $eid = NULL) {
+  function actionForm($form, &$form_state, $party, $data_set, $eid = NULL) {
     module_load_include('inc', 'user', 'user.pages');
 
     $form = user_register_form($form, $form_state);
@@ -91,11 +91,11 @@ class PartyUserDataSetUIAdd extends PartyDefaultDataSetUIAdd {
     return $form;
   }
 
-  function action_form_validate($form, &$form_state) {
+  function actionFormValidate($form, &$form_state) {
 
   }
 
-  function action_form_submit($form, &$form_state) {
+  function actionFormSubmit($form, &$form_state) {
     // Get the original form parameters.
     list ($party, $data_set, $action, $eid) = $form_state['build_info']['args'];
 
diff --git a/party.module b/party.module
index 0f15466..4efe299 100644
--- a/party.module
+++ b/party.module
@@ -237,32 +237,40 @@ function party_menu() {
   // Attached entity menu items
   // Edit an attached entity
   $items['party/%party/%party_data_set/edit/%'] = array(
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('party_attached_entity_edit_form', 1, 2, 4),
+    'title callback' => 'party_data_set_action_page_title',
+    'title arguments' => array('edit', 1, 2, 4),
+    'page callback' => 'party_data_set_action_page',
+    'page arguments' => array('edit', 1, 2, 4),
     'access callback' => 'party_access',
     'access arguments' => array('edit', 1, 2),
     'file' => 'party.pages.inc',
   );
   // Detach an attached entity
   $items['party/%party/%party_data_set/remove/%'] = array(
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('party_attached_entity_remove_confirm', 1, 2, 4),
+    'title callback' => 'party_data_set_action_page_title',
+    'title arguments' => array('detach', 1, 2, 4),
+    'page callback' => 'party_data_set_action_page',
+    'page arguments' => array('detach', 1, 2, 4),
     'access callback' => 'party_access',
     'access arguments' => array('detach', 1, 2),
     'file' => 'party.pages.inc',
   );
   // Add an attached entity
   $items['party/%party/%party_data_set/add'] = array(
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('party_attached_entity_action_form', 1, 2, 'add'),
+    'title callback' => 'party_data_set_action_page_title',
+    'title arguments' => array('add', 1, 2),
+    'page callback' => 'party_data_set_action_page',
+    'page arguments' => array('add', 1, 2),
     'access callback' => 'party_access',
     'access arguments' => array('add', 1, 2),
     'file' => 'party.pages.inc',
   );
   // Attach an existing entity.
   $items['party/%party/%party_data_set/attach'] = array(
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('party_attached_entity_action_form', 1, 2, 'attach'),
+    'title callback' => 'party_data_set_action_page_title',
+    'title arguments' => array('add', 1, 2),
+    'page callback' => 'party_data_set_action_page',
+    'page arguments' => array('attach', 1, 2),
     'access callback' => 'party_access',
     'access arguments' => array('attach', 1, 2),
     'file' => 'party.pages.inc',
@@ -833,7 +841,6 @@ function party_get_data_set_info($data_set_name = NULL, $reset = FALSE) {
               'module' => $module,
               'singleton' => FALSE,
               'view mode' => 'party',
-              'form callback' => 'party_default_attached_entity_form',
               // Singleton entities have no bundle; use the entity type.
               'entity bundle' => $set['entity type'],
               'path element' => $path_element,
@@ -1296,8 +1303,7 @@ function party_views_api() {
 function party_ctools_plugin_type() {
   // Party name label plugins provide ways to generate a party name label
   // from a party.
-  $plugins['party_name_label'] = array(
-  );
+  $plugins['party_name_label'] = array();
 
   return $plugins;
 }
@@ -1317,17 +1323,6 @@ function party_ctools_plugin_directory($owner, $plugin_type) {
 // Party Attached Entity Form API
 
 /**
- * Get the form callback for attached entities.
- *
- * @param $data_set_name
- *  The name of a data set.
- */
-function party_attached_entity_form_callback($data_set_name) {
-  $data_set_info = party_get_data_set_info($data_set_name);
-  return $data_set_info['form callback'];
-}
-
-/**
  * Attach attached entity forms to a form.
  *
  * This takes all attached entities in the $form_state['#attached_entity'] array, runs
@@ -1337,10 +1332,7 @@ function party_attached_entity_form_callback($data_set_name) {
  *  Whether this is being used as a form to edit just the one attached entity.
  *  If true, the fieldset is not shown around the entity form.
  */
-function party_data_set_attach_form(&$form, &$form_state, $data_set, $deltas = FALSE, $create = TRUE) {
-  // Get the form callback.
-  $callback = party_attached_entity_form_callback($data_set->getDataInfo('name'));
-
+function party_data_set_attach_form(&$form, &$form_state, $data_set, $deltas = FALSE, $action = 'edit', $create = TRUE) {
   // Create an array of the $deltas we are working with:
   // If no deltas are specified assume we're using all the deltas.
   if (empty($deltas) && count($data_set->getEntityIds()) > 0) {
@@ -1367,6 +1359,7 @@ function party_data_set_attach_form(&$form, &$form_state, $data_set, $deltas = F
       '#party' => $data_set->getParty(),
       '#data_set_name' => $data_set->getDataInfo('name'),
       '#data_set' => $data_set,
+      '#data_set_action' => $action,
       '#delta' => $delta,
       '#entity' => $entity,
       '#parents' => array($form_key),
@@ -1374,7 +1367,7 @@ function party_data_set_attach_form(&$form, &$form_state, $data_set, $deltas = F
       '#type' => 'fieldset',
       '#title' => entity_label($data_set->getDataInfo('entity type'), $entity),
     );
-    $form[$form_key] += $callback($form, $form_state, $data_set, $delta, $data_set->getParty());
+    $form[$form_key] += $data_set->getAction($action)->actionForm($form, $form_state, $data_set->getParty(), $data_set, $delta);
   }
 
   // Save the data set in the array.
@@ -1403,10 +1396,9 @@ function party_data_set_attach_form_submit(&$form, &$form_state) {
       continue;
     }
 
-    // Get the form callback.
-    $callback = party_attached_entity_form_callback($element['#data_set_name']) . '_submit';
     // Execute the form callback.
-    $return = $callback($element, $form_state, $element['#data_set'], $element['#delta'], $element['#data_set']->getParty());
+    $element['#data_set']->getAction($element['#data_set_action'])
+      ->actionFormSubmit($element, $form_state, $element['#data_set']->getParty(), $element['#data_set'], $element['#delta']);
   }
 
   foreach ($form['#data_set_controllers'] as $controller) {
@@ -1414,63 +1406,6 @@ function party_data_set_attach_form_submit(&$form, &$form_state) {
   }
 }
 
-/**
- * The default data set form callback.
- * This returns a $form array for the entity in $attached_entity
- *
- * @param $form
- *   The whole form so far
- * @param $form_state
- *   The form state
- * @param $attached_entity
- *   The attached entity this form is for
- * @param $party
- *   The party the $attached entity is attached to
- */
-function party_default_attached_entity_form($form, &$form_state, $data_set, $delta, $party) {
-  $attached_entity_form = array();
-  field_attach_form($data_set->getDataInfo('entity type'), $data_set->getEntity($delta), $attached_entity_form, $form_state);
-  return $attached_entity_form;
-}
-
-/**
- *  The default attached entity form validate callback.
- *
- * @param $form
- *   The whole form so far
- * @param $form_state
- *   The form state
- * @param $attached_entity
- *   The attached entity this form is for
- * @param $party
- *   The party the $attached entity is attached to
- */
-function party_default_attached_entity_form_validate($form, &$form_state, &$attached_entity, $party) {
-  // Validate fields.
-  $pseudo_entity = (object) $form_state['values'][$attached_entity->hash()];
-  $psuedo_entity->type = $attached_entity->getEntityBundle();
-  field_attach_form_validate($attached_entity->getEntityType(), $pseudo_entity, $form[$attached_entity->hash()], $form_state);
-}
-
-/**
- * The default attached entity form submit callback.
- *
- * @param $form
- *   The whole form so far
- * @param $form_state
- *   The form state
- * @param $attached_entity
- *   The attached entity this form is for
- * @param $party
- *   The party the $attached entity is attached to
- */
-function party_default_attached_entity_form_submit($element, &$form_state, $data_set, $delta, $party) {
-  // Submit fields.
-  $data_set_info = party_get_data_set_info($data_set->getDataInfo('name'));
-  field_attach_submit($data_set->getDataInfo('entity type'), $data_set->getEntity($delta), $element, $form_state);
-  entity_save($data_set->getDataInfo('entity type'), $data_set->getEntity($delta));
-}
-
 // --------------------------------------------------------------------------------------------------------------
 // Party Actions & Processes
 
@@ -1689,13 +1624,20 @@ function party_find_fields_of_types($types, $reset = FALSE) {
 
 /**
  * Implements hook_forms().
+ *
+ * @todo: Implement drupal caching.
  */
 function party_forms($form_id, $args) {
   $forms = array();
 
-  if (substr($form_id, 0, 36) == 'party_attached_entity_edit_form_form') {
+  if (substr($form_id, 0, 33) == 'party_data_set_action_form_single') {
+    $forms[$form_id] = array(
+      'callback' => 'party_data_set_action_form_single',
+    );
+  }
+  else if (substr($form_id, 0, 26) == 'party_data_set_action_form') {
     $forms[$form_id] = array(
-      'callback' => 'party_attached_entity_edit_form_form',
+      'callback' => 'party_data_set_action_form',
     );
   }
 
diff --git a/party.pages.inc b/party.pages.inc
index 709b12a..766bfa1 100644
--- a/party.pages.inc
+++ b/party.pages.inc
@@ -163,22 +163,8 @@ function party_delete_form_submit($form, &$form_state) {
  *   A data set name.
  */
 function party_view_data_set($party, $data_set_name) {
-  // TODO: some of these calls are redundant!
-  $data_set_controller = party_get_crm_controller($party, $data_set_name);
-  $attached_entities = party_get_attached_entities($party, $data_set_name);
-
-  $build = array();
-  foreach ($attached_entities as $delta => $entity) {
-    $build[$data_set_name . '_' . $delta] = array(
-      // @todo: this is sort of an abuse of what fieldsets are meant for... :/
-      '#type' => 'fieldset',
-      '#title' => $data_set_controller->getLabel($delta),
-    );
-
-    $build[$data_set_name . '_' . $delta]['view'] = $data_set_controller->display($delta);
-  }
-
-  return $build;
+  $data_set_controller = party_get_data_set($party, $data_set_name);
+  return $data_set_controller->getAction('view')->build($party, $data_set_controller);
 }
 
 /**
@@ -213,8 +199,6 @@ function party_page_view_piece_views($party, $view_name, $view_display_id) {
 /**
  * Form builder for a generalized attached entity action.
  *
- * @todo: let this handle built-in actions: 'edit', 'delete', 'detach'.
- *
  * @param $party
  *   A loaded party object.
  * @param $data_set
@@ -228,169 +212,93 @@ function party_page_view_piece_views($party, $view_name, $view_display_id) {
  * @param $eid
  *   The id of the entity, if editing.
  */
-function party_attached_entity_action_form($form, &$form_state, $party, $data_set, $action, $eid = NULL) {
-  $action_controller = new $data_set['actions'][$action]['controller'];
-
-  // Get the page title from the controller.
-  $page_title = $action_controller->get_page_title($party, $data_set, $eid);
-  drupal_set_title(check_plain($page_title));
-
-  $form += $action_controller->action_form($form, $form_state, $party, $data_set, $eid);
-
-  // Ensure that our submit and validate handlers are run (and run last).
-  if (!isset($form['#submit']) || !in_array('party_attached_entity_action_form_submit', $form['#submit'])) {
-    $form['#submit'][] = 'party_attached_entity_action_form_submit';
-  }
-  if (!isset($form['#validate']) || !in_array('party_attached_entity_action_form_validate', $form['#validate'])) {
-    $form['#validate'][] = 'party_attached_entity_action_form_validate';
-  }
-
-  return $form;
-}
+function party_data_set_action_form($form, &$form_state, $action, $party, $data_set, $deltas = FALSE) {
+  form_load_include($form_state, 'inc', 'party', 'party.pages');
 
-/**
- * Validate handler for the general action form.
- */
-function party_attached_entity_action_form_validate($form, &$form_state) {
-  // Retrieve the original form parameters from the build info.
-  // Minor WTF: because $eid is optional, it doesn't show in the array. Shove it
-  // in there as NULL if missing, so controller handlers can rely on it.
-  if (!isset($form_state['build_info']['args'][3])) {
-    $form_state['build_info']['args'][3] = NULL;
+  // If just one delta was passed through, put it in an array.
+  if (is_numeric($deltas)) {
+    $deltas = array($deltas);
   }
-  list ($party, $data_set, $action, $eid) = $form_state['build_info']['args'];
-
-  // Call the validate handler in the action controller.
-  $action_controller = new $data_set['actions'][$action]['controller'];
-  $action_controller->action_form_validate($form, $form_state, $party, $data_set, $eid);
-}
 
-/**
- * Submit handler for the general action form.
- */
-function party_attached_entity_action_form_submit($form, &$form_state) {
-  // Retrieve the original form parameters from the build info.
-  // Minor WTF: because $eid is optional, it doesn't show in the array. Shove it
-  // in there as NULL if missing, so controller handlers can rely on it.
-  if (!isset($form_state['build_info']['args'][3])) {
-    $form_state['build_info']['args'][3] = NULL;
-  }
-  list ($party, $data_set, $action, $eid) = $form_state['build_info']['args'];
+  party_data_set_attach_form($form, $form_state, $data_set, $deltas, $action);
 
-  // Call the submit handler in the action controller.
-  $action_controller = new $data_set['actions'][$action]['controller'];
-  $action_controller->action_form_submit($form, $form_state, $party, $data_set, $eid);
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+    '#weight' => 99,
+  );
 
-  // Redirect to the set piece.
-  $form_state['redirect'] = 'party/' . $party->pid . '/' . $data_set['path element'];
+  return $form;
 }
 
-// =================================== Attached entity action forms.
-
 /**
- * Form to edit or create an attached entity.
+ * Form builder for a generalized attached entity action.
  *
  * @param $party
- *    A loaded party object.
+ *   A loaded party object.
  * @param $data_set
  *   A loaded data set.
  *   We don't actually need this loaded, but we need a menu loader to convert
  *   the path-style string to the machine name, and a menu loader that doesn't
  *   load would be weird too.
+ * @param $action
+ *   The machine name of a data set action, as defined by the data set in
+ *   hook_party_data_set_info().
  * @param $delta
- *    The id of the entity, if editing.
- *
- * @todo: Reimplement actions and edit overlay stuff
+ *   The delta of the entity, if editing.
  */
-function party_attached_entity_edit_form($form, &$form_state, $party, $data_set, $delta = NULL) {
-  $data_set_controller = party_get_crm_controller($party, $data_set['set_name']);
-
-  if (isset($delta)) {
-    $entity = $data_set_controller->getEntity($delta);
-    $form_title = t("Edit @data-set-label (@entity-label)", array(
-      '@data-set-label' => $data_set['label'],
-      '@entity-label' => entity_label($data_set['entity type'], $entity),
-    ));
-  }
-  else {
-    $delta = 0;
-    $entity = $data_set_controller->getEntity(0, TRUE);
-    $form_title = t("Add @data-set-label to @party-label", array(
-      '@data-set-label' => $data_set_controller->getDataInfo('label'),
-      '@party-label' => $party->label,
-    ));
-  }
-  drupal_set_title($form_title);
-
-  // Get the attached entity form as a standalone, as we have just the one.
-  party_data_set_attach_form($form, $form_state, $data_set_controller, array($delta), TRUE);
-
-  // As we know we're only dealine with one item remove the fieldset.
-  $form_key = $data_set_controller->getDataInfo('name') . ':' . $delta;
-  unset($form[$form_key]['#type'], $form[$form_key]['#title']);
+function party_data_set_action_form_single($form, &$form_state, $action, $party, $data_set, $delta = 0) {
+  form_load_include($form_state, 'inc', 'party', 'party.pages');
 
-  $form['#submit'][] = 'party_attached_entity_edit_form_submit';
+  $form['#delta'] = $delta;
+  $form['#data_set_action'] = $action;
+  $form['#data_set_controller'] = $data_set;
 
   $form['submit'] = array(
     '#type' => 'submit',
     '#value' => t('Save'),
     '#weight' => 99,
   );
+
+  $form += $data_set->getAction($action)
+    ->actionForm($form, $form_state, $party, $data_set, $delta);
+
   return $form;
 }
 
 /**
- * Validation handler for the data set edit form.
+ * Validate handler for the general action form.
  */
-function party_attached_entity_edit_form_validate($form, &$form_state) {
-  return TRUE;
+function party_data_set_action_form_single_validate($form, &$form_state) {
+  $data_set = $form['#data_set_controller'];
+  $data_set->getAction($form['#data_set_action'])
+    ->actionFormValidate($form, $form_state, $data_set->getParty(), $data_set, $form['#delta']);
 }
 
 /**
- * Submit handler for the data set edit form.
+ * Submit handler for the general action form.
  */
-function party_attached_entity_edit_form_submit($form, &$form_state) {
-  // Get the data set controller
-  $data_set_controller = reset($form['#data_set_controllers']);
-
-  // Redirect to the set piece.
-  $form_state['redirect'] = 'party/' . $data_set_controller->getParty()->pid . '/' . $data_set_controller->getDataInfo('path element');
+function party_data_set_action_form_single_submit($form, &$form_state) {
+  $data_set = $form['#data_set_controller'];
+  $data_set->getAction($form['#data_set_action'])
+    ->actionFormSubmit($form, $form_state, $data_set->getParty(), $data_set, $form['#delta']);
 }
 
+// =================================== Attached entity action pages.
+
 /**
- * Form to confirm removing a data set from a party.
- *
- * @todo: pass this the delta rather than the entity id.
+ * Page callback for data set action pages.
  */
-function party_attached_entity_remove_confirm($form, &$form_state, $party, $data_set, $eid) {
-  $data_set_controller = party_get_crm_controller($party, $data_set['set_name']);
-
-  $form['#party'] = $party;
-  $form['#data_set'] = $data_set_controller;
-  $form['#entity'] = reset(entity_load($data_set_controller->getDataInfo('entity type'), array($eid)));
-
-  return confirm_form($form,
-    t('Are you sure you want to remove %attached from %party?', array(
-      '%attached' => $data_set_controller->getDataInfo('label'),
-      '%party' =>  $party->label,
-    )),
-    'party/' . $party->pid, // @todo: go back to the dataset page.
-    t('Are you sure you want to remove this data set from this party?') . ' ' . t('This action cannot be undone.'),
-    t('Remove'),
-    t('Cancel')
-  );
+function party_data_set_action_page($action, $party, $data_set_info, $delta = 0, $settings = array()) {
+  $data_set_controller = party_get_data_set($party, $data_set_info['set_name']);
+  return $data_set_controller->getAction($action)->build($party, $data_set_controller, array($delta), $settings);
 }
 
 /**
- * Submit handler for the data set remove form.
+ * Page title callback for data set action pages.
  */
-function party_attached_entity_remove_confirm_submit($form, &$form_state) {
-  $party = $form['#party'];
-  $data_set_controller = $form['#data_set'];
-
-  $data_set_controller->detachEntity($form['#entity']);
-  $data_set_controller->save();
-
-  // Redirect to the set piece.
-  $form_state['redirect'] = 'party/' . $party->pid . '/' . $data_set_controller->getDataInfo('path element');
-}
+function party_data_set_action_page_title($action, $party, $data_set_info, $delta = 0, $settings = array()) {
+  $data_set_controller = party_get_data_set($party, $data_set_info['set_name']);
+  if (!is_object($data_set_controller)) { ddebug_backtrace(); dpm(compact('action', 'party', 'data_set_info')); return 'FAILED'; }
+  return $data_set_controller->getAction($action)->actionTitle($party, $data_set_controller, array($delta));
+}
\ No newline at end of file
diff --git a/plugins/content_types/attached_entity/attached_entity.inc b/plugins/content_types/attached_entity/attached_entity.inc
index 45df3e6..723055a 100644
--- a/plugins/content_types/attached_entity/attached_entity.inc
+++ b/plugins/content_types/attached_entity/attached_entity.inc
@@ -71,58 +71,26 @@ function party_attached_entity_content_type_render($subtype, $conf, $panel_args,
   // Get a shortcut to the party.
   $party = $context->data;
   $data_set_name = $subtype;
-  $data_set = party_get_data_set_info($data_set_name);
-
-  if (!party_access('view', $party, $data_set)) {
-    return;
-  }
+  $data_set_info = party_get_data_set_info($data_set_name);
+  $data_set_controller = party_get_data_set($party, $data_set_name);
 
-  // Get the attached entities in this data set
-  $attached_entities = party_get_attached_entities($party, $data_set_name);
-
-  // If there are none, do not render anything
-  if (empty($attached_entities)) {
+  if (!party_access('view', $party, $data_set_info)) {
     return;
   }
 
-  // Remove all unneccesary attached entities if we're not showing them all
   if ($conf['delta'] != -1) {
-    $attached_entity_list = array_values($attached_entities);
-    // If one with the right delta exists...
-    if (isset($attached_entity_list[$conf['delta']])) {
-      // Replace the array with an array just containing the one attached entity
-      $attached_entity = $attached_entity_list[$conf['delta']];
-      $attached_entities = array(
-        $attached_entity->hash() => $attached_entity,
-      );
-    }
-    // ... otherwise don't render anything
-    else {
-      return;
-    }
+    $deltas = array($conf['delta']);
   }
-
-  // $attached_entities should now be alist of all the ones we want to show so lets convert this into a list of entities
-  $entities = array();
-  foreach($attached_entities as $attached_entity) {
-    $entities[] = $attached_entity;
-  }
-
-  if (empty($entities)) {
-    return;
-  }
-
-  $block_delta = $data_set_name;
-  if ($conf['delta'] != -1) {
-    $block_delta .= '_'.$conf['delta'];
+  else {
+    $deltas = FALSE;
   }
 
   // Build the content type block.
   $block = new stdClass();
   $block->module  = 'attached_entity';
-  $block->title = $data_set['label'];
-  $block->content = entity_view($data_set['entity type'], $entities, $conf['view_mode']);
-  $block->delta   = $block_delta;
+  $block->title = $data_set_controller->getAction('view')->actionTitle($party, $data_set_controller, $deltas);
+  $block->content = $data_set_controller->getAction('view')->build($party, $data_set_controller, $deltas, $conf);
+  $block->delta   = $conf['delta'];
 
   return $block;
 }
diff --git a/plugins/content_types/party_attached_entity_edit_form.inc b/plugins/content_types/party_attached_entity_edit_form.inc
index fcd22d8..02faef2 100644
--- a/plugins/content_types/party_attached_entity_edit_form.inc
+++ b/plugins/content_types/party_attached_entity_edit_form.inc
@@ -47,109 +47,29 @@ function party_attached_entity_edit_form_render($subtype, $conf, $args, $context
     ? $data_sets[$conf['data_set_name']]: NULL;
 
   $block = new stdClass();
-  $block->title = t('Edit Party Attached Entity');
+  $block->title = '';
   $block->content = '';
 
   if (!empty($context->data) && $data_set) {
     // Get our controller
-    $controller = party_get_crm_controller($context->data, $data_set['set_name']);
+    $controller = party_get_data_set($context->data, $data_set['set_name']);
 
     // Get our entity
     $attached_entity = $controller->getEntity(0, !empty($conf['create']));
-    if (!empty($conf['create']) && !empty($attached_entity)) {
-      $controller->attachEntity($attached_entity);
-    }
 
     // If there's no attached_entity return an empty block
     if (!$attached_entity) {
-	  return $block;
+	    return $block;
     }
 
-    $form = drupal_get_form('party_attached_entity_edit_form_form__' . $data_set['set_name'] . '__' . 0,
-      $context->data, $data_set, 0);
-
-    // Render it...
-    $block->content = drupal_render($form);
+    $block->title = $controller->getAction('edit')->actionTitle($context->data, $controller);
+    $block->content = $controller->getAction('edit')->build($context->data, $controller);
   }
 
   return $block;
 }
 
 /**
- * Form
- */
-function party_attached_entity_edit_form_form($form, &$form_state, $party, $data_set, $delta = 0) {
-  // Load the include
-  form_load_include($form_state, 'inc', 'party_dashboard', 'plugins/content_types/party_attached_entity_edit_form');
-  form_load_include($form_state, 'inc', 'party', 'party.pages');
-
-  // Get the form
-  $form = party_attached_entity_edit_form($form, $form_state, $party, $data_set, $delta);
-
-  // Add our submit handler and set the action
-  $form['#submit'][] = 'party_attached_entity_edit_form_form_submit';
-  $form['#action'] = base_path() . str_replace('ajax', 'nojs', request_path());
-
-  // Remove URL Redirects
-  foreach ($form as $name => &$element) {
-    if (substr($name, 0, 1) != '#' && isset($element['redirect'])) {
-      $element['redirect']['#access'] = FALSE;
-    }
-  }
-
-  return $form;
-}
-
-/**
- * Submit Handler
- */
-function party_attached_entity_edit_form_form_submit(&$form, &$form_state) {
-  // Make sure we stop it redirecting anywhere it shouldn't...
-  unset($form_state['redirect']);
-}
-
-/**
- * Config Form
- */
-function party_attached_entity_edit_form_edit_form($form, &$form_state) {
-  $data_sets = party_get_data_set_info();
-  if (count($data_sets)) {
-    $form['data_set_name'] = array(
-      '#type' => 'select',
-      '#title' => t('Data Set'),
-      '#options' => array(),
-      '#default_value' => !empty($form_state['conf']['data_set_name']) ? $form_state['conf']['data_set_name']: '',
-    );
-    foreach ($data_sets as $data_set_name => $data_set) {
-      $form['data_set_name']['#options'][$data_set_name] = $data_set['label'];
-    }
-
-    $form['create'] = array(
-      '#type' => 'checkbox',
-      '#title' => t("Create entity if it doesn't exist"),
-      '#default_value' => !empty($form_state['conf']['create']) ? $form_state['conf']['create']: '',
-    );
-  }
-  else {
-    $form['no_data_set'] = array(
-      '#prefix' => '<p class="error">',
-      '#markup' => t('There are no data sets configured for party.'),
-      '#suffix' => '</p>',
-    );
-  }
-
-  return $form;
-}
-
-function party_attached_entity_edit_form_edit_form_submit(&$form, &$form_state) {
-  foreach (element_children($form) as $key) {
-    if (isset($form_state['values'][$key])) {
-      $form_state['conf'][$key] = $form_state['values'][$key];
-    }
-  }
-}
-
-/**
  * Title Callback
  */
 function party_attached_entity_edit_form_admin_title($subtype, $conf, $context = NULL) {
