Как подружить свой тип данных с Views

При разработке модулей для Drupal мы частенько создаем программно новые типы материалов (то есть нод). В процессе иногда возникает соблазн быстренько запрограммировать вывод какой-нибудь отсортированной коллекции этих материалов. Например, вывод нод-городов, отсортированных по алфавиту, или по древности, или сгруппированных по странам. Таких полезных коллекций может образоваться достаточно много и программно создавать каждую из них — не очень эффективно. Особенно если впоследствии нам захочется что-то поменять, объединить, или переделать. Гораздо аккуратнее — использовать модуль Views.

Вообще говоря, Views — один из краеугольных камней разработки на Drupal. Хотите помесячный архив нод? Views. Хотите вывести только заголовки и даты нод? Views. Хотите вывести ноды, сгруппированные по именам авторов? Views. Хотите получить список IP, с которых пользователь Василий заходил на сайт в течение Великого поста? Тоже Views. Один из самых популярных ответов на вопрос «Как в Друпале сделать…» — Views. Так что, чем быстрее разработчик подружится с этим прекрасным модулем — тем лучше. Для этого, кстати, есть неплохой мануал на английском языке: http://views-help.doc.logrus.com/.

Однако как только разработчик захочет получить с помощью Views коллекцию созданных им данных — например, из самодельной таблицы, сделанной для нового типа материала — он обнаружит, что Views еще не знает о его таблице. Так вот, раз и навсегда рассказать о своей таблице модулю Views — на мой взгляд, гораздо более правильное решение, чем программировать вручную все необходимые представления.

Далее — о том, как это сделать.

Предполагаю, что читатель уже умеет программно создавать свои типы материалов. Рассказывать буду на примере модуля, в котором новый тип уже создан. А чтобы не публиковать код модуля с описанием нового типа, предлагаю просто его скачать:
Скачать модуль mytest.
В модуле уже реализовано все, о чем дальше пойдет речь в заметке.

Итак, у нас есть тип материала testnode, в который мы добавили два целочисленных поля — int_count и int_total, хранящиеся в таблице БД {testnode}. Научим Views загружать эти поля из базы данных, выводить их, а также сортировать и фильтровать по ним коллекцию.

Перво-наперво, доложим Views о том, какую версию API мы используем (а используем мы версию Views 2.8 с Drupal 6.x). Для этого добавим в наш файл mytest.module вызов хука hook_views_api:

function mytest_views_api() {
  return array('api' => 2);
} //mytest_views_api

Затем создадим файл mytest.views.inc, в котором модуль Views самостоятельно сможет найти вызов нужных хуков. Пришла пора рассказать Views про нашу таблицу. Для этого используется хук hook_views_data(), возвращающий массив с информацией об описываемых таблицах и их полях. Реализация хука должна возвращать ассоциативный массив формата $data['имя_таблицы']['имена_полей']. Также в массиве для каждой таблицы может содержаться подмассив $data['имя_таблицы']['table'] с общей информацией о таблице. Рассмотрим хук по порядку:

