Add support for FB Instant Articles
This commit is contained in:
parent
b595834f9d
commit
542774ce95
|
@ -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'),
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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})/$',
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue