diff --git a/storm.admin.inc b/storm.admin.inc
new file mode 100644
index 0000000..e0b4dd4
--- /dev/null
+++ b/storm.admin.inc
@@ -0,0 +1,555 @@
+<?php
+
+/**
+ * @file
+ */
+
+function storm_attribute_list() {
+  $i = new stdClass();
+  $i->type = 'stormattribute';
+
+  $header = array(
+    array(
+      'data' => t('Domain'),
+      'field' => 'domain',
+    ),
+    array(
+      'data' => t('Key'),
+      'field' => 'akey',
+    ),
+    array(
+      'data' => t('Value'),
+      'field' => 'avalue',
+    ),
+    array(
+      'data' => t('Active'),
+    ),
+    array(
+      'data' => t('Default'),
+    ),
+    array(
+      'data' => t('Weight'),
+      'field' => 'weight',
+      'sort' => 'asc',
+    ),
+    array(
+      'data' => storm_icon_add('storm/attributes/add', $i, $_GET),
+      'class' => 'storm_list_operations',
+    ),
+  );
+
+  $s = "SELECT * FROM {stormattribute}";
+  $where = array();
+  $args = array();
+  $filterfields = array();
+
+  if (isset($_SESSION['stormattribute_list_filter']['domain']) && $_SESSION['stormattribute_list_filter']['domain'] != '') {
+    $where[] = "domain='%s'";
+    $args[] = $_SESSION['stormattribute_list_filter']['domain'];
+    $filterfields[] = t('Domain');
+  }
+
+  if (isset($_SESSION['stormattribute_list_filter']['akey']) && $_SESSION['stormattribute_list_filter']['akey'] != '') {
+    $where[] = "LOWER(akey) LIKE LOWER('%s')";
+    $args[] = $_SESSION['stormattribute_list_filter']['akey'];
+    $filterfields[] = t('Key');
+  }
+
+  if (isset($_SESSION['stormattribute_list_filter']['avalue']) && $_SESSION['stormattribute_list_filter']['avalue'] != '') {
+    $where[] = "LOWER(avalue) LIKE LOWER('%s')";
+    $args[] = $_SESSION['stormattribute_list_filter']['avalue'];
+    $filterfields[] = t('Value');
+  }
+
+  if (isset($_SESSION['stormattribute_list_filter']['isactive']) && ($_SESSION['stormattribute_list_filter']['isactive'] != '-')) {
+    $where[] = "isactive = %d";
+    $args[] = $_SESSION['stormattribute_list_filter']['isactive'];
+    $filterfields[] = t('Active');
+  }
+  
+  if (isset($_SESSION['stormattribute_list_filter']['isdefault']) && ($_SESSION['stormattribute_list_filter']['isdefault'] != '-')) {
+    $where[] = "isdefault = %d";
+    $args[] = $_SESSION['stormattribute_list_filter']['isdefault'];
+    $filterfields[] = t('Default');
+  }
+  
+  $itemsperpage = isset($_SESSION['stormattribute_list_filter']['itemsperpage']) ? $_SESSION['stormattribute_list_filter']['itemsperpage'] : variable_get('storm_default_items_per_page', 10);
+
+  $tablesort = tablesort_sql($header);
+
+  if (count($filterfields) == 0) {
+    $filterdesc = t('Not filtered');
+  }
+  else {
+    $filterdesc = t('Filtered by !fields', array('!fields' => implode(", ", array_unique($filterfields))));
+  }
+  $filterdesc .= ' | '. t('!items items per page', array('!items' => $itemsperpage));
+  
+  $o = drupal_get_form('storm_attribute_list_filter', $filterdesc);
+  
+  $s = db_rewrite_sql($s, 'stormattribute', 'aid');
+  $s = storm_rewrite_sql($s, $where) . $tablesort;
+  $r = pager_query($s, $itemsperpage, 0, NULL, $args);
+
+  $attributes = array();
+  while ($attribute = db_fetch_object($r)) {
+    $attributes[] = $attribute;
+  }
+
+  $o .= drupal_get_form('storm_attribute_list_form', $header, $attributes);
+  $o .= theme('pager', NULL, $itemsperpage, 0);
+  return $o;
+}
+
+function storm_attribute_list_form($form_id, $header, $attributes) {
+  $form = array();
+
+  $form['attributes']['#theme'] = 'stormattribute_list';
+
+  $form['attributes']['header'] = array (
+    '#value' => $header,
+  );
+
+  foreach ($attributes as $attribute) {
+    $i = new stdClass();
+    $i->type = 'stormattribute';
+
+    $form['attributes']['attributes'][$attribute->aid]['attribute_domain_'. $attribute->aid] = array (
+      '#value' => $attribute->domain,
+    );
+
+    $form['attributes']['attributes'][$attribute->aid]['attribute_akey_'. $attribute->aid] = array (
+      '#value' => $attribute->akey,
+    );
+
+    $form['attributes']['attributes'][$attribute->aid]['attribute_avalue_'. $attribute->aid] = array (
+      '#value' => $attribute->avalue,
+    );
+
+    $form['attributes']['attributes'][$attribute->aid]['attribute_isactive_'. $attribute->aid] = array (
+      '#type' => 'checkbox',
+      '#default_value' => $attribute->isactive,
+    );
+
+    $form['attributes']['attributes'][$attribute->aid]['attribute_default_'. $attribute->aid] = array (
+      '#default_value' => $attribute->isdefault,
+    );
+
+    $form['attributes']['attributes'][$attribute->aid]['attribute_weight_'. $attribute->aid] = array (
+      '#type' => 'weight',
+      '#default_value' => $attribute->weight,
+    );
+
+    $form['attributes']['attributes'][$attribute->aid]['attribute_operations_'. $attribute->aid] = array (
+      '#value' => storm_icon_edit('storm/attributes/edit/'. $attribute->aid, $i, $_GET) .'&nbsp;'.  storm_icon_delete('storm/attributes/delete/'. $attribute->aid, $i, $_GET),
+    );
+  }
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#submit' => array('storm_attribute_list_submit'),
+    '#value' => t('Save'),
+  );
+
+  return $form;
+}
+
+function storm_attribute_list_submit($form, &$form_state) {
+  $attributes = array();
+  $default_for_domain = array();
+  foreach($_POST as $key=>$value) {
+    $ar = explode('_', $key);
+    if($ar[0]=='attribute') {
+      if($ar[1]=='weight') $attributes[$ar[2]]['weight'] = $value;
+      if($ar[1]=='isactive') $attributes[$ar[2]]['isactive'] = $value;
+      if($ar[1]=='default') {
+        $domain = str_replace('|',' ',$ar[2]);
+        $default_for_domain[$domain] = $value;
+      }
+    }
+  }
+
+  $s = "UPDATE {stormattribute} SET isactive=%d, weight=%d WHERE aid=%d";  
+  foreach($attributes as $aid=>$values) {
+    db_query($s, $values['isactive'], $values['weight'], $aid);
+  }
+
+  foreach($default_for_domain as $domain=>$aid) {
+    $s = "UPDATE {stormattribute} SET isdefault=0 WHERE domain='%s'";  
+    db_query($s, $domain);
+    $s = "UPDATE {stormattribute} SET isdefault=1 WHERE aid=%d";
+    db_query($s, $aid);  
+  }
+
+  drupal_set_message(t('Attributes saved'));
+}
+
+function storm_attribute_list_filter(&$form_state, $filterdesc = 'Filter') {
+  $domain = isset($_SESSION['stormattribute_list_filter']['domain']) ? $_SESSION['stormattribute_list_filter']['domain'] : '';
+  $akey = isset($_SESSION['stormattribute_list_filter']['akey']) ? $_SESSION['stormattribute_list_filter']['akey'] : '';
+  $avalue = isset($_SESSION['stormattribute_list_filter']['avalue']) ? $_SESSION['stormattribute_list_filter']['avalue'] : '';
+  $isactive = isset($_SESSION['stormattribute_list_filter']['isactive']) ? $_SESSION['stormattribute_list_filter']['isactive'] : TRUE;
+  $isdefault = isset($_SESSION['stormattribute_list_filter']['isdefault']) ? $_SESSION['stormattribute_list_filter']['isdefault'] : FALSE;
+
+  $itemsperpage = isset($_SESSION['stormattribute_list_filter']['itemsperpage']) ? $_SESSION['stormattribute_list_filter']['itemsperpage'] : variable_get('storm_default_items_per_page', 10);
+  $_SESSION['stormattribute_list_filter']['itemsperpage'] = $itemsperpage;
+
+  $form['filter'] = array(
+    '#type' => 'fieldset',
+    '#title' => $filterdesc,
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+
+  $form['filter']['group1'] = array(
+    '#type' => 'markup',
+    '#theme' => 'storm_form_group',
+  );
+
+  $form['filter']['group1']['domain'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Domain'),
+    '#default_value' => $domain,
+    '#size' => 30,
+    '#autocomplete_path' => 'storm/attributes/domain/autocomplete',
+  );
+
+  $form['filter']['group1']['akey'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Key'),
+    '#default_value' => $akey,
+    '#size' => 20,
+  );
+
+  $form['filter']['group1']['avalue'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Value'),
+    '#default_value' => $avalue,
+    '#size' => 20,
+  );
+
+  $form['filter']['group2'] = array(
+    '#type' => 'markup',
+    '#theme' => 'storm_form_group',
+  );
+
+  $form['filter']['group2']['isactive'] = array(
+    '#type' => 'select',
+    '#title' => t('Active'),
+    '#default_value' => $isactive,
+    '#options' => array('-'=>'-', '0'=>t('No'), '1'=>t('Yes')),
+  );
+
+  $form['filter']['group2']['isdefault'] = array(
+    '#type' => 'select',
+    '#title' => t('Default'),
+    '#default_value' => $isdefault,
+    '#options' => array('-'=>'-', '0'=>t('No'), '1'=>t('Yes')),
+  );
+
+  $form['filter']['group3'] = array(
+    '#type' => 'markup',
+    '#theme' => 'storm_form_group',
+  );
+
+  $form['filter']['group3']['itemsperpage'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Items'),
+    '#size' => 10,
+    '#default_value' => $itemsperpage,
+    '#prefix' => '<div class="container-inline">',
+    '#suffix' => '</div>',
+  );
+
+  $form['filter']['group3']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Filter'),
+    '#submit' => array('storm_attribute_list_filter_filter'),
+  );
+
+  $form['filter']['group3']['reset'] = array(
+    '#type' => 'submit',
+    '#value' => t('Reset'),
+    '#submit' => array('storm_attribute_list_filter_reset'),
+  );
+
+  return $form;
+}
+
+function storm_attribute_list_filter_filter($form, &$form_state) {
+  $_SESSION['stormattribute_list_filter']['domain'] = $form_state['values']['domain'];
+  $_SESSION['stormattribute_list_filter']['akey'] = $form_state['values']['akey'];
+  $_SESSION['stormattribute_list_filter']['avalue'] = $form_state['values']['avalue'];
+  $_SESSION['stormattribute_list_filter']['isactive'] = $form_state['values']['isactive'];
+  $_SESSION['stormattribute_list_filter']['isdefault'] = $form_state['values']['isdefault'];
+  $_SESSION['stormattribute_list_filter']['itemsperpage'] = $form_state['values']['itemsperpage'];
+}
+
+function storm_attribute_list_filter_reset($form, &$form_state) {
+  unset($_SESSION['stormattribute_list_filter']);
+}
+
+function stormattribute_add() {
+  $attribute = new stdClass();
+  return storm_attribute_form($attribute);
+}
+
+function storm_attribute_add_submit($form, &$form_state) {
+  if($form_state['values']['isdefault']) {
+    db_query("UPDATE {stormattribute} SET isdefault=0 WHERE domain='%s'", $form_state['values']['domain']);
+  }
+
+  db_query("INSERT INTO {stormattribute} (domain, akey, avalue, parent_domain, weight, isactive, isdefault) 
+  VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)", 
+  $form_state['values']['domain'], $form_state['values']['akey'], $form_state['values']['avalue'], $form_state['values']['parent_domain'], 
+  $form_state['values']['weight'], $form_state['values']['isactive'], $form_state['values']['isdefault']);
+
+  if ($_REQUEST['destination']) {
+    $destination = urldecode($_REQUEST['destination']);
+  }
+  else {
+    $destination = 'storm/attributes';
+  }
+  drupal_goto($destination);
+}
+
+function storm_attribute_edit($form_state, $aid) {
+  $attribute = array();
+  if ($aid) {
+    $r = db_query("SELECT * FROM {stormattribute} WHERE aid=%d", $aid);
+    $attribute = db_fetch_object($r);
+  }
+  return storm_attribute_form($attribute);
+}
+
+function storm_attribute_edit_submit($form, &$form_state) {
+  if($form_state['values']['isdefault']) {
+    db_query("UPDATE {stormattribute} SET isdefault=0 WHERE domain='%s'", $form_state['values']['domain']);
+  }
+
+  db_query("UPDATE {stormattribute} SET domain='%s', akey='%s', avalue='%s', 
+  parent_domain='%s', weight=%d, isactive=%d, isdefault=%d WHERE aid=%d", 
+  $form_state['values']['domain'], $form_state['values']['akey'], $form_state['values']['avalue'], 
+  $form_state['values']['parent_domain'], $form_state['values']['weight'], $form_state['values']['isactive'], $form_state['values']['isdefault'], $form_state['values']['aid']);
+
+  if ($_REQUEST['destination']) {
+    $destination = urldecode($_REQUEST['destination']);
+  }
+  else {
+    $destination = 'storm/attributes';
+  }
+  drupal_goto($destination);
+}
+
+function storm_attribute_form_delete($form, &$form_state) {
+  $destination = drupal_get_destination(); 
+  if(array_key_exists('destination', $_REQUEST)) unset($_REQUEST['destination']);
+  drupal_goto('storm/attributes/delete/'. $form_state['values']['aid'], $destination);
+}
+
+function storm_attribute_delete($form_state, $aid) {
+  $destination = drupal_get_destination();
+  if(array_key_exists('destination', $_REQUEST)) {
+    $destination = $_REQUEST['destination'];
+    unset($_REQUEST['destination']);
+    $form['destination'] = array('#type' => 'value', '#value' => $destination);
+  }
+
+  $form['aid'] = array('#type' => 'value', '#value' => $aid);
+  $r = db_query("SELECT * FROM {stormattribute} WHERE aid=%d", $aid);
+  $a = db_fetch_object($r);
+  $title = $a->domain .' : '. $a->avalue;
+
+  return confirm_form($form,
+  t('Are you sure you want to delete the attribute %title?', array('%title' => $title)),
+  array('path'=>'storm/attributes/edit/'. $aid, 'query'=>$destination),
+  t('This action cannot be undone.'),
+  t('Delete'), t('Cancel'));
+}
+
+function storm_attribute_delete_submit($form, &$form_state) {
+  if ($form_state['values']['aid']) {
+    db_query('DELETE FROM {stormattribute} WHERE aid=%d', $form_state['values']['aid']);
+    drupal_set_message('Storm attribute deleted');
+    if ($form_state['values']['destination']) {
+      $destination = $form_state['values']['destination'];
+    }
+    else {
+      $destination = 'storm/attributes';
+    }
+    drupal_goto($destination);
+  }
+}
+
+function storm_attribute_form($attribute = NULL) {
+  $breadcrumb = array();
+  $breadcrumb[] = l(t('Storm'), 'storm');
+  $breadcrumb[] = l(t('Attributes'), 'storm/attributes');
+  drupal_set_breadcrumb($breadcrumb);
+
+  if (arg(2)=='add') {
+    if (array_key_exists('domain', $_GET) && !isset($attribute->domain)) {
+      $attribute->domain = $_GET['domain'];
+    }
+
+    if (isset($_SESSION['stormattribute_list_filter']['domain']) && !isset($attribute->domain)) {
+      $attribute->domain = $_SESSION['stormattribute_list_filter']['domain'];
+    }
+  }
+
+  $form = array();
+  if (isset($attribute->aid)) {
+    $form['aid'] = array(
+      '#type' => 'value',
+      '#value' => $attribute->aid,
+    );
+  }
+
+  $form['group1'] = array(
+    '#type' => 'markup',
+    '#theme' => 'storm_form_group',
+  );
+
+  $form['group1']['domain'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Domain'),
+    '#required' => TRUE,
+    '#default_value' => isset($attribute->domain) ? $attribute->domain : '',
+    '#autocomplete_path' => 'storm/attributes/domain/autocomplete',
+    '#size' => 40,
+  );
+
+  $domains = array();
+  $r = db_query("SELECT DISTINCT(domain) d FROM {stormattribute} ORDER BY domain");
+  while($i=db_fetch_object($r)) {
+    $domains[$i->d] = $i->d;
+  }
+  $form['group1']['parent_domain'] = array(
+    '#type' => 'select',
+    '#title' => t('Parent domain'),
+    '#required' => FALSE,
+    '#default_value' => isset($attribute->parent_domain) ? $attribute->parent_domain : '',
+    '#options' => array(''=>'-') + $domains,
+  );
+
+  $form['group2'] = array(
+    '#type' => 'markup',
+    '#theme' => 'storm_form_group',
+  );
+
+  $form['group2']['akey'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Key'),
+    '#required' => TRUE,
+    '#default_value' => isset($attribute->akey) ? $attribute->akey : '',
+    '#size' => 25,
+    '#maxlength' => 100,
+  );
+
+  $form['group2']['avalue'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Value'),
+    '#required' => TRUE,
+    '#default_value' => isset($attribute->avalue) ? $attribute->avalue : '',
+    '#size' => 25,
+  );
+
+  $form['group2']['weight'] = array(
+    '#type' => 'weight',
+    '#title' => t('Weight'),
+    '#default_value' => isset($attribute->weight) ? $attribute->weight : 0,
+  );
+
+  $form['group3'] = array(
+    '#type' => 'markup',
+    '#theme' => 'storm_form_group',
+  );
+
+  $form['group3']['isactive'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Active'),
+    '#default_value' => isset($attribute->isactive) ? $attribute->isactive : TRUE,
+  );
+
+  $form['group3']['isdefault'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Default'),
+    '#default_value' => isset($attribute->isdefault) ? $attribute->isdefault : FALSE,
+  );
+
+  $form['save'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save')
+  );
+
+  if (isset($attribute->aid)) {
+    $form['delete'] = array(
+      '#type' => 'submit',
+      '#value' => t('Delete'),
+      '#submit' => array('stormattribute_form_delete'),
+    );
+  }
+  
+  return $form;
+}
+
+function _storm_attibute_domain_options() {
+  static $options;
+  if (!$options) {
+    $r = db_query("SELECT DISTINCT(domain) AS domain_name FROM {stormattribute} ORDER BY domain");
+    $options = array();
+    while ($d = db_fetch_object($r)) {
+      $options[$d->domain_name] = $d->domain_name;
+    }
+  }
+  return $options;
+}
+
+function _storm_attribute_domain_autocomplete($string = '') {
+  static $default_domains;
+  if (!$default_domains) {
+    $default_domains = array (
+      'Country'=>'Country',
+      'Currency'=>'Currency',
+      'Task status'=>'Task status',
+      'Project category'=>'Project category',
+      'Project status search'=>'Project status search',
+      'Project status'=>'Project status',
+      'Ticket priority search'=>'Ticket priority search',
+      'Ticket priority'=>'Ticket priority',
+      'Ticket category'=>'Ticket category',
+      'Ticket status search'=>'Ticket status search',
+      'Ticket status'=>'Ticket status',
+      'Task status search'=>'Task status search',
+      'Price mode'=>'Price mode',
+      'Project priority'=>'Project priority',
+      'Project priority search'=>'Project priority search',
+      'Task category'=>'Task category',
+      'Task priority'=>'Task priority',
+      'Task priority search'=>'Task priority search',
+      'Duration unit'=>'Duration unit',
+    );
+  }
+
+  $matches = array();
+  if ($string) {
+    $s = "SELECT DISTINCT(domain) AS domain FROM {stormattribute} WHERE LOWER(domain) LIKE LOWER('%s%%')";
+    $s = db_rewrite_sql($s, 'stormattribute', 'aid');
+    $result = db_query($s, $string);
+    while ($a = db_fetch_object($result)) {
+      $matches[$a->domain] = check_plain($a->domain);
+    }
+  }
+
+  foreach ($default_domains as $domain) {
+    if (strpos(strtoupper($domain), strtoupper($string))===false) {
+    }
+    else {
+      $matches[$domain] = $domain;
+    }
+  }
+
+  $matches = array_slice($matches, 0, 10);
+
+  drupal_json($matches);
+}
diff --git a/storm.module b/storm.module
index e586566..1b84a10 100644
--- a/storm.module
+++ b/storm.module
@@ -26,6 +26,20 @@ function storm_help($path, $arg) {
     case "admin/help#storm":
       $output = '<p>'.  t("Provides a complete project management environment") .'</p>';
       break;
+    case "admin/help#stormattribute":
+      $output = '<p>'.  t("Provides attributes support for Storm") .'</p>';
+      $output .= '<p>'. t("Price Modes - Price modes are used calculate the price of the Storm node when added to an invoice. A price mode can be added to any Storm node type and any type can be added to an invoice. When a node is added to an invoice it looks for a price mode in the following order:") .'</p>';
+      $output .= '<p>'. t("Ticket, Task, Project, Organization.") .'</p>';
+      $output .= '<p>'. t("It will take the price mode for it's current node or the first valid node type above in it's tree. e.g. For a task node type, if that node doesn't have a price mode it will look at the project and then the organization and take the first one it finds. This means you can define a price mode for the organization and it will be used for all nodes under that organization unless it's given a different one.") .'</p>';
+      $output .= '<p>'. t("The following price modes keys are defined:") .'<br />';
+      $output .= '<ul>';
+      $output .= '<li>'. t("not applicable - This is ignored by the system. This means that no price mode is defined.") .'</li>';
+      $output .= '<li>'. t("fixed - Price of 0 is used (so the invoice would need to be manually updated.") .'</li>';
+      $output .= '<li>'. t("hourly - Price taken from node and multiplied by billing duration.") .'</li>';
+      $output .= '<li>'. t("daily - Price given is for daily rate. This price is divided by 8 hours and then multiplied by the billing duration.") .'</li>';
+      $output .= '<li>'. t("fixed_price - Will use the price given as the fixed price for that invoice item.") .'</li>';
+      $output .= '</ul>';
+      break;
   }
 
   return $output;