function mytest_views_data() {
  $data = array();

  $data['testnode'] = array(
    'table' => array(
      'group' => t('Testnode'),
    ),
    //...

Подмассив ['table'] содержит основную информацию о таблице. Элемент ['group'] — это имя группы, в которой объявляемые нами поля появятся в выпадающих списках Views. В принципе, можно указывать имя группы для каждого описываемого поля, но если этого не делать — имя группы унаследуется из секции ['table'].

Прежде чем перечислять поля таблицы {testnode}, небольшое лирическое отступление о Views. Что, собственно, делает модуль Views, когда пользователь указывает в его удобных, хотя и запутанных на первый взгляд колонках интерфейса аргументы, фильтры, поля, отношения и способы сортировки? Правильно, модуль Views по заданным настройкам составляет SQL-запрос, вытягивающий из базы данных те данные, которые пользователь указал, и сортирует их так, как пользователю надо. Так что, чтобы пользователь мог указывать во Views поля таблицы {testnode}, нужно рассказать модулю Views, что он должен вставлять в SQL-запрос.

Для каждого поля нашей таблицы мы можем указывать следующую информацию:

  • ['title'] — заголовок поля, таким он будет показан пользователю в интерфейсе Views;
  • ['group'] — если хочется, можем указать для поля группу, отличную от указанной в ['table'];
  • ['help'] — здесь можно задать текст подсказки по данному полю, который пользователь увидит мелким шрифтом рядом с заголовком;
  • ['real field'] — если в массиве $$data мы в качестве ключа указали для данного поля синоним, то в ['real field'] нужно привести настоящее название поля в таблице БД.

Перечисленные элементы — заголовок, помощь, группа — имеют скорее декоративное назначение. Кроме них нам нужно заполнить элементы массива, имеющие непосредственное отношение к логике работы Views. Это элементы ['field'], ['filter'], ['sort'], ['argument'] и ['relationship'], отвечающие соответственно за чтение поля из БД, фильтрацию поля (с помощью WHERE), сортировку поля (ORDER BY), использование поля в качестве аргумента (в URL) и отношения (то есть присоединение другой таблицы через данное поле). Все пять элементов должны быть ассоциативными массивами. В них нужно обязательно привести значение элемента ['handler'], а также, по необходимости, дополнительные элементы, которые могут варьироваться в зависимости от значения ['handler'].

Здесь нужно еще одно лирическое отступление. В отличие от ядра Друпала и большинства сторонних модулей, API модуля Views — объектно-ориентированный (насколько это возможно). Товарищей, привыкших программировать под Друпал, Views API может несколько смутить, но смею заверить: это плюс, а не минус. Кстати, по поводу отсутствия объектно-ориентированного подхода в «капельке» разные граждане периодически поднимают бучу. Автор статьи, однако, относится к этой особенности безразлично и принимает ее как данность, с которой нужно работать, а не хаять почем зря.

Рассмотрение объектно-ориентированной модели Views API, в принципе, должно быть темой отдельной большой статьи. Поэтому здесь коснемся только того, что нас непосредственно волнует, то есть хэндлеров. Понимающий английское письмо читатель может ознакомиться с подробным описанием Views API на сайте: http://views2.logrus.com/doc/html/index.html.

Корнем модели является класс views_object, а уже от него наследуется класс views_handler. Ветка производных от views_handler классов управляет поведением заданных пользователем элементов представления (аргументов, полей, фильтров и так далее). То есть хэндлеры — это управляющие классы, задача которых следить, чтобы все что нужно было добавлено в запрос, чтобы из БД был получен нужный результат, чтобы был сгенерирован правильный текст поля и так далее. Таким образом, чтобы пользователь мог использовать поля из таблицы {testnode} для генерации своих представлений, нужно указать для каждого поля правильные классы-хэндлеры.

Хэндлеры бывают очень разные в зависимости от типа данных, необходимого эффекта, оказываемого ими на запрос, сложности фильтра, который мы хотим использовать и тому подобное. Для нашего тестового модуля нам понадобятся хэндлеры views_handler_field (для вставки в запрос обычного поля), views_handler_filter_numeric (чтобы в фильтре можно было пользоваться значением поля как числом) и views_handler_sort (обычный хэндлер для сортировки ASC/DESC без извратов). В результате поля таблицы {testnode} мы опишем так:

    //... это продолжение хука mytest_views_data
    'tid' => array(
      'title' => t('Test Node ID'),
      'field' => array(
        'handler' => 'views_handler_field',
      ),
      'filter' => array(
        'handler' => 'views_handler_filter_numeric',
      ),
    ),
    'nid' => array(
      'title' => t('Node ID'),
      'field' => array(
        'handler' => 'views_handler_field',
      ),
      'filter' => array(
        'handler' => 'views_handler_filter_numeric',
      ),
    ),
    'int_count' => array(
      'title' => t('Count'),
      'field' => array(
        'handler' => 'views_handler_field',
      ),
      'sort' => array(
        'handler' => 'views_handler_sort',
      ),
      'filter' => array(
        'handler' => 'views_handler_filter_numeric',
      ),
    ),
    'int_total' => array(
      'title' => t('Total'),
      'field' => array(
        'handler' => 'views_handler_field',
      ),
      'sort' => array(
        'handler' => 'views_handler_sort',
      ),
      'filter' => array(
        'handler' => 'views_handler_filter_numeric',
      ),
    ),
  );

Теперь необходимо снова вернуться к секции ['table'] массива $data['testnode']. Помимо имени группы, в ней содержится важная информация о том, базовая ли это таблица, или ее нужно присоединять к другой при генерации представления. Базовая таблица — это таблица, центральная для Views, как например таблица node для Node view или таблица user — для User view. Будь наша таблица базовой, мы задали бы для нее в массиве $data['имя_таблицы']['table']['base'] элементы ['field'] (название поля-первичного ключа), ['title'] (заголовок таблицы), ['help'] (текст подсказки) и ['database'] (информация о базе данных, если таблица находится не в базе Друпала).

Но в рассматриваемом примере таблица не базовая, поэтому рассмотрим другой массив — $data['имя_таблицы']['table']['join']. В нем мы должны привести информацию о том, как и к какой базовой таблице Views должен присоединить данные таблицы {testnode}:

//join to node
  $data['testnode']['table']['join']['node'] = array(
    'left_field' => 'nid',
    'field'     => 'nid',
  );

  return $data;
} //function mytest_views_data

Поскольку в нашей таблице содержатся поля для нового типа ноды testnode, очевидно, что мы хотели бы присоединить их к таблице {node}, так чтобы поля int_count и int_total могли выводиться в Node View. Для этого в секции ['join'] мы создаем массив ['node'] (имя базовой таблицы) и указываем поля, на базе которых можно объединить данные: 'left_field' — поле в таблице {node} и 'field' — поле в таблице {testnode}. Таблицы, как видно из кода, объединяются на базе поля nid.

На этом интеграция таблицы {testnode} закончена. Модуль можно установить и Views сразу позволит добавлять к представлению поля int_count и int_total, а также сортировать и фильтровать по их значениям вывод представления.

Теперь же решим задачу добавления к «вьюхе» виртуального, то есть несуществующего в таблице {testnode} поля. Пусть оно называется percentage и вычисляется из существующих значений int_count и int_total по формуле (int_count*100/int_total), то есть представляет собой некое процентное соотношение count от total. На этом примере посмотрим, как добавить виртуальное поле во Views, как пользоваться хэндлерами с суффиксом formula и как писать свои унаследованные от существующих хэндлеры.

