I am using views_query_alter to add spatial joins between two tables, and have been successful in manipulating the query object to create the proper join conditions, and return the desired results.

Howefver, I want to display columns from the joined tables, but am unable to figure out how to do so. Ideally I would like to be able to use replacement patterns in a "rewrite", or just feed data to their appropriate drupal fields in the Table 'FIELDS' display area, but this seems as if I am missing something.

Is it possible to introduce columns to the view query and have them exposed to the GUI interface for formatting the query at all? If not, is views query altering only effective for creating more complex joins or filters?

Thanks in advance.

/r/b

Comments

WorldFallz’s picture

Is it possible to introduce columns to the view query and have them exposed to the GUI interface for formatting the query at all?

don't think so-- the alter happens fairly late in the chain, and certainly doesn't feed anything back to the UI, but the best way to get a definitive answer would probably be either IRC or the views issue queue.

Having said that however, what's preventing you from adding them via the UI and manipulating them as desired in the alter or via preprocess or theming?

robertwb’s picture

Thanks for the response.

> what's preventing you from adding them via the UI and manipulating them as desired in the alter or via preprocess or theming?

Probably only my knowledge level - preprocess I am gaining familiarity, theming I am still clueless.

Based on your suggestion, I tried adding the columns that I want first, but I cannot seem to tell the view "use this new column instead of that old column please". Maybe I am missing something (not maybe ... definitely). Can I just tell the query "delete this old column", and "add this new column with the same name as the old column so views thinks that it is what was asked for"?

I don't think so, for example, if I am using this to do as spatial join (which is what I am doing) and I want to show the "title" field for two overlapping nodes, I can add "title" twice in the UI, but the UI will only construct the base query to have a single reference to "title" which it then later handles on it's own (assigning replacement aliases title and title_1). In other words, there is no placeholder created for it, at least not in the query.

Regards,
/r/b

WorldFallz’s picture

I'm not sure I understand what you mean by "spatial join" in this context. however:

...I cannot seem to tell the view "use this new column instead of that old column please".

You can do that with theming which isn't really difficult, especially if you already know how to write query alters and preprocess functions.

Assuming all the data you need is in the view, you can just theme the fields or the view to display that data however you like. See views advanced help and http://nodeone.se/en/theming-views for how to theme your view.

robertwb’s picture

Thanks! Will do.

/r/b

WorldFallz’s picture

holy handgrenades batman! Since we're in the coders forum, the non code way you can do this through the UI completely slipped my mind.

Assuming you have access to all the fields, you can use a field's settings to 'Exclude it from display' which simply means views suppresses it from the output. Then, in another field, you can select the 'rewrite the output of this field' option to modify it almost however you like. Once you select the rewrite option, you'll get a list of tokens for all the fields that come before it in the list that you can use when rewriting. For that reason I usually put all fields with the exclude option checked at the top of the list.

robertwb’s picture

OK - so this is EXACTLY what I have been trying to do -- it just seems that none of my newly added fields get replacement tokens assigned to them. That would make it soooooo easy! Maybe it is really supposed to behave that way and I am not getting it?

Thanks!
/r/b

robertwb’s picture

Just to provide some background. I am doing a "spatial join" which means that I am using two geometry columns (a point and a polygon) to determine if two separate nodes intersect in the same space. My system is based on postgreSQL, so I use the postgis extension to SQL to accomplish the intersection. I have the geometries stored in separate tables. My code is all done in a custom module as below.

My tables:
1. 'node' - My base table
2. 'data_node_point' - a table with a NID that refers and has a point geometry (basically a latitude and longitude)
3. 'data_node_coverage' - A table with an entity_id (also a nid reference) and a multi-polygon geometry (a 2 dimensional spatial area)
4. Both 'data_node_point' and 'data_node_coverage' have a dummy column 'status' which is always 1 in order to provide an initial join condition because it seems that the 'join' requires something non-null there (maybe I am wrong, but no harm occurs I suppose).

My code outline steps are as follows:
1. I join the point table 'data_node_point' to 'node' via matching 'nid' columns - I use the function "$query->addRelationship" to enable me to join to the table 'data_node_point' later.
2. I add columns from 'data_node_point' - 'nid' and 'the_geom'
3. I join the table 'data_node_coverage' to 'data_node_point' via a spatial containment operator - if the lat/lon in 'data_node_point' lies inside of the boundaries of 'data_node_coverage' then I include it.
4. I screen for null geometries which would indicate that the original node has no entry in the point table or no overlapping coverage.


function wsp_devel_views_query_alter(&$view, &$query) {

   if ($view->name=="wsp_coverage_systems") {
      error_log("Found wsp_coverage_systems");

      $join = new views_join;
      $join->construct('data_node_point','node', 'nid', 'nid');
      $res = $query->add_relationship('data_node_point', $join, 'data_node_point', 'node');
      $res = $query->add_field('data_node_point', 'nid');
      $res = $query->add_field('data_node_point', 'the_geom');
      
      $join = new views_join;
      $geojoin = 'data_node_coverage.the_geom && data_node_point.the_geom';
      $geojoin .= ' AND contains(data_node_coverage.the_geom, data_node_point.the_geom )';
      $join->construct('data_node_coverage', 'data_node_point','status', 'status', $geojoin);
      $res = $query->add_table('data_node_coverage', 'data_node_point', $join, 'data_node_coverage');
      $res = $query->add_field('data_node_coverage', 'entity_id');
      $res = $query->add_field('data_node_coverage', 'the_geom');
      
      if (isset($view->args[0])) {
         $query->add_where_expression(0, 'data_node_coverage.entity_id = ' . $view->args[0]);
      }
      $query->add_where_expression(0, 'data_node_point.the_geom is not null');
      $query->add_where_expression(0, 'data_node_point.the_geom is not null');
   }
   
}

