El modulo migrate es aliado perfecto para realizar migraciones desde cualquier fuente a nuestro Drupal.
Cuando encargan una web en drupal a Omitsis es muy habitual que sea una web ya existente y que por lo tanto tenga un contenido que quieran migrar.
En Drupal existe el módulo migrate que está incorporado en el core y que nos permite migrar contenido. Es un módulo muy potente, versátil y que facilita realizar rollbacks de serie. Los rollbacks nos permiten «desimportar», muy útil para cuando aún estamos desarrollando y hay errores.
El módulo migrate permite importar desde diferentes fuentes: un Drupal 7, una base de datos tenga la estructura que sea, desde un csv, un JSON, XML, SOAP, etc.
Es muy común que nos pasen un excel con todos los campos a migrar por cada tipo de contenido. En este post vamos a explicar como realizar este tipo de migración: desde un csv, ya que todas las hojas de cálculo (excel por ejemplo) permite guardar como csv y este es un formato estándar mejor soportado.
Todo lo que vamos a explicar es válido para Drupal 8 y para Drupal 9, ya que todos los módulos están disponibles para ambas versiones excepto el config_devel que parece que estará próximamente y no es un módulo imprescible.
Lo primero es instalar los módulos que serán necesarios. El migrate ya está en el code de Drupal pero vamos a necesitar algunos más: migrate_plus, migrate_tools, migrate_source_csv y config_devel.
Instalamos estos módulos usando composer:
composer require drupal/migrate_plus drupal/migrate_tools drupal/migrate_source_csv drupal/config_devel
Y los activamos, con drush sería así:
drush -y en migrate migrate_plus migrate_tools migrate_source_csv config_devel
Ahora hemos de crear los ficheros yml que definirán las migraciones. Esto lo hemos de hacer dentro de un módulo custom, que podemos tener ya o podemos crear uno específico para el migrate (opción más limpia).
Puedes generar un módulo de forma fácil con la consola, en nuestro caso lo vamos a llamar custom_migrate.
drupal gm
En el directorio de este módulo creamos una subcarpeta llamada config y dentro de esta otra llamada install. Ahí pondremos los ficheros yml’s que definirán las migraciones.
La nomenclatura de estos ficheros hay de ser:
migrate_plus.migration.[ID_MIGRACION].yml
Por ejemplo, si queremos importar productos el nombre del fichero podría ser este:
migrate_plus.migration.products.yml
En este ejemplo el archivo estaría en la siguiente ruta:
web/modules/custom/custom_migrate/config/install/migrate_plus.migration.products.yml
El módulo migrate_plus nos permite también crear grupos, para luego hacer las importaciones de golpe (y los rollbacks). Puedes ver en este post cómo hacerlo. En este ejemplo no los usaremos, pero es bastante práctico.
Ahora hay que decirle a Drupal que hay de tener en cuenta el fichero de importación que definimos. Eso se pone en el .info.yml de nuestro módulo de la siguiente manera:
config_devel:
install:
- migrate_plus.migration.products
El archivo entero sería así:
name: 'Custom migrate'
type: module
description: 'Módulo de importaciones de contenido'
core: 8.x
package: 'Custom'
dependencies:
- migrate
- migrate_plus
- migrate_tools
- config_devel
config_devel:
install:
- migrate_plus.migration.products
# - migrate_plus.migration.otra_importacion
Esto lo podemos hacer gracias al módulo config_devel, ya que sin este módulo solo se pondría esta configuración cuando instalamos el módulo, y no es nada práctico tener que estar instalando y desinstalando.
Ahora nos falta lo más importante: el contenido del yml de importación. Esto daría para muchos posts, así que vamos a poner un ejemplo sencillo:
Primero es necesario definir el id y un label irá bien para ponerle un nombre bonito.
id: products
label: Import products
Luego va la sección del source. Aquí hay que decirle que plugin usaremos y para el caso del csv un path donde estará el csv. En este caso lo hemos puesto dentro del módulo para poderlo integrar en nuestro git.
También le hemos indicado los ids únicos, que en nuestro csv se llamaba «code».
Y finalmente indicamos los campos del csv.
source:
plugin: 'csv'
# Full path to the file.
path: 'modules/custom/custom_migration/data/products.csv'
# Column delimiter. Comma (,) by default.
delimiter: ','
# Field enclosure. Double quotation marks (") by default.
enclosure: '"'
# The row to be used as the CSV header (indexed from 0),
# or null if there is no header row.
header_offset: 0
# The column(s) to use as a key. Each column specified will
# create an index in the migration table and too many columns
# may throw an index size error.
ids:
- code
# Here we identify the columns of interest in the source file.
# Each numeric key is the 0-based index of the column.
# For each column, the key below is the field name assigned to
# the data on import, to be used in field mappings below.
# The label value is a user-friendly string for display by the
# migration UI.
fields:
0:
name: cat
label: 'Categoria'
1:
name: code
label: 'Product code'
2:
name: title
label: 'Title'
3:
name: image
label: 'Image'
Ahora la parte del process, dónde indicamos los campos a dónde hay que poner los datos, el plugin que vamos a usar y la fuente (source).
Si es algo muy simple podría ser tan sencillo como esto:
field_nombre_campo_en_nuestro_drupal: nombre_columna_source
Pero muchas veces no es tan simple. Por ejemplo nos pasan el título todo en mayúsculas, así que combinamos varios plugins. También verás la parte de importación a un campo media. Esto lo explicaremos en otro post en breve.
Un caso real podría ser este:
process:
type:
plugin: default_value
default_value: producto
field_cat:
plugin: entity_lookup
source: division
value_key: name
bundle_key: vid
bundle: cat_producto
entity_type: taxonomy_term
ignore_case: true
field_producto_codigo: code
title:
-
source: title
plugin: callback
callable: mb_strtolower
-
plugin: callback
callable: ucfirst
field_imagen/target_id:
plugin: entity_lookup
value_key: name
source: image
bundle_key: bundle
bundle: image
entity_type: media
ignore_case: 1
access_check: 0
Hay muchos plugins disponibles, los puedes ver aquí. Además podemos crear plugins de forma muy fácil.
Ya solo falta indicar a dónde irá todo este contenido con destination. dependerá de la entidad que estemos importando, en este caso son nodos.
destination:
plugin: entity:node
Y aquí todo entero:
id: products
label: Import products
source:
plugin: 'csv'
# Full path to the file.
path: 'modules/custom/custom_migration/data/products.csv'
# Column delimiter. Comma (,) by default.
delimiter: ','
# Field enclosure. Double quotation marks (") by default.
enclosure: '"'
# The row to be used as the CSV header (indexed from 0),
# or null if there is no header row.
header_offset: 0
# The column(s) to use as a key. Each column specified will
# create an index in the migration table and too many columns
# may throw an index size error.
ids:
- code
# Here we identify the columns of interest in the source file.
# Each numeric key is the 0-based index of the column.
# For each column, the key below is the field name assigned to
# the data on import, to be used in field mappings below.
# The label value is a user-friendly string for display by the
# migration UI.
fields:
0:
name: cat
label: 'Categoria'
1:
name: code
label: 'Product code'
2:
name: title
label: 'Title'
3:
name: image
label: 'Image'
process:
field_cat:
plugin: entity_lookup
source: division
value_key: name
bundle_key: vid
bundle: cat_producto
entity_type: taxonomy_term
ignore_case: true
field_producto_codigo: code
title:
-
source: title
plugin: callback
callable: mb_strtolower
-
plugin: callback
callable: ucfirst
type:
plugin: default_value
default_value: producto
field_imagen/target_id:
plugin: entity_lookup
value_key: name
source: image
bundle_key: bundle
bundle: image
entity_type: media
ignore_case: 1
access_check: 0
destination:
plugin: entity:node
migration_dependencies: {}
Ahora finalmente solo nos queda ejecutar los comandos desde drush para realizar la importación. También se puede hacer desde UI en admin/structure/migrate.
Lo primero es decirle a drupal que cargue todos los archivos de importación con el módulo config_devel:
drush cdi [nombre_del_modulo]
Que en nuestro caso sería:
drush cdi custom_migrate
Luego podemos ver las migraciones que tenemos y su estando con:
drush ms
Para realizar la importación:
drush mim [id_importacion]
Que en nuestro caso sería:
drush mim products
Si algo sale mal, que lo normal es que pase, podemos hacer un rollback con este comando:
drush mr products
También es posible que la migración se atasque, con lo cual primero antes de hacer el rollback hay que ponerla en idle:
drush mrs products
Y esto es más o menos todo, como introducción a una migración con Drupal desde un csv usando el módulo migrate.