Во-первых, нам необходимо добавить поле percentage, для чего, как мы уже знаем, служит элемент ['field']:

  //... продолжение хука mytest_views_data
  $data['testnode']['percentage'] = array(
    'title' => t('Percentage'),
    'field' => array(
      'handler' => 'mytest_handler_field_percentage',
    ),
  //...

Очевидно, мы не можем использовать стандартный field-хэндлер, поскольку он добавит в SQL-запрос поле 'percentage', а в таблице {testnode} такого поля нет. Вместо этого мы бы хотели ничего не добавлять в запрос, а на выходе получить результат в виде числа. Для этого нам потребуется собственный хэндлер, который мы назовем mytest_handler_field_percentage. Общепринятая практика при программировании под Views рекомендует, чтобы первым словом в названии хэндлера было имя модуля, затем слово handler и та опция Views, которой он управляет (то есть field), и в конце название поля таблицы БД. Реализацию хэндлера разместим в файле mytest_handler_field_percentage.inc:
<?php
/**
 * Класс генерирует виртуальное поле, равное round(int_count*100/int_total);
 */

class mytest_handler_field_percentage extends views_handler_field_numeric {
  function query() {
    return ;
  }
  function render($values) {
    if (isset($values->testnode_int_count) && isset($values->testnode_int_total)) {
      return round($values->testnode_int_count*100/$values->testnode_int_total);
    } else {
      return 0;
    }
  }
}

В качестве родительского мы берем хэндлер views_handler_field_numeric, так как этот класс уже умеет обращаться с численными полями, а наше виртуальное поле — это число. В первую очередь мы переопределяем метод query(), который отвечает за модификацию SQL-запроса. В нашем случае мы не хотим ничего добавлять в запрос, поэтому метод query() — пустой.

Затем озаботимся результатом, который будет записан в поле после выполнения запроса Views. За это отвечает метод render($values), где в объекте $values находятся все считанные из БД значения. В переопределенном методе render() мы вычисляем и возвращаем результат по считанным из базы полям int_count и int_total. Обратите внимание, что имя таблицы {testnode} является префиксом для обоих полей. Если же int_count или int_total не были считаны из БД при выполнении запроса, метод render() вернет 0. Правда, такой подход подразумевает, что пользователь должен вручную добавить поля int_count и int_total к представлению. Если мы захотим автоматизировать процесс и добавлять эти поля всякий раз, когда нам нужно поле percentage, то необходимо реализовать метод add_additional_fields(), либо добавить поля int_count и int_total в свойство additional_field в конструкторе хэндлера, либо добавить их к запросу непосредственно в методе query().

Теперь, когда новое виртуальное поле описано, мы бы хотели иметь возможность сортировать по нему коллекции данных. Для этого подходит уже существующий хэндлер views_handler_sort_formula. Этот хэндлер дополнительным параметром требует формулу, по вычисленному значению которой будет отсортирована коллекция. Заданная формула будет вставлена в запрос Views в части ORDER BY.

  //... продолжение массива $data['testnode']['percentage']
  'sort' => array(
      'handler' => 'views_handler_sort_formula',
      'formula' => 'ROUND(testnode.int_count*100/testnode.int_total)',
    ),
  //...

Еще мы хотели бы научить Views фильтровать выборку из БД по виртуального полю percentage. Для этого тоже создадим новый хэндлер:

  //...
    'filter' => array(
      'handler' => 'mytest_handler_filter_percentage',
    ),

Хэндлер mytest_handler_filter_percentage разместим в одноименном .inc-файле. Наследовать его будем от numeric-фильтра:
class mytest_handler_filter_percentage extends views_handler_filter_numeric {
  function query() {
    $this->ensure_my_table();
    $field = "ROUND(testnode.int_count*100/testnode.int_total)";

    $info = $this->operators();
    if (!empty($info[$this->operator]['method'])) {
      $this->{$info[$this->operator]['method']}($field);
    }
  }
}

Здесь мы переопределяем метод query() чтобы вместо названия поля в WHERE-часть запроса была добавлена наша формула. Для этого мы сначала вызовом $this->ensure_my_table() убеждаемся в том, что в запрос будет добавлено имя таблицы, в которой находятся поля int_count и int_total, затем определяем операцию, по которому происходит фильтрация, и указываем для этой операции нашу формулу. Смысл наследования numeric-фильтра в том, что для него уже определены операции сравнения чисел (больше, больше или равно, меньше и так далее). По сути, реализация метода скопирована из реализации родителя, изменить потребовалось только значение $field.

Наконец, прежде чем Views увидит наши новые хэндлеры, необходимо добавить в файл mytest.views.inc реализацию хука hook_views_handlers:

function mytest_views_handlers() {
  $handlers = array();
 
  $handlers['handlers']['mytest_handler_field_percentage'] = array(
    'parent' => 'views_handler_field_numeric',
    'file' => 'mytest_handler_field_percentage.inc',
  );
 
  $handlers['handlers']['mytest_handler_filter_percentage'] = array(
    'parent' => 'views_handler_filter_numeric',
    'file' => 'mytest_handler_filter_percentage.inc',
  );
 
  return $handlers;
} //function mytest_views_handlers

Хук просто возвращает массив новых хэндлеров с информацией о том, какие классы-хэндлеры они наследуют и в каких файлах их искать.

Вот и все. Мы научили Views считывать, выводить и использовать для сортировки и фильтрации результатов запроса виртуальное поле percentage. Осталось только почистить кэш Views со страницы 'admin/build/views/tools' и модуль готов к работе. Задача решена.

Прикрепленный файлРазмер
mytest.tar.gz3.18 кб
allexx (гость)
Аватар пользователя allexx

Спасибо за статью. Для меня это послужило отправной точкой. За выходные более менее вник в азы и сам тоже небольшую заметку выложил по этой теме: http://www.allexx.info/drupal_views_api_1st_example

graker
Аватар пользователя graker

Пожалуйста. К сожалению, информация по Views API очень скудная не только на русском, но и на английском. Поэтому видимо инициативные пользователи Друпала должны сами стараться писать всякое про Views, чтобы набралась критическая масса статей. Тогда общими стараниями мы приблизимся к светлому будущему :)

Vladimir (гость)
Аватар пользователя Vladimir

Поскольку нет инфы о апи, очень помогает разбор инклудов в /views/modules, например из /views/modules/node.views.inc я узнал как выводить поле ввиде даты.

graker
Аватар пользователя graker

