Add support for FB Instant Articles

This commit is contained in:
Iacopo Spalletti 2016-04-23 21:17:51 +02:00
parent b595834f9d
commit 542774ce95
No known key found for this signature in database
GPG Key ID: BDCBC2EB289F60C6
7 changed files with 152 additions and 5 deletions

View File

@ -1,19 +1,27 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals
from django.core.cache import cache
from django.utils.encoding import force_text
from django.utils.feedgenerator import Rss201rev2Feed, rfc2822_date
from aldryn_apphooks_config.utils import get_app_instance
from django.contrib.sites.models import Site
from django.contrib.syndication.views import Feed
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _, get_language_from_request
from djangocms_blog.settings import get_setting
from djangocms_blog.views import PostDetailView
from .models import Post
from .settings import get_setting
class LatestEntriesFeed(Feed):
feed_type = Rss201rev2Feed
def __call__(self, request, *args, **kwargs):
self.request = request
self.namespace, self.config = get_app_instance(request)
return super(LatestEntriesFeed, self).__call__(request, *args, **kwargs)
@ -30,7 +38,7 @@ class LatestEntriesFeed(Feed):
return item.safe_translation_getter('title')
def item_description(self, item):
if get_setting('USE_ABSTRACT'):
if item.app_config.use_abstract:
return item.safe_translation_getter('abstract')
return item.safe_translation_getter('post_text')
@ -42,3 +50,55 @@ class TagFeed(LatestEntriesFeed):
def items(self, obj=None):
return Post.objects.published().filter(tags__slug=obj)[:10]
class FBInstantFeed(Rss201rev2Feed):
def rss_attributes(self):
return {
'version': self._version,
'xmlns:content': 'http://purl.org/rss/1.0/modules/content/'
}
def add_root_elements(self, handler):
handler.addQuickElement("title", self.feed['title'])
handler.addQuickElement("link", self.feed['link'])
handler.addQuickElement("description", self.feed['description'])
if self.feed['language'] is not None:
handler.addQuickElement("language", self.feed['language'])
for cat in self.feed['categories']:
handler.addQuickElement("category", cat)
if self.feed['feed_copyright'] is not None:
handler.addQuickElement("copyright", self.feed['feed_copyright'])
handler.addQuickElement("lastBuildDate", rfc2822_date(self.latest_post_date()))
if self.feed['ttl'] is not None:
handler.addQuickElement("ttl", self.feed['ttl'])
def add_item_elements(self, handler, item):
super(FBInstantFeed, self).add_item_elements(handler, item)
handler.startElement('content:encoded', {})
handler._write('<![CDATA[')
handler._write(force_text(item['content']))
handler._write(']]>')
handler.endElement('content:encoded')
class FBInstantArticles(LatestEntriesFeed):
feed_type = FBInstantFeed
def item_extra_kwargs(self, item):
if not item:
return {}
language = get_language_from_request(self.request, check_path=True)
key = item.get_cache_key(language, 'feed')
content = cache.get(key)
if not content:
view = PostDetailView.as_view(instant_article=True)
response = view(self.request, slug=item.safe_translation_getter('slug'))
response.render()
content = mark_safe(response.content)
cache.set(key, content, timeout=get_setting('FEED_CACHE_TIMEOUT'))
return {
'content': content,
'slug': item.safe_translation_getter('slug'),
}

View File