@@ -80,7 +94,53 @@ function storm_menu() {
     'type' => MENU_DEFAULT_LOCAL_TASK,
     'weight' => -100,
   );
+
+  $items['storm/attributes'] = array(
+    'title' => 'Attributes',
+    'description' => 'Storm Attributes',
+    'page callback' => 'storm_attribute_list',
+    'access arguments' => array('Storm: access administration pages'),
+    'file' => 'storm.admin.inc',
+    'type' => MENU_NORMAL_ITEM,
+    'weight' => 11,
+  );
+
+  $items['storm/attributes/add'] = array(
+    'title' => 'Add a new attribute',
+    'description' => 'Storm Attributes',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('storm_attribute_add'),
+    'access arguments' => array('Storm: access administration pages'),
+    'file' => 'storm.admin.inc',
+    'type' => MENU_CALLBACK);
+  
+  $items['storm/attributes/edit/%'] = array(
+    'title' => 'Edit an attribute',
+    'description' => 'Storm Attributes',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('storm_attribute_edit', 3),
+    'access arguments' => array('Storm: access administration pages'),
+    'file' => 'storm.admin.inc',
+    'type' => MENU_CALLBACK);
   
+  $items['storm/attributes/delete/%'] = array(
+    'title' => 'Delete an attribute',
+    'description' => 'Storm Attributes',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('storm_attribute_delete', 3),
+    'access arguments' => array('Storm: access administration pages'),
+    'file' => 'storm.admin.inc',
+    'type' => MENU_CALLBACK);
+  
+  $items['storm/attributes/domain/autocomplete'] = array(
+    'title' => 'List of attributes',
+    'description' => 'Storm Attributes',
+    'page callback' => '_storm_attribute_domain_autocomplete',
+    'page arguments' => array(4),
+    'access arguments' => array('Storm: access administration pages'),
+    'file' => 'storm.admin.inc',
+    'type' => MENU_CALLBACK);
+
   return $items;
 }
 