Вообще, по API есть информация, в заметке есть ссылки на нее.
Правда, некоторые вещи там не до конца описаны или вовсе не описаны, да, поэтому приходится выискивать их в коде.

vic (гость)
Аватар пользователя vic

А работали с числовыми полями, которые являются ключами от массива значений?
Например хранится в базе поле статус. Но мне при работе со views хочется видеть не сухие цифры, а их значения:
array(
'1' => 'Проект',
'2' => 'На согласовании',
'3' => 'Активный',
'4' => 'Закрыт',
'5' => 'Архив',
);

Почитал документацию по API - вобще скудно

graker
Аватар пользователя graker

Навскидку - через свой филд-хэндлер и метод render() легко делается, примерно так:

class mytest_handler_field_array_key extends views_handler_field {
  function render($values) {
    if (isset($values->my_array_key)) {
      return $my_array[$values->my_array_key];
    }
  }
}

vic (гость)
Аватар пользователя vic

Спасибо за помощь!
Но что то не работает. Во вьюсах вылазит ошибка
"Error: handler for question > status doesn't exist!"

А попутно у меня еще задача выросла. Мне нужно добавить к вьюсам виртуальное поле, которое представляет собой сумму связанных полей из другой таблицы. Например, есть вопрос, а к нему надо добавить виртуальное поле - количество ответов для этого вопроса.
Это нужно опять же свой хендлер и определить там как то функцию function query() ?

graker
Аватар пользователя graker

vic пишет:
Спасибо за помощь!
Но что то не работает. Во вьюсах вылазит ошибка
"Error: handler for question > status doesn't exist!"
Я навскидку написал, не знаю, может вы хэндлеры не так задали или еще чего.

Цитата:
Это нужно опять же свой хендлер и определить там как то функцию function query() ?
Ага, query() и render().

andypost (гость)
Аватар пользователя andypost

Насчет документации views - все основы изложены в самом модуле, просто нужно поставить модуль advanced_help

graker
Аватар пользователя graker

Да я как-то в курсе. Речь про особенности API, а не про работу с модулем.

andypost (гость)
Аватар пользователя andypost

Дык там и особенности API расписаны, как и для панелей.

graker
Аватар пользователя graker

Угу. Там копия вот этого: http://views-help.doc.logrus.com/. Ну или наоборот, по адресу - копия advanced_help-а по Views :) По мне, так для работы с API этого не совсем достаточно. Вот тут получше: http://views2.logrus.com/doc/html/index.html, но тоже мало и недоработано, в куче мест просто куски кода, без какого-либо описания логики.

Dark (гость)
Аватар пользователя Dark

Спасибо за статью! Помогла добавить поля во вьюс
Сижу разбираюсь с модулями Views, в частности с хэндлерами таксономии. У меня есть свой тип материала, с полями использующих словарь, но они на созданную ноду не цепляются, т.е. как термины к ноде. Подключил хэндлер "views_handler_field_term_node_tid", но ясное дело не работает.
Как можно отладить это дело, чем-нибудь пользуетесь, coder, devel?

graker
Аватар пользователя graker

Иногда пользуюсь девелом, иногда просто вывожу в хендлере нужные данные через print_r().
Так или иначе, мне описание проблемы не понятно. Какие-то поля используют словарь но почему-то терминами таксономии не являются. Почему? И в то же время вы используете хэндлер id терминов, хотя это не термины. Почему? И почему нельзя использовать просто таксономию?

Без ответов на эти вопросы понять что-то затруднительно :)

Dark (гость)
Аватар пользователя Dark

Спасибо что так быстро откликнулись!

Попробую объяснить. Я создал поле, используя api модуля hierarchical_select, т.е. это список "страна-область-город". Почему выбранный термин не становится термином ноды я не знаю (надо будет почитать еще раз апи этого селекта). В таблице я храню tid термина, поэтому и решил что правильнее будет использовать хэндлер с tid. Я понимаю что поле не является термином ноды, но как же его тогда вставить во Views? Придется писать свой обработчик?

Для вывода в hook_views я использовал taxonomy_get_term($tid). Как подключить поле, чтобы оно являлось термином ноды?

graker
Аватар пользователя graker

Я наверное чего-то решительно не понимаю, но считаю, что решать проблему надо через корректную интеграцию с таксономией. Тогда все вьюсы автоматически заработают. Там ведь не сложно вроде, я программно термины и создавал, и сохранял, и к нодам добавлял без проблем (правда, без h_select-а). В общем, думаю, что правильнее всего вам будет добить h_select, чтобы термин сразу добавлялся к ноде - и все решится.

И зачем вы где-то в своей таблице храните tid термина, когда для этого есть таблица {term_node}? Храните в ней - это ведь и есть добавление термина к ноде :)

Ingumsky (гость)
Аватар пользователя Ingumsky

Решил я тоже заняться решением задачи, завязанной на views, а потому требовавшей более глубокого понимания views api. В результате пришёл к твоей статье, но задачу всё равно не решил :) Вполне возможно, что я просто не те средства использую для решения своих задач.

Смотри, есть views, который выводит список нод в хронологическом порядке. Я хочу дать пользователям возможность пользоваться exposed фильтром с датами, но при этом сделать определённые ограничения. Хочу, чтобы они могли с его помощью «скакать» по определённым временным отрезкам. Каждый отрезок начинается 1 июля одного года и заканчивается 1 июля следующего. Поэтому мне нужно было жёстко связать два поля фильтра («от» и «до») и привязать их к первому июля, позволив пользователю выбирать только год. Я долго думал, как мне это решить и не придумал ничего лучше, чем создать свою таблицу в БД, в которой задать начальную и конечную даты этого периода, а потом сделать привязку exposed filter к выборке из этой таблицы так, чтобы поле «от» фильтра было больше start_date моей таблицы, а поле «до» было меньше поля «end_date» моей таблицы.

