Related ticket: #3086
We want to create dynamic related content cards that will be built into the template of blog and campaign pages. Eventually, this functionality may be incorporated onto other templates on the foundation site.
The related content cards will be added by a user defining the tags they want to pull from and related cards themselves will pull image and heading information from OG tags.





First heading underlines when a user hovers over any part of the card:

cc: @alanmoo to suggest algorithms for how the tagging will work @kristinashu please add anything else I might have missed!
Blog pages already have a field for tags to be attached to them. We'll be adding that to more templates soon. The way I see this working is the "related content" component can be embedded on any page, and it will look at the tags on the current page, finding other recent pages that have one of those tags.
I'm thinking something like this, but open to alternative approaches that yield diverse results:
foo,bar,bazbar)foo,baz)It looks like if we want to do this for more than just BlogPage objects, we need to put the tags property on the highest possible page. There is no clean way to query for "any other Page that has the tags field`, only for whether that field has content assigned or not.
After some investigation, and barring help from stackoverflow, I thing we have some thinking to do and a choice to make on how to proceed...
We'd like to add "related pages" to any page on the foundation site that has tags, to point to other pages with one or more of the same tags. To allow for this, we've added tags to the Blog pages for now, with the intention to add tags to other pages at some later date.
However, this causes a problem: because Blog pages have been implemented as a subclass of wagtail's Page, we can _only_ find related blog pages right now. Filtering relies on fields that have a corresponding column in the database, and so while we can do this:
# find all related blog pages based on shared tags.
related_pages = BlogPage.all.filter( filter based on blogpage.tags )
We can't do this:
# find all related _wagtail_ pages based on shared tags, which
# right now is just blog pages, but will become more page types later.
related_pages = Page.all.filter( filter based on page.tags )
Because the wagtail Page model has zero knowledge of, or code relating to, tags.
Ideally, we could add tags to the Page model, and thus make sure that every single page on the foundation site at least knows what tags are, even if they don't make use of them. However, as the Page class is provided by the CMS code, we have way to modify it.
So this leaves a few options:
class BlogPage(Page):
...
Ends up looking like this:
class Taggable(models.model):
tags = the definition that sets up tags
class BlogPage(Taggedable, Page):
...
This will require a migration that makes sure that the current "For blogpages only" tag system is copied over to the "for any page type" tag system, and some delicate testing to make sure this _works_ rather than blows up in our face once it gets promoted from staging to production.
This might also not work as mixin, incidentally, in which case we want to slide "our own" page type in between Page and everything that currently inherites from Page, so that we _do_ have a class that we can set some universal page properties on. I expect the mixin will work fine, since that's what we're using for universal SEO tweaks, but... you never know.
This one sucks, imma move on to option 3 now =)
This works, but it's a really bad idea, and I can't recommend this =)
In the event we don't create a parent (mixin) class, could we run a few queries (for each page type) and then mix them together in Python without too much overhead?
For example:
relatedBlogPages = BlogPage.all.filter()
relatedCampaignPages = CampaignPage.all.filter()
# Mix the best 3 together and return them
A problem with "mixing" them together is that for the most part, querysets are "lazy" data: nothing gets transferred and the database response is treated as a promise that there will be data once code actually tries to access it. So doing all the filtering and mixing using queryset commands means python doesn't need the actual data, it just offloads the responsibility for doing the right thing to the (much faster at filtering) database.
The moment we do any "mixing" in Python rather than as a filter action, all the data needs to become real data and you incur a performance hit (both in terms of waiting for the data to be realised from the db, and the python processing of that data) so you can quickly end up in a situation similar to the one we had on pulse, where the list of "created by this user" caused multi-second-page-waits because it loaded far more data upfront than it ultimately needed to form a response.
Currently only Blog pages have tags, so I won't be able to implement anything related to campaigns... however, that said, the blog page comp shows the "related content" as living outside of the "page content", but the tags living inside the "page content":

so I'll implement the actual template logic as two separate blocks, one for the "tags for this page", and another for the "related content" block, so that end up inside the right content divisions.
As immediate followup: given the styling of the tags in the comps, it looks like the tags are supposed to take the user to somewhere, but as far as I'm aware we do not have any pages that list "all pages for a specific tag", so these may not be active links until work has gone into making that real thing. Pinging @alanmoo to confirm
Great point, that makes sense. @kristinashu What will those eventually link to? I'm imagining it's another use case for the generic "container" page like the blog homepage would likely be?
In terms of the related content cards, where is the image to be found? Not all posts may have an associated image, but even for those that do, we can't easily pull it from the content itself so it'd have to come from an SEO field or the like. What should be the lookup policy here?
Clicking a tag would like to a page like this https://redpen.io/zx02a9037c2fd9090d

We would also eventually have some sort of /blog landing page https://redpen.io/kv41677e2ce9eee53d

In terms of the related content cards, where is the image to be found? Not all posts may have an associated image, but even for those that do, we can't easily pull it from the content itself so it'd have to come from an SEO field or the like. What should be the lookup policy here?
We were thinking the image would be pulled from the OG content in the promote tab https://github.com/mozilla/foundation.mozilla.org/issues/3156 (if there's no image there then it would take the parent page OG image of the mozilla logo).
SEO image is by far the easiest, since we have a cascading mixin in place, so that's a matter of asking for page.get_meta_image.
For the tag-dedicated page, do we have an issue opened for implementing that already?
Actually... @alanmoo @kristinashu it looks like blog posts don't _have_ an SEO image field?

So that might be an oversight when we implemented the blog post, but while the code has been set up to tap into the SEO image, the result is effectively nothing meaningful right now =)
For the tag-dedicated page, do we have an issue opened for implementing that already?
Only a design ticket right now https://github.com/mozilla/foundation.mozilla.org/issues/3165. Is making that page blocking this work? If so I can open an implementation ticket.
So that might be an oversight when we implemented the blog post, but while the code has been set up to tap into the SEO image, the result is effectively nothing meaningful right now =)
Mavis is working on it https://github.com/mozilla/foundation.mozilla.org/pull/3292
only insofar as we can't land the template with post tags "as links" until those tag pages exist - we can make the tags "not be links" in the mean time, and we'll still get related posts to show up just fine.
Oooh I think I get it now... please ignore these tags (that will be a separate ticket):

This ticket is just for the related content at the bottom

Aha, gotcha, can do!
An interesting problem: what do we do if there is only 1, or if there are only 2, related posts? CSS won't really let us change the flex alignment based on "number of children", which would have been nice here (left-align if 1 or 2, full width auto spaced for 3...)
as I say that, we _can_ make wagtail add a class that indicates the number of posts that we can tap into on the CSS side for choosing which flex justification/alignment to us, so I could probably make it do that "left align unless there are three" at full width, and "left align unless there are two or more" at intermediate width (mobile only has one element per row, so no need for custom rules there)
I feel like this is exactly the kind of thing Flexbox can handle...maybe paired with a max-width inside a media query on the individual cards?
this is a limitation of CSS, not just flexbox: you can't "count children" in CSS, so there is no way for the flex CSS to know whether it needs to use flex-start or space-between without a helper class.
I believe the blog is done but looks pretty broken without https://github.com/mozilla/foundation.mozilla.org/issues/3156 and https://github.com/mozilla/foundation.mozilla.org/issues/3349
Closing this ticket and opened a new separate ticket for campaign pages https://github.com/mozilla/foundation.mozilla.org/issues/3362