@@ -121,7 +181,11 @@ function storm_theme() {
     'storm_number_items' => array(
       'file'      => 'storm.theme.inc',
       'arguments' => array('number' => ''),
-    )
+    ),
+    'storm_attribute_list' => array(
+      'file'      => 'storm.theme.inc',
+      'arguments' => array('form'),
+    ),
   );
 }
 
@@ -1259,4 +1323,60 @@ function storm_views_pre_render(&$view) {
       }
     }
   }
-}
\ No newline at end of file
+}
+
+function storm_attribute_access($op, $item=NULL, $account=NULL) {
+  if (empty($account)) {
+    global $user;
+    $account = $user;
+  }
+
+  if ($op == 'create') {
+    return user_access('Storm: access administration pages');
+  }
+
+  if ($op == 'delete') {
+    return user_access('Storm: access administration pages');
+  }
+
+  if ($op == 'update') {
+    return user_access('Storm: access administration pages');
+  }
+  
+  return FALSE;
+}
+
+function storm_attributes_bydomain($domain) {
+  static $attributes_cache = array();
+  $attributes = array();
+
+  if (array_key_exists($domain, $attributes_cache)) return $attributes_cache[$domain];
+
+  $s = "SELECT * FROM {stormattribute} WHERE LOWER(domain) LIKE LOWER('%s') AND isactive=1 ORDER BY weight, avalue";
+  $r = db_query($s, $domain);
+  $attributes['values'] = array();
+  while ($attribute = db_fetch_object($r)) {
+    // The variable is deliberately passed through t() for translatability
+    $attributes['values'][$attribute->akey] = t($attribute->avalue);
+    if ($attribute->isdefault) {
+      $attributes['default'] = $attribute->akey;
+    }
+  }
+  if (is_array($attributes['values']) && !array_key_exists('default', $attributes)) {
+    $v = array_flip($attributes['values']);
+    $attributes['default'] = array_shift($v);
+  }
+
+  $attributes_cache[$domain] = $attributes;
+  return $attributes;
+}
+
+
+function storm_attribute_value($domain, $key) {
+  $attributes_array = storm_attributes_bydomain($domain);
+  $attributes = $attributes_array['values'];
+  if (array_key_exists($key, $attributes)) {
+    return $attributes[$key];
+  }
+  return $key;
+}
diff --git a/storm.test b/storm.test
index 806ff54..ebbf108 100644
--- a/storm.test
+++ b/storm.test
@@ -52,4 +52,25 @@ class StormTestCase extends DrupalWebTestCase {
     $this->drupalGet('admin/settings/storm/storm');
     $this->assertText(t('Storm'), t('Make sure the correct page has been displayed by checking that the title of the settings page is "Storm".'));
   }