@ -1,13 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals
import hashlib
from aldryn_apphooks_config.fields import AppHookConfigField
from aldryn_apphooks_config.managers.parler import AppHookConfigTranslatableManager
from cms.models import CMSPlugin, PlaceholderField
from django.conf import settings as dj_settings
from django.contrib.auth import get_user_model
from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from django.utils import timezone
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.html import escape, strip_tags
@ -196,6 +201,12 @@ class Post(KnockerModel, ModelMeta, TranslatableModel):
def __str__(self):
return self.safe_translation_getter('title')
@property
def guid(self, language=None):
if not language:
language = self.get_current_language()
return hashlib.sha256(self.get_absolute_url(language)).hexdigest()
def save(self, *args, **kwargs):
"""
Handle some auto configuration during save
@ -329,6 +340,9 @@ class Post(KnockerModel, ModelMeta, TranslatableModel):
updated = self.app_config.send_knock_update and self.is_published
return new or updated
def get_cache_key(self, language, prefix):
return 'djangocms-blog:{2}:{0}:{1}'.format(language, self.guid, prefix)
class BasePostPlugin(CMSPlugin):
app_config = AppHookConfigField(
@ -422,3 +436,17 @@ class GenericBlogPlugin(BasePostPlugin):
def __str__(self):
return force_text(_('generic blog plugin'))
@receiver(pre_delete, sender=Post)
def cleanup_post(sender, instance, **kwargs):
for language in instance.get_available_languages():
key = instance.get_cache_key(language, 'feed')
cache.delete(key)
@receiver(post_save, sender=Post)
def cleanup_pagemeta(sender, instance, **kwargs):
for language in instance.get_available_languages():
key = instance.get_cache_key(language, 'feed')
cache.delete(key)

View File

@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals
from django.utils.safestring import mark_safe
MENU_TYPE_COMPLETE = 'complete'
MENU_TYPE_CATEGORIES = 'categories'
MENU_TYPE_POSTS = 'posts'
@ -121,6 +123,8 @@ def get_setting(name):
settings, 'BLOG_CATEGORY_PLUGIN_NAME', _('Categories')),
'BLOG_ARCHIVE_PLUGIN_NAME': getattr(
settings, 'BLOG_ARCHIVE_PLUGIN_NAME', _('Archive')),
'BLOG_FEED_CACHE_TIMEOUT': getattr(
settings, 'BLOG_FEED_CACHE_TIMEOUT', 3600),
}
return default['BLOG_%s' % name]

View File

@ -23,4 +23,4 @@
<li class="tag_{{ forloop.counter }}"><a href="{% url 'djangocms_blog:posts-tagged' tag=tag.slug %}" class="blog-tag blog-tag-{{ tag.count }}">{{ tag.name }}</a>{% if not forloop.last %}, {% endif %}</li>
{% endfor %}
{% endif %}
</ul>
</ul>

View File

@ -0,0 +1,44 @@
{% load thumbnail cms_tags %}
<!doctype html>
<html lang="en" prefix="op: http://media.facebook.com/op#">
<head>
<meta charset="utf-8">
{% block canonical_url %}<link rel="canonical" href="{{ meta.url }}"/>{% endblock canonical_url %}
<meta property="op:markup_version" content="v1.0">
</head>
<body>
<article>
<header>
<h1>{{ post.title }}</h1>
<time class="op-published" datetime="{{ post.date_published.isoformat }}">{{ post.date_published|date:"DATE_FORMAT" }}</time>
<time class="op-modified" dateTime="{{ post.date_modified.isoformat }}">{{ post.date_modified|date:"DATE_FORMAT" }}</time>
<address>
{% if og_author_url %}<a rel="facebook" href="{{ og_author_url }}">{% endif %}
{{ post.author }}
{% if og_author_url %}</a>{% endif %}
</address>
<figure>
<img src="{{ meta.image }}" alt="{{ post.main_image.default_alt_text }}" />
{% if post.main_image.default_caption %}
<figcaption>{{ post.main_image.default_caption }}</figcaption>{% endif %}
</figure>
<h3 class="op-kicker">
{{ post.abstract|striptags }}
</h3>
</header>
{% if post.app_config.use_placeholder %}
<div class="blog-content">{% render_placeholder post.content %}</div>
{% else %}
<div class="blog-content">{% render_model post "post_text" "post_text" %}</div>
{% endif %}
</article>
</body>
</html>

View File

@ -3,7 +3,7 @@ from __future__ import absolute_import, print_function, unicode_literals
from django.conf.urls import url
from .feeds import LatestEntriesFeed, TagFeed
from .feeds import FBInstantArticles, LatestEntriesFeed, TagFeed
from .settings import get_setting
from .views import (
AuthorEntriesView, CategoryEntriesView, PostArchiveView, PostDetailView, PostListView,
@ -27,6 +27,8 @@ urlpatterns = [
PostListView.as_view(), name='posts-latest'),
url(r'^feed/$',
LatestEntriesFeed(), name='posts-latest-feed'),
url(r'^feed/fb/$',
FBInstantArticles(), name='posts-latest-feed-fb'),
url(r'^(?P<year>\d{4})/$',
PostArchiveView.as_view(), name='posts-archive'),
url(r'^(?P<year>\d{4})/(?P<month>\d{1,2})/$',

View File

@ -72,6 +72,14 @@ class PostDetailView(TranslatableSlugMixin, BaseBlogView, DetailView):
base_template_name = 'post_detail.html'
slug_field = 'slug'
view_url_name = 'djangocms_blog:post-detail'
instant_article = False
def get_template_names(self):
if self.instant_article:
template_path = (self.config and self.config.template_prefix) or 'djangocms_blog'
return os.path.join(template_path, 'post_instant_article.html')
else:
return super(PostDetailView, self).get_template_names()
def get_queryset(self):
queryset = self.model._default_manager.all()
@ -88,6 +96,7 @@ class PostDetailView(TranslatableSlugMixin, BaseBlogView, DetailView):
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context['meta'] = self.get_object().as_meta()
context['instant_article'] = self.instant_article
context['use_placeholder'] = get_setting('USE_PLACEHOLDER')
setattr(self.request, get_setting('CURRENT_POST_IDENTIFIER'), self.get_object())
return context