wcs: cell for forms the user can access (#55041)
This commit is contained in:
parent
86be2bb861
commit
18bb48c5b3
|
@ -31,6 +31,7 @@ from .models import (
|
|||
WcsCurrentFormsCell,
|
||||
WcsFormCell,
|
||||
WcsFormsOfCategoryCell,
|
||||
WcsFormsUserCanAccessCell,
|
||||
)
|
||||
from .utils import get_wcs_options, get_wcs_services
|
||||
|
||||
|
@ -184,6 +185,17 @@ class WcsCareFormsCellForm(WcsFormsMixin, forms.ModelForm):
|
|||
self._init_categories()
|
||||
|
||||
|
||||
class WcsFormsUserCanAccessCell(WcsFormsMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = WcsFormsUserCanAccessCell
|
||||
fields = ['wcs_site', 'categories']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._init_wcs_site()
|
||||
self._init_categories()
|
||||
|
||||
|
||||
class BackofficeSubmissionCellForm(WcsFormsMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = WcsCurrentDraftsCell
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import combo.apps.wcs.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('data', '0046_text_to_jsonb'),
|
||||
('auth', '0008_alter_user_username_max_length'),
|
||||
('wcs', '0031_auto_20210723_1318'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='WcsFormsUserCanAccessCell',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
('placeholder', models.CharField(max_length=20)),
|
||||
('order', models.PositiveIntegerField()),
|
||||
('slug', models.SlugField(blank=True, verbose_name='Slug')),
|
||||
(
|
||||
'extra_css_class',
|
||||
models.CharField(
|
||||
blank=True, max_length=100, verbose_name='Extra classes for CSS styling'
|
||||
),
|
||||
),
|
||||
('public', models.BooleanField(default=True, verbose_name='Public')),
|
||||
(
|
||||
'restricted_to_unlogged',
|
||||
models.BooleanField(default=False, verbose_name='Restrict to unlogged users'),
|
||||
),
|
||||
('last_update_timestamp', models.DateTimeField(auto_now=True)),
|
||||
('wcs_site', models.CharField(blank=True, max_length=50, verbose_name='Site')),
|
||||
(
|
||||
'categories',
|
||||
django.contrib.postgres.fields.jsonb.JSONField(
|
||||
blank=True, default=dict, verbose_name='Categories'
|
||||
),
|
||||
),
|
||||
('groups', models.ManyToManyField(blank=True, to='auth.Group', verbose_name='Groups')),
|
||||
('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='data.Page')),
|
||||
(
|
||||
'template_name',
|
||||
models.CharField(blank=True, max_length=50, null=True, verbose_name='Cell Template'),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Forms to which user has access',
|
||||
},
|
||||
bases=(
|
||||
combo.apps.wcs.models.CategoriesValidityMixin,
|
||||
combo.apps.wcs.models.CategoriesFilteringMixin,
|
||||
combo.apps.wcs.models.FormsUserCanAccessMixin,
|
||||
models.Model,
|
||||
combo.apps.wcs.models.WcsBlurpMixin,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -714,8 +714,34 @@ class WcsFormsOfCategoryCell(WcsCommonCategoryCell, WcsBlurpMixin):
|
|||
yield {'url': formdef['url'], 'title': formdef['title'], 'text': text}
|
||||
|
||||
|
||||
class FormsUserCanAccessMixin:
|
||||
def get_cell_extra_context(self, context):
|
||||
context = super().get_cell_extra_context(context)
|
||||
|
||||
categories_filter = []
|
||||
if self.categories:
|
||||
for category in self.categories.get('data', []):
|
||||
categories_filter.append(tuple(category.split(':')))
|
||||
|
||||
# regroup all forms in a flat list
|
||||
context['forms'] = []
|
||||
for wcs_site in context[self.variable_name]:
|
||||
if not context[self.variable_name].get(wcs_site):
|
||||
continue
|
||||
context[self.variable_name][wcs_site]['categories'] = [
|
||||
v for k, v in categories_filter if k == wcs_site
|
||||
]
|
||||
if not context[self.variable_name][wcs_site].get('data'):
|
||||
continue
|
||||
context['forms'].extend(context[self.variable_name][wcs_site]['data'])
|
||||
|
||||
return context
|
||||
|
||||
|
||||
@register_cell_class
|
||||
class WcsCareFormsCell(CategoriesValidityMixin, CategoriesFilteringMixin, WcsDataBaseCell):
|
||||
class WcsCareFormsCell(
|
||||
CategoriesValidityMixin, CategoriesFilteringMixin, FormsUserCanAccessMixin, WcsDataBaseCell
|
||||
):
|
||||
categories = JSONField(_('Categories'), blank=True, default=dict)
|
||||
|
||||
api_url = '/api/forms/?limit=10'
|
||||
|
@ -732,20 +758,26 @@ class WcsCareFormsCell(CategoriesValidityMixin, CategoriesFilteringMixin, WcsDat
|
|||
|
||||
return WcsCareFormsCellForm
|
||||
|
||||
def get_cell_extra_context(self, context):
|
||||
context = super().get_cell_extra_context(context)
|
||||
|
||||
categories_filter = []
|
||||
if self.categories:
|
||||
for category in self.categories.get('data', []):
|
||||
categories_filter.append(tuple(category.split(':')))
|
||||
@register_cell_class
|
||||
class WcsFormsUserCanAccessCell(
|
||||
CategoriesValidityMixin, CategoriesFilteringMixin, FormsUserCanAccessMixin, WcsDataBaseCell
|
||||
):
|
||||
categories = JSONField(_('Categories'), blank=True, default=dict)
|
||||
|
||||
for wcs_site in context['care_forms']:
|
||||
if not context['care_forms'].get(wcs_site):
|
||||
continue
|
||||
context['care_forms'][wcs_site]['categories'] = [v for k, v in categories_filter if k == wcs_site]
|
||||
api_url = '/api/forms/'
|
||||
variable_name = 'user_forms'
|
||||
default_template_name = 'combo/wcs/user_can_access_forms.html'
|
||||
cache_duration = 120
|
||||
user_dependant = True
|
||||
|
||||
return context
|
||||
class Meta:
|
||||
verbose_name = _('Forms to which user has access')
|
||||
|
||||
def get_default_form_class(self):
|
||||
from .forms import WcsFormsUserCanAccessCell
|
||||
|
||||
return WcsFormsUserCanAccessCell
|
||||
|
||||
|
||||
@register_cell_class
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
{% load i18n %}
|
||||
{% block cell-content %}
|
||||
<h2>{% trans 'Forms you can access' %}</h2>
|
||||
{% if forms %}
|
||||
{% for slug, forms in user_forms.items %}
|
||||
<div class="links-list user-can-access-forms-{{ slug }} user-can-access-forms list-of-forms">
|
||||
{% include "combo/wcs/list_of_forms.html" with forms=forms %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% include "combo/pagination.html" %}
|
||||
{% else %}
|
||||
<div class="cell--body"><p class="empty-message">{% trans "There are no forms you can access." %}</p></div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -868,7 +868,7 @@ def test_site_export_import_json(app, admin_user):
|
|||
resp.form['site_file'] = Upload('site-export.json', site_export, 'application/json')
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = resp.form.submit()
|
||||
assert len(ctx.captured_queries) in [298, 299]
|
||||
assert len(ctx.captured_queries) in [303, 304]
|
||||
assert Page.objects.count() == 4
|
||||
assert PageSnapshot.objects.all().count() == 4
|
||||
|
||||
|
@ -879,7 +879,7 @@ def test_site_export_import_json(app, admin_user):
|
|||
resp.form['site_file'] = Upload('site-export.json', site_export, 'application/json')
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = resp.form.submit()
|
||||
assert len(ctx.captured_queries) == 268
|
||||
assert len(ctx.captured_queries) == 272
|
||||
assert set(Page.objects.get(slug='one').related_cells['cell_types']) == set(
|
||||
['data_textcell', 'data_linkcell']
|
||||
)
|
||||
|
@ -2208,7 +2208,7 @@ def test_page_versionning(app, admin_user):
|
|||
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp2 = resp.click('view', index=1)
|
||||
assert len(ctx.captured_queries) == 70
|
||||
assert len(ctx.captured_queries) == 71
|
||||
assert Page.snapshots.latest('pk').related_cells == {'cell_types': ['data_textcell']}
|
||||
assert resp2.text.index('Hello world') < resp2.text.index('Foobar3')
|
||||
|
||||
|
@ -2269,7 +2269,7 @@ def test_page_versionning(app, admin_user):
|
|||
resp = resp.click('restore', index=6)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = resp.form.submit().follow()
|
||||
assert len(ctx.captured_queries) == 142
|
||||
assert len(ctx.captured_queries) == 144
|
||||
|
||||
resp2 = resp.click('See online')
|
||||
assert resp2.text.index('Foobar1') < resp2.text.index('Foobar2') < resp2.text.index('Foobar3')
|
||||
|
|
|
@ -1371,7 +1371,7 @@ def test_index_site_num_queries(settings, app):
|
|||
assert IndexedCell.objects.count() == 50
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
index_site()
|
||||
assert len(ctx.captured_queries) == 223
|
||||
assert len(ctx.captured_queries) == 224
|
||||
|
||||
SearchCell.objects.create(
|
||||
page=page, placeholder='content', order=0, _search_services={'data': ['search1']}
|
||||
|
|
|
@ -33,6 +33,7 @@ from combo.apps.wcs.models import (
|
|||
WcsCurrentFormsCell,
|
||||
WcsFormCell,
|
||||
WcsFormsOfCategoryCell,
|
||||
WcsFormsUserCanAccessCell,
|
||||
)
|
||||
from combo.data.library import get_cell_classes
|
||||
from combo.data.models import CellBase, LinkListCell, Page, ValidityInfo
|
||||
|
@ -96,10 +97,21 @@ WCS_FORMS_DATA = [
|
|||
{
|
||||
'form_receipt_datetime': '2019-10-17T16:46:03',
|
||||
'form_url_backoffice': '/backoffice/management/foobar/1/',
|
||||
'readable': True,
|
||||
'title': 'Title',
|
||||
'url': '/forms/1/',
|
||||
},
|
||||
{
|
||||
'form_receipt_datetime': '2019-10-17T16:46:04',
|
||||
'form_url_backoffice': '/backoffice/management/foobar/2/',
|
||||
'title': 'Title',
|
||||
'url': '/forms/2/',
|
||||
},
|
||||
{
|
||||
'form_receipt_datetime': '2019-10-17T16:46:04',
|
||||
'form_url_backoffice': '/backoffice/management/foobar/3/',
|
||||
'readable': True,
|
||||
'url': '/forms/3/',
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -906,6 +918,175 @@ def test_care_forms_cell_render_single_site(mock_send, context):
|
|||
assert 'other' not in data
|
||||
|
||||
|
||||
def test_forms_user_can_access_cell_setup():
|
||||
cell = WcsFormsUserCanAccessCell()
|
||||
form_class = cell.get_default_form_class()
|
||||
form = form_class()
|
||||
assert form.fields['wcs_site'].widget.choices == [
|
||||
('', 'All'),
|
||||
(u'default', u'test'),
|
||||
(u'other', u'test2'),
|
||||
]
|
||||
assert cell.get_additional_label() == 'All Sites'
|
||||
cell.wcs_site = 'default'
|
||||
assert cell.get_additional_label() == 'test'
|
||||
|
||||
|
||||
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
|
||||
def test_forms_user_can_access_cell_render(mock_send, context):
|
||||
page = Page(title='xxx', slug='test_forms_user_can_access_cell_render', template_name='standard')
|
||||
page.save()
|
||||
cell = WcsFormsUserCanAccessCell(page=page, placeholder='content', order=0)
|
||||
cell.save()
|
||||
|
||||
context['request'].user = MockUser()
|
||||
|
||||
# query should fail as nothing is cached
|
||||
cache.clear()
|
||||
with pytest.raises(NothingInCacheException):
|
||||
result = cell.render(context)
|
||||
|
||||
context['synchronous'] = True # to get fresh content
|
||||
|
||||
result = cell.render(context)
|
||||
|
||||
assert 'http://127.0.0.1:8999/forms/1' in result
|
||||
assert 'http://127.0.0.1:8999/forms/2' not in result # not readable
|
||||
assert 'http://127.0.0.1:8999/forms/3' not in result # no title
|
||||
assert 'http://127.0.0.2:8999/forms/1' in result
|
||||
assert 'http://127.0.0.2:8999/forms/2' not in result # not readable
|
||||
assert 'http://127.0.0.2:8999/forms/3' not in result # no title
|
||||
|
||||
data = cell.get_data(context)
|
||||
assert 'default' in data
|
||||
assert 'other' in data
|
||||
|
||||
with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
|
||||
mock_json = mock.Mock(status_code=200)
|
||||
requests_get.return_value = mock_json
|
||||
cell.render(context)
|
||||
assert requests_get.call_args_list[0][0][0] == '/api/forms/'
|
||||
assert requests_get.call_args_list[1][0][0] == '/api/forms/'
|
||||
|
||||
# limit to a list of categories
|
||||
cell.categories = {'data': ['default:test-3', 'other:test-4']}
|
||||
|
||||
with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
|
||||
mock_json = mock.Mock(status_code=200)
|
||||
requests_get.return_value = mock_json
|
||||
cell.render(context)
|
||||
assert len(requests_get.call_args_list) == 2
|
||||
assert requests_get.call_args_list[0][0][0] == '/api/forms/?category_slugs=test-3'
|
||||
assert requests_get.call_args_list[0][1]['remote_service']['url'] == 'http://127.0.0.1:8999/'
|
||||
assert requests_get.call_args_list[1][0][0] == '/api/forms/?category_slugs=test-4'
|
||||
assert requests_get.call_args_list[1][1]['remote_service']['url'] == 'http://127.0.0.2:8999/'
|
||||
|
||||
# limit to a single category
|
||||
cell.categories = {'data': ['default:test-3']}
|
||||
|
||||
with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
|
||||
mock_json = mock.Mock(status_code=200)
|
||||
requests_get.return_value = mock_json
|
||||
cell.render(context)
|
||||
assert len(requests_get.call_args_list) == 1
|
||||
assert requests_get.call_args_list[0][0][0] == '/api/forms/?category_slugs=test-3'
|
||||
assert requests_get.call_args_list[0][1]['remote_service']['url'] == 'http://127.0.0.1:8999/'
|
||||
|
||||
# no category selected: call all sites
|
||||
cell.categories = {'data': []}
|
||||
with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
|
||||
mock_json = mock.Mock(status_code=200)
|
||||
requests_get.return_value = mock_json
|
||||
cell.render(context)
|
||||
assert len(requests_get.call_args_list) == 2
|
||||
|
||||
|
||||
def test_forms_user_can_access_cell_validity(context):
|
||||
page = Page.objects.create(
|
||||
title='xxx', slug='test_forms_user_can_access_cell_render', template_name='standard'
|
||||
)
|
||||
cell = WcsFormsUserCanAccessCell.objects.create(page=page, placeholder='content', order=0)
|
||||
context['synchronous'] = True # to get fresh content
|
||||
with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
|
||||
mock_json = mock.Mock(status_code=200)
|
||||
requests_get.return_value = mock_json
|
||||
cell.get_data(context)
|
||||
assert ValidityInfo.objects.exists() is False
|
||||
|
||||
# can not retrieve data, don't set cell as invalid
|
||||
with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
|
||||
mock_resp = Response()
|
||||
mock_resp.status_code = 500
|
||||
requests_get.return_value = mock_resp
|
||||
cell.get_data(context)
|
||||
assert ValidityInfo.objects.exists() is False
|
||||
with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
|
||||
requests_get.side_effect = ConnectionError()
|
||||
cell.get_data(context)
|
||||
assert ValidityInfo.objects.exists() is False
|
||||
|
||||
# can not retrieve data, don't set cell as invalid
|
||||
with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
|
||||
mock_json = mock.Mock(status_code=500)
|
||||
requests_get.return_value = mock_json
|
||||
cell.get_data(context)
|
||||
assert ValidityInfo.objects.exists() is False
|
||||
|
||||
with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
|
||||
mock_json = mock.Mock(status_code=404)
|
||||
requests_get.return_value = mock_json
|
||||
cell.get_data(context)
|
||||
validity_info = ValidityInfo.objects.latest('pk')
|
||||
assert validity_info.invalid_reason_code == 'wcs_data_failure'
|
||||
assert validity_info.invalid_since is not None
|
||||
|
||||
|
||||
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
|
||||
def test_forms_user_can_access_cell_check_validity(mock_send, context):
|
||||
page = Page.objects.create(
|
||||
title='xxx', slug='test_forms_user_can_access_cell_render', template_name='standard'
|
||||
)
|
||||
cell = WcsFormsUserCanAccessCell.objects.create(page=page, placeholder='content', order=0)
|
||||
|
||||
# no category
|
||||
cell.check_validity()
|
||||
assert ValidityInfo.objects.exists() is False
|
||||
|
||||
# valid categories
|
||||
cell.categories = {'data': ['default:test-3', 'default:test-9']}
|
||||
cell.save()
|
||||
cell.check_validity()
|
||||
assert ValidityInfo.objects.exists() is False
|
||||
|
||||
# can not retrieve data, don't set cell as invalid
|
||||
with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
|
||||
mock_resp = Response()
|
||||
mock_resp.status_code = 500
|
||||
requests_get.return_value = mock_resp
|
||||
cell.check_validity()
|
||||
assert ValidityInfo.objects.exists() is False
|
||||
with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
|
||||
requests_get.side_effect = ConnectionError()
|
||||
cell.check_validity()
|
||||
assert ValidityInfo.objects.exists() is False
|
||||
|
||||
# can not retrieve categories, don't set cell as invalid
|
||||
with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
|
||||
mock_resp = Response()
|
||||
mock_resp.status_code = 404
|
||||
requests_get.return_value = mock_resp
|
||||
cell.check_validity()
|
||||
assert ValidityInfo.objects.exists() is False
|
||||
|
||||
# invalid category
|
||||
cell.categories = {'data': ['default:foobar', 'default:test-9']}
|
||||
cell.save()
|
||||
cell.check_validity()
|
||||
validity_info = ValidityInfo.objects.latest('pk')
|
||||
assert validity_info.invalid_reason_code == 'wcs_category_not_found'
|
||||
assert validity_info.invalid_since is not None
|
||||
|
||||
|
||||
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
|
||||
def test_forms_of_category_cell_setup(mock_send):
|
||||
cell = WcsFormsOfCategoryCell()
|
||||
|
@ -1528,23 +1709,6 @@ def test_cards_cell_render_user(mock_send, context, nocache):
|
|||
assert 'email' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
|
||||
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
|
||||
def test_manager_card_cell(mock_send, app, admin_user):
|
||||
page = Page.objects.create(title='xxx', slug='test_cards_cell_save_cache', template_name='standard')
|
||||
cell = WcsCardInfosCell.objects.create(page=page, placeholder='content', order=0)
|
||||
app = login(app)
|
||||
|
||||
resp = app.get('/manage/pages/%s/' % page.pk)
|
||||
assert ('data-cell-reference="%s"' % cell.get_reference()) in resp.text
|
||||
assert cell.without_user is False
|
||||
assert resp.forms[0]['c%s-with_user' % cell.get_reference()].value == 'on'
|
||||
resp.forms[0]['c%s-with_user' % cell.get_reference()].value = False
|
||||
resp.forms[0].submit().follow()
|
||||
cell.refresh_from_db()
|
||||
assert cell.without_user is True
|
||||
assert resp.forms[0]['c%s-with_user' % cell.get_reference()].value is None
|
||||
|
||||
|
||||
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
|
||||
def test_card_cell_setup(mock_send):
|
||||
cell = WcsCardInfosCell()
|
||||
|
@ -1642,6 +1806,15 @@ def test_manager_card_cell(mock_send, app, admin_user):
|
|||
resp = app.get('/manage/pages/%s/' % page.pk)
|
||||
assert '<script id="cell-%s-card-schema-default:card_model_1" type="application/json">' % cell.pk in resp
|
||||
|
||||
assert ('data-cell-reference="%s"' % cell.get_reference()) in resp.text
|
||||
assert cell.without_user is False
|
||||
assert resp.forms[0]['c%s-with_user' % cell.get_reference()].value == 'on'
|
||||
resp.forms[0]['c%s-with_user' % cell.get_reference()].value = False
|
||||
resp.forms[0].submit().follow()
|
||||
cell.refresh_from_db()
|
||||
assert cell.without_user is True
|
||||
assert resp.forms[0]['c%s-with_user' % cell.get_reference()].value is None
|
||||
|
||||
|
||||
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
|
||||
def test_card_cell_load(mock_send):
|
||||
|
@ -2283,6 +2456,7 @@ def test_hourly():
|
|||
WcsCurrentFormsCell,
|
||||
WcsCurrentDraftsCell,
|
||||
WcsFormsOfCategoryCell,
|
||||
WcsFormsUserCanAccessCell,
|
||||
BackofficeSubmissionCell,
|
||||
]:
|
||||
with mock.patch('combo.apps.wcs.models.%s.check_validity' % klass.__name__) as check_validity:
|
||||
|
|
Loading…
Reference in New Issue