+
+  public function testStormAttributesAccess() {
+    $this->drupalGet('storm/attributes');
+    $this->assertResponse(403, t('Make sure access is denied to Storm Attributes list for anonymous user'));
+    $this->drupalGet('storm/attributes/add');
+    $this->assertResponse(403, t('Make sure access is denied to Storm Attributes form for anonymous user'));
+
+    $basic_user = $this->drupalCreateUser();
+    $this->drupalLogin($basic_user);
+    $this->drupalGet('storm/attributes');
+    $this->assertResponse(403, t('Make sure access is denied to Storm Attributes list for basic user'));
+    $this->drupalGet('storm/attributes/add');
+    $this->assertResponse(403, t('Make sure access is denied to Storm Attributes form for basic user'));
+
+    $privileged_user = $this->drupalCreateUser(array('Storm: access administration pages'));
+    $this->drupalLogin($privileged_user);
+    $this->drupalGet('storm/attributes');
+    $this->assertText(t('Attributes'), t('Make sure the correct page has been displayed by checking that the title is "Attributes".'));
+    $this->drupalGet('storm/attributes/add');
+    $this->assertText(t('Add a new attribute'), t('Make sure the correct page has been displayed by checking that the title is "Add a new attribute".'));
+  }
 }
diff --git a/storm.theme.inc b/storm.theme.inc
index 0331de4..a9eff9c 100644
--- a/storm.theme.inc
+++ b/storm.theme.inc
@@ -308,3 +308,47 @@ function theme_storm_number_items($number) {
   }
   return $content;
 }
