general: add POST support on json cells (#16901)
This commit is contained in:
parent
3eb6d93882
commit
0e5d85fe49
|
@ -27,7 +27,7 @@ from django.apps import apps
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError, PermissionDenied
|
||||
from django.core import serializers
|
||||
from django.db import models
|
||||
from django.db.models.base import ModelBase
|
||||
|
@ -54,6 +54,10 @@ from combo import utils
|
|||
from combo.utils import NothingInCacheException
|
||||
|
||||
|
||||
class PostException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def element_is_visible(element, user=None):
|
||||
if hasattr(element, 'restricted_to_unlogged') and element.restricted_to_unlogged:
|
||||
return bool(user is None or user.is_anonymous())
|
||||
|
@ -826,6 +830,7 @@ class JsonCellBase(CellBase):
|
|||
template_string = None
|
||||
varnames = None
|
||||
force_async = False
|
||||
actions = {}
|
||||
|
||||
_json_content = None
|
||||
|
||||
|
@ -835,7 +840,7 @@ class JsonCellBase(CellBase):
|
|||
def is_visible(self, user=None):
|
||||
return bool(self.url) and super(JsonCellBase, self).is_visible(user=user)
|
||||
|
||||
def get_cell_extra_context(self, context):
|
||||
def get_cell_extra_context(self, context, invalidate_cache=False):
|
||||
extra_context = super(JsonCellBase, self).get_cell_extra_context(context)
|
||||
if self.varnames and context.get('request'):
|
||||
for varname in self.varnames:
|
||||
|
@ -855,6 +860,7 @@ class JsonCellBase(CellBase):
|
|||
cache_duration=self.cache_duration,
|
||||
without_user=True,
|
||||
raise_if_not_cached=not(context.get('synchronous')),
|
||||
invalidate_cache=invalidate_cache,
|
||||
)
|
||||
if json_response.status_code == 200:
|
||||
try:
|
||||
|
@ -878,6 +884,42 @@ class JsonCellBase(CellBase):
|
|||
return 'combo/json-list-cell.html'
|
||||
return 'combo/json-cell.html'
|
||||
|
||||
def post(self, request):
|
||||
if not 'action' in request.POST:
|
||||
raise PermissionDenied()
|
||||
action = request.POST['action']
|
||||
if not action in self.actions:
|
||||
raise PermissionDenied()
|
||||
|
||||
error_message = self.actions[action].get('error-message')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
context = RequestContext(request, {'request': request, 'synchronous': True})
|
||||
try:
|
||||
url = utils.get_templated_url(self.actions[action]['url'], context)
|
||||
except utils.UnknownTemplateVariableError:
|
||||
logger.warning('unknown variable in URL (%s)', self.actions[action]['url'])
|
||||
raise PostException(error_message)
|
||||
|
||||
content = {}
|
||||
for key, value in request.POST.items():
|
||||
if key == 'action':
|
||||
continue
|
||||
content[key] = value
|
||||
|
||||
json_response = utils.requests.post(url,
|
||||
headers={'Accept': 'application/json'},
|
||||
remote_service='auto',
|
||||
json=content,
|
||||
without_user=True)
|
||||
|
||||
if json_response.status_code // 100 != 2: # 2xx
|
||||
logger.error('error POSTing data to URL (%s)', url)
|
||||
raise PostException(error_message)
|
||||
|
||||
if self.cache_duration:
|
||||
self.get_cell_extra_context(context, invalidate_cache=True)
|
||||
|
||||
def render(self, context):
|
||||
if self.force_async and not context.get('synchronous'):
|
||||
raise NothingInCacheException()
|
||||
|
@ -951,6 +993,11 @@ class ConfigJsonCell(JsonCellBase):
|
|||
return settings.JSON_CELL_TYPES[self.key].get('force_async',
|
||||
JsonCellBase.force_async)
|
||||
|
||||
@property
|
||||
def actions(self):
|
||||
return settings.JSON_CELL_TYPES[self.key].get('actions',
|
||||
JsonCellBase.actions)
|
||||
|
||||
@property
|
||||
def template_name(self):
|
||||
return 'combo/json/%s.html' % self.key
|
||||
|
@ -966,9 +1013,9 @@ class ConfigJsonCell(JsonCellBase):
|
|||
(ConfigJsonForm,), {'formdef': formdef})
|
||||
return config_form_class
|
||||
|
||||
def get_cell_extra_context(self, context):
|
||||
def get_cell_extra_context(self, context, **kwargs):
|
||||
context.update(self.parameters) # early push for templated URLs
|
||||
ctx = super(ConfigJsonCell, self).get_cell_extra_context(context)
|
||||
ctx = super(ConfigJsonCell, self).get_cell_extra_context(context, **kwargs)
|
||||
ctx['parameters'] = self.parameters
|
||||
ctx.update(self.parameters)
|
||||
return ctx
|
||||
|
|
|
@ -30,6 +30,7 @@ from django.http import (Http404, HttpResponse, HttpResponseRedirect,
|
|||
from django.shortcuts import render, resolve_url
|
||||
from django.template import RequestContext, Template
|
||||
from django.utils import lorem_ipsum, timezone
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.forms.widgets import Media
|
||||
|
@ -41,7 +42,7 @@ if 'mellon' in settings.INSTALLED_APPS:
|
|||
else:
|
||||
get_idps = lambda: []
|
||||
|
||||
from combo.data.models import CellBase, Page, ParentContentCell, TextCell
|
||||
from combo.data.models import CellBase, PostException, Page, ParentContentCell, TextCell
|
||||
from combo.profile.models import Profile
|
||||
from combo.apps.search.models import SearchCell
|
||||
from combo import utils
|
||||
|
@ -65,6 +66,7 @@ def logout(request, next_page=None):
|
|||
next_page = '/'
|
||||
return HttpResponseRedirect(next_page)
|
||||
|
||||
@csrf_exempt
|
||||
def ajax_page_cell(request, page_pk, cell_reference):
|
||||
try:
|
||||
page = Page.objects.get(id=page_pk)
|
||||
|
@ -80,7 +82,24 @@ def ajax_page_cell(request, page_pk, cell_reference):
|
|||
if not cell.is_visible(request.user):
|
||||
raise PermissionDenied()
|
||||
|
||||
return render_cell(request, cell)
|
||||
exception = None
|
||||
if request.method == 'POST':
|
||||
if not hasattr(cell, 'post'):
|
||||
raise PermissionDenied()
|
||||
try:
|
||||
cell.post(request)
|
||||
except PostException as e:
|
||||
exception = e
|
||||
if not request.is_ajax():
|
||||
messages.error(request, e.message or _('Error sending data.'))
|
||||
|
||||
if not request.is_ajax():
|
||||
return HttpResponseRedirect(cell.page.get_online_url())
|
||||
|
||||
response = render_cell(request, cell)
|
||||
if exception:
|
||||
response['x-error-message'] = exception.message
|
||||
return response
|
||||
|
||||
def render_cell(request, cell):
|
||||
context = RequestContext(request, {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<form method="post" action="{% url 'combo-public-ajax-page-cell' page_pk=cell.page.id cell_reference=cell.get_reference %}">
|
||||
<input type="hidden" name="action" value="create"/>
|
||||
<input name="value">
|
||||
<button>submit</button>
|
||||
</form>
|
|
@ -1,13 +1,19 @@
|
|||
import mock
|
||||
from webtest import TestApp
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import pytest
|
||||
import os
|
||||
import urllib
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import override_settings
|
||||
|
||||
from combo.wsgi import application
|
||||
from combo.data.models import Page, CellBase, TextCell, ParentContentCell, FeedCell, LinkCell
|
||||
from combo.data.models import (Page, CellBase, TextCell, ParentContentCell,
|
||||
FeedCell, LinkCell, ConfigJsonCell)
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
@ -299,3 +305,74 @@ def test_welcome_page(app, admin_user):
|
|||
app.cookiejar.clear()
|
||||
app = login(app)
|
||||
resp = app.get('/', status=200)
|
||||
|
||||
|
||||
def test_post_cell(app):
|
||||
Page.objects.all().delete()
|
||||
page = Page(title='Home', slug='index', template_name='standard')
|
||||
page.save()
|
||||
cell = TextCell(page=page, placeholder='content', text='<p>Foobar</p>', order=0)
|
||||
cell.save()
|
||||
|
||||
# check it's not possible to post to cell that doesn't have POST support.
|
||||
resp = app.post(reverse('combo-public-ajax-page-cell',
|
||||
kwargs={'page_pk': page.id, 'cell_reference': cell.get_reference()}),
|
||||
params={'hello': 'world'},
|
||||
status=403)
|
||||
|
||||
with override_settings(JSON_CELL_TYPES={
|
||||
'test-post-cell': {
|
||||
'name': 'Foobar',
|
||||
'url': 'http://test-post-cell/',
|
||||
'actions': {
|
||||
'create': {
|
||||
'url': 'http://test-post-cell/create/',
|
||||
}
|
||||
}
|
||||
}},
|
||||
TEMPLATE_DIRS=['%s/templates-1' % os.path.abspath(os.path.dirname(__file__))]
|
||||
):
|
||||
cell = ConfigJsonCell(page=page, placeholder='content', order=0)
|
||||
cell.key = 'test-post-cell'
|
||||
cell.save()
|
||||
|
||||
with mock.patch('combo.utils.requests.get') as requests_get:
|
||||
requests_get.return_value = mock.Mock(content=json.dumps({'hello': 'world'}), status_code=200)
|
||||
|
||||
resp = app.get('/', status=200)
|
||||
resp.form['value'] = 'plop'
|
||||
|
||||
with mock.patch('combo.utils.requests.post') as requests_post:
|
||||
requests_post.return_value = mock.Mock(content=json.dumps({'err': 0}), status_code=200)
|
||||
resp2 = resp.form.submit()
|
||||
assert requests_post.call_args[0][0] == 'http://test-post-cell/create/'
|
||||
assert requests_post.call_args[1]['json'] == {'value': 'plop'}
|
||||
assert resp2.location == 'http://testserver/'
|
||||
|
||||
# check ajax call
|
||||
with mock.patch('combo.utils.requests.post') as requests_post:
|
||||
requests_post.return_value = mock.Mock(content=json.dumps({'err': 0}), status_code=200)
|
||||
resp2 = resp.form.submit(headers={'x-requested-with': 'XMLHttpRequest'})
|
||||
assert requests_post.call_args[0][0] == 'http://test-post-cell/create/'
|
||||
assert requests_post.call_args[1]['json'] == {'value': 'plop'}
|
||||
assert resp2.content.startswith('<form')
|
||||
|
||||
# check error on POST
|
||||
with mock.patch('combo.utils.requests.post') as requests_post:
|
||||
requests_post.return_value = mock.Mock(content=json.dumps({'err': 0}), status_code=400)
|
||||
resp2 = resp.form.submit()
|
||||
assert resp2.location == 'http://testserver/'
|
||||
resp2 = resp2.follow()
|
||||
assert 'Error sending data.' in resp2.content
|
||||
|
||||
settings.JSON_CELL_TYPES['test-post-cell']['actions']['create']['error-message'] = 'Failed to create stuff.'
|
||||
resp2 = resp.form.submit()
|
||||
assert resp2.location == 'http://testserver/'
|
||||
resp2 = resp2.follow()
|
||||
assert 'Failed to create stuff.' in resp2.content
|
||||
|
||||
with mock.patch('combo.utils.requests.post') as requests_post:
|
||||
requests_post.return_value = mock.Mock(content=json.dumps({'err': 0}), status_code=400)
|
||||
resp2 = resp.form.submit(headers={'x-requested-with': 'XMLHttpRequest'})
|
||||
assert resp2.content.startswith('<form')
|
||||
assert resp2.headers['x-error-message'] == 'Failed to create stuff.'
|
||||
|
|
Loading…
Reference in New Issue