Но, судя по всему, сделать такую привязку не получится. Моя таблица периодов не может быть 'join' (ей не к чему джойниться), а 'base', вроде как для этого случая не подходит, или я просто не понимаю, как его в данном случае связать с моей выборкой нод. Через дополнительный views? Через relatioships?

graker
Аватар пользователя graker

Хы. Извращенный конечно способ, но навскидку ничего в голову не приходит. А ты не искал модулей-расширений для работы с датами во views? Не исключено, что есть.

А вообще, таблица тут как-то не к месту. Может, попробовать свой фильтр-хэндлер написать?

Ingumsky (гость)
Аватар пользователя Ingumsky

Я ещё раньше искал для другой задачи — хотел сделать через views блок типа On this day, но так ничего и не нашёл.

Таблица не к месту, но мне всё равно хотелось попробовать её вывести во views, чтобы посмотреть, как это делается :) Оказалось, что это довольно просто, хотя поля типа дата у меня views пока отказывается распознавать даже несмотря на наличие views_handler_field_date, указанного в качестве хендлера поля, и показывает мне все даты как 1 января 1970 :)

А так, видимо, действительно придётся думать о своём хендлере, иначе какая-то ерунда получается :)

graker
Аватар пользователя graker

Да хэндлеры не очень сложно делать, не боись. А как научишься - сразу куча пользы :)

Ingumsky (гость)
Аватар пользователя Ingumsky

Пока у меня не очень срастается :) Тут вообще, как я понимаю, проблема следующая — views не очень-то дружен с датами, поэтому-то как раз у меня и не получилось в своё время сделать через него On this date — он не позволяет выставить в фильтре только число и месяц и делать выборку, опираясь на совпадение числа и месяца, но вне зависимости от года.

Собственно, та задача, которой я сейчас занимаюсь, имеет ту же проблему, но с обратной стороны. Мне важно, чтобы пользователь указывал год (причём только один), а остальные значения (число и месяц в дате «от» и полную дату в дате «до») я должен проставлять за него сам. Пока что у меня не очень получается это делать. Точнее — вообще не получается :)

Как ни странно, нет модулей для Друпала, которые решали бы хотя бы задачу on this date во views. Да и в сети как-то не особенно много описаний работы хендлеров с полями даты. Я нашёл только один рассказ: http://www.dave.st/content/creating-datetime-view-handler-views2 Парень под свои задачи написал хендлер, который обрабатывает DATETIME, который ему достался в наследство от Event. Но, если честно, я пока не сумел применить полученные от прочтения этой статьи знания, да и от других статей по хендлерам тоже.

К сожалению, документация не так хороша, как хотелось бы, поэтому приходится снова и снова копаться в чужом коде, чтобы постараться понять, что и как работает. Вот, например, мне совершенно непонятно до сих пор, как сделать обрабатывать своим хендлером только конкретные фильтры. Если это применять к своей таблице, «породнившейся» со views, это понятно — у тебя в статье как раз об этом. А если мне нужно «натравливать» свой фильтр на другие таблицы? Пока в документации я этого не встретил, хотя это должно быть в начале — ведь это одна из основ работы с API. В общем, вопросы, вопросы, вопросы.

graker
Аватар пользователя graker

Я б помог, но сейчас времени категорически нету, а в понедельник в отпуск уезжаю.
Но если разберешься - обязательно расскажи :)

Насчет натравливания хэндлера: в data-хуке ты можешь описать любую таблицу, а не только свою. Но пользоваться нужно осторожно.

Ingumsky (гость)
Аватар пользователя Ingumsky

Если разберусь (а я рассчитываю разобраться), обязательно расскажу :) И спасибо за подсказку по хендлеру :)

А ты куда и как надолго? Когда ждать обзора модулей? -)

graker
Аватар пользователя graker

В теплые страны, на две недели :) Потом напишу.

Обзор, надеюсь, будет завтра-послезавтра, хотя времени мало.

Ingumsky (гость)
Аватар пользователя Ingumsky

В тёплые страны? О-о-о. Хорошо. Завидую :) Я в этом году невыездной.

Gingervm (гость)
Аватар пользователя Gingervm

Добрый день, такой вопрос - в вышеизложенной статье описано как к вьюзу присоединить свою таблицу, а если нужно присоединить таблицу, находящуюся в бд, надо же где-то прописать что это за бд, так вот как это сделать?

заранее спасибо за помощь.

Gingervm (гость)
Аватар пользователя Gingervm

все, мой предыдущий вопрос отменяется - я не заметила кусок про присоединение базы, прошу прощения

graker
Аватар пользователя graker

Ничего страшного, бывает :)

А я уж было подумал - надо присоединить к Views таблицу, которая в другой БД находится, не с Друпалом.

Gingervm (гость)
Аватар пользователя Gingervm

только теперь я не очень понимаю что значит базовая таблица, а что не базовая...прошу прощения если спрашиваю что-то глупое, но я с этим со всем еще не очень разобралась. я создала свой view для таблицы Классы, куда в самом друпале добавила поля из материала, который тоже создала с помощью модуля сск, т.е. теперь я хочу отображать в этих полях данные из своей базы, из таблицы Классы, которая лежит на сервере - как мне прописать в модуль мою базу и таблицу? и нужно ли ее присоединять к какой-то базовой? и созданные мной поля нужны или они уже получаются описаны в этом модуле?

Gingervm (гость)
Аватар пользователя Gingervm

хм...прочитав ваш предыдущий комментарий мне кажется что именно это мне и нужно....или так вообще нельзя делать? но это же как-то странно получается....