+
+function theme_storm_attribute_list($form) {
+  drupal_add_tabledrag('attributes', 'order', 'sibling', 'attribute-weight'); 
+
+  $header = array();
+  $row = array();
+  $rows = array();
+
+  $header = $form['header']['#value'];
+  unset($form['header']);
+
+  if(isset($form['attributes'])) {
+    foreach (element_children($form['attributes']) as $key) {
+      $form['attributes'][$key]['attribute_weight_'. $key]['#attributes']['class'] = 'attribute-weight';
+      $domain = check_markup(drupal_render($form['attributes'][$key]['attribute_domain_'. $key]));
+      $isdefault = $form['attributes'][$key]['attribute_default_'. $key]['#default_value'];
+      if($isdefault) {
+        $ischecked = 'checked="checked"';
+      }
+      else {
+        $ischecked = '';
+      }
+      $domain_name = 'attribute_default_'. str_replace(' ','|', $domain);
+      $default_ctl = '<input type="radio" name="'. $domain_name .'" value="'. $key .'" '. $ischecked .'"/>';
+      $data = array (
+        $domain,
+        check_markup(drupal_render($form['attributes'][$key]['attribute_akey_'. $key])),
+        check_markup(drupal_render($form['attributes'][$key]['attribute_avalue_'. $key])),
+        drupal_render($form['attributes'][$key]['attribute_isactive_'. $key]),
+        $default_ctl,
+        drupal_render($form['attributes'][$key]['attribute_weight_'. $key]),
+        array (
+          'data' => drupal_render($form['attributes'][$key]['attribute_operations_'. $key]),
+          'class' => 'storm_list_operations',
+        ),
+      );
+      $row['data'] = $data;
+      $row['class'] = empty($row['class']) ? 'draggable' : $row['class'] .' draggable';
+      $rows[] = $row;
+    }
+  }
+  $o = theme('table', $header, $rows, array('id' => 'attributes'));
+  return $o;
+}
diff --git a/storm_handler_field_attributes_domain.inc b/storm_handler_field_attributes_domain.inc
new file mode 100644
index 0000000..93649fc
--- /dev/null
+++ b/storm_handler_field_attributes_domain.inc
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * Show attribute values rather than keys in Storm Views fields.
+ */
+
+class stormattribute_handler_field_attributes_domain extends views_handler_field {
+
+  function render($values) {
+    $key = $values->{$this->field_alias};
+    $value = stormattribute_value($this->definition['domain'], $key);
+    if (!empty($this->definition['icon'])) {
+      return storm_icon($this->definition['icon'].'_'.$key, $value);
+    }
+    return check_plain($value);
+  }
+
+}
diff --git a/storm_handler_filter_attributes_domain.inc b/storm_handler_filter_attributes_domain.inc
new file mode 100644
index 0000000..a714341
--- /dev/null
+++ b/storm_handler_filter_attributes_domain.inc
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * Filter by vocabulary id
+ */
+
+class stormattribute_handler_filter_attributes_domain extends views_handler_filter_in_operator {
+  function get_value_options() {
+    if (!isset($this->value_options)) {
+      $this->value_options = stormattribute_attributes_bydomain($this->definition['domain']);
+      $this->value_options = $this->value_options['values'];
+    }
+  }
+}
diff --git a/stormattribute/stormattribute.admin.inc b/stormattribute/stormattribute.admin.inc
deleted file mode 100644
index 2808a3f..0000000
--- a/stormattribute/stormattribute.admin.inc
+++ /dev/null
@@ -1,556 +0,0 @@
-<?php
-
-/**
- * @file
- */
-
-function stormattribute_list() {
-  $i = new stdClass();
-  $i->type = 'stormattribute';
-
-  $header = array(
-    array(
-      'data' => t('Domain'),
-      'field' => 'domain',
-    ),
-    array(
-      'data' => t('Key'),
-      'field' => 'akey',
-    ),
-    array(
-      'data' => t('Value'),
-      'field' => 'avalue',
-    ),
-    array(
-      'data' => t('Active'),
-    ),
-    array(
-      'data' => t('Default'),
-    ),
-    array(
-      'data' => t('Weight'),
-      'field' => 'weight',
-      'sort' => 'asc',
-    ),
-    array(
-      'data' => storm_icon_add('storm/attributes/add', $i, $_GET),
-      'class' => 'storm_list_operations',
-    ),
-  );
-
-  $s = "SELECT * FROM {stormattribute}";
-  $where = array();
-  $args = array();
-  $filterfields = array();
-
-  if (isset($_SESSION['stormattribute_list_filter']['domain']) && $_SESSION['stormattribute_list_filter']['domain'] != '') {
-    $where[] = "domain='%s'";
-    $args[] = $_SESSION['stormattribute_list_filter']['domain'];
-    $filterfields[] = t('Domain');
-  }
-
-  if (isset($_SESSION['stormattribute_list_filter']['akey']) && $_SESSION['stormattribute_list_filter']['akey'] != '') {
-    $where[] = "LOWER(akey) LIKE LOWER('%s')";
-    $args[] = $_SESSION['stormattribute_list_filter']['akey'];
-    $filterfields[] = t('Key');
-  }
-
-  if (isset($_SESSION['stormattribute_list_filter']['avalue']) && $_SESSION['stormattribute_list_filter']['avalue'] != '') {
-    $where[] = "LOWER(avalue) LIKE LOWER('%s')";
-    $args[] = $_SESSION['stormattribute_list_filter']['avalue'];
-    $filterfields[] = t('Value');
-  }
-
-  if (isset($_SESSION['stormattribute_list_filter']['isactive']) && ($_SESSION['stormattribute_list_filter']['isactive'] != '-')) {
-    $where[] = "isactive = %d";
-    $args[] = $_SESSION['stormattribute_list_filter']['isactive'];
-    $filterfields[] = t('Active');
-  }
-  
-  if (isset($_SESSION['stormattribute_list_filter']['isdefault']) && ($_SESSION['stormattribute_list_filter']['isdefault'] != '-')) {
-    $where[] = "isdefault = %d";
-    $args[] = $_SESSION['stormattribute_list_filter']['isdefault'];
-    $filterfields[] = t('Default');
-  }
-  
-  $itemsperpage = isset($_SESSION['stormattribute_list_filter']['itemsperpage']) ? $_SESSION['stormattribute_list_filter']['itemsperpage'] : variable_get('storm_default_items_per_page', 10);
-
-  $tablesort = tablesort_sql($header);
-
-  if (count($filterfields) == 0) {
-    $filterdesc = t('Not filtered');
-  }
-  else {
-    $filterdesc = t('Filtered by !fields', array('!fields' => implode(", ", array_unique($filterfields))));
-  }
-  $filterdesc .= ' | '. t('!items items per page', array('!items' => $itemsperpage));
-  
-  $o = drupal_get_form('stormattribute_list_filter', $filterdesc);
-  
-  $s = db_rewrite_sql($s, 'stormattribute', 'aid');
-  $s = storm_rewrite_sql($s, $where) . $tablesort;
-  $r = pager_query($s, $itemsperpage, 0, NULL, $args);
-
-  $attributes = array();
-  while ($attribute = db_fetch_object($r)) {
-    $attributes[] = $attribute;
-  }
-
-  $o .= drupal_get_form('stormattribute_list_form', $header, $attributes);
-  $o .= theme('pager', NULL, $itemsperpage, 0);
-  return $o;
-}
-
-function stormattribute_list_form($form_id, $header, $attributes) {
-  $form = array();
-
-  $form['attributes']['#theme'] = 'stormattribute_list';
-
-  $form['attributes']['header'] = array (
-    '#value' => $header,
-  );
-
-  foreach ($attributes as $attribute) {
-    $i = new stdClass();
-    $i->type = 'stormattribute';
-
-    $form['attributes']['attributes'][$attribute->aid]['attribute_domain_'. $attribute->aid] = array (
-      '#value' => $attribute->domain,
-    );
-
-    $form['attributes']['attributes'][$attribute->aid]['attribute_akey_'. $attribute->aid] = array (
-      '#value' => $attribute->akey,
-    );
-
-    $form['attributes']['attributes'][$attribute->aid]['attribute_avalue_'. $attribute->aid] = array (
-      '#value' => $attribute->avalue,
-    );
-
-    $form['attributes']['attributes'][$attribute->aid]['attribute_isactive_'. $attribute->aid] = array (
-      '#type' => 'checkbox',
-      '#default_value' => $attribute->isactive,
-    );
-
-    $form['attributes']['attributes'][$attribute->aid]['attribute_default_'. $attribute->aid] = array (
-      '#default_value' => $attribute->isdefault,
-    );
-
-    $form['attributes']['attributes'][$attribute->aid]['attribute_weight_'. $attribute->aid] = array (
-      '#type' => 'weight',
-      '#default_value' => $attribute->weight,
-    );
-
-    $form['attributes']['attributes'][$attribute->aid]['attribute_operations_'. $attribute->aid] = array (
-      '#value' => storm_icon_edit('storm/attributes/edit/'. $attribute->aid, $i, $_GET) .'&nbsp;'.  storm_icon_delete('storm/attributes/delete/'. $attribute->aid, $i, $_GET),
-    );
-  }
-
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#submit' => array('stormattribute_list_submit'),
-    '#value' => t('Save'),
-  );
-
-  return $form;
-}
-
-function stormattribute_list_submit($form, &$form_state) {
-  $attributes = array();
-  $default_for_domain = array();
-  foreach($_POST as $key=>$value) {
-    $ar = explode('_', $key);
-    if($ar[0]=='attribute') {
-      if($ar[1]=='weight') $attributes[$ar[2]]['weight'] = $value;
-      if($ar[1]=='isactive') $attributes[$ar[2]]['isactive'] = $value;
-      if($ar[1]=='default') {
-        $domain = str_replace('|',' ',$ar[2]);
-        $default_for_domain[$domain] = $value;
-      }
-    }
-  }
-
-  $s = "UPDATE {stormattribute} SET isactive=%d, weight=%d WHERE aid=%d";  
-  foreach($attributes as $aid=>$values) {
-    db_query($s, $values['isactive'], $values['weight'], $aid);
-  }
-
-  foreach($default_for_domain as $domain=>$aid) {
-    $s = "UPDATE {stormattribute} SET isdefault=0 WHERE domain='%s'";  
-    db_query($s, $domain);
-    $s = "UPDATE {stormattribute} SET isdefault=1 WHERE aid=%d";
-    db_query($s, $aid);  
-  }
-
-  drupal_set_message(t('Attributes saved'));
-}
-
-function stormattribute_list_filter(&$form_state, $filterdesc = 'Filter') {
-  $domain = isset($_SESSION['stormattribute_list_filter']['domain']) ? $_SESSION['stormattribute_list_filter']['domain'] : '';
-  $akey = isset($_SESSION['stormattribute_list_filter']['akey']) ? $_SESSION['stormattribute_list_filter']['akey'] : '';
-  $avalue = isset($_SESSION['stormattribute_list_filter']['avalue']) ? $_SESSION['stormattribute_list_filter']['avalue'] : '';
-  $isactive = isset($_SESSION['stormattribute_list_filter']['isactive']) ? $_SESSION['stormattribute_list_filter']['isactive'] : TRUE;
-  $isdefault = isset($_SESSION['stormattribute_list_filter']['isdefault']) ? $_SESSION['stormattribute_list_filter']['isdefault'] : FALSE;
-
-  $itemsperpage = isset($_SESSION['stormattribute_list_filter']['itemsperpage']) ? $_SESSION['stormattribute_list_filter']['itemsperpage'] : variable_get('storm_default_items_per_page', 10);
-  $_SESSION['stormattribute_list_filter']['itemsperpage'] = $itemsperpage;
-
-  $form['filter'] = array(
-    '#type' => 'fieldset',
-    '#title' => $filterdesc,
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-  );
-
-  $form['filter']['group1'] = array(
-    '#type' => 'markup',
-    '#theme' => 'storm_form_group',
-  );
-
-  $form['filter']['group1']['domain'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Domain'),
-    '#default_value' => $domain,
-    '#size' => 30,
-    '#autocomplete_path' => 'storm/attributes/domain/autocomplete',
-  );
-
-  $form['filter']['group1']['akey'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Key'),
-    '#default_value' => $akey,
-    '#size' => 20,
-  );
-
-  $form['filter']['group1']['avalue'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Value'),
-    '#default_value' => $avalue,
-    '#size' => 20,
-  );
-
-  $form['filter']['group2'] = array(
-    '#type' => 'markup',
-    '#theme' => 'storm_form_group',
-  );
-
-  $form['filter']['group2']['isactive'] = array(
-    '#type' => 'select',
-    '#title' => t('Active'),
-    '#default_value' => $isactive,
-    '#options' => array('-'=>'-', '0'=>t('No'), '1'=>t('Yes')),
-  );
-
-  $form['filter']['group2']['isdefault'] = array(
-    '#type' => 'select',
-    '#title' => t('Default'),
-    '#default_value' => $isdefault,
-    '#options' => array('-'=>'-', '0'=>t('No'), '1'=>t('Yes')),
-  );
-
-  $form['filter']['group3'] = array(
-    '#type' => 'markup',
-    '#theme' => 'storm_form_group',
-  );
-
-  $form['filter']['group3']['itemsperpage'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Items'),
-    '#size' => 10,
-    '#default_value' => $itemsperpage,
-    '#prefix' => '<div class="container-inline">',
-    '#suffix' => '</div>',
-  );
-
-  $form['filter']['group3']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Filter'),
-    '#submit' => array('stormattribute_list_filter_filter'),
-  );
-
-  $form['filter']['group3']['reset'] = array(
-    '#type' => 'submit',
-    '#value' => t('Reset'),
-    '#submit' => array('stormattribute_list_filter_reset'),
-  );
-
-  return $form;
-}
-
-function stormattribute_list_filter_filter($form, &$form_state) {
-  $_SESSION['stormattribute_list_filter']['domain'] = $form_state['values']['domain'];
-  $_SESSION['stormattribute_list_filter']['akey'] = $form_state['values']['akey'];
-  $_SESSION['stormattribute_list_filter']['avalue'] = $form_state['values']['avalue'];
-  $_SESSION['stormattribute_list_filter']['isactive'] = $form_state['values']['isactive'];
-  $_SESSION['stormattribute_list_filter']['isdefault'] = $form_state['values']['isdefault'];
-  $_SESSION['stormattribute_list_filter']['itemsperpage'] = $form_state['values']['itemsperpage'];
-}
-
-function stormattribute_list_filter_reset($form, &$form_state) {
-  unset($_SESSION['stormattribute_list_filter']);
-}
-
-function stormattribute_add() {
-  $attribute = new stdClass();
-  return stormattribute_form($attribute);
-}
-
-function stormattribute_add_submit($form, &$form_state) {
-  if($form_state['values']['isdefault']) {
-    db_query("UPDATE {stormattribute} SET isdefault=0 WHERE domain='%s'", $form_state['values']['domain']);
-  }
-
-  db_query("INSERT INTO {stormattribute} (domain, akey, avalue, parent_domain, weight, isactive, isdefault) 
-  VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)", 
-  $form_state['values']['domain'], $form_state['values']['akey'], $form_state['values']['avalue'], $form_state['values']['parent_domain'], 
-  $form_state['values']['weight'], $form_state['values']['isactive'], $form_state['values']['isdefault']);
-
-  if ($_REQUEST['destination']) {
-    $destination = urldecode($_REQUEST['destination']);
-  }
-  else {
-    $destination = 'storm/attributes';
-  }
-  drupal_goto($destination);
-}
-
-function stormattribute_edit($form_state, $aid) {
-  $attribute = array();
-  if ($aid) {
-    $r = db_query("SELECT * FROM {stormattribute} WHERE aid=%d", $aid);
-    $attribute = db_fetch_object($r);
-  }
-  return stormattribute_form($attribute);
-}
-
-function stormattribute_edit_submit($form, &$form_state) {
-  if($form_state['values']['isdefault']) {
-    db_query("UPDATE {stormattribute} SET isdefault=0 WHERE domain='%s'", $form_state['values']['domain']);
-  }
-
-  db_query("UPDATE {stormattribute} SET domain='%s', akey='%s', avalue='%s', 
-  parent_domain='%s', weight=%d, isactive=%d, isdefault=%d WHERE aid=%d", 
-  $form_state['values']['domain'], $form_state['values']['akey'], $form_state['values']['avalue'], 
-  $form_state['values']['parent_domain'], $form_state['values']['weight'], $form_state['values']['isactive'], $form_state['values']['isdefault'], $form_state['values']['aid']);
-
-  if ($_REQUEST['destination']) {
-    $destination = urldecode($_REQUEST['destination']);
-  }
-  else {
-    $destination = 'storm/attributes';
-  }
-  drupal_goto($destination);
-}
-
-function stormattribute_form_delete($form, &$form_state) {
-  $destination = drupal_get_destination(); 
-  if(array_key_exists('destination', $_REQUEST)) unset($_REQUEST['destination']);
-  drupal_goto('storm/attributes/delete/'. $form_state['values']['aid'], $destination);
-}
-
-function stormattribute_delete($form_state, $aid) {
-  $destination = drupal_get_destination();
-  if(array_key_exists('destination', $_REQUEST)) {
-    $destination = $_REQUEST['destination'];
-    unset($_REQUEST['destination']);
-    $form['destination'] = array('#type' => 'value', '#value' => $destination);
-  }
-
-  $form['aid'] = array('#type' => 'value', '#value' => $aid);
-  $r = db_query("SELECT * FROM {stormattribute} WHERE aid=%d", $aid);
-  $a = db_fetch_object($r);
-  $title = $a->domain .' : '. $a->avalue;
-
-  return confirm_form($form,
-  t('Are you sure you want to delete the attribute %title?', array('%title' => $title)),
-  array('path'=>'storm/attributes/edit/'. $aid, 'query'=>$destination),
-  t('This action cannot be undone.'),
-  t('Delete'), t('Cancel'));
-}
-
-function stormattribute_delete_submit($form, &$form_state) {
-  if ($form_state['values']['aid']) {
-    db_query('DELETE FROM {stormattribute} WHERE aid=%d', $form_state['values']['aid']);
-    drupal_set_message('Storm attribute deleted');
-    if ($form_state['values']['destination']) {
-      $destination = $form_state['values']['destination'];
-    }
-    else {
-      $destination = 'storm/attributes';
-    }
-    drupal_goto($destination);
-  }
-}
-
-function stormattribute_form($attribute = NULL) {
-  $breadcrumb = array();
-  $breadcrumb[] = l(t('Storm'), 'storm');
-  $breadcrumb[] = l(t('Attributes'), 'storm/attributes');
-  drupal_set_breadcrumb($breadcrumb);
-
-  if (arg(2)=='add') {
-    if (array_key_exists('domain', $_GET) && !isset($attribute->domain)) {
-      $attribute->domain = $_GET['domain'];
-    }
-
-    if (isset($_SESSION['stormattribute_list_filter']['domain']) && !isset($attribute->domain)) {
-      $attribute->domain = $_SESSION['stormattribute_list_filter']['domain'];
-    }
-  }
-
-  $form = array();
-  if (isset($attribute->aid)) {
-    $form['aid'] = array(
-      '#type' => 'value',
-      '#value' => $attribute->aid,
-    );
-  }
-
-  $form['group1'] = array(
-    '#type' => 'markup',
-    '#theme' => 'storm_form_group',
-  );
-
-  $form['group1']['domain'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Domain'),
-    '#required' => TRUE,
-    '#default_value' => isset($attribute->domain) ? $attribute->domain : '',
-    '#autocomplete_path' => 'storm/attributes/domain/autocomplete',
-    '#size' => 40,
-  );
-
-  $domains = array();
-  $r = db_query("SELECT DISTINCT(domain) d FROM {stormattribute} ORDER BY domain");
-  while($i=db_fetch_object($r)) {
-    $domains[$i->d] = $i->d;
-  }
-  $form['group1']['parent_domain'] = array(
-    '#type' => 'select',
-    '#title' => t('Parent domain'),
-    '#required' => FALSE,
-    '#default_value' => isset($attribute->parent_domain) ? $attribute->parent_domain : '',
-    '#options' => array(''=>'-') + $domains,
-  );
-
-  $form['group2'] = array(
-    '#type' => 'markup',
-    '#theme' => 'storm_form_group',
-  );
-
-  $form['group2']['akey'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Key'),
-    '#required' => TRUE,
-    '#default_value' => isset($attribute->akey) ? $attribute->akey : '',
-    '#size' => 25,
-    '#maxlength' => 100,
-  );
-
-  $form['group2']['avalue'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Value'),
-    '#required' => TRUE,
-    '#default_value' => isset($attribute->avalue) ? $attribute->avalue : '',
-    '#size' => 25,
-  );
-
-  $form['group2']['weight'] = array(
-    '#type' => 'weight',
-    '#title' => t('Weight'),
-    '#default_value' => isset($attribute->weight) ? $attribute->weight : 0,
-  );
-
-  $form['group3'] = array(
-    '#type' => 'markup',
-    '#theme' => 'storm_form_group',
-  );
-
-  $form['group3']['isactive'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Active'),
-    '#default_value' => isset($attribute->isactive) ? $attribute->isactive : TRUE,
-  );
-
-  $form['group3']['isdefault'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Default'),
-    '#default_value' => isset($attribute->isdefault) ? $attribute->isdefault : FALSE,
-  );
-
-  $form['save'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save')
-  );
-
-  if (isset($attribute->aid)) {
-    $form['delete'] = array(
-      '#type' => 'submit',
-      '#value' => t('Delete'),
-      '#submit' => array('stormattribute_form_delete'),
-    );
-  }
-  
-  return $form;
-}
-
-function _stormattibute_domain_options() {
-  static $options;
-  if (!$options) {
-    $r = db_query("SELECT DISTINCT(domain) AS domain_name FROM {stormattribute} ORDER BY domain");
-    $options = array();
-    while ($d = db_fetch_object($r)) {
-      $options[$d->domain_name] = $d->domain_name;
-    }
-  }
-  return $options;
-}
-
-function _stormattribute_domain_autocomplete($string = '') {
-  static $default_domains;
-  if (!$default_domains) {
-    $default_domains = array (
-      'Country'=>'Country',
-      'Currency'=>'Currency',
-      'Task status'=>'Task status',
-      'Project category'=>'Project category',
-      'Project status search'=>'Project status search',
-      'Project status'=>'Project status',
-      'Ticket priority search'=>'Ticket priority search',
-      'Ticket priority'=>'Ticket priority',
-      'Ticket category'=>'Ticket category',
-      'Ticket status search'=>'Ticket status search',
-      'Ticket status'=>'Ticket status',
-      'Task status search'=>'Task status search',
-      'Price mode'=>'Price mode',
-      'Project priority'=>'Project priority',
-      'Project priority search'=>'Project priority search',
-      'Task category'=>'Task category',
-      'Task priority'=>'Task priority',
-      'Task priority search'=>'Task priority search',
-      'Duration unit'=>'Duration unit',
-    );
-  }
-
-  $matches = array();
-  if ($string) {
-    $s = "SELECT DISTINCT(domain) AS domain FROM {stormattribute} WHERE LOWER(domain) LIKE LOWER('%s%%')";
-    $s = db_rewrite_sql($s, 'stormattribute', 'aid');
-    $result = db_query($s, $string);
-    while ($a = db_fetch_object($result)) {
-      $matches[$a->domain] = check_plain($a->domain);
-    }
-  }
-
-  foreach ($default_domains as $domain) {
-    if (strpos(strtoupper($domain), strtoupper($string))===false) {
-    }
-    else {
-      $matches[$domain] = $domain;
-    }
-  }
-
-  $matches = array_slice($matches, 0, 10);
-
-  drupal_json($matches);
-}
-
diff --git a/stormattribute/stormattribute.info b/stormattribute/stormattribute.info
deleted file mode 100644
index 8bd5cd1..0000000
--- a/stormattribute/stormattribute.info
+++ /dev/null
@@ -1,9 +0,0 @@
-
-; Basic information
-name = Storm Attribute
-description = "Provides attributes (allowed field values) for use within Storm modules"
-core = 6.x
-package = Storm
-
-; Dependencies
-dependencies[] = storm
diff --git a/stormattribute/stormattribute.module b/stormattribute/stormattribute.module
deleted file mode 100644
index 8493ec9..0000000
--- a/stormattribute/stormattribute.module
+++ /dev/null
@@ -1,148 +0,0 @@
-<?php
-
-/**
- * @file
- */
-
-function stormattribute_help($path, $arg) {
-  $output = '';
-
-  switch ($path) {
-    case "admin/help#stormattribute":
-      $output = '<p>'.  t("Provides attributes support for Storm") .'</p>';
-      $output .= '<p>'. t("Price Modes - Price modes are used calculate the price of the Storm node when added to an invoice. A price mode can be added to any Storm node type and any type can be added to an invoice. When a node is added to an invoice it looks for a price mode in the following order:") .'</p>';
-      $output .= '<p>'. t("Ticket, Task, Project, Organization.") .'</p>';
-      $output .= '<p>'. t("It will take the price mode for it's current node or the first valid node type above in it's tree. e.g. For a task node type, if that node doesn't have a price mode it will look at the project and then the organization and take the first one it finds. This means you can define a price mode for the organization and it will be used for all nodes under that organization unless it's given a different one.") .'</p>';
-      $output .= '<p>'. t("The following price modes keys are defined:") .'<br />';
-      $output .= '<ul>';
-      $output .= '<li>'. t("not applicable - This is ignored by the system. This means that no price mode is defined.") .'</li>';
-      $output .= '<li>'. t("fixed - Price of 0 is used (so the invoice would need to be manually updated.") .'</li>';
-      $output .= '<li>'. t("hourly - Price taken from node and multiplied by billing duration.") .'</li>';
-      $output .= '<li>'. t("daily - Price given is for daily rate. This price is divided by 8 hours and then multiplied by the billing duration.") .'</li>';
-      $output .= '<li>'. t("fixed_price - Will use the price given as the fixed price for that invoice item.") .'</li>';
-      $output .= '</ul>';
-      break;
-  }
-
-  return $output;
-}
-
-
-function stormattribute_menu() {
-  
-  $items['storm/attributes'] = array(
-    'title' => 'Attributes',
-    'description' => 'Storm Attributes',
-    'page callback' => 'stormattribute_list',
-    'access arguments' => array('Storm: access administration pages'),
-    'file' => 'stormattribute.admin.inc',
-    'type' => MENU_NORMAL_ITEM,
-    'weight' => 11,
-  );
-
-  $items['storm/attributes/add'] = array(
-    'title' => 'Add a new attribute',
-    'description' => 'Storm Attributes',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('stormattribute_add'),
-    'access arguments' => array('Storm: access administration pages'),
-    'file' => 'stormattribute.admin.inc',
-    'type' => MENU_CALLBACK);
-  
-  $items['storm/attributes/edit/%'] = array(
-    'title' => 'Edit an attribute',
-    'description' => 'Storm Attributes',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('stormattribute_edit', 3),
-    'access arguments' => array('Storm: access administration pages'),
-    'file' => 'stormattribute.admin.inc',
-    'type' => MENU_CALLBACK);
-  
-  $items['storm/attributes/delete/%'] = array(
-    'title' => 'Delete an attribute',
-    'description' => 'Storm Attributes',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('stormattribute_delete', 3),
-    'access arguments' => array('Storm: access administration pages'),
-    'file' => 'stormattribute.admin.inc',
-    'type' => MENU_CALLBACK);
-  
-  $items['storm/attributes/domain/autocomplete'] = array(
-    'title' => 'List of attributes',
-    'description' => 'Storm Attributes',
-    'page callback' => '_stormattribute_domain_autocomplete',
-    'page arguments' => array(4),
-    'access arguments' => array('Storm: access administration pages'),
-    'file' => 'stormattribute.admin.inc',
-    'type' => MENU_CALLBACK);
-  
-  return $items;
-}
-
-
-function stormattribute_theme() {
-  return array(
-    'stormattribute_list' => array(
-      'file'      => 'stormattribute.theme.inc',
-      'arguments' => array('form'),
-    ),
-  );
-}
-
-
-function stormattribute_access($op, $item=NULL, $account=NULL) {
-  if (empty($account)) {
-    global $user;
-    $account = $user;
-  }
-
-  if ($op == 'create') {
-    return user_access('Storm: access administration pages');
-  }
-
-  if ($op == 'delete') {
-    return user_access('Storm: access administration pages');
-  }
-
-  if ($op == 'update') {
-    return user_access('Storm: access administration pages');
-  }
-  
-  return FALSE;
-}
-
-
-function stormattribute_attributes_bydomain($domain) {
-  static $attributes_cache = array();
-  $attributes = array();
-
-  if (array_key_exists($domain, $attributes_cache)) return $attributes_cache[$domain];
-
-  $s = "SELECT * FROM {stormattribute} WHERE LOWER(domain) LIKE LOWER('%s') AND isactive=1 ORDER BY weight, avalue";
-  $r = db_query($s, $domain);
-  $attributes['values'] = array();
-  while ($attribute = db_fetch_object($r)) {
-    // The variable is deliberately passed through t() for translatability
-    $attributes['values'][$attribute->akey] = t($attribute->avalue);
-    if ($attribute->isdefault) {
-      $attributes['default'] = $attribute->akey;
-    }
-  }
-  if (is_array($attributes['values']) && !array_key_exists('default', $attributes)) {
-    $v = array_flip($attributes['values']);
-    $attributes['default'] = array_shift($v);
-  }
-
-  $attributes_cache[$domain] = $attributes;
-  return $attributes;
-}
-
-
-function stormattribute_value($domain, $key) {
-  $attributes_array = stormattribute_attributes_bydomain($domain);
-  $attributes = $attributes_array['values'];
-  if (array_key_exists($key, $attributes)) {
-    return $attributes[$key];
-  }
-  return $key;
-}
diff --git a/stormattribute/stormattribute.test b/stormattribute/stormattribute.test
deleted file mode 100644
index 896f155..0000000
--- a/stormattribute/stormattribute.test
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-// $Id:$
-
-/**
- * @file
- * Tests for the Storm Attribute module
- */
-class StormattributeTestCase extends DrupalWebTestCase {
-  public static function getInfo() {
-    return array(
-      'name' => 'Storm Attribute functionality',
-      'description' => 'Test the functionality of the Storm Attribute module',
-      'group' => 'Storm',
-    );
-  }
-
-  public function setUp() {
-    parent::setUp('storm', 'stormattribute');
-  }
-
-  public function testStormattributeAccess() {
-    $this->drupalGet('storm/attributes');
-    $this->assertResponse(403, t('Make sure access is denied to Storm Attributes list for anonymous user'));
-    $this->drupalGet('storm/attributes/add');
-    $this->assertResponse(403, t('Make sure access is denied to Storm Attributes form for anonymous user'));
-
-    $basic_user = $this->drupalCreateUser();
-    $this->drupalLogin($basic_user);
-    $this->drupalGet('storm/attributes');
-    $this->assertResponse(403, t('Make sure access is denied to Storm Attributes list for basic user'));
-    $this->drupalGet('storm/attributes/add');
-    $this->assertResponse(403, t('Make sure access is denied to Storm Attributes form for basic user'));
-
-    $privileged_user = $this->drupalCreateUser(array('Storm: access administration pages'));
-    $this->drupalLogin($privileged_user);
-    $this->drupalGet('storm/attributes');
-    $this->assertText(t('Attributes'), t('Make sure the correct page has been displayed by checking that the title is "Attributes".'));
-    $this->drupalGet('storm/attributes/add');
-    $this->assertText(t('Add a new attribute'), t('Make sure the correct page has been displayed by checking that the title is "Add a new attribute".'));
-  }
-}
diff --git a/stormattribute/stormattribute.theme.inc b/stormattribute/stormattribute.theme.inc
deleted file mode 100644
index 357f224..0000000
--- a/stormattribute/stormattribute.theme.inc
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-function theme_stormattribute_list($form) {
-  drupal_add_tabledrag('attributes', 'order', 'sibling', 'attribute-weight'); 
-
-  $header = array();
-  $row = array();
-  $rows = array();
-
-  $header = $form['header']['#value'];
-  unset($form['header']);
-
-  if(isset($form['attributes'])) {
-    foreach (element_children($form['attributes']) as $key) {
-      $form['attributes'][$key]['attribute_weight_'. $key]['#attributes']['class'] = 'attribute-weight';
-      $domain = check_markup(drupal_render($form['attributes'][$key]['attribute_domain_'. $key]));
-      $isdefault = $form['attributes'][$key]['attribute_default_'. $key]['#default_value'];
-      if($isdefault) {
-        $ischecked = 'checked="checked"';
-      }
-      else {
-        $ischecked = '';
-      }
-      $domain_name = 'attribute_default_'. str_replace(' ','|', $domain);
-      $default_ctl = '<input type="radio" name="'. $domain_name .'" value="'. $key .'" '. $ischecked .'"/>';
-      $data = array (
-        $domain,
-        check_markup(drupal_render($form['attributes'][$key]['attribute_akey_'. $key])),
-        check_markup(drupal_render($form['attributes'][$key]['attribute_avalue_'. $key])),
-        drupal_render($form['attributes'][$key]['attribute_isactive_'. $key]),
-        $default_ctl,
-        drupal_render($form['attributes'][$key]['attribute_weight_'. $key]),
-        array (
-          'data' => drupal_render($form['attributes'][$key]['attribute_operations_'. $key]),
-          'class' => 'storm_list_operations',
-        ),
-      );
-      $row['data'] = $data;
-      $row['class'] = empty($row['class']) ? 'draggable' : $row['class'] .' draggable';
-      $rows[] = $row;
-    }
-  }
-  $o = theme('table', $header, $rows, array('id' => 'attributes'));
-  return $o;
-}
-
diff --git a/stormattribute/stormattribute_handler_field_attributes_domain.inc b/stormattribute/stormattribute_handler_field_attributes_domain.inc
deleted file mode 100644
index 93649fc..0000000
--- a/stormattribute/stormattribute_handler_field_attributes_domain.inc
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-
-/**
- * Show attribute values rather than keys in Storm Views fields.
- */
-
-class stormattribute_handler_field_attributes_domain extends views_handler_field {
-
-  function render($values) {
-    $key = $values->{$this->field_alias};
-    $value = stormattribute_value($this->definition['domain'], $key);
-    if (!empty($this->definition['icon'])) {
-      return storm_icon($this->definition['icon'].'_'.$key, $value);
-    }
-    return check_plain($value);
-  }
-
-}
diff --git a/stormattribute/stormattribute_handler_filter_attributes_domain.inc b/stormattribute/stormattribute_handler_filter_attributes_domain.inc
deleted file mode 100644
index a714341..0000000
--- a/stormattribute/stormattribute_handler_filter_attributes_domain.inc
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-
-/**
- * Filter by vocabulary id
- */
-
-class stormattribute_handler_filter_attributes_domain extends views_handler_filter_in_operator {
-  function get_value_options() {
-    if (!isset($this->value_options)) {
-      $this->value_options = stormattribute_attributes_bydomain($this->definition['domain']);
-      $this->value_options = $this->value_options['values'];
-    }
-  }
-}
diff --git a/stormexpense/stormexpense.info b/stormexpense/stormexpense.info
index aeb742f..ce53b85 100644
--- a/stormexpense/stormexpense.info
+++ b/stormexpense/stormexpense.info
@@ -1,7 +1,6 @@
 name = Storm Expense
 description = "Allows recording of expenses based on organizations, projects, tasks and tickets."
