Skip to content

Commit

Permalink
Blog index with tags rebase (#3464)
Browse files Browse the repository at this point in the history
* Add tag "filtering" through dedicated URLs
  • Loading branch information
Pomax authored Aug 19, 2019
1 parent 3e659f0 commit e7f2574
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 26 deletions.
20 changes: 19 additions & 1 deletion cypress/integration/endpoint-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,25 @@ describe(`Visual regression testing for foundation.mozilla.org`, () => {
cy.percySnapshot();
});

it(`Blog page`, function() {
it(`Blog index page`, function() {
cy.visit(`/en/blog`);
cy.window()
.its(`main-js:react:finished`)
.should(`equal`, true);
cy.wait(500);
cy.percySnapshot();
});

it(`Blog index filtered on tag`, function() {
cy.visit(`/en/blog/tags/iot`);
cy.window()
.its(`main-js:react:finished`)
.should(`equal`, true);
cy.wait(500);
cy.percySnapshot();
});

it(`Fixed blog page`, function() {
cy.visit(`/en/blog/initial-test-blog-post-with-fixed-title`);
cy.window()
.its(`main-js:react:finished`)
Expand Down
12 changes: 7 additions & 5 deletions network-api/networkapi/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
'wagtail.core',
'wagtail.contrib.forms',
'wagtail.contrib.redirects',
'wagtail.contrib.routable_page',
'wagtail.contrib.styleguide' if DEBUG else None,
'wagtail.contrib.modeladmin',
'experiments',
Expand Down Expand Up @@ -267,15 +268,16 @@
'networkapi.context_processor.cloudinary',
])),
'libraries': {
'bg_nav_tags': 'networkapi.buyersguide.templatetags.bg_nav_tags',
'card_tags': 'networkapi.wagtailpages.templatetags.card_tags',
'class_tags': 'networkapi.wagtailpages.templatetags.class_tags',
'homepage_tags': 'networkapi.wagtailpages.templatetags.homepage_tags',
'localization': 'networkapi.wagtailpages.templatetags.localization',
'settings_value': 'networkapi.utility.templatetags.settings_value',
'mini_site_tags': 'networkapi.wagtailpages.templatetags.mini_site_tags',
'homepage_tags': 'networkapi.wagtailpages.templatetags.homepage_tags',
'card_tags': 'networkapi.wagtailpages.templatetags.card_tags',
'primary_page_tags': 'networkapi.wagtailpages.templatetags.primary_page_tags',
'multi_image_tags': 'networkapi.wagtailpages.templatetags.multi_image_tags',
'nav_tags': 'networkapi.utility.templatetags.nav_tags',
'bg_nav_tags': 'networkapi.buyersguide.templatetags.bg_nav_tags',
'primary_page_tags': 'networkapi.wagtailpages.templatetags.primary_page_tags',
'settings_value': 'networkapi.utility.templatetags.settings_value',
'wagtailcustom_tags': 'networkapi.wagtailcustomization.templatetags.wagtailcustom_tags',
}
},
Expand Down
3 changes: 2 additions & 1 deletion network-api/networkapi/wagtailpages/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _

from wagtail.admin.menu import MenuItem
from wagtail.core import hooks
from django.urls import reverse


class HowToWagtailMenuItem(MenuItem):
Expand Down
79 changes: 69 additions & 10 deletions network-api/networkapi/wagtailpages/models.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import json
import re

from django.db import models
from django.conf import settings
from django.http import HttpResponseRedirect
from taggit.models import Tag

from . import customblocks

from wagtail.admin.edit_handlers import FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel, StreamFieldPanel
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
from wagtail.core import blocks
from wagtail.core.models import Page
from wagtail.core.models import Page, Orderable as WagtailOrderable
from wagtail.core.fields import StreamField, RichTextField
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel, MultiFieldPanel, FieldRowPanel
from wagtail.snippets.edit_handlers import SnippetChooserPanel
from wagtail.snippets.models import register_snippet
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtail.core.models import Orderable as WagtailOrderable
from wagtail.images.models import Image
from wagtail.snippets.edit_handlers import SnippetChooserPanel
from wagtail.snippets.models import register_snippet

from wagtailmetadata.models import MetadataPageMixin

from taggit.models import Tag, TaggedItemBase
from modelcluster.fields import ParentalKey
from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase
from wagtail.admin.edit_handlers import InlinePanel
from wagtailmetadata.models import MetadataPageMixin

from .utils import (
set_main_site_nav_information,
Expand Down Expand Up @@ -532,7 +534,7 @@ def get_context(self, request):
return get_page_tree_information(self, context)


class IndexPage(FoundationMetadataPageMixin, Page):
class IndexPage(FoundationMetadataPageMixin, RoutablePageMixin, Page):
"""
This is a page type for creating "index" pages that
can show cards for all their child content.
Expand All @@ -556,13 +558,70 @@ class IndexPage(FoundationMetadataPageMixin, Page):
FieldPanel('intro'),
]

def filter_entries_for_tag(self, context):
"""
Realise the 'entries' queryset and filter it for tags presences.
We need to perform this realisation because there is no guarantee
that all children for this IndexPage in fact have a `tags` field,
so in order to test this each entry needs to be "cast" into its
specific model before we can test for whether i) there are tags
to work with and then ii) those tags match the specified ones.
"""
type = self.filtered.get('type')
context['filtered'] = type

if type == 'tag':
terms = self.filtered.get('terms')
context['terms'] = terms

entries = [
entry
for
entry in context['entries'].specific()
if
hasattr(entry, 'tags')
and not
# Determine whether there is any overlap between 'all tags' and
# the tags specified. This effects ANY matching (rather than ALL).
set([tag.slug for tag in entry.tags.all()]).isdisjoint(terms)
]

context['entries'] = entries

def get_context(self, request):
"""
Bootstrap this page in similar fashion to the PrimaryPage,
but include an `entries` context variable that represents
all public children under this page.
Additionally, if this is a fall-through render due to a
tag filtering subroute call, perform that filtering of
all entries.
"""
context = super().get_context(request)
context = set_main_site_nav_information(self, context, 'Homepage')
context = get_page_tree_information(self, context)
context['entries'] = self.get_children().live().order_by('-first_published_at')
if hasattr(self, 'filtered'):
self.filter_entries_for_tag(context)
return context

@route(r'^tags/(?P<tag>.+)$')
def entries_by_tag(self, request, tag, *args, **kwargs):
"""
If this page was called with `/tags/...` as suffix, extract
the tags to filter prior to rendering this page. Multiple
tags are specified as subpath: `/tags/tag1/tag2/...`
"""
terms = list(filter(None, re.split('/', tag)))

self.filtered = {
'type': 'tag',
'terms': terms
}

return IndexPage.serve(self, request, *args, **kwargs)


class NewsPage(PrimaryPage):
parent_page_types = ['Homepage']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
{% load wagtailcore_tags mini_site_tags %}

{% block bodyID %}blog{% endblock %}

{% block subcontent %}
<div class="offset-lg-1 col-lg-1 py-4 py-md-5 text-center">
<div class="blog-sticky-side hidden-md-down"">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,42 @@
{% extends "./generic-card.html" %}

{% load wagtailcore_tags %}
{% load wagtailcore_tags wagtailroutablepage_tags class_tags %}

{% block card_type %}blog-card{% endblock %}

The following block needs to know where the blog IndexPage is,
so it checks whether "we" are the index page, and if not, it
crawls up the ancestor tree until it has found it.

(Without it, we cannot generate the links to /blob/tag/... for
each blog page's dominant tag)

{% block tags %}
{% if filtered %}
<ul class="tag-list mt-3">
{% for tag in page.specific.tags.all %}
<li class="tag">
<a href="{% routablepageurl root.specific "entries_by_tag" tag.slug %}">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
</li>
{% endfor %}
</ul>
{% else %}
{# Show the tag's own "main" tag (i.e. first tag in its tag list) as each blog card's visible tag #}

{% block tags-DISABLED-FOR-NOW %}
<div>
{% with tag=page.specific.tags.first %}
<a class="main-tag" href="#">{{ tag }}</a>
{% if tag %}
{% if root %}
{# If we have a "root" context variable, we know this card is generated on an index page (or index page subroute) #}
<a class="main-tag d-block mt-3" href="{% routablepageurl root.specific "entries_by_tag" tag.slug %}">{{ tag }}</a>
{% else %}
{# If we do not have a "root" context variable, this card is being generated outside of an index page, and so needs to use its parent as root #}
<a class="main-tag d-block mt-3" href="{% routablepageurl page.get_parent.specific "entries_by_tag" tag.slug %}">{{ tag }}</a>
{% endif %}
{% endif %}
{% endwith %}
</div>
{% endif %}
{% endblock %}

{% block byline %}
By {{ page.specific.author }} on {{ page.first_published_at|date:"F j, Y" }}
{% endblock%}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% load wagtailcore_tags wagtailimages_tags %}

<div class="child-card col-6 col-md-4 mb-5">
<div>
<div class="entry-card {% block card_type %}{% endblock %} col-6 col-md-4 mb-5">
<div class="card-image">
<a href="{{ page.url }}">
{% image page.specific.get_meta_image fill-400x225 %}
</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,33 @@ <h1 class="h1-heading mb-0 mt-1 pt-2">
</div>
</div>

<div class="row mb-4">
{% if filtered %} {# Start of IF for filtered page content. Note that this injects an entire row. #}
<div class="col-12 d-flex justify-content-between">
<div class="result-count">
<h3 class="body result-info">
{% with count=entries|length %}
{{ count }} result{% if count != 1 %}s{% endif %} for
{% endwith %}
<ul class="term-list">
{% for term in terms %}
<li class="term body-small">{{ term }}</li>
{% endfor %}
</ul>
</h3>
</div>


<div class="clear-link">
<a href="{{ root.url }}">clear filters</a>
</div>
</div>
</div>

<div class="row">
{% block subcontent %}
{% endif %} {# End of IF for filtered page content _after_ starting a new row. #}

{% block subcontent %}
{% for entry in entries %}
{% with type=entry.specific_class.get_verbose_name|lower %}
{% if type == "blog page" %}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django import template

register = template.Library()


@register.filter
def to_class_name(value):
return value.__class__.__name__
1 change: 1 addition & 0 deletions source/sass/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ $bp-xl: #{map-get($grid-breakpoints, xl)}; // >= 1200px
@import "./views/projects";
@import "./views/initiatives";
@import "./views/participate";
@import "./views/indexpage";
@import "./views/blog";
@import "./views/style-guide";

Expand Down
18 changes: 18 additions & 0 deletions source/sass/views/blog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,21 @@
}
}
}

.blog-card {
.main-tag {
text-transform: capitalize;
}

.tag-list {
display: inline-block;
list-style: none;
padding: 0;
margin: 0;

.tag {
display: inline-block;
text-transform: capitalize;
}
}
}
16 changes: 16 additions & 0 deletions source/sass/views/indexpage.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.term-list {
display: inline-block;
list-style: none;
padding: 0;
margin: 0;

.term {
display: inline-block;
padding: 0 0.5em;
margin-left: 0.25em;
border: 1px solid $black;
border-radius: 10px;
color: $black;
text-transform: capitalize;
}
}

0 comments on commit e7f2574

Please sign in to comment.