I can't get views 3 relationships to work correctly with a field in D7. The field works fine, but it is not offered as a relationship in views 3.

I don't know that this is a bug, but it may be a key missing feature for D7 Fields. Or it may just be a hole in my knowledge.

I'm porting Amazon module to D7. (All the code is in the dev version at
http://drupal.org/project/amazon).

I can't get views relationships to work correctly, and don't know if it's a
views issue or fields issue (probably?)

1. The asin module in the Amazon project provides an ASIN field. This works
fine already. You can add asin elements to a node, it displays right, themes
right, everything.

2. Views is happy to do a node-type view and offers an ASIN as a field, and
displays it, themed correctly.

3. Amazon provides a full amazon_item data type to views, quite a rich set
of data about any amazon item, with prices, pictures, author, and all manner
of detail data all available as views fields. Views is very happy to present
all this data in an amazon_item-type view.

4. BUT: The relationship between a node with an ASIN field and the Amazon
item data is not there. No relationship is offered. So the most popular
feature of D6 Views with Amazon is out of reach: The ability to display an
incredible views list of nodes with lots of rich data about the Amazon items
in its fields.

In D6, asin_field_settings() (hook_field_settings()) had the mapping, I
think, provided in a $op called 'views data'. But hook_field_settings() was
broken up into hook_field_schema() and hook_field_form_settings() and there
doesn't seem to be anything like this any more.

Below is what the D6 asin_field_settings() had:

function asin_field_settings($op, $field) {
  switch ($op) {
    case 'database columns':
      $columns['asin'] = array('type' => 'varchar', 'length' => 32, 'not
null' => FALSE);
      return $columns;

    case 'views data':
      $data = content_views_field_views_data($field);
      $db_info = content_database_info($field);
      $table_alias = content_views_tablename($field);

      // Filter: Add a 'many to one' filter.
      $copy = $data[$table_alias][$field['field_name'] .'_asin'];
      $copy['title'] = t('@label (!name) - Allowed values', array('@label'
=> $field['widget']['label'], '!name' => $field['field_name']));
      $copy['filter']['handler'] = 'views_handler_filter_many_to_one';
      unset($copy['field'], $copy['argument'], $copy['sort']);
      $data[$table_alias][$field['field_name'] .'_value_many_to_one'] =
$copy;
      // Argument : swap the handler to the 'many to one' operator.
      $data[$table_alias][$field['field_name']
.'_value']['argument']['handler'] = 'views_handler_argument_many_to_one';

      // Add a relationship for related node.
      $data[$table_alias][$field['field_name'] .'_asin']['relationship'] =
array(
        'base' => 'amazon_item',
        'field' => $db_info['columns']['asin']['column'],
        'handler' => 'views_handler_relationship',
      );
      return $data;
  }
}

Comments

yched’s picture

Project: Drupal core » Views (for Drupal 7)
Version: 7.x-dev » 7.x-3.x-dev
Component: field system » Views Data
Category: bug » support
Status: Active » Fixed

The hook to let field type modules expose their data for Views is now defined by Views module.
See views/modules/field.views.inc. From a brief look at the code, it's named hook_field_views_data($field).

Same principle as in D6 : your hook implementation can either grab 'sensible default data' provided by field_views_field_default_views_data($field) and build from there (highly recommended), or start from scratch and build the whole array of views data on its own.

After that, I don't really know whether the syntax for exposing relationships has changed since Views 6.

rfay’s picture

Status: Fixed » Active

@yched, thanks so much for the detailed answer. I'm still a bit adrift, and hope you can bear with me for a bit more:

You say:

Same principle as in D6 : your hook implementation can either grab 'sensible default data' provided by field_views_field_default_views_data($field) and build from there (highly recommended), or start from scratch and build the whole array of views data on its own.

So you're saying that I can implement hook_views_data_alter() in my module and alter the views data to add relationships? That makes sense, but as a practical matter, my module has no idea which of the items in the $data might be its own. Field module knows, that, but my module doesn't.

I've implemented hook_views_data_alter() in my module per dereine's suggestion in IRC, but can't see how to select out of $data what would belong to my module. Is it possible to do so?

Thanks again,
-Randy

dawehner’s picture

Look into modules/field.views.inc
I guess the field integration iterates over every field instance. You could do the same, but check additional for your field type.

rfay: Can you suggest a new hook which makes your life easier? I would like to see a patch, if it helps you.

rfay’s picture

Thanks for the continuing help. Implementing hook_field_views_data() is going to be the ticket, I think. You probably said that before, but you know how it goes.

So a couple of questions:
I'm going to copy field_views_field_default_views_data() in asin_field_views_data().

I notice in field_views_field_default_views_data() this note:

  // Note: this should be in the $field object already, see http://drupal.org/node/645926.
  $field_storage_details = field_sql_storage_field_storage_details($field, NULL);
  $field += $field_storage_details;

That issue did land. Should we be pulling this out?

Second, in field_views_data() we have

    $module = $field['module'];
    $result = (array) module_invoke($module, 'field_views_data', $field);
    drupal_alter('field_views_data', $result, $field);

    if (empty($result)) {
      $result = field_views_field_default_views_data($field);
    }

The drupal_alter() comes after the module_invoke(). Wouldn't it be more useful after the defaults are loaded? In fact, it would probably be better for my module to do an asin_field_views_data_alter() than to replicate the entire default data.

rfay’s picture

Status: Active » Fixed

My code is WORKING! Thanks so much for the help.

Related issues per #4 above:
#761754: Removed outdated storage details in field_views_field_default_views_data()
#761758: Move drupal_alter() for hook_field_views_data_alter() to more useful spot

If anybody else needs to implement relationships like this, the resulting code will be in Amazon module, asin/asin.module, in asin_field_views_data().

Pretty amazing community support.
yched++
dereine++

rfay’s picture

I added a minor paragraph about this in the CCK to Fields update page hook_field_settings() section. It can be better. Maybe when I have the actual code boiled down better I'll improve it.

yched’s picture

@rfay : just to be clear - I was suggesting that your asin_field_views_data() starts by calling field_views_field_default_views_data() and tweaks the result before returning it, not duplicating the code of course. Sorry if this is stating the obvious :-)

rfay’s picture

Thanks, yched, I did understand that.

But I did actually start by copying the code. And I haven't finished doing it right. It's not obvious what the correct approach is to alter or to build from what field_views_field_default_views_data() does.

If all I want to do is add a relationship, and I called

$data = field_views_field_default_views_data();
... do the right thing here to add a relationship ...

What would the right thing be?
What I did in the copied code was:

foreach ($tables as $table) {

...

      // Add a relationship for related node.
      $data[$table][$column_real_name]['relationship'] = array(
        'base' => 'amazon_item',
        'field' => $column_real_name,
        'handler' => 'views_handler_relationship',
      );
}

So it looks like the work remaining is to get $column_real_name, and that's about it, right?

But then $tables is not exactly equivalent to $data.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.