-dependencies[] = storm 
-dependencies[] = stormattribute
+dependencies[] = storm
 dependencies[] = stormorganization
 dependencies[] = stormproject
 dependencies[] = stormtask
diff --git a/storminvoice/storminvoice.info b/storminvoice/storminvoice.info
index 6cfad22..36e2a27 100644
--- a/storminvoice/storminvoice.info
+++ b/storminvoice/storminvoice.info
@@ -1,8 +1,7 @@
 name = Storm Invoice
 description = "Allows invoices to be created based on organizations and projects."
 dependencies[] = storm
-dependencies[] = stormattribute
 dependencies[] = stormorganization
 dependencies[] = stormproject
 package = Storm
-core = 6.x
\ No newline at end of file
+core = 6.x
diff --git a/stormknowledgebase/stormknowledgebase.info b/stormknowledgebase/stormknowledgebase.info
index 7ee86e2..c8c6c74 100644
--- a/stormknowledgebase/stormknowledgebase.info
+++ b/stormknowledgebase/stormknowledgebase.info
@@ -1,4 +1,3 @@
-
 ; Basic information
 name = Storm Knowledge Base
 description = "Provides means of storing and categorising knowledge."
@@ -7,4 +6,3 @@ package = Storm
 
 ; Dependencies
 dependencies[] = storm
