From 183011f09db5e187a9ecefc282ccb61319910c66 Mon Sep 17 00:00:00 2001 From: Pomax Date: Wed, 25 Sep 2019 10:50:54 -0700 Subject: [PATCH] RSS/Atom for Blog (#3681) * rss/atom for /blog/ posts --- Pipfile | 1 + Pipfile.lock | 78 +++++++++++----------- network-api/networkapi/settings.py | 4 ++ network-api/networkapi/urls.py | 7 ++ network-api/networkapi/wagtailpages/rss.py | 48 +++++++++++++ 5 files changed, 100 insertions(+), 38 deletions(-) create mode 100644 network-api/networkapi/wagtailpages/rss.py diff --git a/Pipfile b/Pipfile index feca4042337..34cefb186fd 100644 --- a/Pipfile +++ b/Pipfile @@ -4,6 +4,7 @@ verify_ssl = true name = "pypi" [packages] +beautifulsoup4 = "*" "boto3" = "*" Django = "==2.2.5" dj-database-url = "*" diff --git a/Pipfile.lock b/Pipfile.lock index d3fcd28b611..761110d067f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "a5db16c2300b91d83510fb8029e7bc34cb5b3463222eb6a5c96fb3a9b3314ea8" + "sha256": "1fadbbd8999ad59316b8f205ed3943eb1d3a0afe17dc971ba82c76210a65209e" }, "pipfile-spec": 6, "requires": { @@ -22,29 +22,30 @@ "sha256:7015e76bf32f1f574636c4288399a6de66ce08fb7b2457f628a8d70c0fbabb11", "sha256:808b6ac932dccb0a4126558f7dfdcf41710dd44a4ef497a0bb59a77f9f078e89" ], + "index": "pypi", "version": "==4.6.0" }, "boto3": { "hashes": [ - "sha256:19a77d8ecb05d87123e88a65cba49cdbc8c66717ced21c2093a6f091492c22da", - "sha256:e184590781c127358c2d9ae1eab6607441d92fbddd88ba08b891b8c14d0bbfff" + "sha256:273d3cb6d0206d40b2020e1871e1d95b284cff870fdf1e8816701da1063474c4", + "sha256:6f9c9f393d4951b9e86d4b26ff5cf1d5b9845bd1c3eb9a2f6b969887c2e4155e" ], "index": "pypi", - "version": "==1.9.205" + "version": "==1.9.234" }, "botocore": { "hashes": [ - "sha256:748fe4ee5cc8b10ef09e52c740b488402d6f6d4d1f0dde0c936da232b42b1bdd", - "sha256:9ffd9264e4ad999d2929cfe1c7e413d4cdf76a8bd92f011dce31874f056d2e18" + "sha256:4103382697418c24702b7731ef2bd73606ca6a86336b1a898e97c34d0d241c69", + "sha256:d809ac570f630491778b47ae8d02475162f04eeb0ca39ccd3f4112d729d7eaa7" ], - "version": "==1.12.220" + "version": "==1.12.234" }, "certifi": { "hashes": [ - "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", - "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" + "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", + "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" ], - "version": "==2019.6.16" + "version": "==2019.9.11" }, "chardet": { "hashes": [ @@ -86,18 +87,19 @@ }, "django-admin-sortable": { "hashes": [ - "sha256:693703bd47149e50fd1bd6f4aaa73b4528409f78932a6db594d00c0c538eda55" + "sha256:bb282bb73028bab72b49f9acd87e7742d183490a6426f7153d21d25faae3447d", + "sha256:cae6d4675738469b7f146bd311bff0e1bd5af4d99a361b5d61060377cf842d0b" ], "index": "pypi", - "version": "==2.1.17" + "version": "==2.1.18" }, "django-cors-headers": { "hashes": [ - "sha256:5b80bf0f8d7fc6e2bcb4f40781d5ff3661961bbf1982e52daec77241dea3b890", - "sha256:ebf3e2cf25aa6993b959a8e6a87828ebb3c8fe5bc3ec4a2d6e65f3b8d9b4212c" + "sha256:e4b12209b3a0bc577883fe0ac0aa3adac9e82742389f8ddb6c6b41c66b1e9c4f", + "sha256:e69b1c909f2eddc7ef2a24f071583bc22b73b871731ea3370ac52b3318c43b3c" ], "index": "pypi", - "version": "==3.0.2" + "version": "==3.1.0" }, "django-csp": { "hashes": [ @@ -146,11 +148,11 @@ }, "django-storages": { "hashes": [ - "sha256:8e35d2c7baeda5dc6f0b4f9a0fc142d25f9a1bf72b8cebfcbc5db4863abc552d", - "sha256:b1a63cd5ea286ee5a9fb45de6c3c5c0ae132d58308d06f1ce9865cfcd5e470a7" + "sha256:87287b7ad2e789cd603373439994e1ac6f94d9dc2e5f8173d2a87aa3ed458bd9", + "sha256:f3b3def96493d3ccde37b864cea376472baf6e8a596504b209278801c510b807" ], "index": "pypi", - "version": "==1.7.1" + "version": "==1.7.2" }, "django-taggit": { "hashes": [ @@ -167,11 +169,11 @@ }, "djangorestframework": { "hashes": [ - "sha256:42979bd5441bb4d8fd69d0f385024a114c3cae7df0f110600b718751250f6929", - "sha256:aedb48010ebfab9651aaab1df5fd3b4848eb4182afc909852a2110c24f89a359" + "sha256:5488aed8f8df5ec1d70f04b2114abc52ae6729748a176c453313834a9ee179c8", + "sha256:dc81cbf9775c6898a580f6f1f387c4777d12bd87abf0f5406018d32ccae71090" ], "index": "pypi", - "version": "==3.10.2" + "version": "==3.10.3" }, "docutils": { "hashes": [ @@ -197,11 +199,11 @@ }, "faker": { "hashes": [ - "sha256:1d3f700e8dfcefd6e657118d71405d53e86974448aba78884f119bbd84c0cddf", - "sha256:d5366e120191c5610fceeebfe1c298dc46da0277096f639c6dd7e2eaee0fa547" + "sha256:45cc9cca3de8beba5a2da3bd82a6e5544f53da1a702645c8485f682366c15026", + "sha256:a6459ff518d1fc6ee2238a7209e6c899517872c7e1115510279033ffe6fe8ef3" ], "index": "pypi", - "version": "==2.0.1" + "version": "==2.0.2" }, "filebrowser-safe": { "hashes": [ @@ -347,10 +349,10 @@ }, "python-slugify": { "hashes": [ - "sha256:a9f468227cb11e20e251670d78e1b5f6b0b15dd37bbd5c9814a25a904e44ff66" + "sha256:575d03256a132fc1efb4c52966c6eb11c57a13b071618f0b26076057a23f6937" ], "index": "pypi", - "version": "==3.0.3" + "version": "==3.0.4" }, "python3-openid": { "hashes": [ @@ -429,10 +431,10 @@ }, "text-unidecode": { "hashes": [ - "sha256:5a1375bb2ba7968740508ae38d92e1f889a0832913cb1c447d5e2046061a396d", - "sha256:801e38bd550b943563660a91de8d4b6fa5df60a542be9093f7abf819f86050cc" + "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", + "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" ], - "version": "==1.2" + "version": "==1.3" }, "tqdm": { "hashes": [ @@ -450,11 +452,11 @@ }, "urllib3": { "hashes": [ - "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", - "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" + "sha256:2f3eadfea5d92bc7899e75b5968410b749a054b492d5a6379c1344a1481bc2cb", + "sha256:9c6c593cb28f52075016307fc26b0a0f8e82bc7d1ff19aaaa959b91710a56c47" ], "markers": "python_version >= '3.4'", - "version": "==1.25.3" + "version": "==1.25.5" }, "wagtail": { "hashes": [ @@ -529,10 +531,10 @@ "develop": { "certifi": { "hashes": [ - "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", - "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" + "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", + "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" ], - "version": "==2019.6.16" + "version": "==2019.9.11" }, "chardet": { "hashes": [ @@ -664,11 +666,11 @@ }, "urllib3": { "hashes": [ - "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", - "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" + "sha256:2f3eadfea5d92bc7899e75b5968410b749a054b492d5a6379c1344a1481bc2cb", + "sha256:9c6c593cb28f52075016307fc26b0a0f8e82bc7d1ff19aaaa959b91710a56c47" ], "markers": "python_version >= '3.4'", - "version": "==1.25.3" + "version": "==1.25.5" } } } diff --git a/network-api/networkapi/settings.py b/network-api/networkapi/settings.py index e6dc4f79930..f07a25aec78 100644 --- a/network-api/networkapi/settings.py +++ b/network-api/networkapi/settings.py @@ -75,6 +75,7 @@ CLOUDINARY_CLOUD_NAME=(str, ''), CLOUDINARY_API_KEY=(str, ''), CLOUDINARY_API_SECRET=(str, ''), + FEED_LIMIT=(int, 10), ) # Read in the environment @@ -587,3 +588,6 @@ # Use network_url to check if we're running prod or not NETWORK_SITE_URL = env('NETWORK_SITE_URL') + +# RSS / ATOM settings +FEED_LIMIT = env('FEED_LIMIT') diff --git a/network-api/networkapi/urls.py b/network-api/networkapi/urls.py index 3400436b2be..25aa1eb8246 100644 --- a/network-api/networkapi/urls.py +++ b/network-api/networkapi/urls.py @@ -4,6 +4,7 @@ from django.conf.urls.static import static from django.contrib import admin from django.views.generic.base import RedirectView +from django.urls import path from wagtail.admin import urls as wagtailadmin_urls from wagtail.documents import urls as wagtaildocs_urls @@ -12,6 +13,7 @@ from networkapi.views import EnvVariablesView, review_app_help_view from networkapi.buyersguide import views as buyersguide_views +from networkapi.wagtailpages.rss import RSSFeed, AtomFeed from experiments import views as experiment_views admin.autodiscover() @@ -55,6 +57,11 @@ # Buyer's Guide / Privacy Not Included url(r'^privacynotincluded/', include('networkapi.buyersguide.urls')), + # Blog RSS feed + path('blog/rss/', RSSFeed()), + path('blog/atom/', AtomFeed()), + + # wagtail-managed data url(r'', include(wagtail_urls)), ) diff --git a/network-api/networkapi/wagtailpages/rss.py b/network-api/networkapi/wagtailpages/rss.py new file mode 100644 index 00000000000..107ccb3b185 --- /dev/null +++ b/network-api/networkapi/wagtailpages/rss.py @@ -0,0 +1,48 @@ +from django.conf import settings +from django.contrib.syndication.views import Feed +from django.utils.feedgenerator import Atom1Feed + +from bs4 import BeautifulSoup + +from .models import IndexPage + + +class RSSFeed(Feed): + """ + Blog page RSS feed, using the content:encoded serializer above + """ + + title = 'Mozilla Foundation Blog' + link = 'https://foundation.mozilla.org/blog/' + feed_url = 'https://foundation.mozilla.org/blog/rss/' + description = 'The Mozilla Foundation Blog' + + def items(self): + blog_index = IndexPage.objects.get(title__iexact='blog') + blog_pages = blog_index.get_all_entries() + return blog_pages[:settings.FEED_LIMIT] + + def item_title(self, item): + return item.title + + def item_link(self, item): + return item.full_url + + def item_description(self, item): + page = item.specific + html = str(page.body) + parsed = BeautifulSoup(html, 'html.parser') + text = parsed.get_text()[:1000] + if len(text) == 1000: + text = f'{text}[...]' + return text + + def item_pubdate(self, item): + return item.first_published_at + + +class AtomFeed(RSSFeed): + feed_type = Atom1Feed + link = RSSFeed.link + feed_url = 'https://foundation.mozilla.org/blog/atom/' + subtitle = RSSFeed.description