backoffice: add option to consider user roles in backoffice submission (#44887) #1430

Open
fpeters wants to merge 2 commits from wip/44887-backoffice-submission-user-roles into main
17 changed files with 296 additions and 95 deletions

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -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'

View File

@ -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

View File

@ -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'),
Review

Ajout à l'écran des options de saisie (introduit dans #84494) d'un paramétrage du type d'association de la demande/fiche à un utilisateur :

  • none : uniquement disponible pour les fiches, valeur par défaut,
  • any : possibilité d'associer un utilisateur, valeur par défaut pour les demandes,
  • any-required : obligation d'associer un utilisateur, ça correspond à la demande dans #57781,
  • roles : obligation d'associer un utilisateur qui correspond à "rôles du demandeur", uniquement disponible pour les deandes. (les fiches n'ont pas "rôles du demandeur").
Ajout à l'écran des options de saisie (introduit dans #84494) d'un paramétrage du type d'association de la demande/fiche à un utilisateur : * none : uniquement disponible pour les fiches, valeur par défaut, * any : possibilité d'associer un utilisateur, valeur par défaut pour les demandes, * any-required : obligation d'associer un utilisateur, ça correspond à la demande dans #57781, * roles : obligation d'associer un utilisateur qui correspond à "rôles du demandeur", uniquement disponible pour les deandes. (les fiches n'ont pas "rôles du demandeur").
]
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
Review

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'),
),

View File

@ -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 = (

View File

@ -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'),

View File

@ -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)

View File

@ -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,

View File

@ -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)
Review

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)
Review

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)

View File

@ -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]
Review

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()

View File

@ -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'
Review

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

View File

@ -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',

View File

@ -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,
)

View File

@ -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) {
Review

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).

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() {

View File

@ -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>