Привет, хабр!
В Друпале как правило все данные хранятся в нодах, термах и сущностях другого типа (например commerce product). Да, есть ещё переменные, в которых хранятся настройки.
В нодах хранить данные удобно: есть куча модулей, хуки, настройки, права и т.п. И есть очень большой соблазн любые данные складывать в ноды/термы. Проблема лишь в том, что для каждого поля в ноде создаётся аж 2 таблицы (field_data_field_*, field_revision_field_*)! И при большом объёме данных удар по производительности очевиден.

Выход есть: в Drupal CMS есть очень удобное и простое API для создания своих таблиц, а модуль Views позаботился о том, чтобы этими данными можно было очень легко играться.

Под катом — руководство, как сделать свою таблицу на Друпал и интегрировать её с Views.
Чтобы интереснее было читать — создадим с нуля тестовый «портал» для скачивания торрентов.

Создание своей таблицы


Итак, ставим чистый Drupal 7.23, ставим на него модули Ctools, Views.
Создаём ноду типа Film с полями: field_film_image (постер), field_film_description (описание), создаём пару фильмов для теста.
Торренты будем хранить в кастомной таблице, ссылаясь на нужную ноду фильма.

Создаём тестовый модуль (назову его torrents).
В torrents.install прописываем hook_schema, где описываем нашу таблицу:
torrents.install
/*
 * hook_schema implementation
 */
function torrents_schema() {
  $schema['torrents'] = array(
    'fields' => array(
      // id торрента
      'id' => array(
        'description' => t('Torrent identifier'),
        'type' => 'serial',
        'unsigned' => TRUE,
        'not null' => TRUE,
      ),
      // название торрента
      'title' => array(
        'description' => t('Torrent title'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'default' => '',
      ),
      // nid фильма, к которому привязан торрент
      'nid' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => FALSE,
        'default' => 0,
        'description' => t('Film nid'),
      ),
      // ссылка на торрент
      'link' => array(
        'description' => t('Torrent link to download'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => FALSE,
        'default' => '',
      ),
      // когда торрент добавлен
      'created' => array(
        'description' => t('Torrent added'),
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
    ),
    'primary key' => array('id'),
  );
   
  return $schema;
}


После включения модуля — таблица создаётся.
После отключения модуля и удаления — таблица удаляется.

Дружим нашу таблицу с views


В модуле torrents.module указываем, какой views api мы используем:
/**
 * Implements hook_views_api().
 */
function torrents_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'torrents'),
  );
}


Создаём в папке с модулем файл torrents.views.inc, который собственно и содержит информацию о том, как вьюхам понимать данные в нашей таблице:
torrents.views.inc
/**
 * Implements hook_views_data()
 */
