En post anteriores he mencionado muchas veces el módulo Views, la razón es que es uno de esos modulos impresindible en cualquier proyecto (tanto así que fue incluido en el core de Drupal 8). Sin embargo, en muchos proyectos existirán requerimientos que lamentablemente Views no podrá suplir, salvo que como desarrolladores hagamos algo al respecto. Algunos optan por instalar Views PHP y agregar código en las mismas vistas, siendo esta una de las peores prácticas. Otros optan por hacer sus vistas manualmente (No usando Views). Pero existen otras opciones usando el método "Drupal Way", una de ellas es simplemente extendiendo la funcionalidad de Views para resolver nuestros requerimientos. De esto último tratará este post, en donde resolveremos un problema con este método.
Antes de empezar ¿Que hace Views?
Nos permite, en la interfaz de administración, hacer vistas con la información que existe en nuestra base de datos (u otra fuente), definiendo que datos mostrar, como filtrar, como ordenar, etc. Esto se logra gracias a varios factores, por un lado Views se encarga de "mapear" lo que viene ya por defecto en Drupal: nodos, users, comentarios, taxonomias y otros. Y el módulo Entity API se encarga de "mapear" todas las entidades restantes creadas por otros módulos o por uno mismo. Este "mapeo" se logra por medio del hook_views_data en donde se informa de la existencia de las tablas y el como interpretarlo. También existe el hook_views_data_alter, que nos permitirá alterar la información. Ya sabiendo esto procederemos a analizar el problema.
El Problema a resolver
Supongamos que se debe desarrollar un proyecto en el que interactuan muchos usuarios. En éste se solicita que cada usuario pueda, opcionalmente, agregar su nombre completo en el perfil de usuario. Por otra parte se solicitan vistas de responsabilidades y que en esta se imprima el Nombre Completo o el username en caso de no tenerlo. En estas mismas se debe agregar un único filtro que busque por el Nombre Completo o por username.
Para resolver esto vamos a ir paso a paso:
Paso 1: ¿Donde registramos el Nombre Completo? Usemos fields
A las entidad usuario, en Drupal 7, podemos extender su información usando Fields. Entonces vamos a crear un campo de texto simple para registrar el nombre completo, cuyo nombre de máquina será "field_user_nombre_completo" (Pueden usar cualquiera, pero deben tenerlo en cuenta ya que lo usaremos después).
Paso 2: Creamos nuestro módulo
Necesitamos tener un módulo custom para hacer el pedido (Personalmente esto lo adjunto al módulo base del proyecto). Como ejemplo nuestro módulo se llamará "mimodulo" y creamos los siguientes archivos:
- mimodulo.info
- mimodulo.module
En mimodulo.info definen el nombre del módulo y la descripción. También agregan los archivos que vamos a crear en el futuro.
- name = MIModule
- description = Modulo base para nuestro ejemplo.
- core = 7.x
- package = MIModule
- files[] = views/handlers/mimodulo_nombre_completo_views_handler_field.inc
- files[] = views/handlers/mimodulo_nombre_completo_views_handler_filter.inc
En mimodulo.module implementan el hook_views_api().
<?php/** * Implements hook_views_api(). */function mimodulo_views_api() { return array( 'api' => 3, 'path' => drupal_get_path('module', 'mimodulo') . '/views', );} ?>
Paso 3: Extendiendo la información de usuario en Views.
Creamos el archivo mimodulo.views.inc en la carpeta views del módulo en donde vamos a definir nuestro nuevo campo y filtro con nombre completo.
- mimodulo.info
- mimodulo.module
- views/mimodulo.views.inc
En dicho archivo adjuntamos el siguiente código
<?php/** * Implements hook_views_data_alter(). */function mimodulo_views_data_alter(&$data) { $field_name = 'field_user_nombre_completo'; $data['users']['mimodulo_nombre'] = array( 'title' => 'Nombre Usuario (MiModulo)', 'help' => 'Nombre del usuario, si tiene nombre completo lo usa.', 'field' => array( 'title' => 'Nombre Usuario (MiModulo)', 'field' => 'name', 'handler' => 'mimodulo_nombre_usuario_handler_field', 'additional fields' => array( 'nombre_completo' => array( 'table' => 'field_data_' . $field_name, 'field' => $field_name . '_value', ), ), ), 'filter' => array( 'field' => 'name', 'table' => 'users', 'handler' => 'mimodulo_nombre_usuario_handler_filter', 'allow empty' => 1, 'additional fields' => array( 'nombre_completo' => array( 'table' => 'field_data_' . $field_name, 'field' => $field_name . '_value', ), ), ), );}?>
Lo que hacemos es agregar otro campo a la tabla users que claramente no existe, es por eso que especificamos que es en base del campo name. También agregamos el campo adicional que corresponde al field_user_nombre_completo (Esto realizará el respectivo join).
Paso 4: Definiendo el Handler Field
Como campo hemos definido al handler mimodulo_nombre_usuario_handler_field. Vamos a crear el archivo con dicho nombre en la ruta views/handlers.
- mimodulo.info
- mimodulo.module
- views/mimodulo.views.inc
- views/handlers/mimodulo_nombre_usuario_handler_field.inc
Dicho archivo tendrá lo siguiente:
<?php class mimodulo_nombre_usuario_handler_field extends views_handler_field_user_name { function set_value($values, $field, $value) { $alias = isset($field) ? $this->aliases[$field] : $this->field_alias; if (isset($values->{$alias})) { $values->{$alias} = $value; } } function render_link($data, $values) { $nombre_completo = $this->get_value($values, 'nombre_completo'); if ($nombre_completo) { $this->set_value($values, NULL, $nombre_completo); } return parent::render_link($data, $values); }}?>
Lo que hacemos es crear la clase extendiendo la que usa el campo username. Así reutilizamos la configuraciones que suele darnos el campo, por ejemplo imprimir el campo como link al perfil del usuario.
Paso 5: Definiendo el Handler Filter
Este es el handler más complejo, necesitamos que no solo filtre por el nombre completo sino que también por el username. ¿Por qué? Porque pueden haber usuarios que no hayan definido su nombre completo (También fue pedido). Al igual que el handler field, creamos el archivo para el filter.
- mimodulo.info
- mimodulo.module
- views/mimodulo.views.inc
- views/handlers/mimodulo_nombre_usuario_handler_field.inc
- views/handlers/mimodulo_nombre_usuario_handler_filter.inc
En este archivo adjuntamos el siguiente código
<?phpclass mimodulo_nombre_usuario_handler_filter extends views_handler_filter_string { var $additional_fields = array(); var $fields = array(); var $aliases = array(); function construct() { parent::construct(); $this->additional_fields = array(); if (!empty($this->definition['additional fields'])) { $this->additional_fields = $this->definition['additional fields']; } } // Solo puede haber dos tipos de operaciones. El que contiene cualquiera o todas las palabras. function operators() { $operators = array( 'word' => array( 'title' => t('Contains any word'), 'short' => t('has word'), 'method' => 'op_word', 'values' => 1, ), 'allwords' => array( 'title' => t('Contains all words'), 'short' => t('has all'), 'method' => 'op_word', 'values' => 1, ), ); return $operators; } function query() { $this->ensure_my_table(); $this->add_additional_fields(); $field_name = "$this->table_alias.$this->real_field"; $this->fields[] = $field_name; $info = $this->operators(); if (!empty($info[$this->operator]['method'])) { $this->{$info[$this->operator]['method']}($this->fields); } } // Copiamos el método op_word y lo modificamos para soportar multiple campos. function op_word($fields) { $or = db_or(); // Don't filter on empty strings. if (empty($this->value)) { return; } preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' ' . $this->value, $matches, PREG_SET_ORDER); foreach ($fields as $field) { $where = $this->operator == 'word' ? db_or() : db_and(); foreach ($matches as $match) { $phrase = false; // Strip off phrase quotes if ($match[2]{0} == '"') { $match[2] = substr($match[2], 1, -1); $phrase = true; } $words = trim($match[2], ',?!();:-'); $words = $phrase ? array($words) : preg_split('/ /', $words, -1, PREG_SPLIT_NO_EMPTY); foreach ($words as $word) { $placeholder = $this->placeholder(); $where->condition($field, '%' . db_like(trim($word, " ,!?")) . '%', 'LIKE'); } } $or->condition($where); } if (!$or) { return; } // previously this was a call_user_func_array but that's unnecessary // as views will unpack an array that is a single arg. $this->query->add_where($this->options['group'], $or); } // Se copia el metodo add_additional_fields de views_handler_field y lo modificamos. function add_additional_fields($fields = NULL) { if (!isset($fields)) { // notice check if (empty($this->additional_fields)) { return; } $fields = $this->additional_fields; } $group_params = array(); if ($this->options['group_type'] != 'group') { $group_params = array( 'function' => $this->options['group_type'], ); } if (!empty($fields) && is_array($fields)) { foreach ($fields as $identifier => $info) { if (is_array($info)) { if (isset($info['table'])) { $table_alias = $this->query->ensure_table($info['table'], $this->relationship); } else { $table_alias = $this->table_alias; } if (empty($table_alias)) { debug(t('Handler @handler tried to add additional_field @identifier but @table could not be added!', array('@handler' => $this->definition['handler'], '@identifier' => $identifier, '@table' => $info['table']))); $this->aliases[$identifier] = 'broken'; continue; } $params = array(); if (!empty($info['params'])) { $params = $info['params']; } $params += $group_params; $this->fields[] = $table_alias . '.' . $info['field']; $this->aliases[$identifier] = $this->query->add_field($table_alias, $info['field'], NULL, $params); } else { $this->aliases[$info] = $this->query->add_field($this->table_alias, $info, NULL, $group_params); } } } }}?>
Esta clase extiende de views_handler_filter_string para reutilizar algunos métodos, sin embargo tenemos que agregar varias modificaciones para hacer compatible el campo adicional y que busque en más de una tabla. Ya con esto podremos usar el filtro que busca tanto en el Campo field_user_nombre_completo como en el username.
Resultado
Crearemos una vista de usuarios, imprimiendo el nombre completo y username. También agregaremos el filtro. Al agregar un campo en administración, nos aparecerá lo siguiente:
Vemos el nuevo campo que hemos creado en el grupo User. Lo seleccionamos y debería mostrar algo así:
En esta lista se visualiza que el usuario abogado1 no tiene asignado un nombre completo. Si es el caso de los otros usuarios. Ahora agregaremos el filtro y filtraremos por la palabra "Usuario".
El sistema lo encuentra en el Campo field_user_nombre_completo. Y si en cambio buscamos por "administrador":
El sistema lo encuentra en el campo username.
Así podrán asegurarse de que el filtro siempre encontrará a los usuarios en el sistema, sea usando el username o el Nombre Completo.
En otra oportunidad veremos otros ejemplos más complejos en donde haremos que views sea un mejor aliado para nuestros proyectos. Espero que esto les sea de utilidad.
Últimos Comentarios