graker
Аватар пользователя graker

Хм. Вроде можно, есть ключ 'database' для этого.

Скорее всего, нужно присоединять к базовой таблице {node} на основе nid. Но описание несколько сумбурное, а мне отсюда не видно, как у вас чего сделано :)

Базовая таблица - таблица, данные из которой могут отображаться во вьюхах сами по себе, без зависимостей от других таблиц. Базовые таблицы - это node, user и т.п. Если вы хотите создать совершенно новый тип представления - нужна базовая таблица. Если же нужно просто дополнить представление с материалами (пользователями, терминами таксономии) дополнительной информацией - присоединяйте таблицу к базовой, на базе которой ваше представление работает. Подсказка: если вы создали представление Node (то есть вывод материалов), базовая таблица будет {node}, если User (вывод пользователей) - то {user} и т.п.

Gingervm (гость)
Аватар пользователя Gingervm

так, а где этот ключ прописывать ? там же по идее надо еще параметры передавать - localhost, логин, пароль....для базы я имею в виду.

graker
Аватар пользователя graker

В settings.php помимо дефолтной базы ($db_url['default']=...) добавить еще одну строчку для другой базы. Но это только для базовых таблиц подходит, а для присоединений надо чтоб таблицы в одной базе были.

Gingervm (гость)
Аватар пользователя Gingervm

в этом файле вот что написано по этому поводу:
* To specify multiple connections to be used in your site (i.e. for
* complex custom modules) you can also specify an associative array
* of $db_url variables with the 'default' element used until otherwise
* requested.

т.е. как я поняла тогда вместо

$db_url = 'mysqli://root@localhost/modb';
$db_prefix = '';

мне надо задать две переменные с url и сделать из них массив
$db_url = 'mysqli://root@localhost/modb';
$db_url1 = 'mysqli://root@localhost/mydb';

а как мне тогда описать массив из них? или тогда надо переменной $db_url присвоить массив из нескольких url?

graker
Аватар пользователя graker

  $db_url['default'] = 'mysqli://root@localhost/modb';
  $db_url['mynewbase'] = 'mysqli://root@localhost/mydb';

Как-то так.

Но мне почему-то кажется, что вы делаете нечто странное и лишнее. Действительно ли нужно хранить эту вашу таблицу в отдельной базе данных?

Gingervm (гость)
Аватар пользователя Gingervm

ну дело в том что там не одна таблица, а около 20, и мне не только ее нужно отображать, а собственно их все, ну и запросы к ним соответственно делать, конечно можно все это перенести в друпаловскую базу и тогда я так понимаю по принципу изложенному в статье с ними работать, но я не уверена что это будет проще...там же связи и все такое....

graker
Аватар пользователя graker

Ну значит, я так понимаю, либо переносить - либо создавать базовую таблицу и т.о. делать новый тип представления, без джойнов с друпаловскими таблицами.

Gingervm (гость)
Аватар пользователя Gingervm

эх, попробую разобраться....

jedi (гость)
Аватар пользователя jedi

я подружил кое как пока через Table Wizard(галочками) с внешней таблицей.

$db_url['default'] = 'mysqli://root:mysql@localhost/_test'; //основная БД друпала
$db_url['transport'] = 'mysqli://root:mysql@localhost/transport' ; //другая БД, где находится наша автономная таблица

$db_prefix = array(
  'default' => '_test_',   // префикс таблиц _test_ у друпаловского движка
  'log' => 'transport.',   // внешняя таблица log без префиксов в базе transport
  'locales_source' => '_test._test_', // затычки для модуля Table Wizard
  'locales_target' => '_test._test_', // затычки для модуля Table Wizard
);
 

Теперь schema и views знаю про мою таблицу в другой бд, которая не пренадлежит друпалу

Гость (гость)
Аватар пользователя Гость

Вопрос:

А если мне надо что бы функция модифицировала запрос и вернула не в формате views_handler_field_numeric а обычный текст? Это значит надо писать что-то типа views_handler_field_text ? И в каком виде она должна возвращать return?

graker
Аватар пользователя graker

Не очень понимаю, о какой функции речь и кто эта «она».

Предположите на секунду, что мне неведомы ваши помыслы, что я не знаю ничего о вашей задаче — и попробуйте объяснить так, чтобы было понятно. Иначе я вряд ли смогу ответить что-то вразумительное.

Гость (гость)
Аватар пользователя Гость

Я понял, извините пожалуйста.

У меня есть определенный тип материала и мне надо вывести через Views некоторые его поля. Для того что бы их вывести нужно сформировать определенный запрос к БД, результатом которого будет текст или строчки, ну вообщем не цифры, поэтому extends views_handler_field_numeric не подходит нужно работать с текстом. Так вот меня интересует какие есть еще стандартные функции такие как views_handler_field_numeric, только для текста. Вот

graker
Аватар пользователя graker

А, ясно. Для текста подойдет просто views_handler_field.
А вообще — вот тут они все перечислены: http://views2.logrus.com/doc/html/index.html

Гость (гость)
Аватар пользователя Гость

Спасибо большое, буду добить)

Гость (гость)
Аватар пользователя Гость

Здравствуйте, а вот у меня вопрос.

А что если мне надо к определенному полю, которое выводиться через Views, прикрепить, допустим, количество записей в определенной таблице?

graker
Аватар пользователя graker

Посмотрите, как устроены вот эти модули:
http://drupal.org/project/views_groupby
http://drupal.org/project/views_calc

Гость (гость)
Аватар пользователя Гость

Спасибо большое! Сейчас буду разбираться)

Гость (гость)
Аватар пользователя Гость