function torrents_views_data() {
  // объявляем новый тип вьюх - торренты
  $data['torrents']['table']['group'] = t('Torrents');
  $data['torrents']['table']['base'] = array(
    'field' => 'id',
    'title' => t('Torrents'),
    'help' => t('Torrents table contains torrents info and can be related to nodes'),
  );
 
  // мы ссылаемся на ноды, поэтому сообщаем для отношений
  // что нам надо прилепить в случае их использования
  $data['torrents']['table']['join'] = array(
    'node' => array(
      'left_field' => 'nid',
      'field' => 'nid',
    ),
  );
  $data['torrents']['nid'] = array(
    'title' => t('Film nid'),
    'relationship' => array(
      'base' => 'node',
      'base field' => 'nid',
      'handler' => 'views_handler_relationship',
      'label' => t('Related film for torrent'),
      'title' => t('Film nid'),
    ),
  );
  
  // далее мы для каждого поля определяем, какой хендлер в каких случаях использовать во вьюхах
  $data['torrents']['id'] = array(
    'title' => t('Torrent ID'),
    'field' => array(
      'handler' => 'views_handler_field',
      'click sortable' => TRUE,
    ),
    'sort' => array(
      'handler' => 'views_handler_sort',
    ),
    'filter' => array(
      'handler' => 'views_handler_filter_numeric',
    ),
    'argument' => array(
      'handler' => 'views_handler_argument_numeric',
    ),
  );

  $data['torrents']['created'] = array(
    'title' => t('Created timestamp'),
    'field' => array(
      'handler' => 'views_handler_field_date',
      'click sortable' => TRUE,
    ),
    'sort' => array(
      'handler' => 'views_handler_sort',
    ),
    'filter' => array(
      'handler' => 'views_handler_filter_date',
    ),
    'argument' => array(
      'handler' => 'views_handler_argument_date',
    ),
  );
  
  $data['torrents']['title'] = array(
    'title' => t('Title'),
    'help' => t('Just a title'),
    'field' => array(
      'handler' => 'views_handler_field',
      'click sortable' => TRUE,
    ),
    'sort' => array(
      'handler' => 'views_handler_sort',
    ),
    'filter' => array(
      'handler' => 'views_handler_filter_string',
    ),
    'argument' => array(
      'handler' => 'views_handler_argument_string',
    ),
  );
  
  $data['torrents']['link'] = array(
    'title' => t('Download link'),
    'help' => t('Download link'),
    'field' => array(
      'handler' => 'views_handler_field',
      'click sortable' => TRUE,
    ),
    'sort' => array(
      'handler' => 'views_handler_sort',
    ),
    'filter' => array(
      'handler' => 'views_handler_filter_string',
    ),
    'argument' => array(
      'handler' => 'views_handler_argument_string',
    ),
  );
  return $data;
}



Дописываем в torrents.module плюшку в виде блока с формой, чтобы добавлять торренты в таблицу.
Привожу код всего модуля:
torrents.module
/*
 * Form to submit torrents
 */
function torrents_add_form($form, &$form_state) {
  $form['title'] = array(
    '#type' => 'textfield',
    '#attributes' => array('placeholder' => 'Название'),
    '#required' => TRUE,
  );
  $form['link'] = array(
    '#type' => 'textfield',
    '#attributes' => array('placeholder' => 'Ссылка на торрент'),
    '#required' => TRUE,
  );
  
  $films = db_select('node', 'n')
    ->fields('n', array('nid', 'title'))
    ->condition('n.type', 'film')
    ->execute()
    ->fetchAllAssoc('nid');
  
  $options = array();
  foreach ($films as $value) {
    $options[$value->nid] = $value->title;
  }
  
  $form['film'] = array(
    '#type' => 'select',
    '#attributes' => array('placeholder' => 'Фильм'),
    '#options' => $options,
    '#required' => TRUE,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => 'Добавить торрент',
  );
  return $form;
}

/*
 * Submit torrents form callback
 */
function torrents_add_form_submit($form, &$form_state) {
  $id = db_insert('torrents')
    ->fields(array(
      'nid' => $form_state['values']['film'],
      'title' => filter_xss($form_state['values']['title']),
      'link' => filter_xss($form_state['values']['link']),
      'created' => time(),
    ))->execute();
}


/*
 * Implements hook_block_info()
 */
function torrents_block_info() {
  $blocks['add_torrent'] = array(
    'info' => t('Add torrent'), 
    'cache' => DRUPAL_NO_CACHE,
  );
  return $blocks;
}

/*
 * Implements hook_block_view()
 */
function torrents_block_view($delta = '') {
  $block = array();
  switch ($delta) {
    case 'add_torrent':
      $block['content'] = drupal_get_form('torrents_add_form');
      break;
    default:
      break;
  }
  return $block;
}

/**
 * Implements hook_views_api()
 */
function torrents_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'torrents'),
  );
}



Созадём views



Далее просто включаем модуль и начинаем создавать вьюшку.
Если сделали всё правильно — у нас при создании вьюхи будет новый доступный тип для выбора: Torrents.
В полях можно будет добавить id торрента, название, ссылку.
В отношениях добавить nid фильма и потом использовать данные ноды.
Также есть фильтры по нашим полям, что конечно же удобно (хотя кое-чего не хватает).

Скриншоты, как это заработало
image

Вот они, наши поля:
image

Добавление отношения:
image



Посмотреть демо и добавить торрентов можно здесь.