general: add new json content cell (#10639)

This commit is contained in:
Frédéric Péters 2017-02-10 08:32:58 +01:00
parent 4aaad3ea8d
commit 0820f8870e
6 changed files with 191 additions and 1 deletions

View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('data', '0020_auto_20160928_1152'),
]
operations = [
migrations.CreateModel(
name='JsonCell',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('placeholder', models.CharField(max_length=20)),
('order', models.PositiveIntegerField()),
('slug', models.SlugField(verbose_name='Slug', blank=True)),
('extra_css_class', models.CharField(max_length=100, verbose_name='Extra classes for CSS styling', blank=True)),
('public', models.BooleanField(default=True, verbose_name='Public')),
('restricted_to_unlogged', models.BooleanField(default=False, verbose_name='Restrict to unlogged users')),
('title', models.CharField(max_length=150, verbose_name='Title', blank=True)),
('url', models.URLField(verbose_name='URL', blank=True)),
('template_string', models.TextField(null=True, verbose_name='Template', blank=True)),
('cache_duration', models.PositiveIntegerField(default=60, verbose_name='Cache duration')),
('groups', models.ManyToManyField(to='auth.Group', verbose_name='Groups', blank=True)),
('page', models.ForeignKey(to='data.Page')),
],
options={
'verbose_name': 'JSON Feed',
},
),
]

View File

@ -17,6 +17,7 @@
import feedparser
import hashlib
import json
import logging
import os
import requests
import subprocess
@ -37,6 +38,7 @@ from django.utils.safestring import mark_safe
from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _
from django.forms.widgets import MediaDefiningClass
from django.template import Context, Template
from ckeditor.fields import RichTextField
import cmsplugin_blurp.utils
@ -770,3 +772,74 @@ class ParentContentCell(CellBase):
def render(self, context):
return ''
@register_cell_class
class JsonCell(CellBase):
title = models.CharField(_('Title'), max_length=150, blank=True)
url = models.URLField(_('URL'), blank=True)
template_string = models.TextField(_('Template'), blank=True, null=True)
cache_duration = models.PositiveIntegerField(
_('Cache duration'), default=60)
_json_content = None
class Meta:
verbose_name = _('JSON Feed')
def is_visible(self, user=None):
return bool(self.url) and super(JsonCell, self).is_visible(user=user)
def get_cell_extra_context(self, context):
context = super(JsonCell, self).get_cell_extra_context(context)
url = utils.get_templated_url(self.url)
cache_key = self.cache_key()
json_content = cache.get(cache_key)
self._json_content = None
if not json_content:
json_response = requests.get(url,
headers={'Accept': 'application/json'},
remote_service='auto')
if json_response.status_code == 200:
json_content = json_response.content
try:
self._json_content = json.loads(json_content)
except ValueError:
json_content = None
logger = logging.getLogger(__name__)
logger.error('invalid json content')
else:
cache.set(cache_key, json_content, self.cache_duration)
else:
self._json_content = json.loads(json_content)
context['json'] = self._json_content
return context
def cache_key(self):
return hashlib.md5(utils.get_templated_url(self.url)).hexdigest()
@property
def template_name(self):
json_content = self._json_content
if json_content is None:
return 'combo/json-error-cell.html'
if json_content.get('data'):
if isinstance(json_content['data'], list):
first_element = json_content['data'][0]
if isinstance(first_element, dict):
if 'url' in first_element and 'text' in first_element:
return 'combo/json-list-cell.html'
return 'combo/json-cell.html'
def render(self, context):
json_content = cache.get(self.cache_key())
if not context.get('synchronous') and json_content is None:
raise NothingInCacheException()
context.update(self.get_cell_extra_context(context))
if self.template_string:
tmpl = Template(self.template_string)
return tmpl.render(context)
return super(JsonCell, self).render(context)

View File

@ -0,0 +1,4 @@
{% if title %}<h2>{{title}}</h2>{% endif %}
<!--
{{json}}
-->

View File

@ -0,0 +1,3 @@
{% load i18n %}
{% if title %}<h2>{{title}}</h2>{% endif %}
<p>{% trans "Technical error getting data." %}</p>

View File

@ -0,0 +1,9 @@
<div class="links-list">
{% if title %}<h2>{{title}}</h2>{% endif %}
{% for row in json.data %}
<ul>
<li><a href="{{row.url}}">{{row.text}}</a></li>
</ul>
{% endfor %}
</div>
</div>

View File

@ -1,10 +1,16 @@
import json
import mock
import os
import pytest
import requests
from combo.data.models import Page, CellBase, TextCell, LinkCell
from combo.data.models import Page, CellBase, TextCell, LinkCell, JsonCell
from django.forms.widgets import Media
from django.template import Context
from django.test import override_settings
from combo.utils import NothingInCacheException
pytestmark = pytest.mark.django_db
@ -115,3 +121,63 @@ def test_variant_templates():
assert cell.render(ctx).strip() == '<div class="XXX"><p>foobar</p></div>'
assert cell.render(ctx).strip() == '<p>foobar</p>'
def mocked_request(*args, **kwargs):
pass
def test_json_cell():
page = Page(title='example page', slug='example-page')
page.save()
cell = JsonCell()
cell.page = page
cell.title = 'Example Site'
cell.order = 0
cell.save()
cell._json_content = None
assert cell.template_name == 'combo/json-error-cell.html'
cell._json_content = {}
assert cell.template_name == 'combo/json-cell.html'
cell._json_content = {'data': []}
assert cell.template_name == 'combo/json-cell.html'
cell._json_content = {'data': [{'url': 'xxx', 'text': 'xxx'}]}
assert cell.template_name == 'combo/json-list-cell.html'
cell._json_content = {'data': [{'foo': 'xxx', 'bar': 'xxx'}]}
assert cell.template_name == 'combo/json-cell.html'
with mock.patch('combo.data.models.requests.get') as requests_get:
data = {'data': [{'url': 'xxx', 'text': 'xxx'}]}
requests_get.return_value = mock.Mock(content=json.dumps(data), status_code=200)
context = cell.get_cell_extra_context({})
assert context['json'] == data
cell.url = 'http://test2'
requests_get.return_value = mock.Mock(content=json.dumps(data), status_code=404)
context = cell.get_cell_extra_context({})
assert context['json'] is None
with pytest.raises(NothingInCacheException):
cell.url = 'http://test3'
cell.render({})
with mock.patch('combo.data.models.requests.get') as requests_get:
data = {'data': [{'url': 'http://a.b', 'text': 'xxx'}]}
requests_get.return_value = mock.Mock(content=json.dumps(data), status_code=200)
cell.url = 'http://test4'
result = cell.render(Context({'synchronous': True}))
assert 'http://a.b' in result
cell.template_string = '{{json.data.0.text}}'
result = cell.render(Context({'synchronous': True}))
assert result == 'xxx'
with mock.patch('combo.data.models.requests.get') as requests_get:
requests_get.return_value = mock.Mock(content='garbage', status_code=200)
cell.url = 'http://test5'
result = cell.render(Context({'synchronous': True}))
assert result == ''