Allow to limit access to form submissions (#3016)

Add the filter_form_submissions_for_user hook

Thanks @kaedroho for the code review and docs.
pull/3016/merge
Mikalai Radchuk 2016-09-21 13:40:39 +03:00
rodzic 9a57e39cfd
commit fb93a6d6b9
3 zmienionych plików z 128 dodań i 5 usunięć

Wyświetl plik

@ -215,7 +215,36 @@ Hooks for building new areas of the admin interface (alongside pages, images, do
``register_permissions``
~~~~~~~~~~~~~~~~~~~~~~~~
Return a queryset of Permission objects to be shown in the Groups administration area.
Return a queryset of ``Permission`` objects to be shown in the Groups administration area.
.. _filter_form_submissions_for_user:
``filter_form_submissions_for_user``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows access to form submissions to be customised on a per-user, per-form basis.
This hook takes two parameters:
- The user attempting to access form submissions
- A ``QuerySet`` of form pages
The hook must return a ``QuerySet`` containing a subset of these form pages which the user is allowed to access the submissions for.
For example, to prevent non-superusers from accessing form submissions:
.. code-block:: python
from wagtail.wagtailcore import hooks
@hooks.register('filter_form_submissions_for_user')
def construct_forms_for_user(user, queryset):
if not user.is_superuser:
queryset = queryset.none()
return queryset
Editor interface

Wyświetl plik

@ -15,6 +15,7 @@ from unidecode import unidecode
from wagtail.wagtailadmin.edit_handlers import FieldPanel
from wagtail.wagtailadmin.utils import send_mail
from wagtail.wagtailcore import hooks
from wagtail.wagtailcore.models import Orderable, Page, UserPagePermissionsProxy, get_page_models
from .forms import FormBuilder, WagtailAdminFormPageForm
@ -140,8 +141,14 @@ def get_forms_for_user(user):
"""
Return a queryset of form pages that this user is allowed to access the submissions for
"""
editable_pages = UserPagePermissionsProxy(user).editable_pages()
return editable_pages.filter(content_type__in=get_form_types())
editable_forms = UserPagePermissionsProxy(user).editable_pages()
editable_forms = editable_forms.filter(content_type__in=get_form_types())
# Apply hooks
for fn in hooks.get_hooks('filter_form_submissions_for_user'):
editable_forms = fn(user, editable_forms)
return editable_forms
class AbstractForm(Page):

Wyświetl plik

@ -50,7 +50,7 @@ class TestFormResponsesPanel(TestCase):
self.assertEqual('', result)
class TestFormsIndex(TestCase):
class TestFormsIndex(TestCase, WagtailTestUtils):
fixtures = ['test.json']
def setUp(self):
@ -133,6 +133,25 @@ class TestFormsIndex(TestCase):
# Check that the user can see the form page
self.assertIn(self.form_page, response.context['form_pages'])
def test_cant_see_forms_after_filter_form_submissions_for_user_hook(self):
# Hook allows to see forms only to superusers
def construct_forms_for_user(user, queryset):
if not user.is_superuser:
queryset = queryset.none()
return queryset
response = self.client.get(reverse('wagtailforms:index'))
# Check that an user can see the form page
self.assertIn(self.form_page, response.context['form_pages'])
with self.register_hook('filter_form_submissions_for_user', construct_forms_for_user):
response = self.client.get(reverse('wagtailforms:index'))
# Check that an user can't see the form page
self.assertNotIn(self.form_page, response.context['form_pages'])
# TODO: Rename to TestFormsSubmissionsList
class TestFormsSubmissions(TestCase, WagtailTestUtils):
@ -185,6 +204,24 @@ class TestFormsSubmissions(TestCase, WagtailTestUtils):
self.assertTemplateUsed(response, 'wagtailforms/index_submissions.html')
self.assertEqual(len(response.context['data_rows']), 2)
def test_list_submissions_after_filter_form_submissions_for_user_hook(self):
# Hook forbids to delete form submissions for everyone
def construct_forms_for_user(user, queryset):
return queryset.none()
response = self.client.get(reverse('wagtailforms:list_submissions', args=(self.form_page.id,)))
# An user can see form submissions without the hook
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'wagtailforms/index_submissions.html')
self.assertEqual(len(response.context['data_rows']), 2)
with self.register_hook('filter_form_submissions_for_user', construct_forms_for_user):
response = self.client.get(reverse('wagtailforms:list_submissions', args=(self.form_page.id,)))
# An user cant' see form submissions with the hook
self.assertEqual(response.status_code, 403)
def test_list_submissions_filtering_date_from(self):
response = self.client.get(
reverse('wagtailforms:list_submissions', args=(self.form_page.id,)), {'date_from': '01/01/2014'}
@ -268,6 +305,33 @@ class TestFormsSubmissions(TestCase, WagtailTestUtils):
self.assertEqual(data_lines[1], '2013-01-01 12:00:00+00:00,old@example.com,this is a really old message,None\r')
self.assertEqual(data_lines[2], '2014-01-01 12:00:00+00:00,new@example.com,this is a fairly new message,None\r')
def test_list_submissions_csv_export_after_filter_form_submissions_for_user_hook(self):
# Hook forbids to delete form submissions for everyone
def construct_forms_for_user(user, queryset):
return queryset.none()
response = self.client.get(
reverse('wagtailforms:list_submissions', args=(self.form_page.id,)),
{'action': 'CSV'}
)
# An user can export form submissions without the hook
self.assertEqual(response.status_code, 200)
data_lines = response.content.decode().split("\n")
self.assertEqual(data_lines[0], 'Submission date,Your email,Your message,Your choices\r')
self.assertEqual(data_lines[1], '2013-01-01 12:00:00+00:00,old@example.com,this is a really old message,None\r')
self.assertEqual(data_lines[2], '2014-01-01 12:00:00+00:00,new@example.com,this is a fairly new message,None\r')
with self.register_hook('filter_form_submissions_for_user', construct_forms_for_user):
response = self.client.get(
reverse('wagtailforms:list_submissions', args=(self.form_page.id,)),
{'action': 'CSV'}
)
# An user can't export form submission with the hook
self.assertEqual(response.status_code, 403)
def test_list_submissions_csv_export_with_date_from_filtering(self):
response = self.client.get(
reverse('wagtailforms:list_submissions', args=(self.form_page.id,)),
@ -365,7 +429,7 @@ class TestFormsSubmissions(TestCase, WagtailTestUtils):
# TODO: add TestCustomFormsSubmissionsList
class TestDeleteFormSubmission(TestCase):
class TestDeleteFormSubmission(TestCase, WagtailTestUtils):
fixtures = ['test.json']
def setUp(self):
@ -408,6 +472,29 @@ class TestDeleteFormSubmission(TestCase):
# Check that the deletion has not happened
self.assertEqual(FormSubmission.objects.count(), 2)
def test_delete_submission_after_filter_form_submissions_for_user_hook(self):
# Hook forbids to delete form submissions for everyone
def construct_forms_for_user(user, queryset):
return queryset.none()
with self.register_hook('filter_form_submissions_for_user', construct_forms_for_user):
response = self.client.post(reverse(
'wagtailforms:delete_submission',
args=(self.form_page.id, FormSubmission.objects.first().id)
))
# An user can't delete a from submission with the hook
self.assertEqual(response.status_code, 403)
self.assertEqual(FormSubmission.objects.count(), 2)
# An user can delete a form submission without the hook
response = self.client.post(reverse(
'wagtailforms:delete_submission',
args=(self.form_page.id, FormSubmission.objects.first().id)
))
self.assertEqual(FormSubmission.objects.count(), 1)
self.assertRedirects(response, reverse("wagtailforms:list_submissions", args=(self.form_page.id,)))
# TODO: add TestDeleteCustomFormSubmission