diff --git a/bedrock/settings/base.py b/bedrock/settings/base.py index 68ab337c73..6e88bb1290 100644 --- a/bedrock/settings/base.py +++ b/bedrock/settings/base.py @@ -430,6 +430,7 @@ INSTALLED_APPS = ( 'bedrock.thunderbird', 'bedrock.shapeoftheweb', 'bedrock.utils', + 'bedrock.wordpress', # last so that redirects here will be last 'bedrock.redirects', @@ -543,7 +544,18 @@ BLOG_FEEDS = { }, 'internetcitizen': { 'url': 'https://blog.mozilla.org/internetcitizen/', - 'name': 'Internet Citizen' + 'name': 'Internet Citizen', + }, +} +# same as above, but uses the JSON REST API and thus gets +# more data (e.g. tags and featured image urls). +# TODO: Once this rolls out for the Firefox blog, we should +# convert the others and remove the old XML feed blogs above. +WP_BLOGS = { + 'firefox': { + 'url': 'https://blog.mozilla.org/firefox/', + 'name': 'The Firefox Frontier', + 'num_posts': 20, }, } diff --git a/bedrock/wordpress/__init__.py b/bedrock/wordpress/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bedrock/wordpress/api.py b/bedrock/wordpress/api.py new file mode 100644 index 0000000000..c2b07cb8dd --- /dev/null +++ b/bedrock/wordpress/api.py @@ -0,0 +1,67 @@ +from __future__ import print_function, unicode_literals +from django.conf import settings + +import requests +from raven.contrib.django.raven_compat.models import client as sentry_client + + +def _request(api_url, limit=None, page=1): + # 100 is max per page from WP + per_page = limit or 100 + resp = requests.get(api_url, params={'per_page': per_page, 'page': page}, timeout=5) + resp.raise_for_status() + data = resp.json() + if limit is None and page == 1: + num_pages = int(resp.headers.get('x-wp-totalpages', 1)) + if num_pages > 1: + for i in range(2, num_pages + 1): + data.extend(_request(api_url, page=i)) + + return data + + +def _api_url(feed_url, data_type, data_id): + api_url = '{}/wp-json/wp/v2/{}/'.format(feed_url.rstrip('/'), data_type) + if data_id: + api_url += str(data_id) + return api_url + + +def get_wp_data(feed_id, data_type, data_id=None, limit=None): + try: + feed_config = settings.WP_BLOGS[feed_id] + if data_type == 'posts' and limit is None: + limit = feed_config.get('num_posts', 20) + api_url = _api_url(feed_config['url'], data_type, data_id) + if data_id: + data = _request(api_url, limit=1) + else: + data = _request(api_url, limit=limit) + + return data + except Exception: + sentry_client.captureException() + return None + + +def get_posts_data(feed_id, num_posts=None): + posts = get_wp_data(feed_id, 'posts', limit=num_posts) + tags = get_wp_data(feed_id, 'tags') + if not (posts and tags): + return None + + tags = {t['id']: t['slug'] for t in tags} + for post in posts: + post['tags'] = [tags[t] for t in post['tags']] + # some blogs set featured_media to 0 when none is set + if 'featured_media' in post and post['featured_media']: + media = get_wp_data(feed_id, 'media', post['featured_media']) + if media: + post['featured_media'] = media + else: + post['featured_media'] = {} + + return { + 'posts': posts, + 'wp_blog_slug': feed_id, + } diff --git a/bedrock/wordpress/management/__init__.py b/bedrock/wordpress/management/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bedrock/wordpress/management/commands/__init__.py b/bedrock/wordpress/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bedrock/wordpress/management/commands/update_wordpress.py b/bedrock/wordpress/management/commands/update_wordpress.py new file mode 100644 index 0000000000..8fb0f023c3 --- /dev/null +++ b/bedrock/wordpress/management/commands/update_wordpress.py @@ -0,0 +1,28 @@ +from __future__ import print_function + +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError + +from bedrock.wordpress.models import BlogPost + + +class Command(BaseCommand): + def add_arguments(self, parser): + parser.add_argument('-q', '--quiet', action='store_true', dest='quiet', default=False, + help='If no error occurs, swallow all output.'), + parser.add_argument('--database', default='default', + help=('Specifies the database to use. ' + 'Defaults to "default".')), + + def handle(self, *args, **options): + errors = [] + for feed_id in settings.WP_BLOGS: + updated = BlogPost.objects.refresh(feed_id, options['database']) + if updated and not options['quiet']: + print('Refreshed %s posts from the %s blog' % (updated, feed_id)) + + if not updated: + errors.append('Something has gone wrong with refreshing the %s blog' % feed_id) + + if errors: + raise CommandError('\n'.join(errors)) diff --git a/bedrock/wordpress/migrations/0001_initial.py b/bedrock/wordpress/migrations/0001_initial.py new file mode 100644 index 0000000000..80c383d749 --- /dev/null +++ b/bedrock/wordpress/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django_extensions.db.fields.json + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='BlogPost', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('wp_id', models.IntegerField()), + ('wp_blog_slug', models.CharField(max_length=50)), + ('date', models.DateTimeField()), + ('modified', models.DateTimeField()), + ('title', models.CharField(max_length=255)), + ('excerpt', models.TextField()), + ('link', models.URLField()), + ('featured_media', django_extensions.db.fields.json.JSONField()), + ('tags', django_extensions.db.fields.json.JSONField()), + ], + options={ + 'ordering': ['-date'], + 'get_latest_by': 'date', + }, + ), + ] diff --git a/bedrock/wordpress/migrations/__init__.py b/bedrock/wordpress/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bedrock/wordpress/models.py b/bedrock/wordpress/models.py new file mode 100644 index 0000000000..cf18194298 --- /dev/null +++ b/bedrock/wordpress/models.py @@ -0,0 +1,118 @@ +from __future__ import print_function, unicode_literals + +import operator +from django.conf import settings +from django.db import models, transaction +from django.db.models import Q +from django.db.utils import DatabaseError +from django.utils.html import strip_tags + +import bleach +from django_extensions.db.fields.json import JSONField +from jinja2 import Markup +from raven.contrib.django.raven_compat.models import client as sentry_client + +from bedrock.wordpress.api import get_posts_data + + +class BlogPostQuerySet(models.QuerySet): + def filter_by_blog(self, blog_slug): + return self.filter(wp_blog_slug=blog_slug) + + def filter_by_tags(self, *tags): + tag_qs = [Q(tags__contains='"{}"'.format(t)) for t in tags] + return self.filter(reduce(operator.or_, tag_qs)) + + +class BlogPostManager(models.Manager): + def get_queryset(self): + return BlogPostQuerySet(self.model, using=self._db) + + def filter_by_blog(self, blog_slug): + return self.get_queryset().filter_by_blog(blog_slug) + + def filter_by_tags(self, *tags): + return self.get_queryset().filter_by_tags(*tags) + + def update_posts(self, data, database='default'): + with transaction.atomic(using=database): + count = 0 + posts = data['posts'] + self.filter_by_blog(data['wp_blog_slug']).delete() + for post in posts: + try: + self.create( + wp_id=post['id'], + wp_blog_slug=data['wp_blog_slug'], + date=post['date_gmt'], + modified=post['modified_gmt'], + title=bleach.clean(post['title']['rendered']), + excerpt=bleach.clean(post['excerpt']['rendered']), + link=post['link'], + featured_media=post['featured_media'], + tags=post['tags'], + ) + count += 1 + except (DatabaseError, KeyError): + sentry_client.captureException() + raise + + return count + + def refresh(self, feed_id, database='default', num_posts=None): + data = get_posts_data(feed_id, num_posts) + if data: + return self.update_posts(data, database) + + # no data returned. something wrong. + return 0 + + +class BlogPost(models.Model): + wp_id = models.IntegerField() + wp_blog_slug = models.CharField(max_length=50) + date = models.DateTimeField() + modified = models.DateTimeField() + title = models.CharField(max_length=255) + excerpt = models.TextField() + link = models.URLField() + featured_media = JSONField() + tags = JSONField() + + objects = BlogPostManager() + + class Meta: + get_latest_by = 'date' + ordering = ['-date'] + + def __unicode__(self): + return '%s: %s' % (self.blog_name, self.title) + + def get_absolute_url(self): + return self.link + + def htmlify(self): + summary = strip_tags(self.excerpt).strip() + if summary.lower().endswith('continue reading'): + summary = summary[:-16] + + return Markup(summary) + + @property + def blog_title(self): + title = strip_tags(self.title).strip() + return Markup(title).unescape() + + @property + def blog_link(self): + return settings.WP_BLOGS[self.wp_blog_slug]['url'] + + @property + def blog_name(self): + return settings.WP_BLOGS[self.wp_blog_slug]['name'] + + def get_featured_image_url(self, size='large'): + try: + return self.featured_media['media_details']['sizes'][size]['source_url'] + except KeyError: + return None diff --git a/bedrock/wordpress/tests/__init__.py b/bedrock/wordpress/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bedrock/wordpress/tests/test_api.py b/bedrock/wordpress/tests/test_api.py new file mode 100644 index 0000000000..fab98a308d --- /dev/null +++ b/bedrock/wordpress/tests/test_api.py @@ -0,0 +1,72 @@ +from django.test import override_settings + +from bedrock.wordpress import api + +import responses +from mock import patch + + +TEST_WP_BLOGS = { + 'firefox': { + 'url': 'https://blog.mozilla.org/firefox/', + 'name': 'The Firefox Frontier', + 'num_posts': 10, + }, +} + + +@patch.object(api, 'requests') +def test_limited_request(req_mock): + api._request('some_url', limit=10) + req_mock.get.assert_called_once_with('some_url', + params={'per_page': 10, 'page': 1}, + timeout=5) + + +@responses.activate +def test_unlimited_request(): + api_url = api._api_url(TEST_WP_BLOGS['firefox']['url'], 'tags', None) + responses.add(responses.GET, + api_url + '?per_page=100&page=1', + match_querystring=True, + json=[1], + adding_headers={'X-WP-TotalPages': '3'}) + responses.add(responses.GET, + api_url + '?per_page=100&page=2', + json=[2, 2], + match_querystring=True, + adding_headers={'X-WP-TotalPages': '3'}) + responses.add(responses.GET, + api_url + '?per_page=100&page=3', + json=[3, 3, 3], + match_querystring=True, + adding_headers={'X-WP-TotalPages': '3'}) + + data = api._request(api_url, limit=None) + assert data == [1, 2, 2, 3, 3, 3] + assert len(responses.calls) == 3 + + +def test_api_url(): + assert (api._api_url('https://moz.blog/', 'posts', 4) == + 'https://moz.blog/wp-json/wp/v2/posts/4') + assert (api._api_url('https://moz.blog/', 'tags', None) == + 'https://moz.blog/wp-json/wp/v2/tags/') + assert (api._api_url('https://moz.blog', 'media', 55) == + 'https://moz.blog/wp-json/wp/v2/media/55') + + +@override_settings(WP_BLOGS=TEST_WP_BLOGS) +@patch.object(api, '_request') +def test_get_wp_data(req_mock): + api.get_wp_data('firefox', 'posts') + api_url = api._api_url(TEST_WP_BLOGS['firefox']['url'], 'posts', None) + req_mock.assert_called_with(api_url, limit=10) + + api.get_wp_data('firefox', 'media', 75) + api_url = api._api_url(TEST_WP_BLOGS['firefox']['url'], 'media', 75) + req_mock.assert_called_with(api_url, limit=1) + + api.get_wp_data('firefox', 'tags') + api_url = api._api_url(TEST_WP_BLOGS['firefox']['url'], 'tags', None) + req_mock.assert_called_with(api_url, limit=None) diff --git a/bedrock/wordpress/tests/test_data/media_75.json b/bedrock/wordpress/tests/test_data/media_75.json new file mode 100644 index 0000000000..c02156698d --- /dev/null +++ b/bedrock/wordpress/tests/test_data/media_75.json @@ -0,0 +1,139 @@ +{ + "id": 75, + "date": "2017-03-14T14:13:24", + "date_gmt": "2017-03-14T21:13:24", + "guid": { + "rendered": "https:\/\/blog.mozilla.org\/firefox\/files\/2017\/03\/Put-Your-Trust-in-Rust.png" + }, + "modified": "2017-03-14T14:14:55", + "modified_gmt": "2017-03-14T21:14:55", + "slug": "put-your-trust-in-rust", + "status": "inherit", + "type": "attachment", + "link": "https:\/\/blog.mozilla.org\/firefox\/put-trust-rust-shipping-now-firefox\/put-your-trust-in-rust\/", + "title": { + "rendered": "Put-Your-Trust-in-Rust" + }, + "author": 1431, + "comment_status": "closed", + "ping_status": "closed", + "template": "", + "meta": [], + "description": { + "rendered": "

\"Trustworthy<\/a><\/p>\n

Firefox puts user security and privacy first<\/p>\n" + }, + "caption": { + "rendered": "

Firefox puts user security and privacy first<\/p>\n" + }, + "alt_text": "Trustworthy browser", + "media_type": "image", + "mime_type": "image\/png", + "media_details": { + "width": 1200, + "height": 630, + "file": "2017\/03\/Put-Your-Trust-in-Rust.png", + "sizes": { + "thumbnail": { + "file": "Put-Your-Trust-in-Rust-150x150.png", + "width": 150, + "height": 150, + "mime_type": "image\/png", + "source_url": "https:\/\/blog.mozilla.org\/firefox\/files\/2017\/03\/Put-Your-Trust-in-Rust-150x150.png" + }, + "medium": { + "file": "Put-Your-Trust-in-Rust-300x158.png", + "width": 300, + "height": 158, + "mime_type": "image\/png", + "source_url": "https:\/\/blog.mozilla.org\/firefox\/files\/2017\/03\/Put-Your-Trust-in-Rust-300x158.png" + }, + "medium_large": { + "file": "Put-Your-Trust-in-Rust-768x403.png", + "width": 768, + "height": 403, + "mime_type": "image\/png", + "source_url": "https:\/\/blog.mozilla.org\/firefox\/files\/2017\/03\/Put-Your-Trust-in-Rust-768x403.png" + }, + "large": { + "file": "Put-Your-Trust-in-Rust-600x315.png", + "width": 600, + "height": 315, + "mime_type": "image\/png", + "source_url": "https:\/\/blog.mozilla.org\/firefox\/files\/2017\/03\/Put-Your-Trust-in-Rust-600x315.png" + }, + "post-large": { + "file": "Put-Your-Trust-in-Rust-600x330.png", + "width": 600, + "height": 330, + "mime_type": "image\/png", + "source_url": "https:\/\/blog.mozilla.org\/firefox\/files\/2017\/03\/Put-Your-Trust-in-Rust-600x330.png" + }, + "post-thumbnail": { + "file": "Put-Your-Trust-in-Rust-300x165.png", + "width": 300, + "height": 165, + "mime_type": "image\/png", + "source_url": "https:\/\/blog.mozilla.org\/firefox\/files\/2017\/03\/Put-Your-Trust-in-Rust-300x165.png" + }, + "extra-large": { + "file": "Put-Your-Trust-in-Rust-1000x525.png", + "width": 1000, + "height": 525, + "mime_type": "image\/png", + "source_url": "https:\/\/blog.mozilla.org\/firefox\/files\/2017\/03\/Put-Your-Trust-in-Rust-1000x525.png" + }, + "full": { + "file": "Put-Your-Trust-in-Rust.png", + "width": 1200, + "height": 630, + "mime_type": "image\/png", + "source_url": "https:\/\/blog.mozilla.org\/firefox\/files\/2017\/03\/Put-Your-Trust-in-Rust.png" + } + }, + "image_meta": { + "aperture": "0", + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": "0", + "copyright": "", + "focal_length": "0", + "iso": "0", + "shutter_speed": "0", + "title": "", + "orientation": "0", + "keywords": [] + } + }, + "post": 74, + "source_url": "https:\/\/blog.mozilla.org\/firefox\/files\/2017\/03\/Put-Your-Trust-in-Rust.png", + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/media\/75" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/media" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/types\/attachment" + } + ], + "author": [ + { + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/users\/1431" + } + ], + "replies": [ + { + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/comments?post=75" + } + ] + } +} diff --git a/bedrock/wordpress/tests/test_data/posts.json b/bedrock/wordpress/tests/test_data/posts.json new file mode 100644 index 0000000000..0942014efe --- /dev/null +++ b/bedrock/wordpress/tests/test_data/posts.json @@ -0,0 +1,319 @@ +[ + { + "id": 10, + "date": "2017-03-14T13:58:50", + "date_gmt": "2017-03-14T20:58:50", + "guid": { + "rendered": "https:\/\/blog.mozilla.org\/firefox\/?p=10" + }, + "modified": "2017-03-15T15:14:33", + "modified_gmt": "2017-03-15T22:14:33", + "slug": "fast-responsive-reliable-browser-multi-tab-age", + "status": "publish", + "type": "post", + "link": "https:\/\/blog.mozilla.org\/firefox\/fast-responsive-reliable-browser-multi-tab-age\/", + "title": { + "rendered": "Fast, Responsive, Reliable: A Browser For the Multi-Tab Age" + }, + "content": { + "rendered": "

Take a look at your browser window: how many tabs do you have open? It\u2019s likely your answer is somewhere between \u201cten\u201d and \u201ca ton,\u201d and you\u2019re not alone. These days, users consume web content in a smorgasbord of tabs, leaving pages open (to look at later) even as we open new ones (for more immediate tasks and browsing). Most of the time, the web offers five-star buffet service, responding to our commands and serving up satisfying content on command.<\/p>\n

\"\"<\/p>\n

But then there are times when you stand in line at the Internet buffet moving nowhere. You eye the lavish food displays while one person stalls, carefully picking over the salad and heaping spoonfuls of entr\u00e9es onto his tray. This kind of hang time is frustrating. Your browser, overloaded with processing, temporarily freezes and fails to respond to your clicks and keystrokes, no matter how insistently you tap away. This is because some pages require a lot of your computer\u2019s processing power, and the machine puts all of its energy into processing the page, rather than responding to you.<\/p>\n

Last August, we began the rollout of Electrolysis (e10s), our multi-process browser functionality. Basically, multi-process means that the browser runs in two separate lanes: one for the user interface, and another for your many tabs (i.e., web content). This means that Firefox can react quickly to your insistent taps (input), instead of temporarily freezing while processing a page (or, let\u2019s be honest, your dozens of open pages!) In terms of numbers, the multi-process Firefox browser hangs 86% less while web pages are loading, and 75% less after loading.<\/p>\n

This means that the Firefox browser is more stable than ever before. You know how a single hanging page can lead to the entire browser crashing? Because multi-process Firefox runs user interface and content processes separately, if a page ever hangs and crashes, it doesn\u2019t take the whole browser down with it.<\/p>\n

We know you expect a flexible and responsive browsing experience that keeps pace with your internet appetite. The Firefox user experience is now faster, smoother, and more reliable than ever before. So feel free to feast on the Internet, and save room for dessert. Or (let\u2019s be honest) \u2014 desserts.<\/p>\n


\n

Browse Fast. Browse Free<\/h4>\n

\"Download<\/a><\/p>\n", + "protected": false + }, + "excerpt": { + "rendered": "

Take a look at your browser window: how many tabs do you have open? It\u2019s likely your answer is somewhere between \u201cten\u201d and \u201ca ton,\u201d and you\u2019re not alone. These … Read more<\/a><\/p>\n", + "protected": false + }, + "author": 144, + "featured_media": 0, + "comment_status": "closed", + "ping_status": "closed", + "sticky": false, + "template": "", + "format": "standard", + "meta": [], + "categories": [ + 1 + ], + "tags": [ + 43, + 301510 + ], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts\/10" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/types\/post" + } + ], + "author": [ + { + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/users\/144" + } + ], + "replies": [ + { + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/comments?post=10" + } + ], + "version-history": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts\/10\/revisions" + } + ], + "wp:featuredmedia": [ + { + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/media\/45" + } + ], + "wp:attachment": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/media?parent=10" + } + ], + "wp:term": [ + { + "taxonomy": "category", + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/categories?post=10" + }, + { + "taxonomy": "post_tag", + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags?post=10" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 69, + "date": "2017-03-13T12:56:57", + "date_gmt": "2017-03-13T19:56:57", + "guid": { + "rendered": "https:\/\/blog.mozilla.org\/firefox\/?p=69" + }, + "modified": "2017-04-04T08:36:59", + "modified_gmt": "2017-04-04T15:36:59", + "slug": "smooth-scrolling-web-browser-removes-jank", + "status": "publish", + "type": "post", + "link": "https:\/\/blog.mozilla.org\/firefox\/smooth-scrolling-web-browser-removes-jank\/", + "title": { + "rendered": "Smooth Scrolling: How a Web Browser Removes Jank" + }, + "content": { + "rendered": "

Here\u2019s the scenario: it\u2019s about 6 AM on a weekday, and your alarm didn\u2019t go off. You\u2019re rushing to catch a flight that you\u2019re probably going to be late for. Your phone is displaying your approaching Lyft car, which you figure might help you win some time. Hurrying to get the info from the airline\u2019s site on your laptop (because you forgot to download the app, of course), you\u2019re scrolling through the obligatory travel disclosures when your browser window suddenly freezes on you, mid-scroll. Right then the Lyft arrives, the driver sends you a text. Frustration ensues as you try in vain to get the browser to respond; the Lyft honks outside when suddenly, the page responds to your scrolling efforts, and you end up at the bottom of the page. Thankfully, you learn that the flight is delayed 20 minutes, but no thanks to your slow browser!<\/p>\n

\"Smooth<\/p>\n

This responsiveness issue is known as jank, and it\u2019s a problem we’ve addressed with the rollout of Electrolysis (e10s), our multi-process browser functionality. The solution to scrolling-induced browser jank is called asynchronous panning and zooming (APZ). APZ allows content to be pre-rendered before it enters the viewing window.<\/p>\n

Though we\u2019ve had variations of APZ around for a while, our new multi-process Firefox makes scrolling smoother and more responsive than ever. With the older, single-process Firefox browser, jank was the result of the browser being unable to handle the demands of the juggling required to display a web page at the same time as user input (like scrolling).<\/p>\n

Multiple processes means that your clicks and scrolls can be managed much more efficiently because they are immediately processed, rather than getting stalled behind other content (and making you late for your flight.)<\/p>\n


\n

Browse Fast. Browse Free<\/h4>\n

\"Download<\/a><\/p>\n", + "protected": false + }, + "excerpt": { + "rendered": "

Here\u2019s the scenario: it\u2019s about 6 AM on a weekday, and your alarm didn\u2019t go off. You\u2019re rushing to catch a flight that you\u2019re probably going to be late for. … Read more<\/a><\/p>\n", + "protected": false + }, + "author": 144, + "featured_media": 0, + "comment_status": "closed", + "ping_status": "closed", + "sticky": false, + "template": "", + "format": "standard", + "meta": [], + "categories": [ + 1 + ], + "tags": [ + 301510, + 302022, + 302197, + 301766 + ], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts\/69" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/types\/post" + } + ], + "author": [ + { + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/users\/144" + } + ], + "replies": [ + { + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/comments?post=69" + } + ], + "version-history": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts\/69\/revisions" + } + ], + "wp:featuredmedia": [ + { + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/media\/72" + } + ], + "wp:attachment": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/media?parent=69" + } + ], + "wp:term": [ + { + "taxonomy": "category", + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/categories?post=69" + }, + { + "taxonomy": "post_tag", + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags?post=69" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 74, + "date": "2017-03-12T14:15:32", + "date_gmt": "2017-03-12T21:15:32", + "guid": { + "rendered": "https:\/\/blog.mozilla.org\/firefox\/?p=74" + }, + "modified": "2017-04-04T08:37:02", + "modified_gmt": "2017-04-04T15:37:02", + "slug": "put-trust-rust-shipping-now-firefox", + "status": "publish", + "type": "post", + "link": "https:\/\/blog.mozilla.org\/firefox\/put-trust-rust-shipping-now-firefox\/", + "title": { + "rendered": "Put your Trust in Rust – Shipping Now in Firefox" + }, + "content": { + "rendered": "

At Mozilla, security is one of our highest priorities, as the openness and accessibility of the Internet are threatened when user safety and privacy is not protected. Seven years ago we began sponsoring the development of the Rust programming language, with the vision of building a systems programming language that emphasizes security, speed, and parallelism. Parallelism means that multiple CPU cores can execute different code at the same time, and it makes for computing that is more powerful and efficient.<\/p>\n

\"Trustworthy<\/p>\n

With many languages, writing parallel programs is a challenge because developers have to go to great lengths to avoid errors. This means the code has a higher probability of containing bugs, which can lead to security vulnerabilities \u2014 and a single programming mistake can be greatly damaging to web security, like a leak in a dam.<\/p>\n

 <\/p>\n

Rust, as a memory-safe programming language, solves this because it\u2019s able to check source code before it\u2019s converted (or \u201ccompiled\u201d) into low-level instructions, making it far easier for developers to write programs with confidence that the code is safe. This feature is akin to having a guardian angel watching over your work, pointing out errors and guiding you towards safe and secure code. This empowers developers to feel more comfortable and confident in building parallel algorithms, which with Project Quantum<\/a> \u2014 our effort to develop Mozilla\u2019s future web engine \u2014 will greatly speed up the browser.<\/p>\n

 <\/p>\n

Last summer, with the release of Firefox 48, we shipped the very first browser component to be written in the Rust programming language<\/a> \u2014 an MP4 parser for video files. Streaming media files in your browser can be particularly risky if you don\u2019t know or trust the source of the file, as these can maliciously take advantage of bugs in a browser\u2019s code. Rust\u2019s memory-safe capabilities prevent these vulnerabilities from being built into the code in the first place.<\/p>\n

 <\/p>\n

We\u2019re proud to have helped create a language that will power our next-generation browser and put user security and privacy first.<\/p>\n


\n

Browse Fast. Browse Free<\/h4>\n

\"Download<\/a><\/p>\n", + "protected": false + }, + "excerpt": { + "rendered": "

At Mozilla, security is one of our highest priorities, as the openness and accessibility of the Internet are threatened when user safety and privacy is not protected. Seven years ago … Read more<\/a><\/p>\n", + "protected": false + }, + "author": 144, + "featured_media": 75, + "comment_status": "closed", + "ping_status": "closed", + "sticky": false, + "template": "", + "format": "standard", + "meta": [], + "categories": [ + 1 + ], + "tags": [ + 301510, + 847, + 617, + 16179, + 69 + ], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts\/74" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/types\/post" + } + ], + "author": [ + { + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/users\/144" + } + ], + "replies": [ + { + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/comments?post=74" + } + ], + "version-history": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts\/74\/revisions" + } + ], + "wp:featuredmedia": [ + { + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/media\/75" + } + ], + "wp:attachment": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/media?parent=74" + } + ], + "wp:term": [ + { + "taxonomy": "category", + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/categories?post=74" + }, + { + "taxonomy": "post_tag", + "embeddable": true, + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags?post=74" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + } +] diff --git a/bedrock/wordpress/tests/test_data/tags.json b/bedrock/wordpress/tests/test_data/tags.json new file mode 100644 index 0000000000..09852d8f6e --- /dev/null +++ b/bedrock/wordpress/tests/test_data/tags.json @@ -0,0 +1,353 @@ +[ + { + "id": 43, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/browser\/", + "name": "browser", + "slug": "browser", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/43" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=43" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 301510, + "count": 3, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/fastest\/", + "name": "fastest", + "slug": "fastest", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/301510" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=301510" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 302022, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/jank\/", + "name": "jank", + "slug": "jank", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/302022" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=302022" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 847, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/privacy\/", + "name": "privacy", + "slug": "privacy", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/847" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=847" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 617, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/programming\/", + "name": "programming", + "slug": "programming", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/617" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=617" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 16179, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/rust\/", + "name": "rust", + "slug": "rust", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/16179" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=16179" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 302197, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/scrolling\/", + "name": "scrolling", + "slug": "scrolling", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/302197" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=302197" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 69, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/security\/", + "name": "security", + "slug": "security", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/69" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=69" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 301766, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/web-browser\/", + "name": "web browser", + "slug": "web-browser", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/301766" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=301766" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + } +] diff --git a/bedrock/wordpress/tests/test_data/tags_all.json b/bedrock/wordpress/tests/test_data/tags_all.json new file mode 100644 index 0000000000..09852d8f6e --- /dev/null +++ b/bedrock/wordpress/tests/test_data/tags_all.json @@ -0,0 +1,353 @@ +[ + { + "id": 43, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/browser\/", + "name": "browser", + "slug": "browser", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/43" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=43" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 301510, + "count": 3, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/fastest\/", + "name": "fastest", + "slug": "fastest", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/301510" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=301510" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 302022, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/jank\/", + "name": "jank", + "slug": "jank", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/302022" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=302022" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 847, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/privacy\/", + "name": "privacy", + "slug": "privacy", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/847" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=847" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 617, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/programming\/", + "name": "programming", + "slug": "programming", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/617" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=617" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 16179, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/rust\/", + "name": "rust", + "slug": "rust", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/16179" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=16179" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 302197, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/scrolling\/", + "name": "scrolling", + "slug": "scrolling", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/302197" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=302197" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 69, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/security\/", + "name": "security", + "slug": "security", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/69" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=69" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + }, + { + "id": 301766, + "count": 1, + "description": "", + "link": "https:\/\/blog.mozilla.org\/firefox\/tag\/web-browser\/", + "name": "web browser", + "slug": "web-browser", + "taxonomy": "post_tag", + "meta": [], + "_links": { + "self": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags\/301766" + } + ], + "collection": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/tags" + } + ], + "about": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/taxonomies\/post_tag" + } + ], + "wp:post_type": [ + { + "href": "https:\/\/blog.mozilla.org\/firefox\/wp-json\/wp\/v2\/posts?tags=301766" + } + ], + "curies": [ + { + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + } + ] + } + } +] diff --git a/bedrock/wordpress/tests/test_models.py b/bedrock/wordpress/tests/test_models.py new file mode 100644 index 0000000000..610c230969 --- /dev/null +++ b/bedrock/wordpress/tests/test_models.py @@ -0,0 +1,74 @@ +from django.test import override_settings + +from bedrock.wordpress import api +from bedrock.wordpress.models import BlogPost + +import pytest +import responses +from pathlib2 import Path + + +TEST_DATA = Path(__file__).with_name('test_data') +TEST_WP_BLOGS = { + 'firefox': { + 'url': 'https://blog.mozilla.org/firefox/', + 'name': 'The Firefox Frontier', + 'num_posts': 10, + }, +} + + +def get_test_file_content(filename): + with TEST_DATA.joinpath(filename).open() as fh: + return fh.read() + + +def setup_responses(): + posts_url = api._api_url(TEST_WP_BLOGS['firefox']['url'], 'posts', None) + tags_url = api._api_url(TEST_WP_BLOGS['firefox']['url'], 'tags', None) + media_url = api._api_url(TEST_WP_BLOGS['firefox']['url'], 'media', 75) + responses.add(responses.GET, posts_url, body=get_test_file_content('posts.json')) + responses.add(responses.GET, tags_url, body=get_test_file_content('tags.json')) + responses.add(responses.GET, media_url, body=get_test_file_content('media_75.json')) + + +@responses.activate +@override_settings(WP_BLOGS=TEST_WP_BLOGS) +def test_get_posts_data(): + setup_responses() + data = api.get_posts_data('firefox') + assert data['wp_blog_slug'] == 'firefox' + assert data['posts'][0]['tags'] == ['browser', 'fastest'] + assert not data['posts'][0]['featured_media'] + assert not data['posts'][1]['featured_media'] + assert data['posts'][2]['featured_media']['id'] == 75 + assert len(responses.calls) == 3 + + +@responses.activate +@override_settings(WP_BLOGS=TEST_WP_BLOGS) +@pytest.mark.django_db +def test_refresh_posts(): + setup_responses() + BlogPost.objects.refresh('firefox') + blog = BlogPost.objects.filter_by_blog('firefox') + assert len(blog) == 3 + bp = blog.get(wp_id=10) + assert bp.tags == ['browser', 'fastest'] + assert bp.wp_blog_slug == 'firefox' + bp = blog.get(wp_id=74) + assert bp.tags == ['fastest', 'privacy', 'programming', 'rust', 'security'] + assert bp.featured_media['id'] == 75 + assert bp.get_featured_image_url('large').endswith('Put-Your-Trust-in-Rust-600x315.png') + + +@responses.activate +@override_settings(WP_BLOGS=TEST_WP_BLOGS) +@pytest.mark.django_db +def test_filter_by_tags(): + setup_responses() + BlogPost.objects.refresh('firefox') + blog = BlogPost.objects.filter_by_blog('firefox') + assert len(blog.filter_by_tags('browser')) == 1 + assert len(blog.filter_by_tags('fastest')) == 3 + assert len(blog.filter_by_tags('browser', 'jank')) == 2 diff --git a/bedrock/wordpress/tests/test_views.py b/bedrock/wordpress/tests/test_views.py new file mode 100644 index 0000000000..78949469bd --- /dev/null +++ b/bedrock/wordpress/tests/test_views.py @@ -0,0 +1,43 @@ +from django.test import override_settings, RequestFactory + +import pytest +import responses + +from bedrock.wordpress.models import BlogPost +from bedrock.wordpress.tests.test_models import setup_responses, TEST_WP_BLOGS +from bedrock.wordpress.views import BlogPostsView + + +@responses.activate +@override_settings(WP_BLOGS=TEST_WP_BLOGS) +@pytest.mark.django_db +def test_blog_posts_view(): + setup_responses() + BlogPost.objects.refresh('firefox') + + req = RequestFactory().get('/') + view = BlogPostsView(blog_slug='firefox', blog_tags=['browser', 'jank'], + template_name='mozorg/book.html') + view.request = req + blog_posts = view.get_context_data()['blog_posts'] + assert len(blog_posts) == 2 + assert blog_posts[0].wp_id == 10 + assert blog_posts[1].wp_id == 69 + + # no tags defined + view = BlogPostsView(blog_slug='firefox', template_name='mozorg/book.html') + view.request = req + blog_posts = view.get_context_data()['blog_posts'] + assert len(blog_posts) == 3 + + # incorrect blog slug + view = BlogPostsView(blog_slug='not-a-blog', template_name='mozorg/book.html') + view.request = req + blog_posts = view.get_context_data()['blog_posts'] + assert len(blog_posts) == 0 + + # no blog defined + view = BlogPostsView(template_name='mozorg/book.html') + view.request = req + blog_posts = view.get_context_data()['blog_posts'] + assert len(blog_posts) == 0 diff --git a/bedrock/wordpress/views.py b/bedrock/wordpress/views.py new file mode 100644 index 0000000000..39e05282ce --- /dev/null +++ b/bedrock/wordpress/views.py @@ -0,0 +1,26 @@ +from django.views.generic import TemplateView + +from bedrock.wordpress.models import BlogPost +from lib.l10n_utils import LangFilesMixin + + +class BlogPostsMixin(object): + blog_tags = None + blog_slug = None + + def get_context_data(self, **kwargs): + ctx = super(BlogPostsMixin, self).get_context_data(**kwargs) + if self.blog_slug: + blog = BlogPost.objects.filter_by_blog(self.blog_slug) + if self.blog_tags: + blog = blog.filter_by_tags(*self.blog_tags) + + ctx['blog_posts'] = blog + else: + ctx['blog_posts'] = [] + + return ctx + + +class BlogPostsView(BlogPostsMixin, LangFilesMixin, TemplateView): + pass diff --git a/bin/cron.py b/bin/cron.py index 4ce2c382c9..fcd3b2357f 100755 --- a/bin/cron.py +++ b/bin/cron.py @@ -102,6 +102,7 @@ def schedule_database_jobs(): @scheduled_job('interval', hours=1) def update_blog_feeds(): call_command('update_blog_feeds --database bedrock') + call_command('update_wordpress --database bedrock') def schedul_l10n_jobs(): diff --git a/bin/sync-all.sh b/bin/sync-all.sh index 1ef0e815b5..e1677d1572 100755 --- a/bin/sync-all.sh +++ b/bin/sync-all.sh @@ -10,6 +10,7 @@ fi ./manage.py cron update_ical_feeds ./manage.py update_product_details_files --database bedrock ./manage.py update_blog_feeds --database bedrock +./manage.py update_wordpress --database bedrock ./manage.py update_externalfiles ./manage.py update_security_advisories ./manage.py l10n_update diff --git a/requirements/dev.txt b/requirements/dev.txt index 3051a50ec6..85ac1fa29c 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -38,3 +38,9 @@ funcsigs==1.0.2 \ pbr==1.10.0 \ --hash=sha256:f5cf7265a80636ecff66806d13494cbf9d77a3758a65fd8b4d4d4bee81b0c375 \ --hash=sha256:186428c270309e6fdfe2d5ab0949ab21ae5f7dea831eab96701b86bd666af39c +responses==0.5.1 \ + --hash=sha256:3a907f7aae2fd2286d06cfdf238957786c38bbcadc451adceecc769a4ef882b7 \ + --hash=sha256:8cad64c45959a651ceaf0023484bd26180c927fea64a81e63d334ddf6377ecea +cookies==2.2.1 \ + --hash=sha256:15bee753002dff684987b8df8c235288eb8d45f8191ae056254812dfd42c81d3 \ + --hash=sha256:d6b698788cae4cfa4e62ef8643a9ca332b79bd96cb314294b864ae8d7eb3ee8e