Здравствуйте! а подскажите пожалуйста как можно экспортировать созданный мной вид Views вместе с модулем. Что бы вид устанавливался вместе с модулем. Спасибо

graker
Аватар пользователя graker

Делается это так.
Создаете свой собственный модуль и в нем сохраняете дефолтную вьюху как написано тут: http://views-help.doc.logrus.com/help/views/api-default-views.
Затем добавляете в зависимость к этому модулю тот, который нужно устанавливать вместе с.

Все, теперь установить вашу вьюху можно только с заданным модулем.

Гость (гость)
Аватар пользователя Гость

О! Спасибо! Разобрался) Работает.

А еще вопрос такой. Мне нужен фильтр Views что бы он отфильтровал выводимые данные по значению из URL. Ну то есть допустим заходим на страницу category/4 и он выдавал только материалы у которых в БД в столбце category стоит 4.

Спасибо

graker
Аватар пользователя graker

Это называется «аргументы».

Вы бы эта, по приведенным ссылкам прошли и прочитали все не урывками, а целиком и по порядку. И большинство вопросов отпало бы само собой.

Гость (гость)
Аватар пользователя Гость

Я, наверное, задаю очень глупые вопросы ответы на которые просто очевидны?

graker
Аватар пользователя graker

Какие-то — да, какие-то — нет. Одно дело вопросы про связь вьюхи с модулем или про количество записей — это нормальные вопросы, на них можно коротко ответить, мне не трудно.

Другое дело — вопросы, демонстрирующие незнание материальной части (как про аргументы). На такие вопросы я всегда отвечаю одинаково: предложением сэкономить время нам обоим и ознакомиться с документацией.

После чего всегда можно вернуться и задать другие, годные вопросы :)

Гость (гость)
Аватар пользователя Гость

Ясно. Ну тогда наверное вот это будет годный вопрос. Как создать свой views_handler_argument, а то что то вообще ничего не нашел на русском(

graker
Аватар пользователя graker

Так же как и другие хэндлеры — унаследовать views_handler_argument или views_handler_argument_numeric и изменить поведение там, где это требуется.

Посмотрите как это делается для уже существующих аргументов. Например, views/modules/user/views_handler_argument_user_uid.inc. Или прямо в код views_handler_argument_numeric.

Гость (гость)
Аватар пользователя Гость

Здравствуйте, у меня такая проблема.

У меня есть несколько материалов. Они были созданы не совсем правильно картинки к этим материалам были прикреплены через обычное atach files. Мне надо теперь перегнать как -то все эти картинки в CCK Image. как это можно сделать?

Спасибо.

graker
Аватар пользователя graker

Не знаю :)
Я бы вручную перегнал. Ну или курил бы устройство таблиц БД в CCK и писал код — смотря сколько материалов и картинок.

Гость (гость)
Аватар пользователя Гость

более 60 тыс =(

graker
Аватар пользователя graker

Значит — надо писать код для автопереноса.

Гость (гость)
Аватар пользователя Гость

Та. надо. Я вот думаю это будет как то так)
Надо сначала по вытаскивать адреса всех атаченых файлов, а потом их закинуть в ImageAPI. Как-то так)

Владимир (гость)
Аватар пользователя Владимир

Как добавить виртуальное поле считающее количество записей в некой таблице для данного материала?
Поле добавить получилось переопределением:

class lot_handler_field_counts extends views_handler_field {
  function render($values) {
     return db_result(db_query('SELECT count(bid) as counts from {lot_bids} where nid = %d',$values->nid));  
  }  
  function query() {
  }
}

Но возникает проблема при попытке сортировать по этому полю выдается ошибка что поле не найдено.
Подскажите пожалуйста. подробно я описал проблему здесь:
http://www.drupal.ru/node/61385

graker
Аватар пользователя graker

1. Вы сделали только хэндлер поля. Для сортировки нужно реализовать sort handler. Кроме того, поскольку у вас рендеринг идет отдельным запросом, а в query() вообще ничего нет, естественно что поле отсутствует и в запрос для сортировки не попадет никак. Ведь Views не сортирует данные после их считывания из БД, он сортирует их в ходе своего запроса.

2. Для этой задачи есть модуль. Если не ошибаюсь, Views Calc подходит.

Владимир (гость)
Аватар пользователя Владимир

1. Переопределил для поля

        'sort' => array(
                'handler' => 'lot_handler_sort_counts',
        ),

Я так понимаю просто formula тут не подойдет?...
Сделал связь:
  $handlers['handlers']['lot_handler_sort_counts'] = array(
    'parent' => 'views_handler_sort',
    'file' => 'lot_handler_sort_counts.inc',
  );

И создал отдельный файл.

class lot_handler_sort_counts extends views_handler_sort {
  function render($values) {
  }
  function query() {
    return ;
  }
}

Если я правильно понимаю то нужно переопределить функцию query() так как мы меняем запрос в разделе ORDER BY.
Но что то не пойму что в этом случае надо сюда писать... примеров таких не нашел.

2. Насчет модуля Views_calc - насколько я понимаю он считает итоги таблицы - то есть количество записей в нашем view. Тут же надо высчитать свое количество для каждой строки таблицы. Поправьте меня, если я ошибаюсь пожалуйста.

Владимир (гость)
Аватар пользователя Владимир

Вообще если просто писать запрос к базе, то:
1. К view (существующему запросу) надо присоединить таблицу
 INNER JOIN (SELECT nid, count(bid) as count FROM {lot_bids} GROUP BY nid) cnt ON cnt.nid = node.nid
2. Вывести поле
 cnt.count
3. И добавить сортировку
  ORDER BY cnt.count

Можете объяснить что из этого куда во Views нужно перенести, чтобы это все сгенерировалось.. не совсем понимаю связь...