WorldFallz’s picture

ah ok, I think I'm getting it now. The issue is your custom tables-- views doesn't know anything about them. And when you say 'newly added fields' I'm assuming that means those added in the query_alter not the list of fields in the views UI. The rewrite/tokens/exclude options will only apply to fields views knows about.

Given the additional information, I would probably recommend using the views api to describe your custom tables and their relationships to views in your module. Once you do that, you can use them via the UI. Unfortunately, I'm still not much of a guru on this topic though I have managed to muddle my way through a few times. Check the advanced help for the 'views api' topic for how to do this, and if you have any additional questions I can always take a stab at them.

Alternatively, since the additional fields are added via a query_alter, they should be available in the theming layer with the rest of the data so you can use the method I originally recommended.

robertwb’s picture

Thanks again - you are correct, by "newly added" I meant those added in query_alter.

* I may give a shot at "describing tables and relationships" as you suggest, I just have to figure out how to go about that. It seems that if I can do that after the fact like "views_query_alter", there may be a method to do it earlier in the process.
* The theming idea seems a good one, I just have to screw up my courage to learn another thing -- it has been a pretty enriching week thus far and I am near to maxed out!

Thanks so much for your responses,
/r/b

jaypan’s picture

* I may give a shot at "describing tables and relationships" as you suggest, I just have to figure out how to go about that

At a quick glance, this looks like a good overview on the system, and at least should be a good starting point.

You can actually supply new elements to any part of the views, including the UI, by implementing the proper hooks. You can also override everything. It just takes a lot of googling and Drupaling to figure it out. It's also good to look in the code to see how existing functionality is provided. I'll often take a piece of text that looks like it should be unique or close to it, and search the entire code base for it. I'll then work backwards to see what is happening. This is also a good way to debug, when something is not working in the code.

Contact me to contract me for D7 -> D10/11 migrations.

robertwb’s picture

That page was just what I needed - thanks! Hours of googling on my part not wasted, since I hacked my way through far enough to be able to understand what the link you sent tells me.

Much appreciated

WorldFallz’s picture

holy smokes... that's the best info I've seen yet about describing data to views! I can't believe I never stumbled across it... i've googled this topic an insane amount, lol.

robertwb’s picture

OK, I feel like I pretty much achieved what is to me the holy grail: Use hooks to tell Views about a table that exists in the db, and allow it to display several columns from that table in Views using the UI dialogs. Thanks so much to @WorldFallz and @Jaypan for pointing me in the right directions. I managed to update the node_example code from the 2.0 docs, and would love to contribute that somewhere if anyone has advice on where (I will probably hit IRC in a minute).

Here is some notes on what I did, and the critical block of code that exposed the desired data fields to the Views UI.

  • Objective: Allow views to see and use data from a table developed outside of Drupal, with the fields definable in the UI, and the relationships to node automatically generated by proper Views parameterization.
  • Table Name: 'wsp_sysloc_cache' - a complicated table that is generated from data both within and outside of Drupal
  • Relationship: joins to 'node' - 'nid' via its column named 'wsp_sysloc_cache' - 'system_nid'
  • Columns: proj_use_mgy - the first of many columns that shows the external calculation for projected water use for a given node

Notes

  • Critical Fields - I struggled to get my "$data" addition to be recognized in the UI until I used all of the columns defined in the "node_example" array. My earlier reading implied that 'title' was all that was needed for a field, but that proved to be insufficient (or maybe I had something else screwed up that just created that illusion - either way, including all the params sure can't hurt).
  • Data Module - I first thought that I needed "Data" to tell Views about my table, but have found that the Views query doesn't mind if I join another table that is not added via Data. As long as I tell it about the table in views_data (or possibly views_data_alter) it does not seem to care how the table is added to the system.
  • 'views_data' hook - I use 'views_data' hook to supply the definition for my columns, based on the "node_example" module shown here: http://views-help.doc.logrus.com/help/views/api-example
  • 'node_example' tutorial - The "node_example" tutorial is for Views 2.0, but it only needed the slightest of tweaks to run in 3.0 - a ".info" file and changing the API version to 3.0
    function wsp_devel_views_data() {
       error_log("Adding defintion for 'wsp_sysloc_cache' ");
       $data = array();
       
       $data['wsp_sysloc_cache']['table']['group'] = t('WSP Data');
       //$data['wsp_sysloc_cache']['table']['title'] = t('wsp_sysloc_cache');
       //$data['wsp_sysloc_cache']['table']['help'] = t('wsp_sysloc_cache');
       
       $data['wsp_sysloc_cache']['table']['join']['node'] = array(
         'left_field' => 'nid',
         'field' => 'system_nid'
       );
       $data['wsp_sysloc_cache']['proj_use_mgy'] = array(
          'title' => t('Projected Use MGY'),
          'help' => t('Projected Use in MGY from a generated non-Drupal table.'), // The help that appears on the UI,
          'argument' => array(
             'handler' => 'views_handler_argument_numeric',
          ),
          'field' => array(
             'handler' => 'views_handler_field',
             'click sortable' => TRUE,
          ),
          'filter' => array(
             'handler' => 'views_handler_filter_numeric',
          ),
          'argument' => array(
             'handler' => 'views_handler_argument_numeric',
          ),
          'sort' => array(
             'handler' => 'views_handler_sort',
          )
       );
       return $data;
    }
    
    
  • jaypan’s picture

    holy smokes... that's the best info I've seen yet about describing data to views! I can't believe I never stumbled across it... i've googled this topic an insane amount, lol.

    Nice! To be honest, I only took a real quick glance at it - saw they were using hook_views_data() and had some code, and posted it. Barely even looked! :D

    Contact me to contract me for D7 -> D10/11 migrations.