backoffice: add option to consider user roles in backoffice submission (#44887) #1430
|
@ -549,18 +549,20 @@ def test_card_user_support(pub):
|
|||
carddef = CardDef()
|
||||
carddef.name = 'card title'
|
||||
carddef.fields = []
|
||||
carddef.backoffice_submission_roles = ['1']
|
||||
carddef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/cards/1/')
|
||||
assert '<span class="label">User support</span> <span class="value">No</span>' in resp.text
|
||||
resp = resp.click(href='user_support')
|
||||
resp.forms[0]['user_support'] = 'optional'
|
||||
assert '<span class="label">Submission</span> <span class="value">Default</span>' in resp.text
|
||||
resp = resp.click(href='options/backoffice-submission')
|
||||
assert resp.forms[0]['submission_user_association'].value == 'none'
|
||||
resp.forms[0]['submission_user_association'].value = 'any'
|
||||
resp.forms[0].submit('submit')
|
||||
assert CardDef.get(carddef.id).user_support == 'optional'
|
||||
|
||||
resp = app.get('/backoffice/cards/1/')
|
||||
assert '<span class="label">User support</span> <span class="value">Optional</span>' in resp.text
|
||||
assert '<span class="label">Submission</span> <span class="value">Custom</span>' in resp.text
|
||||
|
||||
|
||||
def test_card_delete_field_existing_data(pub):
|
||||
|
|
|
@ -261,7 +261,7 @@ def test_carddata_management_user_support(pub):
|
|||
assert 'Associated User' not in resp.text
|
||||
assert 'user_id' not in resp.form.fields
|
||||
|
||||
carddef.user_support = 'foobar'
|
||||
carddef.user_support = None
|
||||
carddef.store()
|
||||
resp = app.get('/backoffice/data/foo/add/')
|
||||
assert 'Associated User' not in resp.text
|
||||
|
@ -1975,7 +1975,15 @@ def test_carddata_add_edit_related(pub):
|
|||
# check page_limit parameter has some checks
|
||||
app.get(resp.pyquery('select#form_f2').attr('data-select2-url') + '?q=foo&page_limit=xxx', status=400)
|
||||
|
||||
# check there's no "add" button if card must be associated to an user
|
||||
child.submission_user_association = 'any-required'
|
||||
child.store()
|
||||
resp = app.get('/backoffice/data/family/%s/wfedit-_editable' % familydata.id)
|
||||
assert 'Add another Child' not in resp
|
||||
assert '/backoffice/data/child/add/?_popup=1' not in resp
|
||||
|
||||
# user has no creation rights on child
|
||||
child.submission_user_association = 'none'
|
||||
child.backoffice_submission_roles = None
|
||||
child.store()
|
||||
resp = app.get('/backoffice/data/family/%s/wfedit-_editable' % familydata.id)
|
||||
|
|
|
@ -1856,6 +1856,142 @@ def test_backoffice_submission_user_selection(pub):
|
|||
assert '<p>%s</p>' % random_user.name in resp
|
||||
|
||||
|
||||
def test_backoffice_submission_required_user_selection(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
user1 = pub.user_class()
|
||||
user1.name = 'user1'
|
||||
user1.roles = []
|
||||
user1.store()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [
|
||||
fields.PageField(id='0', label='1st page'),
|
||||
fields.StringField(id='1', label='Field on 1st page'),
|
||||
fields.PageField(id='2', label='2nd page'),
|
||||
fields.StringField(id='3', label='Field on 2nd page'),
|
||||
]
|
||||
formdef.backoffice_submission_roles = user.roles[:]
|
||||
formdef.workflow_roles = {'_receiver': 1}
|
||||
formdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
# any user, optional
|
||||
resp = app.get(formdef.get_submission_url(backoffice=True))
|
||||
assert resp.pyquery('.submit-user-selection')
|
||||
resp.form['f1'] = 'test submission'
|
||||
resp = resp.form.submit('submit') # -> 2nd page
|
||||
assert resp.pyquery('#steps [aria-valuenow]').attr['aria-valuenow'] == '2'
|
||||
assert not resp.pyquery('.global-errors').text()
|
||||
|
||||
# any user but required
|
||||
formdef.submission_user_association = 'any-required'
|
||||
formdef.store()
|
||||
resp = app.get(formdef.get_submission_url(backoffice=True))
|
||||
resp.form['f1'] = 'test submission'
|
||||
resp = resp.form.submit('submit') # -> 2nd page
|
||||
# 2nd page but with a message about associating an user
|
||||
assert resp.pyquery('#steps [aria-valuenow]').attr['aria-valuenow'] == '2'
|
||||
assert resp.pyquery('.global-errors').text() == 'The form must be associated to an user.'
|
||||
resp.form['f3'] = 'test submission'
|
||||
resp = resp.form.submit('submit') # -> still on second page (with error)
|
||||
assert resp.pyquery('#steps [aria-valuenow]').attr['aria-valuenow'] == '2'
|
||||
assert resp.pyquery('.global-errors').text() == 'The form must be associated to an user.'
|
||||
resp.form['user_id'] = str(user1.id) # happens via javascript
|
||||
resp = resp.form.submit('submit') # -> validation page
|
||||
assert resp.pyquery('#steps [aria-valuenow]').attr['aria-valuenow'] == '3'
|
||||
assert not resp.pyquery('.global-errors').text()
|
||||
resp = resp.form.submit('submit', status=302) # -> submit
|
||||
|
||||
# formdef with no pages
|
||||
formdef.fields = [
|
||||
fields.StringField(id='1', label='Field on 1st page'),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
resp = app.get(formdef.get_submission_url(backoffice=True))
|
||||
resp.form['f1'] = 'test submission'
|
||||
resp = resp.form.submit('submit') # -> 1st page, still error
|
||||
assert resp.pyquery('#steps [aria-valuenow]').attr['aria-valuenow'] == '1'
|
||||
assert resp.pyquery('.global-errors').text() == 'The form must be associated to an user.'
|
||||
resp.form['user_id'] = str(user1.id) # happens via javascript
|
||||
resp = resp.form.submit('submit') # -> validation page
|
||||
assert resp.pyquery('#steps [aria-valuenow]').attr['aria-valuenow'] == '2'
|
||||
assert not resp.pyquery('.global-errors').text()
|
||||
resp = resp.form.submit('submit', status=302) # -> submit
|
||||
|
||||
# formdef with no confirmation page
|
||||
formdef.confirmation = False
|
||||
formdef.store()
|
||||
resp = app.get(formdef.get_submission_url(backoffice=True))
|
||||
resp.form['f1'] = 'test submission'
|
||||
resp = resp.form.submit('submit') # -> 1st page, still error
|
||||
assert resp.pyquery('#steps [aria-valuenow]').attr['aria-valuenow'] == '1'
|
||||
assert resp.pyquery('.global-errors').text() == 'The form must be associated to an user.'
|
||||
resp.form['user_id'] = str(user1.id) # happens via javascript
|
||||
resp = resp.form.submit('submit', status=302) # -> submit
|
||||
|
||||
|
||||
def test_backoffice_submission_required_user_with_role_selection(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
role1 = pub.role_class(name='foo')
|
||||
role1.store()
|
||||
role2 = pub.role_class(name='foo2')
|
||||
role2.store()
|
||||
|
||||
user1 = pub.user_class()
|
||||
user1.name = 'user1'
|
||||
user1.roles = [role1.id]
|
||||
user1.store()
|
||||
|
||||
user2 = pub.user_class()
|
||||
user2.name = 'user2'
|
||||
user2.roles = [role2.id]
|
||||
user2.store()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [
|
||||
fields.PageField(id='0', label='1st page'),
|
||||
fields.StringField(id='1', label='Field on 1st page'),
|
||||
fields.PageField(id='2', label='2nd page'),
|
||||
fields.StringField(id='3', label='Field on 2nd page'),
|
||||
]
|
||||
formdef.backoffice_submission_roles = user.roles[:]
|
||||
formdef.workflow_roles = {'_receiver': 1}
|
||||
formdef.roles = [role1.id]
|
||||
formdef.submission_user_association = 'roles'
|
||||
formdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get(formdef.get_submission_url(backoffice=True))
|
||||
assert resp.pyquery('.submit-user-selection select').attr['data-users-api-roles'] == str(role1.id)
|
||||
assert [x['user_display_name'] for x in app.get(f'/api/users/?roles={role1.id}').json['data']] == [
|
||||
'user1'
|
||||
]
|
||||
|
||||
formdef.roles = [role1.id, role2.id]
|
||||
formdef.store()
|
||||
resp = app.get(formdef.get_submission_url(backoffice=True))
|
||||
assert (
|
||||
resp.pyquery('.submit-user-selection select').attr['data-users-api-roles'] == f'{role1.id},{role2.id}'
|
||||
)
|
||||
assert [
|
||||
x['user_display_name'] for x in app.get(f'/api/users/?roles={role1.id},{role2.id}').json['data']
|
||||
] == ['user1', 'user2']
|
||||
|
||||
resp.form['f1'] = 'test submission'
|
||||
resp = resp.form.submit('submit') # -> 2nd page
|
||||
# 2nd page but with a message about associating an user
|
||||
assert resp.pyquery('#steps [aria-valuenow]').attr['aria-valuenow'] == '2'
|
||||
assert resp.pyquery('.global-errors').text() == 'The form must be associated to an user.'
|
||||
|
||||
|
||||
def test_backoffice_submission_user_selection_then_live(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
|
|
|
@ -1803,3 +1803,13 @@ def test_card_update_related_deleted(pub):
|
|||
|
||||
carddef.remove_self()
|
||||
job.execute()
|
||||
|
||||
|
||||
def test_migrate_user_support(pub):
|
||||
CardDef.wipe()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'foo'
|
||||
carddef.user_support = 'optional'
|
||||
carddef.migrate()
|
||||
assert carddef.submission_user_association == 'any'
|
||||
|
|
|
@ -643,7 +643,7 @@ def test_card_snapshot_browse(pub):
|
|||
for nav_item in resp.pyquery('.snapshots-navigation a:not(.disabled)'):
|
||||
resp.click(href=nav_item.attrib['href'], index=0)
|
||||
# check option dialogs only have a cancel button
|
||||
resp = resp.click('User support')
|
||||
resp = resp.click(href='options/management')
|
||||
assert [x[0].name for x in resp.form.fields.values() if x[0].tag == 'button'] == ['cancel']
|
||||
assert pub.custom_view_class.count() == 0 # custom views are not restore on preview
|
||||
|
||||
|
|
|
@ -256,7 +256,6 @@ class OptionsDirectory(Directory):
|
|||
'management',
|
||||
'appearance',
|
||||
'templates',
|
||||
'user_support',
|
||||
('backoffice-submission', 'backoffice_submission'),
|
||||
]
|
||||
|
||||
|
@ -372,6 +371,25 @@ class OptionsDirectory(Directory):
|
|||
|
||||
def backoffice_submission(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
|
||||
submission_user_association_options = [
|
||||
('none', _('No user'), 'none'),
|
||||
('any', _('Any user (optional)'), 'any'),
|
||||
('any-required', _('Any user (required)'), 'any-required'),
|
||||
('roles', _('User with appropriate role'), 'roles'),
|
||||
|
||||
]
|
||||
submission_user_association_options = [
|
||||
x
|
||||
for x in submission_user_association_options
|
||||
if x[0] in self.formdef.submission_user_association_available_options
|
||||
]
|
||||
form.add(
|
||||
SingleSelectWidget,
|
||||
'submission_user_association',
|
||||
title=_('Submission user assocation'),
|
||||
value=self.formdef.submission_user_association,
|
||||
options=submission_user_association_options,
|
||||
)
|
||||
form.add(
|
||||
CheckboxesWidget,
|
||||
'submission_sidebar_items',
|
||||
|
@ -488,20 +506,6 @@ class OptionsDirectory(Directory):
|
|||
get_session().message = ('info', _('Existing forms will be updated in the background.'))
|
||||
return result
|
||||
|
||||
def user_support(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
form.add(
|
||||
SingleSelectWidget,
|
||||
'user_support',
|
||||
title=_('User support'),
|
||||
value=self.formdef.user_support,
|
||||
options=[
|
||||
(None, pgettext_lazy('user_support', 'No'), ''),
|
||||
('optional', pgettext_lazy('user_support', 'Optional'), 'optional'),
|
||||
],
|
||||
)
|
||||
return self.handle(form, _('User support'))
|
||||
|
||||
def handle(self, form, title):
|
||||
if not self.formdef.is_readonly():
|
||||
form.add_submit('submit', _('Submit'))
|
||||
|
@ -531,9 +535,9 @@ class OptionsDirectory(Directory):
|
|||
'submission_lateral_template',
|
||||
'drafts_lifespan',
|
||||
'drafts_max_per_user',
|
||||
'user_support',
|
||||
'management_sidebar_items',
|
||||
'submission_sidebar_items',
|
||||
'submission_user_association',
|
||||
'history_pane_default_mode',
|
||||
]
|
||||
for attr in attrs:
|
||||
|
@ -544,6 +548,9 @@ class OptionsDirectory(Directory):
|
|||
if has_error:
|
||||
continue
|
||||
new_value = widget.parse()
|
||||
if attr == 'submission_user_association':
|
||||
# keep user_support option in sync (only relevant for cards)
|
||||
self.formdef.user_support = 'optional' if new_value != 'none' else None
|
||||
fpeters
commented
Le paramétrage "user_support" devient redondant mais je le conserve en tant qu'attribut parce que c'est plus facile pour maintenir ainsi une compatibilité avec l'existant (par exemple un export de modèle de fiche qui aurait une valeur pour user_support). Le paramétrage "user_support" devient redondant mais je le conserve en tant qu'attribut parce que c'est plus facile pour maintenir ainsi une compatibilité avec l'existant (par exemple un export de modèle de fiche qui aurait une valeur pour user_support).
|
||||
if attr == 'management_sidebar_items':
|
||||
new_value = set(new_value or [])
|
||||
if new_value == self.formdef.get_default_management_sidebar_items():
|
||||
|
@ -863,6 +870,8 @@ class FormDefPage(Directory, TempfileDirectoryMixin, DocumentableMixin):
|
|||
if (
|
||||
self.formdef.submission_sidebar_items
|
||||
not in ({'__default__'}, self.formdef.get_default_submission_sidebar_items())
|
||||
or self.formdef.submission_user_association
|
||||
!= self.formdef.__class__.submission_user_association
|
||||
)
|
||||
else _('Default'),
|
||||
),
|
||||
|
|
|
@ -1198,6 +1198,10 @@ class ApiUsersDirectory(Directory):
|
|||
criteria_fields.append(FtsMatch(query))
|
||||
criterias.append(Or(criteria_fields))
|
||||
|
||||
roles = get_request().form.get('roles')
|
||||
if roles:
|
||||
criterias.append(Intersects('roles', roles.split(',')))
|
||||
|
||||
def as_dict(user):
|
||||
users_cfg = get_cfg('users', {})
|
||||
template = (
|
||||
|
|
|
@ -169,13 +169,6 @@ class CardDefPage(FormDefPage):
|
|||
self.backoffice_submission_role_label,
|
||||
self._get_roles_label('backoffice_submission_roles'),
|
||||
)
|
||||
if self.formdef.user_support == 'optional':
|
||||
user_support_status = pgettext_lazy('user_support', 'Optional')
|
||||
else:
|
||||
user_support_status = pgettext_lazy('user_support', 'No')
|
||||
options['user_support'] = self.add_option_line(
|
||||
'options/user_support', _('User support'), user_support_status
|
||||
)
|
||||
options['management'] = self.add_option_line(
|
||||
'options/management',
|
||||
pgettext_lazy('cards', 'Management'),
|
||||
|
|
|
@ -390,6 +390,7 @@ class CardFillPage(FormFillPage):
|
|||
has_channel_support = False
|
||||
has_user_support = False
|
||||
already_submitted_message = _('This card has already been submitted.')
|
||||
required_user_message = _('The card must be associated to an user.')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
|
|
@ -29,7 +29,7 @@ from django.utils.timezone import is_naive, make_aware, make_naive, now
|
|||
from quixote import get_publisher, get_request, get_response, get_session, redirect
|
||||
from quixote.directory import Directory
|
||||
from quixote.errors import RequestError
|
||||
from quixote.html import TemplateIO, htmlescape, htmltext
|
||||
from quixote.html import TemplateIO, htmlescape, htmltag, htmltext
|
||||
from quixote.http_request import parse_query
|
||||
|
||||
from wcs.api_access import ApiAccess
|
||||
|
@ -3508,7 +3508,12 @@ class FormBackOfficeStatusPage(FormStatusPage):
|
|||
r += htmltext('<div class="submit-user-selection" style="display: none;">')
|
||||
get_response().add_javascript(['select2.js'])
|
||||
r += htmltext('<h3>%s</h3>') % _('Associated User')
|
||||
r += htmltext('<select>')
|
||||
attrs = {
|
||||
'class': 'user-selection',
|
||||
}
|
||||
if self.formdef.submission_user_association == 'roles' and self.formdef.roles:
|
||||
attrs['data-users-api-roles'] = ','.join([str(x) for x in self.formdef.roles])
|
||||
r += htmltag('select', **attrs)
|
||||
if parent and parent.selected_user_id:
|
||||
r += htmltext('<option value="%s">%s</option>') % (
|
||||
parent.selected_user_id,
|
||||
|
|
|
@ -29,6 +29,7 @@ from wcs.formdata import FormData
|
|||
from wcs.formdef import FormDef
|
||||
from wcs.forms.common import FormStatusPage
|
||||
from wcs.forms.root import FormPage as PublicFormFillPage
|
||||
from wcs.forms.root import RequiredUserException
|
||||
from wcs.sql_criterias import Contains, Equal, StrictNotEqual
|
||||
|
||||
from ..qommon import _, errors, get_cfg, misc, template
|
||||
|
@ -112,6 +113,7 @@ class FormFillPage(PublicFormFillPage):
|
|||
has_channel_support = True
|
||||
has_user_support = True
|
||||
ensure_parent_category_in_url = False
|
||||
required_user_message = _('The form must be associated to an user.')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -174,6 +176,21 @@ class FormFillPage(PublicFormFillPage):
|
|||
self.selected_user_id = get_request().form.get('user_id')
|
||||
return super()._q_index(*args, **kwargs)
|
||||
|
||||
def is_missing_user(self):
|
||||
return self.formdef.submission_user_association not in ('none', 'any') and not self.selected_user_id
|
||||
|
||||
def page(self, page, *args, **kwargs):
|
||||
page_error_messages = kwargs.pop('page_error_messages', None) or []
|
||||
if self.is_missing_user() and not kwargs.get('arrival'):
|
||||
page_error_messages.append(self.required_user_message)
|
||||
return super().page(page, *args, **kwargs, page_error_messages=page_error_messages)
|
||||
fpeters
commented
S'il manque l'association à l'utilisateur on affiche un message, mais on laisse avancer sur la page suivante. (ça pourrait se discuter, dire qu'il faut bloquer dès la première page). S'il manque l'association à l'utilisateur on affiche un message, mais on laisse avancer sur la page suivante. (ça pourrait se discuter, dire qu'il faut bloquer dès la première page).
|
||||
|
||||
def validating(self, *args, **kwargs):
|
||||
if self.is_missing_user():
|
||||
page_error_messages = kwargs.pop('page_error_messages', None) or []
|
||||
return self.page(self.pages[-1], page_change=False, page_error_messages=page_error_messages)
|
||||
fpeters
commented
Si ça arrive sur la page de validation on renvoit sur la page précédente. (parce que dans la barre latérale de la page de validation il n'y a pas de sélecteur d'utilisateur). Si ça arrive sur la page de validation on renvoit sur la page précédente. (parce que dans la barre latérale de la page de validation il n'y a pas de sélecteur d'utilisateur).
|
||||
return super().validating(*args, **kwargs)
|
||||
|
||||
def lateral_block(self):
|
||||
get_response().raw = True
|
||||
response = self.get_lateral_block()
|
||||
|
@ -368,6 +385,8 @@ class FormFillPage(PublicFormFillPage):
|
|||
return mark_safe(str(r.getvalue()))
|
||||
|
||||
def submitted(self, form, *args):
|
||||
if self.is_missing_user():
|
||||
raise RequiredUserException()
|
||||
filled = self.get_current_draft() or self.formdef.data_class()()
|
||||
if filled.id and filled.status != 'draft':
|
||||
get_session().message = ('error', self.already_submitted_message)
|
||||
|
|
|
@ -46,12 +46,29 @@ class CardDef(FormDef):
|
|||
|
||||
confirmation = False
|
||||
history_pane_default_mode = 'collapsed'
|
||||
submission_user_association = 'none'
|
||||
|
||||
# users are not allowed to access carddata where they're submitter.
|
||||
user_allowed_to_access_own_data = False
|
||||
|
||||
submission_user_association_available_options = ['none', 'any', 'any-required']
|
||||
|
||||
category_class = CardDefCategory
|
||||
|
||||
def migrate(self):
|
||||
super().migrate()
|
||||
if self.__dict__.get('fields') is Ellipsis:
|
||||
# don't run migration on lightweight objects
|
||||
return
|
||||
|
||||
changed = False
|
||||
if self.user_support and self.submission_user_association == 'none': # 2024-04-27
|
||||
self.submission_user_association = 'any'
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
self.store(comment=_('Automatic update'), snapshot_store_user=False)
|
||||
|
||||
def data_class(self, mode=None):
|
||||
if 'carddef' not in sys.modules:
|
||||
sys.modules['carddef'] = sys.modules[__name__]
|
||||
|
@ -328,18 +345,6 @@ class CardDef(FormDef):
|
|||
excluded_parts.append('user')
|
||||
return [x for x in super().get_management_sidebar_available_items() if x[0] not in excluded_parts]
|
||||
|
||||
def get_default_submission_sidebar_items(self):
|
||||
sidebar_items = super().get_default_submission_sidebar_items()
|
||||
if not self.user_support:
|
||||
sidebar_items.remove('user')
|
||||
return sidebar_items
|
||||
|
||||
def get_submission_sidebar_available_items(self):
|
||||
excluded_parts = []
|
||||
if not self.user_support:
|
||||
excluded_parts.append('user')
|
||||
return [x for x in super().get_submission_sidebar_available_items() if x[0] not in excluded_parts]
|
||||
fpeters
commented
Comme on a dans la même fenêtre de paramétrage la possibilité de s'associer à un utilisateur et ce qui est affiché dans la barre latérale, je choisis de toujours afficher le choix "utilisateur" pour la barre latéréale (au pire il est coché mais ça n'apparait pas si c'est paramétré avec pas d'association possible). Comme on a dans la même fenêtre de paramétrage la possibilité de s'associer à un utilisateur et ce qui est affiché dans la barre latérale, je choisis de toujours afficher le choix "utilisateur" pour la barre latéréale (au pire il est coché mais ça n'apparait pas si c'est paramétré avec pas d'association possible).
|
||||
|
||||
|
||||
def get_cards_graph(category=None, show_orphans=False):
|
||||
out = io.StringIO()
|
||||
|
|
|
@ -369,7 +369,10 @@ class ItemField(WidgetField, MapOptionsMixin, ItemFieldMixin, ItemWithImageField
|
|||
carddef = self.get_carddef()
|
||||
url_kwargs = {}
|
||||
if get_request().is_in_backoffice() and carddef:
|
||||
if carddef.can_user_add_cards(get_request().user):
|
||||
if (
|
||||
carddef.can_user_add_cards(get_request().user)
|
||||
and carddef.submission_user_association != 'any-required'
|
||||
fpeters
commented
En mode popup on n'a pas la possibilité d'associer un utilisateur alors on n'affiche pas le bouton "ajouter" dans ce cas. En mode popup on n'a pas la possibilité d'associer un utilisateur alors on n'affiche pas le bouton "ajouter" dans ce cas.
|
||||
):
|
||||
kwargs['add_related_url'] = carddef.get_backoffice_submission_url()
|
||||
kwargs['with_related'] = True
|
||||
url_kwargs['with_related'] = True
|
||||
|
|
|
@ -187,6 +187,7 @@ class FormDef(StorableObject):
|
|||
drafts_lifespan = None
|
||||
drafts_max_per_user = None
|
||||
user_support = None
|
||||
submission_user_association = 'any'
|
||||
documentation = None
|
||||
|
||||
geolocations = None
|
||||
|
@ -205,6 +206,8 @@ class FormDef(StorableObject):
|
|||
# users are allowed to access formdata where they're submitter.
|
||||
user_allowed_to_access_own_data = True
|
||||
|
||||
submission_user_association_available_options = ['any', 'any-required', 'roles']
|
||||
|
||||
# declarations for serialization
|
||||
TEXT_ATTRIBUTES = [
|
||||
'name',
|
||||
|
@ -222,6 +225,7 @@ class FormDef(StorableObject):
|
|||
'drafts_max_per_user',
|
||||
'user_support',
|
||||
'documentation',
|
||||
'submission_user_association',
|
||||
]
|
||||
BOOLEAN_ATTRIBUTES = [
|
||||
'discussion',
|
||||
|
|
|
@ -68,6 +68,10 @@ class SubmittedDraftException(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class RequiredUserException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def tryauth(url):
|
||||
# tries to log the user in before redirecting to the asked url; this won't
|
||||
# do anything for local logins but will use a passive SAML request when
|
||||
|
@ -1594,23 +1598,20 @@ class FormPage(Directory, TempfileDirectoryMixin, FormTemplateMixin):
|
|||
|
||||
try:
|
||||
return self.submitted(form, existing_formdata)
|
||||
except SetValueError as e:
|
||||
except (SetValueError, RequiredUserException) as e:
|
||||
page_error_messages = []
|
||||
if isinstance(e, SetValueError):
|
||||
page_error_messages = [{'summary': _('Technical error, please try again.'), 'details': e}]
|
||||
|
||||
if get_request().form.get('step') == '2':
|
||||
# submit came from the validation page
|
||||
return self.validating(
|
||||
form_data,
|
||||
page_error_messages=[
|
||||
{'summary': _('Technical error, please try again.'), 'details': e}
|
||||
],
|
||||
)
|
||||
return self.validating(form_data, page_error_messages=page_error_messages)
|
||||
else:
|
||||
# last page
|
||||
return self.page(
|
||||
page,
|
||||
page_change=False,
|
||||
page_error_messages=[
|
||||
{'summary': _('Technical error, please try again.'), 'details': e}
|
||||
],
|
||||
page_error_messages=page_error_messages,
|
||||
transient_formdata=transient_formdata,
|
||||
)
|
||||
|
||||
|
|
|
@ -192,53 +192,55 @@ $(function() {
|
|||
});
|
||||
|
||||
/* user id */
|
||||
var user_select2_options = {
|
||||
language: {
|
||||
errorLoading: function() { return WCS_I18N.s2_errorloading; },
|
||||
noResults: function () { return WCS_I18N.s2_nomatches; },
|
||||
inputTooShort: function (input, min) { return WCS_I18N.s2_tooshort; },
|
||||
loadingMore: function () { return WCS_I18N.s2_loadmore; },
|
||||
searching: function () { return WCS_I18N.s2_searching; }
|
||||
},
|
||||
ajax: {
|
||||
delay: 250,
|
||||
dataType: 'json',
|
||||
data: function(params) {
|
||||
return {q: params.term, limit: 10};
|
||||
$('select.user-selection').each(function(idx, elem) {
|
||||
fpeters
commented
Avant c'était Avant c'était `$('div.submit-user-selection, select.user-selection')`, la classe "user-selection" est ajoutée plus haut, comme ça le sélecteur peut être simplifié. (à part ça le code ne change pas, c'est juste un changement d'indentation).
|
||||
var user_select2_api_url = '/api/users/';
|
||||
var user_select2_api_roles_param = $(elem).data('users-api-roles') || '';
|
||||
const user_select2_options = {
|
||||
language: {
|
||||
errorLoading: function() { return WCS_I18N.s2_errorloading; },
|
||||
noResults: function () { return WCS_I18N.s2_nomatches; },
|
||||
inputTooShort: function (input, min) { return WCS_I18N.s2_tooshort; },
|
||||
loadingMore: function () { return WCS_I18N.s2_loadmore; },
|
||||
searching: function () { return WCS_I18N.s2_searching; }
|
||||
},
|
||||
processResults: function (data, params) {
|
||||
return {results: data.data};
|
||||
ajax: {
|
||||
delay: 250,
|
||||
dataType: 'json',
|
||||
data: function(params) {
|
||||
return {q: params.term, roles: user_select2_api_roles_param, limit: 10};
|
||||
},
|
||||
processResults: function (data, params) {
|
||||
return {results: data.data};
|
||||
},
|
||||
url: user_select2_api_url
|
||||
},
|
||||
url: '/api/users/'
|
||||
},
|
||||
placeholder: '-',
|
||||
allowClear: true,
|
||||
templateResult: function (state) {
|
||||
if (!state.description) {
|
||||
return state.text;
|
||||
placeholder: '-',
|
||||
allowClear: true,
|
||||
templateResult: function (state) {
|
||||
if (!state.description) {
|
||||
return state.text;
|
||||
}
|
||||
var $template_string = $('<span>');
|
||||
$template_string.append(
|
||||
$('<span>', {text: state.text})).append(
|
||||
$('<br>')).append(
|
||||
$('<span>' + state.description + '</span>'));
|
||||
return $template_string;
|
||||
}
|
||||
var $template_string = $('<span>');
|
||||
$template_string.append(
|
||||
$('<span>', {text: state.text})).append(
|
||||
$('<br>')).append(
|
||||
$('<span>' + state.description + '</span>'));
|
||||
return $template_string;
|
||||
}
|
||||
}
|
||||
if ($('div.submit-user-selection, select.user-selection').length) {
|
||||
$('div.submit-user-selection select, select.user-selection').select2(user_select2_options);
|
||||
$('div.submit-user-selection select, select.user-selection').on('select2:open', function (e) {
|
||||
$(elem).select2(user_select2_options);
|
||||
$(elem).on('select2:open', function (e) {
|
||||
var available_height = $(window).height() - $(this).offset().top;
|
||||
$('ul.select2-results__options').css('max-height', (available_height - 100) + 'px');
|
||||
});
|
||||
$('div.submit-user-selection').show().find('select').on('change', function() {
|
||||
$('input[type=hidden][name=user_id]').val($(this).val());
|
||||
$('form[data-live-url]').trigger(
|
||||
'wcs:change',
|
||||
{modified_field: 'user', selected_user_id: $(this).val()}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
$('div.submit-user-selection').show().find('select').on('change', function() {
|
||||
$('input[type=hidden][name=user_id]').val($(this).val());
|
||||
$('form[data-live-url]').trigger(
|
||||
'wcs:change',
|
||||
{modified_field: 'user', selected_user_id: $(this).val()}
|
||||
);
|
||||
});
|
||||
|
||||
/* new action form */
|
||||
$('#new-action-form select').on('change', function() {
|
||||
|
|
|
@ -43,7 +43,6 @@
|
|||
<h3>{% trans "Options" %}</h3>
|
||||
<ul class="biglist optionslist">
|
||||
{{ options.templates|safe }}
|
||||
{{ options.user_support|safe }}
|
||||
{% if formdef.backoffice_submission_roles %}{{ options.backoffice_submission_options|safe }}{% endif %}
|
||||
{{ options.management|safe }}
|
||||
</ul>
|
||||
|
|
Ajout à l'écran des options de saisie (introduit dans #84494) d'un paramétrage du type d'association de la demande/fiche à un utilisateur :