diff --git a/djangocms_blog/feeds.py b/djangocms_blog/feeds.py index 7583018..95635f6 100644 --- a/djangocms_blog/feeds.py +++ b/djangocms_blog/feeds.py @@ -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('') + 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'), + } diff --git a/djangocms_blog/models.py b/djangocms_blog/models.py index 350fe45..0e5a3ea 100644 --- a/djangocms_blog/models.py +++ b/djangocms_blog/models.py @@ -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) diff --git a/djangocms_blog/settings.py b/djangocms_blog/settings.py index 80ee1a8..3e0c203 100644 --- a/djangocms_blog/settings.py +++ b/djangocms_blog/settings.py @@ -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] diff --git a/djangocms_blog/templates/djangocms_blog/includes/blog_meta.html b/djangocms_blog/templates/djangocms_blog/includes/blog_meta.html index 37eb62a..0705dc2 100644 --- a/djangocms_blog/templates/djangocms_blog/includes/blog_meta.html +++ b/djangocms_blog/templates/djangocms_blog/includes/blog_meta.html @@ -23,4 +23,4 @@
  • {{ tag.name }}{% if not forloop.last %}, {% endif %}
  • {% endfor %} {% endif %} - \ No newline at end of file + diff --git a/djangocms_blog/templates/djangocms_blog/post_instant_article.html b/djangocms_blog/templates/djangocms_blog/post_instant_article.html new file mode 100644 index 0000000..5e3247c --- /dev/null +++ b/djangocms_blog/templates/djangocms_blog/post_instant_article.html @@ -0,0 +1,44 @@ +{% load thumbnail cms_tags %} + + + + + {% block canonical_url %}{% endblock canonical_url %} + + + +
    +
    +

    {{ post.title }}

    + + + + + +
    + {% if og_author_url %}{% endif %} + {{ post.author }} + {% if og_author_url %}{% endif %} +
    + +
    + {{ post.main_image.default_alt_text }} + {% if post.main_image.default_caption %} +
    {{ post.main_image.default_caption }}
    {% endif %} +
    + +

    + {{ post.abstract|striptags }} +

    + +
    + + {% if post.app_config.use_placeholder %} +
    {% render_placeholder post.content %}
    + {% else %} +
    {% render_model post "post_text" "post_text" %}
    + {% endif %} + +
    + + diff --git a/djangocms_blog/urls.py b/djangocms_blog/urls.py index 14ad98f..84b9db2 100644 --- a/djangocms_blog/urls.py +++ b/djangocms_blog/urls.py @@ -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\d{4})/$', PostArchiveView.as_view(), name='posts-archive'), url(r'^(?P\d{4})/(?P\d{1,2})/$', diff --git a/djangocms_blog/views.py b/djangocms_blog/views.py index d6b80f2..6e49205 100644 --- a/djangocms_blog/views.py +++ b/djangocms_blog/views.py @@ -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