Drupal https://www.theladderline.com/ en Migrated to Drupal 8 https://www.theladderline.com/node/10777 <span property="schema:name" class="field field--name-title field--type-string field--label-hidden">Migrated to Drupal 8</span> <span rel="schema:author" class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">admin</span></span> <span property="schema:dateCreated" content="2015-09-09T01:22:32+00:00" class="field field--name-created field--type-created field--label-hidden">Tue, 09/08/2015 - 22:22</span> Wed, 09 Sep 2015 01:22:32 +0000 admin 10777 at https://www.theladderline.com Drupal | Multiple authors for an article and Views with one to many relationships. Part two https://www.theladderline.com/drupal-multiple-authors-article-and-views-one-many-relationships-part-two <span class="field field--name-title field--type-string field--label-hidden">Drupal | Multiple authors for an article and Views with one to many relationships. Part two</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p> In <a href="http://www.theladderline.com/drupal-multiple-authors-article-and-views-one-many-relationships-part-one">Part one</a> I described the challenge of creating a view which displays a list of Articles (nodes) which have a multi-value reference field pointing to one or more Writers / Authors (users). We want each article to be listed only once regardless of how many writers it has and we want several fields from the related writers displayed below the article title etc.</p> <p> In more general terms, we want a view with a one to many relationship to display the  parent data one row at a time along with a collection of related child rows. The pager must be related to the parent rows.</p> <p> I solved this problem a couple of years ago with a Drupal 6 site by writing my own module which used SQL queries to directly query the database. I did this on two queries. The first query retrieved the page of articles only. My PHP code then looped through those results collecting the nids (node ids). The second query retrieved the writers for those nodes. The SQL included WHERE n.nid IN(...). Inside the parentheses was a comma separated list of nids. PHP code then associated the returned users with nodes returned in the first query based on the nid. The combined data was outputed with a theme template. I like that way of retrieving the data. It's clean, logical and is easy to optimize with good use of indexes etc. I've used that technique in many non-Drupal and non-PHP applications.</p> <p> Now I am creating a Drupal 7 site with the same requirement. When my attempts with views described in part one failed I started several times down the same path of writing my own module but stopped every time because it just seemed like a bad idea. I really wanted to use Views because it's such an incredible module. If at all possible, I wanted to avoid accessing the database with my own SQL. The Drupal 7 schema is more complex than Drupal 6 so there is a greater possiblity of getting it wrong. Views knows how to access the database and comes with lots of other flexibility.</p> <p> Then it struck me. I could do the same sort of thing as I did with my Drupal 6 module but using Views for the queries. The solution is really quite simple. It requires some code in a hook implementation and a custom Views template.</p> <p> I made two views. Both are primarily based on nodes (i.e., <em>Content</em> on that first wizard page). Within the view, they both <em>Show</em> <em>Fields</em> rather than <em>Content</em>.</p> <p> The first or "parent" view does not have a relationship to the writers. It just returns the fields we want from the Articles. There is no duplication problem because it does not have a one to many relationship with anything. The pager works correctly. This view has a <em>Page</em> display.</p> <p> The other view also starts with nodes but it does have a relationship defined to the users representing the writers. The only field we return from the Articles is nid. We include the first name, last name, image and any other user defined fields we want from the Writer / user. This query may of course return multiple writers with the same nid but that's okay. It has no pager and is not defined for a page or a block so it only has the <em>Master</em> display. The only filter it has that really matters is a contextual filter by nid which allows multiple values. It probably also makes good security sense to also have fixed filters for the usual stuff like content type and published etc.</p> <p> The key to all this is merging the results. That's where we need some code. <a href="http://api.drupal.org/api/views/views.api.php/function/hook_views_post_execute/7">hook_views_post_execute</a> seems to be a good place to merge the results. I put this in my module called views_demo</p> <hr /><p> function views_demo_views_post_execute(&amp;$view) {</p> <p>   // my view is called articles_list<br />   // process it if we're not editing it and the result array is not empty</p> <p>   if ($view-&gt;name == 'articles_list' &amp;&amp; !$view-&gt;editing &amp;&amp; $view-&gt;result) {</p> <p>    // build the array $nids to use as a quick way of looking up the row in<br />    // $view-&gt;result in nid</p> <p>     $nids = array();<br />     $idx = 0;<br />     <br />     foreach($view-&gt;result as $row) {<br />       $nids[$row-&gt;nid] = $idx;<br />       $row-&gt;writers = array();<br />       $idx++;<br />     }</p> <p>    // the other view is called article_writers<br />    // call it with a parameter of a comma separated list of nids</p> <p>     $params = implode(',', array_keys($nids));<br />     $writers = views_get_view_result('article_writers', null, $params);<br />     <br />     foreach($writers as $writer) {<br />       $idx = $nids[$writer-&gt;nid];</p> <p>       // attach the writers to the correct node.<br />       $view-&gt;result[$idx]-&gt;writers[] = $writer;<br />     }<br />   }<br /> }</p> <hr /><p> The data returned by a view is returned in $view-&gt;result which is an array of objects, one for each item or row. <a href="http://api.drupal.org/api/views/views.module/function/views_get_view_result/7">views_get_view_result</a> runs a view and returns the result in an array of objects similar to $view-&gt;result. I guess the other stuff in $view gets added later. In the code above, we add an extra property to each row in $view-&gt;result. That property is an array of zero or more writers.</p> <p> After executing that code an article row in $view-&gt;result which has two writers looks like this.</p> <p> <img alt="" src="/sites/default/files/result1.png" style="width: 521px; height: 579px;" /></p> <p> The writer data that my code has inserted is not going to magically appear on the page. For that we need to customize the view's <em>Row style output</em> template<em>.</em> In my case it means copying views-view-fields.tpl.php from the Views module to views-view-fields--articles-list.tpl.php in my theme. It ended up like this:</p> <p> &lt;?php /* get the writers array */<br /> $writers = $view-&gt;result[$view-&gt;row_index]-&gt;writers;<br /> ?&gt;</p> <p> &lt;div&gt;<br /> &lt;?php print $fields['title']-&gt;content; ?&gt; &lt;?php print $fields['created']-&gt;content; ?&gt;<br /> &lt;/div&gt;<br /> &lt;?php print $fields['body']-&gt;content; ?&gt;<br /> &lt;div class="clearfix"&gt;<br /> &lt;?php foreach($writers as $writer): ?&gt;<br /> &lt;div class="writer"&gt;<br /> &lt;a href="/user/&lt;?php print $writer-&gt;users_field_data_field_writers_uid; ?&gt;"&gt;&lt;?php print render($writer-&gt;field_field_image[0]['rendered']); ?&gt;&lt;/a&gt;<br /> &lt;?php print render($writer-&gt;field_field_first_name[0]['rendered']); ?&gt; &lt;?php print render($writer-&gt;field_field_last_name[0]['rendered']); ?&gt;<br /> &lt;/div&gt;<br /> &lt;?php endforeach; ?&gt;<br /> &lt;/div&gt;</p> <p> The fields from the articles view appear in the $fields collection which is the usual place where the template reads it but the data I inserted from the writers view is still where I put it in the items of $view-&gt;result.</p> <p> The $view variable is available in the row style template. Luckily, the property $view-&gt;row_index tells us which row we are displaying. That allows me to get the writers array in the first line of code. As you can see from the krumo output above, the writer fields contain a render array. That's convenient because code like this outputs it correctly.</p> <p> print render($writer-&gt;field_field_image[0]['rendered']);</p> <h2>  </h2> <h2> Some problems, opportunies and final thoughts</h2> <p> Well, that's about it. I'm quite happy with how it works. I have all the flexibilty of views along with an efficient (IMHO) non-klugy way of getting the data.</p> <p> One potential issue is that the data from the second view is only in a convenient ready to print format for user defined fields (i.e, "CCK" type fields) of the entity. Those fields have a nice render array ready to process with the render function. The standard fields on an object do not provide that. I discovered that when I initially used the standard user <em>Picture</em> for a user. All I get from views_get_view_result() for that field from is the file id. I assume Views does its magic later before the data would normally appear in the $fields array in the row style template. I "fixed" this problem by defining an <em>Image</em> field and using that instead. I think the user <em>Picture</em> is kind of a legacy feature these days anyway. In my production site, I don't really have fields defined in User. I'm using the <a href="http://drupal.org/project/profile2">Profile2</a> module and my writers' images and other data are fields defined in a profile. That means my second view has two relationships, one to the writer and another to the profile but the concept is the same.</p> <p> It would be nice if there was a way to really merge the results seamlessly so that the writer data ends up as a array which is one 'field' in the $fields array in the template. I haven't dug into it further to try to do that or know if it's even possible.</p> <p> It seems like it would be possible to write a module to do all this in a generic way. I can visualize an admin page where you tell it what view is to be used for the "parent" data and then define one or more "child" views and what fields to use for the relationship. I may have a go at it if and when time permits.</p> <p> I just found the <a href="http://drupal.org/project/views_field_view">views_field_view</a> module. I haven't tried it but it looks good and I think it can achieve the same result. The only issue I see is one of efficiency and performance. I assume it runs the second "field view" once for every row of the main view. That's got to be quite a performance hit if the page has lots of items. But... with good caching, especially with anonymous users so the page caching works, maybe that's not such an issue. I'd prefer to get all the child records with one database query.</p> <p> Thanks for any feedback.</p> <p> <a href="http://en.wikipedia.org/wiki/73_%28number%29#In_other_fields">73</a><br /> Ross</p> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/3" typeof="schema:Person" property="schema:name" datatype="">Ross</span></span> <span class="field field--name-created field--type-created field--label-hidden">Sun, 06/03/2012 - 16:58</span> <section class="field field--name-comment field--type-comment field--label-hidden comment-wrapper"> </section> <div class="field field--name-tags1 field--type-entity-reference field--label-above"> <div class="field__label">Tags</div> <div class="field__items"> <div class="field__item"><a href="/taxonomy/term/7" hreflang="en">Drupal</a></div> </div> </div> Sun, 03 Jun 2012 19:58:05 +0000 Ross 10775 at https://www.theladderline.com Drupal | Multiple authors for an article and Views with one to many relationships. Part one https://www.theladderline.com/drupal-multiple-authors-article-and-views-one-many-relationships-part-one <span class="field field--name-title field--type-string field--label-hidden">Drupal | Multiple authors for an article and Views with one to many relationships. Part one</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p> I know this is supposed to be a ham radio blog but I'm going to switch channels to Drupal for a moment.</p> <p> I'd like to share my solution to a problem which I've encountered several times. Specifically, it is how to have more than one author for an article and display a list of articles using Views which shows the authors for each article in a natural way. More generally its a technique to merge the output of two views into one where there is a one to many or parent to child type relationship.</p> <p> This is not intended as to be exact "how to" instructions but more of a general guide and me just throwing some ideas around. You'll need to know a bit about Views and some code for my solution to make sense.</p> <p> Please let me know if I'm missing an easier way of doing this. All this relates to Drupal 7. I just haven't got around to upgrading <em>this</em> site to Drupal 7 yet.</p> <p> I won't be surprised if someone lets me know "there's a module for that". I have found one module which I think would do the same thing although perhaps not as efficiently. I mention this at the end.</p> <p> The goal is to end up with something like this.</p> <p> <img alt="" src="/sites/default/files/ross/201206/shot1.png" style="width: 665px; height: 399px;" /></p> <p> I'm using the word "Writer" instead of "Author" to avoid confusion with the usual node author which the node uid refers to.</p> <p> The writers are Drupal users. I am using the <a href="http://drupal.org/project/entityreference">Entity Reference</a> module to create a field in my Article content type which is a reference to users and allows multiple values. I have added  fields for First Name, Last Name and an Image to the User (People / Account Settings Manage Fields). More about the image later.</p> <p> Creating that listing might look simple enough, given the flexibility Views module but it's more difficult than it looks. Several methods come frustratingly close.</p> <ul><li> Setup your data something like I've done.</li> <li> Create some articles. Make sure some have more than one writer.</li> <li> Create a view of Articles showing Fields, not Content.</li> <li> Add a relationship to Users.</li> <li> Add the node title, post date, a trimmed version of the body or whatever you want to show from the article.</li> <li> Add the First Name, Last Name and Image fields from the related user. I'm using a user defined Image field rather than the built-in <em>Picture</em>. I'll mention why in the final discussion at the end of part two.</li> </ul><p> Now run the view. There is an immediate problem. The node information is repeated for each writer of that article. It will show something like:</p> <p> Article 1 (date, summary etc) - Writer 1<br /> Article 2 (date, summary etc) - Writer 1<br /> Article 2 (date, summary etc) - Writer 2</p> <p> The article 2 info is duplicated. We don't want that. We only want Article 2 to show once with writer 1 and writer 2 somehow connected.  It's not a surprise that this happens when we consider the SQL. I'm simplifying it but the SQL is something like this:</p> <p> SELECT node.title, user.name FROM node<br /> LEFT OUTER JOIN authors ON authors.nid = node.nid<br /> LEFT OUTER JOIN users ON users.uid = authors.uid</p> <p> That will return a row per author.</p> <p> Let's try to fix it.  It's looks like a grouping problem so lets Group on the node fields. This can be done in the <em>Format: Settings</em> of the View.</p> <p> The almost works. You can generate something like this:</p> <p> Article 1 (date, summary etc)<br /> Writer 1<br /> Article 2 (date, summary etc)<br /> Writer 1<br /> Writer 2</p> <p> That looks promising and is pretty much what we want. It just needs some custom templating and styling. I'm not sure how difficult that would be because I haven't played with the Views group templates. Unfortunately, there is a problem. If the view uses a pager then the pager doesn't work as expected. The data returned by the view has one row per writer, not per node. Grouping doesn't change that. The pager just counts rows. This means that an article can be split between pages. In my example above with a page size of 2, Article 2 will appear on the first page with writer 1 and the second page with writer 2. That might be okay in some situations but it wouldn't satisfy my use case. Whenever article 2 is shown, it should have both writers attached. This method also feels a little kludgy to me. Unless I'm missing something, you need to group on every field that the view shows from the node. I assume the grouping works in the code by comparing values from the previous row to see if it's changed. One of my displayed fields is a trimmed body which is a big blob of text. I probably shouldn't worry about it but it just feels wrong to be comparing all these fields when we know that nodes are uniquely defined by node id.</p> <p> Lets try something else. If we base our View on <em>Content</em> rather than <em>Fields</em> and display the <em>Full Content</em> view mode then we do indeed see the multiple writers associated with each article. Unfortunately we have another problem. We only see the user names of the writers. We don't see the name and image fields that we added to <em>People</em>. We don't want the full body and other node fields either. I think it's quite easy with a hook and a few lines of code to create other view modes and we can create custom node templates so some of this might be relatively easily solvable but I don't know how to get fields from related entities. If we define a relationship in our view then that only causes nodes with multiple related records to be duplicated. You can't get at those related entities. I'm sure there are ways to do that with another module or something but... that just feels like it's getting too complicated.</p> <p> Another solution I comtemplated was to simply denormalize the data. The vast majority of our articles have one writer. A few have two and very few have three. I could have easily created fields for writer-1, writer-2, writer-3 etc. This can seem quite tempting but it's usually a bad idea. How many fields should I create? I don't think we've ever had more than three writers for an article but it could just happen. The other problem with doing this is that it would complicate creating a list of articles authored by a specific writer because the filter would need to be something like node.writer1_id = writer_id OR node.writer2_id = writer_id OR node.writer3_id = writer_id etc. Perhaps if we had an enforced limit of two or just maybe three writers then this might be an option but otherwise it feels like a path to pain and regret.</p> <p> If you search online for anything to do with Drupal views and duplicates you will probably find people suggesting turning on <em>Distinct </em>in the Query settings. This doesn't help us at all. It's basically the same as grouping with every field grouped. I think Distinct is useful in situations where you have a one to many relationship but the related data is only used for filtering rather than outputted.</p> <p> That seems like a good place to break. See <a href="http://www.theladderline.com/drupal-multiple-authors-article-and-views-one-many-relationships-part-two">Part two</a> for my solution.</p> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/3" typeof="schema:Person" property="schema:name" datatype="">Ross</span></span> <span class="field field--name-created field--type-created field--label-hidden">Sun, 06/03/2012 - 12:33</span> <section class="field field--name-comment field--type-comment field--label-hidden comment-wrapper"> </section> <div class="field field--name-tags1 field--type-entity-reference field--label-above"> <div class="field__label">Tags</div> <div class="field__items"> <div class="field__item"><a href="/taxonomy/term/7" hreflang="en">Drupal</a></div> </div> </div> Sun, 03 Jun 2012 15:33:16 +0000 Ross 10774 at https://www.theladderline.com