-dependencies[] = stormattribute
diff --git a/stormorganization/stormorganization.info b/stormorganization/stormorganization.info
index 07d6286..364b3dd 100644
--- a/stormorganization/stormorganization.info
+++ b/stormorganization/stormorganization.info
@@ -1,6 +1,5 @@
 name = Storm Organization
 description = "Allows storing of organizations for use within Storm"
 dependencies[] = storm
-dependencies[] = stormattribute
 package = Storm
 core = 6.x
diff --git a/stormperson/stormperson.info b/stormperson/stormperson.info
index 5631f76..e9baa48 100644
--- a/stormperson/stormperson.info
+++ b/stormperson/stormperson.info
@@ -1,7 +1,6 @@
 name = Storm Person
 description = "Allows storage of people within organizations, and linking of these to Drupal user accounts."
-dependencies[] = storm 
-dependencies[] = stormattribute
+dependencies[] = storm
 dependencies[] = stormorganization
 package = Storm
 core = 6.x
diff --git a/stormproject/stormproject.info b/stormproject/stormproject.info
index 34710d9..b8311bd 100644
--- a/stormproject/stormproject.info
+++ b/stormproject/stormproject.info
@@ -1,7 +1,6 @@
 name = Storm Project
 description = "Allows recording of projects based on Storm Organizations"
