Drupal | Multiple authors for an article and Views with one to many relationships. Part one

I know this is supposed to be a ham radio blog but I'm going to switch channels to Drupal for a moment.

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.

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.

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 this site to Drupal 7 yet.

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.

The goal is to end up with something like this.

I'm using the word "Writer" instead of "Author" to avoid confusion with the usual node author which the node uid refers to.

The writers are Drupal users. I am using the Entity Reference 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.

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.

  • Setup your data something like I've done.
  • Create some articles. Make sure some have more than one writer.
  • Create a view of Articles showing Fields, not Content.
  • Add a relationship to Users.
  • Add the node title, post date, a trimmed version of the body or whatever you want to show from the article.
  • 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 Picture. I'll mention why in the final discussion at the end of part two.

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:

Article 1 (date, summary etc) - Writer 1
Article 2 (date, summary etc) - Writer 1
Article 2 (date, summary etc) - Writer 2

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:

SELECT node.title, user.name FROM node
LEFT OUTER JOIN authors ON authors.nid = node.nid
LEFT OUTER JOIN users ON users.uid = authors.uid

That will return a row per author.

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 Format: Settings of the View.

The almost works. You can generate something like this:

Article 1 (date, summary etc)
Writer 1
Article 2 (date, summary etc)
Writer 1
Writer 2

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.

Lets try something else. If we base our View on Content rather the Fields and display the Full Content 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 People. 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.

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.

If you search online for anything to do with Drupal views and duplicates you will probably find people suggesting turning on Distinct 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.

That seems like a good place to break. See Part two for my solution.