general: add new json content cell (#10639)
This commit is contained in:
parent
4aaad3ea8d
commit
0820f8870e
|
@ -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',
|
||||
},
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{% if title %}<h2>{{title}}</h2>{% endif %}
|
||||
<!--
|
||||
{{json}}
|
||||
-->
|
|
@ -0,0 +1,3 @@
|
|||
{% load i18n %}
|
||||
{% if title %}<h2>{{title}}</h2>{% endif %}
|
||||
<p>{% trans "Technical error getting data." %}</p>
|
|
@ -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>
|
|
@ -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 == ''
|
||||
|
|
Loading…
Reference in New Issue