-dependencies[] = storm 
-dependencies[] = stormattribute
+dependencies[] = storm
 dependencies[] = stormorganization
 package = Storm
 core = 6.x
diff --git a/stormtask/stormtask.info b/stormtask/stormtask.info
index f25eef7..e4da6da 100644
--- a/stormtask/stormtask.info
+++ b/stormtask/stormtask.info
@@ -1,7 +1,6 @@
 name = Storm Task
 description = "Allows recording of tasks for Storm Organizations and Projects"
-dependencies[] = storm 
-dependencies[] = stormattribute
+dependencies[] = storm
 dependencies[] = stormorganization
 dependencies[] = stormproject
 package = Storm
diff --git a/stormticket/stormticket.info b/stormticket/stormticket.info
index 35cae76..5fbfc63 100644
--- a/stormticket/stormticket.info
+++ b/stormticket/stormticket.info
@@ -1,7 +1,6 @@
 name = Storm Ticket
 description = "Allows creation of tickets for Storm Projects and Tasks"
-dependencies[] = storm 
-dependencies[] = stormattribute
+dependencies[] = storm
 dependencies[] = stormorganization
 dependencies[] = stormproject
 dependencies[] = stormtask
diff --git a/stormtimetracking/stormtimetracking.info b/stormtimetracking/stormtimetracking.info
index 35c1f3b..38bd5e3 100644
--- a/stormtimetracking/stormtimetracking.info
+++ b/stormtimetracking/stormtimetracking.info
@@ -1,7 +1,6 @@
 name = Storm Timetracking
 description = "Allows recording of times worked based on project, task and/or ticket"
-dependencies[] = storm 
-dependencies[] = stormattribute
+dependencies[] = storm
 dependencies[] = stormorganization
 dependencies[] = stormproject
 dependencies[] = stormtask
