Skip to content

Commit 0d4afce

Browse files
authored
Merge pull request #137 from novafloss/134-poc-post-save-callback
Added a callback mechanism when creating / updating a form via the builder
2 parents 162ea65 + ae5cfd6 commit 0d4afce

14 files changed

+640
-48
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Release 0.5.0dev (unreleased)
66
=============================
77

88
* Fix the demo site to work with Django 1.8 *and* with logged-in users (#146).
9+
* Added a callback on success / failure mechanism (#134).
910

1011

1112
Release 0.4.0 (2017-04-01)

demo/demo/__init__.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import logging
2+
3+
from django.contrib import messages
4+
5+
logger = logging.getLogger(__name__)
6+
7+
8+
class DemoCallbackException(Exception):
9+
"A dummy callback exception"
10+
11+
12+
def callback_save(*args, **kwargs):
13+
"""
14+
This function will be called as a dummy callback on form post-save/create.
15+
16+
It doesn't do much, and that's fine.
17+
"""
18+
return True
19+
20+
21+
def callback_exception(*args, **kwargs):
22+
"""
23+
This function will be called-back on form post-save/create
24+
25+
It'll raise an exception
26+
"""
27+
raise DemoCallbackException()
28+
29+
30+
def callback_success_message(request):
31+
"""
32+
This function will be called a form post-save/create.
33+
34+
It adds a logging message
35+
36+
"""
37+
msg = 'Sucessfully recorded form :)'
38+
logger.info(msg)
39+
messages.info(request._request, msg)
40+
41+
42+
def callback_fail_message(request):
43+
"""
44+
This function will be called a form post-save/create.
45+
46+
It adds a logging message (error)
47+
"""
48+
msg = 'Form storing has failed :('
49+
logger.error(msg)
50+
messages.error(request._request, msg)

demo/demo/builder/templates/formidable/formidable_list.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
<div class="title">
55
<h1>May the Form be with you!</h1>
66
</div>
7+
8+
{% if messages %}
9+
<ul class="messages">
10+
{% for message in messages %}
11+
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
12+
{% endfor %}
13+
</ul>
14+
{% endif %}
15+
716
<div>
817
<table>
918
<thead>

demo/demo/settings.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,10 @@
115115
]
116116

117117
CORS_ORIGIN_ALLOW_ALL = True
118+
119+
120+
# Formidable call back post-create/update
121+
FORMIDABLE_POST_CREATE_CALLBACK_SUCCESS = 'demo.callback_success_message'
122+
FORMIDABLE_POST_UPDATE_CALLBACK_SUCCESS = 'demo.callback_success_message'
123+
FORMIDABLE_POST_CREATE_CALLBACK_FAIL = 'demo.callback_fail_message'
124+
FORMIDABLE_POST_UPDATE_CALLBACK_FAIL = 'demo.callback_fail_message'

demo/tests/__init__.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
form_data = {
3+
"label": "test create",
4+
"description": "my first formidable by api",
5+
"fields": [
6+
{
7+
"label": "hello",
8+
"slug": "textslug",
9+
"type_id": "text",
10+
"placeholder": None,
11+
"help_text": None,
12+
"default": None,
13+
"accesses": [
14+
{"access_id": "padawan", "level": "REQUIRED"},
15+
{"access_id": "jedi", "level": "EDITABLE"},
16+
{"access_id": "jedi-master", "level": "READONLY"},
17+
{"access_id": "human", "level": "HIDDEN"},
18+
]
19+
},
20+
]
21+
}
22+
23+
form_data_items = {
24+
"label": "test create",
25+
"description": "my first formidable by api",
26+
"fields": [{
27+
"label": "my_dropdwon",
28+
"slug": "dropdown_slug",
29+
"type_id": "dropdown",
30+
"placeholder": None,
31+
"help_text": "Lesfrites c'est bon",
32+
"default": None,
33+
"accesses": [
34+
{"access_id": "padawan", "level": "REQUIRED"},
35+
{"access_id": "jedi", "level": "EDITABLE"},
36+
{"access_id": "jedi-master", "level": "READONLY"},
37+
{"access_id": "human", "level": "HIDDEN"},
38+
],
39+
"items": [
40+
{'value': 'tuto', 'label': 'toto'},
41+
{'value': 'plop', 'label': 'coin'},
42+
],
43+
"multiple": False
44+
}]
45+
}
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
from copy import deepcopy
5+
from django.core.exceptions import ImproperlyConfigured
6+
from django.core.urlresolvers import reverse
7+
from django.conf import settings
8+
from django.test import TestCase, override_settings
9+
10+
from rest_framework.test import APITestCase
11+
12+
from formidable.models import Formidable
13+
from formidable.views import check_callback_configuration
14+
15+
from . import form_data, form_data_items
16+
17+
import six
18+
if six.PY3:
19+
from unittest.mock import patch
20+
else:
21+
from mock import patch
22+
23+
CALLBACK = 'demo.callback_save'
24+
CALLBACK_EXCEPTION = 'demo.callback_exception'
25+
26+
27+
class CreateFormTestCase(APITestCase):
28+
29+
@override_settings(
30+
FORMIDABLE_POST_CREATE_CALLBACK_SUCCESS=CALLBACK,
31+
FORMIDABLE_POST_CREATE_CALLBACK_FAIL=CALLBACK
32+
)
33+
def test_do_no_call_on_get(self):
34+
with patch(CALLBACK) as patched_callback:
35+
res = self.client.get(
36+
reverse('formidable:form_create')
37+
)
38+
self.assertEqual(res.status_code, 405)
39+
# No call on GET
40+
self.assertEqual(patched_callback.call_count, 0)
41+
42+
@override_settings(FORMIDABLE_POST_CREATE_CALLBACK_SUCCESS=CALLBACK)
43+
def test_create_no_error_post(self):
44+
with patch(CALLBACK) as patched_callback:
45+
res = self.client.post(
46+
reverse('formidable:form_create'), form_data, format='json'
47+
)
48+
self.assertEqual(res.status_code, 201)
49+
self.assertEqual(patched_callback.call_count, 1)
50+
51+
@override_settings(FORMIDABLE_POST_CREATE_CALLBACK_FAIL=CALLBACK)
52+
def test_create_error_post(self):
53+
with patch(CALLBACK) as patched_callback:
54+
form_data_without_items = deepcopy(form_data_items)
55+
form_data_without_items['fields'][0].pop('items')
56+
57+
res = self.client.post(
58+
reverse('formidable:form_create'), form_data_without_items,
59+
format='json'
60+
)
61+
self.assertEquals(res.status_code, 400)
62+
self.assertEqual(patched_callback.call_count, 1)
63+
64+
@override_settings(
65+
FORMIDABLE_POST_CREATE_CALLBACK_SUCCESS=CALLBACK_EXCEPTION
66+
)
67+
def test_create_exception(self):
68+
# The called function raises an error, but the treatment proceeds
69+
# as if nothing has happened
70+
res = self.client.post(
71+
reverse('formidable:form_create'), form_data, format='json'
72+
)
73+
self.assertEqual(res.status_code, 201)
74+
75+
@override_settings(
76+
FORMIDABLE_POST_CREATE_CALLBACK_SUCCESS=CALLBACK_EXCEPTION
77+
)
78+
def test_create_exception_logger(self):
79+
# The called function raises an error, but the treatment proceeds
80+
# as if nothing has happened
81+
with patch('formidable.views.logger.error') as logger_error:
82+
res = self.client.post(
83+
reverse('formidable:form_create'), form_data, format='json'
84+
)
85+
self.assertEqual(res.status_code, 201)
86+
self.assertEqual(logger_error.call_count, 1)
87+
88+
@override_settings(FORMIDABLE_POST_CREATE_CALLBACK_SUCCESS='non.existent')
89+
def test_create_callback_is_non_existent(self):
90+
# A non-existing module is treated separately.
91+
with patch('formidable.views.logger.error') as logger_error:
92+
res = self.client.post(
93+
reverse('formidable:form_create'), form_data, format='json'
94+
)
95+
self.assertEqual(res.status_code, 201)
96+
self.assertEqual(logger_error.call_count, 1)
97+
98+
99+
class UpdateFormTestCase(APITestCase):
100+
101+
def setUp(self):
102+
super(UpdateFormTestCase, self).setUp()
103+
self.form = Formidable.objects.create(
104+
label='test', description='test'
105+
)
106+
107+
@override_settings(
108+
FORMIDABLE_POST_UPDATE_CALLBACK_SUCCESS=CALLBACK,
109+
FORMIDABLE_POST_UPDATE_CALLBACK_FAIL=CALLBACK
110+
)
111+
def test_do_no_call_on_get(self):
112+
with patch(CALLBACK) as patched_callback:
113+
res = self.client.get(
114+
reverse('formidable:form_detail', args=[self.form.id])
115+
)
116+
self.assertEqual(res.status_code, 200)
117+
# No call on GET
118+
self.assertEqual(patched_callback.call_count, 0)
119+
120+
@override_settings(FORMIDABLE_POST_UPDATE_CALLBACK_SUCCESS=CALLBACK)
121+
def test_update_no_error_post(self):
122+
with patch(CALLBACK) as patched_callback:
123+
res = self.client.put(
124+
reverse('formidable:form_detail', args=[self.form.id]),
125+
form_data, format='json'
126+
)
127+
self.assertEqual(res.status_code, 200)
128+
self.assertEqual(patched_callback.call_count, 1)
129+
130+
@override_settings(FORMIDABLE_POST_UPDATE_CALLBACK_FAIL=CALLBACK)
131+
def test_update_error_post(self):
132+
with patch(CALLBACK) as patched_callback:
133+
form_data_without_items = deepcopy(form_data_items)
134+
form_data_without_items['fields'][0].pop('items')
135+
136+
res = self.client.put(
137+
reverse('formidable:form_detail', args=[self.form.id]),
138+
form_data_without_items, format='json'
139+
)
140+
self.assertEquals(res.status_code, 400)
141+
self.assertEqual(patched_callback.call_count, 1)
142+
143+
@override_settings(
144+
FORMIDABLE_POST_UPDATE_CALLBACK_SUCCESS=CALLBACK_EXCEPTION
145+
)
146+
def test_update_exception(self):
147+
# The called function raises an error, but the treatment proceeds
148+
# as if nothing has happened
149+
res = self.client.put(
150+
reverse('formidable:form_detail', args=[self.form.id]),
151+
form_data, format='json'
152+
)
153+
self.assertEqual(res.status_code, 200)
154+
155+
@override_settings(
156+
FORMIDABLE_POST_UPDATE_CALLBACK_SUCCESS=CALLBACK_EXCEPTION
157+
)
158+
def test_update_exception_logger(self):
159+
# The called function raises an error, but the treatment proceeds
160+
# as if nothing has happened
161+
with patch('formidable.views.logger.error') as logger_error:
162+
res = self.client.put(
163+
reverse('formidable:form_detail', args=[self.form.id]),
164+
form_data, format='json'
165+
)
166+
self.assertEqual(res.status_code, 200)
167+
self.assertEqual(logger_error.call_count, 1)
168+
169+
@override_settings(FORMIDABLE_POST_UPDATE_CALLBACK_SUCCESS='non.existent')
170+
def test_update_callback_is_non_existent(self):
171+
# A non-existing module is treated separately.
172+
with patch('formidable.views.logger.error') as logger_error:
173+
res = self.client.put(
174+
reverse('formidable:form_detail', args=[self.form.id]),
175+
form_data, format='json'
176+
)
177+
self.assertEqual(res.status_code, 200)
178+
self.assertEqual(logger_error.call_count, 1)
179+
180+
181+
class ConfigurationLoadingTestCases(TestCase):
182+
183+
@override_settings()
184+
def test_all_deleted(self):
185+
del settings.FORMIDABLE_POST_UPDATE_CALLBACK_SUCCESS
186+
del settings.FORMIDABLE_POST_UPDATE_CALLBACK_FAIL
187+
del settings.FORMIDABLE_POST_CREATE_CALLBACK_SUCCESS
188+
del settings.FORMIDABLE_POST_CREATE_CALLBACK_FAIL
189+
self.assertTrue(check_callback_configuration())
190+
191+
@override_settings(
192+
FORMIDABLE_POST_UPDATE_CALLBACK_SUCCESS=None,
193+
FORMIDABLE_POST_UPDATE_CALLBACK_FAIL=None,
194+
FORMIDABLE_POST_CREATE_CALLBACK_SUCCESS=None,
195+
FORMIDABLE_POST_CREATE_CALLBACK_FAIL=None
196+
)
197+
def test_all_none(self):
198+
self.assertTrue(check_callback_configuration())
199+
200+
@override_settings(
201+
FORMIDABLE_POST_UPDATE_CALLBACK_SUCCESS='',
202+
FORMIDABLE_POST_UPDATE_CALLBACK_FAIL='',
203+
FORMIDABLE_POST_CREATE_CALLBACK_SUCCESS='',
204+
FORMIDABLE_POST_CREATE_CALLBACK_FAIL=''
205+
)
206+
def test_all_empty(self):
207+
self.assertTrue(check_callback_configuration())
208+
209+
@override_settings(
210+
FORMIDABLE_POST_UPDATE_CALLBACK_SUCCESS='non.existing',
211+
)
212+
def test_update_success_unknown(self):
213+
with self.assertRaises(ImproperlyConfigured):
214+
check_callback_configuration()
215+
216+
@override_settings(
217+
FORMIDABLE_POST_UPDATE_CALLBACK_FAIL='non.existing',
218+
)
219+
def test_update_fail_unknown(self):
220+
with self.assertRaises(ImproperlyConfigured):
221+
check_callback_configuration()
222+
223+
@override_settings(
224+
FORMIDABLE_POST_CREATE_CALLBACK_SUCCESS='non.existing',
225+
)
226+
def test_create_success_unknown(self):
227+
with self.assertRaises(ImproperlyConfigured):
228+
check_callback_configuration()
229+
230+
@override_settings(
231+
FORMIDABLE_POST_CREATE_CALLBACK_FAIL='non.existing',
232+
)
233+
def test_create_fail_unknown(self):
234+
with self.assertRaises(ImproperlyConfigured):
235+
check_callback_configuration()

0 commit comments

Comments
 (0)