graker
Аватар пользователя graker

Владимир пишет:
Насчет модуля Views_calc - насколько я понимаю он считает итоги таблицы - то есть количество записей в нашем view. Тут же надо высчитать свое количество для каждой строки таблицы. Поправьте меня, если я ошибаюсь пожалуйста.
Там есть динамическое создание полей в админке. Посмотрите, что можно сделать, посмотрите как реализовано.

Владимир (гость)
Аватар пользователя Владимир

1. Попробовал views_calc
Добавил Views_calc поле COUNT(%lot_bids.bid)
Добавляю поле во view.
Ругается так как нет GROUP BY блока. и где его добавить честно говоря непонятно
2. Попробовал views_group by
Все тоже самое получилось сделать - но проблема с сортировкой - Есть даже топик на эту тему (сортировать по вычисленному значению не получается) : http://drupal.org/node/529912 и http://drupal.org/node/1091456
Но видимо они пока проблему не рашают

Ольга (гость)
Аватар пользователя Ольга

Спасибо за ссылку на учебник по Views. Хотя и освоила этот модуль, Arguments и Relationships попрежнему остаются загадкой. И PHP ждёт своего часа. Эх, нелегко это - быть начинающим друпалером. :)

Ольга (гость)
Аватар пользователя Ольга

Прошу прощения, "по-прежнему".

graker
Аватар пользователя graker

Ольга пишет:
Хотя и освоила этот модуль, Arguments и Relationships попрежнему остаются загадкой. И PHP ждёт своего часа. Эх, нелегко это - быть начинающим друпалером. :)
Задавайте вопросы, Оля, ответим :)

Гость (гость)
Аватар пользователя Гость

Учусь щас работать с ним над своим сайтом

graker
Аватар пользователя graker

Зачем ты врешь?

Гость (гость)
Аватар пользователя Гость

Приветствую graker,

Помоги разобраться с проблемкой:

Имеем каталог недвижки построеной на nodehierarchy:

Тип (Термин) | ССК | ССК
Квартира | Площадь | Цена - самостоятельная квартира
Комплекс | (родительский объект)
Квартира | Площадь | Цена - квартира в комплексе (дочерний объект)
Квартира | Площадь | Цена - квартира в комплексе (дочерний объект)

Нужно сделать расширенный поиск по базе (в принципе все реализовано, но не нравится скорость выполнения)

Создал View:страница с аргументом "nid"

/result/площадь от/площадь до/цена от/цена до - так передаются параметры странице

аргументы обрабатываю своей функцией, которая делает несколько запросов к базе:
1. выбор nid удовлетворяющих условиям поиска для самостоятельных квартир
2. выбор nid для дочерних объектов
-- получение уникального списка родительских объектов
3. объединение результатов
4. передача списка nid'ов в аргумент views

Выводим все поля для каждого nid, если это самостоятельный объект, то поле этого объекта, если это комплекс, то для него выводим все дочерние объекты с ценой.

Теперь проблема с сортировкой
Создано поле PHP, которое если есть цена у объекта выводит ее, если нет то ищет минимальную цену в дочерних объекта (опять же используя условия поиска) и выводит ее. Ставлю возможность сортировать по этому полю, и получаю 1900 запросов к базе.

Оказывается что Views сначала выполняет все операции над полями для сортировки, а затем повторно эти же запросы для вывода.
Отключаем сортировку 900 запросов. (База не маленькая.)

Вижу единственный выход в передачи отсортированного списка nid'ов, (своя сортировка выполняется в 100 раз быстрее чем через views),
но тут проблема не вижу у Views возможности выводить ноды в порядке поступивших аргументов.

Чувствую что пропустил еще варианты решения проблемы но не могу достучаться до своего мозга )

graker
Аватар пользователя graker

Вот уж не знаю, как на такое ответить :)
Может, надо прямо php-скриптом результаты сортировать, перед выводом?

Гость (гость)
Аватар пользователя Гость

В шаблоне вывода views?

graker
Аватар пользователя graker

Ну я это имел в виду, да.

Гость (гость)
Аватар пользователя Гость

хммм, тут проблемка с paginate, все спокойно сортируется но на каждой странице отдельно )))
я так понимаю нужно отключить paginate у views и опять же сделать его вручную в шаблоне? ))

а есть ли встроенные drupal функции для paginate или придется ручками делать?

graker
Аватар пользователя graker

theme('pager'), наверное :)

Гость (гость)
Аватар пользователя Гость

Получилось но более проще, как я и планировал изначально.
нашел модуль который сортирует по очередности поступивших аргументов.
Загрузка страницы снизилась с 12 сек до 1.5 сек

graker
Аватар пользователя graker

В комментарии явно не хватает названия модуля :)

Гость (гость)
Аватар пользователя Гость

Есть два подобных модуля:
1. Views Argument Sort - в данный момент работает и быстро
2. Views Arguments Extras - помимо сортировки может делать что-то еще, но пока не работает =)

Оба еще в стадии разработки.

Андрей (гость)
Аватар пользователя Андрей

Интересно а как дело обстоит с VIEWS3 и Drupal7 ? Пробовал ли уже кто-то подключать свою таблицу в семерке? Есть ли отличия? также интересует, если например эта таблица создана как entity и потом к ней добавлены поля уже через админку, то эти поля будут отбражаться во вьювс, или их каким-то образом тоже надо будет подключать?

Буду благодарен за любые комментарии

Отправить комментарий

CAPTCHA
Пройдите, пожалуйста, проверку. Она нужна, чтобы отличать людей от спам-роботов. А если не хотите проходить эту проверку регулярно — зарегистрируйтесь.
Image CAPTCHA
Введите цифры, изображенные на картинке (без пробелов).