backoffice: remove user view (#56534)

This commit is contained in:
Frédéric Péters 2021-08-31 10:59:32 +02:00
parent c05735d66d
commit e704234efb
2 changed files with 2 additions and 446 deletions

View File

@ -3750,112 +3750,6 @@ def test_menu_json(pub):
assert resp.headers['content-type'] == 'application/javascript'
def test_per_user_view(pub):
if not pub.is_using_postgresql():
pytest.skip('this requires SQL')
return
user = create_user(pub)
create_environment(pub)
app = login(get_app(pub))
resp = app.get('/backoffice/management/').follow()
assert 'Per User View' not in resp.text
if not pub.site_options.has_section('options'):
pub.site_options.add_section('options')
pub.site_options.set('options', 'per-user-view', 'true')
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
pub.site_options.write(fd)
resp = app.get('/backoffice/management/').follow()
assert 'Per User View' in resp.text
resp = resp.click(' User View')
assert 'Use the search field on the right' in resp.text
resp.form['q'] = 'admin'
resp = resp.form.submit()
assert resp.text.count('<tr') == 2 # header + user
form_class = FormDef.get_by_urlname('form-title').data_class()
to_match = []
for formdata in form_class.select():
if formdata.data['1'] in ('FOO BAR 30', 'FOO BAR 33'):
formdata.user_id = user.id
formdata.store()
to_match.append('/management/form-title/%s/' % formdata.id)
resp = app.get('/backoffice/management/users/%s/' % user.id)
for item in to_match:
assert item in resp.text
count_li = resp.text.count('<li')
# check list items are displayed, without links, if we cannot view them.
formdef = FormDef.get_by_urlname('form-title')
formdef.workflow_roles['_receiver'] = 'XXX'
formdef.store()
formdef.data_class().rebuild_security()
resp = app.get('/backoffice/management/users/%s/' % user.id)
for item in to_match:
assert item not in resp.text # not linked
assert resp.text.count('<li') == count_li
# mark formdef so it's not listed in per-user view
formdef.skip_from_360_view = True
formdef.store()
# check formdatas are no longer part of the page
resp = app.get('/backoffice/management/users/%s/' % user.id)
assert resp.text.count('<li') == (count_li - 2)
def test_per_user_view_tracking_code(pub, emails, sms_mocking):
if not pub.is_using_postgresql():
pytest.skip('this requires SQL')
return
user = create_user(pub)
create_environment(pub)
app = login(get_app(pub))
formdef = FormDef.get_by_urlname('form-title')
form_class = formdef.data_class()
number31 = [x for x in form_class.select() if x.data['1'] == 'FOO BAR 30'][0]
number31.user_id = user.id
number31.store()
resp = app.get('/backoffice/management/users/%s/' % user.id)
assert 'Send tracking code' not in resp.text
formdef.enable_tracking_codes = True
formdef.store()
user_view_resp = app.get('/backoffice/management/users/%s/' % user.id)
assert 'Send tracking code' in user_view_resp.text
pub.cfg['sms'] = {}
pub.write_cfg()
resp = user_view_resp.click('Send tracking code')
assert 'sms' not in resp.form.fields
assert resp.form['email'].value == user.email
resp = resp.form.submit()
resp = resp.follow()
assert emails.count() == 1
assert emails.get('Tracking Code reminder')['email_rcpt'] == [user.email]
assert form_class.get(number31.id).tracking_code in emails.get('Tracking Code reminder')['payload']
pub.cfg['sms'] = {'passerelle_url': 'xx', 'sender': 'xx'}
pub.write_cfg()
resp = user_view_resp.click('Send tracking code', index=0)
resp.form['method'].value = 'SMS'
resp.form['sms'].value = '0123456789'
resp = resp.form.submit()
resp = resp.follow()
assert sms_mocking.sms[-1]['destinations'] == ['0123456789']
assert form_class.get(number31.id).tracking_code in sms_mocking.sms[-1]['text']
def test_backoffice_resume_folded(pub):
create_user(pub)
create_environment(pub)

View File

@ -32,7 +32,6 @@ from quixote.html import TemplateIO, htmlescape, htmltext
from quixote.http_request import parse_query
from wcs.admin.forms import UpdateDigestAfterJob
from wcs.admin.settings import UserFieldsFormDef
from wcs.api_utils import get_user_from_api_query_string
from wcs.carddef import CardDef
from wcs.categories import Category
@ -45,9 +44,7 @@ from wcs.roles import logged_users_role
from wcs.variables import LazyFieldVar
from wcs.workflows import ActionsTracingEvolutionPart, WorkflowStatusItem, item_classes, template_on_formdata
from ..qommon import _, emails, errors, ezt, force_str, get_cfg, get_logger, misc, ngettext, ods, sms
from ..qommon.admin.emails import EmailsDirectory
from ..qommon.admin.menu import command_icon
from ..qommon import _, errors, ezt, force_str, get_cfg, get_logger, misc, ngettext, ods
from ..qommon.afterjobs import AfterJob
from ..qommon.backoffice.listing import pagination_links
from ..qommon.backoffice.menu import html_top
@ -55,7 +52,6 @@ from ..qommon.evalutils import make_datetime
from ..qommon.form import (
CheckboxWidget,
DateWidget,
EmailWidget,
Form,
HiddenWidget,
HtmlWidget,
@ -74,7 +70,6 @@ from ..qommon.storage import (
Equal,
FtsMatch,
GreaterOrEqual,
ILike,
Intersects,
LessOrEqual,
NotEqual,
@ -145,339 +140,8 @@ def geojson_formdatas(formdatas, geoloc_key='base', fields=None):
return geojson
class SendCodeFormdefDirectory(Directory):
formdef = None
def __init__(self, formdef):
self.formdef = formdef
def _q_lookup(self, component):
html_top('management', _('Management'))
formdata = self.formdef.data_class().get(component)
submitter_email = formdata.formdef.get_submitter_email(formdata)
mail_subject = EmailsDirectory.get_subject('tracking-code-reminder')
mail_body = EmailsDirectory.get_body('tracking-code-reminder')
form = Form()
form.add(TextWidget, 'text', title=_('Message'), required=True, cols=60, rows=5, value=mail_body)
form.add(EmailWidget, 'email', title=_('Email'), required=False, value=submitter_email)
sms_class = None
if get_publisher().use_sms_feature:
sms_class = sms.SMS.get_sms_class()
if sms_class:
form.add(StringWidget, 'sms', title=_('SMS Number'), required=False)
form.add(
RadiobuttonsWidget,
'method',
options=[('email', _('Email')), ('sms', _('SMS'))],
value='email',
required=True,
extra_css_class='widget-inline-radio',
)
form.add_submit('submit', _('Send'))
form.add_submit('cancel', _('Cancel'))
if not form.is_submitted() or form.has_errors():
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Send tracking code')
r += form.render()
return r.getvalue()
if not formdata.tracking_code:
tracking_code = get_publisher().tracking_code_class()
tracking_code.formdata = formdata # this stores both objects
message = form.get_widget('text').parse()
data = {
'form_tracking_code': formdata.tracking_code,
'tracking_code': formdata.tracking_code,
'email': form.get_widget('email').parse(),
}
data.update(self.formdef.get_substitution_variables(minimal=True))
if sms_class and form.get_widget('method').parse() == 'sms':
# send sms
sitename = get_cfg('misc', {}).get('sitename') or 'w.c.s.'
sms_cfg = get_cfg('sms', {})
sender = sms_cfg.get('sender', sitename)[:11]
message = Template(message).render(data)
try:
sms_class.send(sender, [form.get_widget('sms').parse()], message)
except errors.SMSError as e:
get_logger().error(e)
get_session().message = ('info', _('SMS with tracking code sent to the user'))
else:
# send mail
emails.template_email(
mail_subject, message, mail_body_data=data, email_rcpt=form.get_widget('email').parse()
)
get_session().message = ('info', _('Email with tracking code sent to the user'))
return redirect('../..')
class SendCodeDirectory(Directory):
def _q_lookup(self, component):
try:
formdef = FormDef.get_by_urlname(component)
if not formdef.enable_tracking_codes:
raise errors.TraversalError()
return SendCodeFormdefDirectory(formdef)
except KeyError:
raise errors.TraversalError()
class UserViewDirectory(Directory):
_q_exports = ['', 'sendcode']
sendcode = SendCodeDirectory()
user = None
def __init__(self, user):
self.user = user
def _q_index(self):
get_response().breadcrumb.append(('%s/' % self.user.id, self.user.display_name))
html_top('management', _('Management'))
# display list of open formdata for the user
formdefs = [x for x in FormDef.select(lightweight=True) if not x.skip_from_360_view]
user_roles = set([logged_users_role().id] + get_request().user.get_roles())
criterias = [
Equal('is_at_endpoint', False),
Equal('user_id', str(self.user.id)),
Contains('formdef_id', [x.id for x in formdefs]),
]
from wcs import sql
formdatas = sql.AnyFormData.select(criterias, order_by='receipt_time')
criterias = [
Equal('is_at_endpoint', False),
Equal('user_id', str(self.user.id)),
Intersects('concerned_roles_array', user_roles),
]
viewable_formdatas = sql.AnyFormData.select(criterias)
viewable_formdatas_ids = {}
for viewable_formdata in viewable_formdatas:
viewable_formdatas_ids[(viewable_formdata.formdef.id, viewable_formdata.id)] = True
r = TemplateIO(html=True)
r += get_session().display_message()
r += htmltext('<div class="bo-block">')
r += htmltext('<h2>%s</h2>') % self.user.display_name
formdef = UserFieldsFormDef()
r += htmltext('<div class="form">')
for field in formdef.get_all_fields():
if not hasattr(field, 'get_view_value'):
continue
value = self.user.form_data.get(field.id)
if not value:
continue
r += htmltext('<div class="title">')
r += field.label
r += htmltext('</div>')
r += htmltext('<div class="StringWidget content">')
r += field.get_view_value(value)
r += htmltext('</div>')
r += htmltext('</div>')
r += htmltext('</div>')
if formdatas:
categories = {}
formdata_by_category = {}
for formdata in formdatas:
if formdata.formdef.category_id not in categories:
categories[formdata.formdef.category_id] = formdata.formdef.category
formdata_by_category[formdata.formdef.category_id] = []
formdata_by_category[formdata.formdef.category_id].append(formdata)
cats = list(categories.values())
Category.sort_by_position(cats)
for cat in cats:
r += htmltext('<div class="bo-block">')
if len(cats) > 1:
if cat is None:
r += htmltext('<h2>%s</h2>') % _('Misc')
cat_formdatas = formdata_by_category[None]
else:
r += htmltext('<h2>%s</h2>') % cat.name
cat_formdatas = formdata_by_category[cat.id]
else:
cat_formdatas = formdatas
r += htmltext('<ul class="biglist c-360-user-view">')
for formdata in cat_formdatas:
status_label = formdata.get_status_label()
submit_date = misc.strftime(misc.date_format(), formdata.receipt_time)
formdata_key_id = (formdata.formdef.id, formdata.id)
if formdata_key_id in viewable_formdatas_ids:
r += htmltext(
'<li><a href="%s">%s, '
'<span class="datetime">%s</span> '
'<span class="status">(%s)</span></a>'
% (
formdata.get_url(backoffice=True),
formdata.formdef.name,
submit_date,
status_label,
)
)
else:
r += htmltext(
'<li><span>%s, '
'<span class="datetime">%s</span> '
'<span class="status">(%s)</span></span>'
% (formdata.formdef.name, submit_date, status_label)
)
if formdata.formdef.enable_tracking_codes:
r += htmltext('<p class="commands">')
r += command_icon(
'sendcode/%s/%s' % (formdata.formdef.url_name, formdata.id),
'export',
label=_('Send tracking code'),
popup=True,
)
r += htmltext('</p>')
r += htmltext('</ul>')
r += htmltext('</div>')
r += htmltext('</div>')
return r.getvalue()
class UsersViewDirectory(Directory):
_q_exports = ['']
def _q_traverse(self, path):
if not get_publisher().is_using_postgresql():
raise errors.TraversalError()
get_response().breadcrumb.append(('users', _('Per User View')))
return super()._q_traverse(path)
def get_search_sidebar(self, offset=None, limit=None, order_by=None):
get_response().add_javascript(['wcs.listing.js'])
r = TemplateIO(html=True)
r += htmltext('<form id="listing-settings" action=".">')
if offset or limit:
if not offset:
offset = 0
r += htmltext('<input type="hidden" name="offset" value="%s"/>') % offset
if limit:
r += htmltext('<input type="hidden" name="limit" value="%s"/>') % limit
if order_by is None:
order_by = ''
r += htmltext('<input type="hidden" name="order_by" value="%s"/>') % order_by
r += htmltext('<h3>%s</h3>') % _('Search')
if get_request().form.get('q'):
q = force_text(get_request().form.get('q'))
r += htmltext('<input name="q" value="%s">') % force_str(q)
else:
r += htmltext('<input name="q">')
r += htmltext('<input type="submit" value="%s"/>') % _('Search')
return r.getvalue()
def _q_index(self):
html_top('management', _('Management'))
r = TemplateIO(html=True)
limit = misc.get_int_or_400(
get_request().form.get('limit', get_publisher().get_site_option('default-page-size')) or 20
)
offset = misc.get_int_or_400(get_request().form.get('offset', 0))
order_by = misc.get_order_by_or_400(get_request().form.get('order_by', None)) or 'name'
query = get_request().form.get('q')
get_response().filter['sidebar'] = self.get_search_sidebar(
limit=limit, offset=offset, order_by=order_by
)
if not query:
r += htmltext('<div id="listing">')
r += htmltext('<div class="big-msg-info">')
r += htmltext('<p>%s</p>') % _('Use the search field on the right ' 'to look for an user.')
r += htmltext('</div>')
r += htmltext('</div>')
if get_request().form.get('ajax') == 'true':
get_response().filter = {'raw': True}
return r.getvalue()
formdef = UserFieldsFormDef()
criteria_fields = [
ILike('name', query),
ILike('ascii_name', misc.simplify(query, ' ')),
ILike('email', query),
]
for field in formdef.get_all_fields():
if field.type in ('string', 'text', 'email'):
criteria_fields.append(ILike('f%s' % field.id, query))
if get_publisher().is_using_postgresql():
criteria_fields.append(FtsMatch(query))
criterias = [Or(criteria_fields)]
users = get_publisher().user_class.select(
order_by=order_by, clause=criterias, limit=limit, offset=offset
)
total_count = get_publisher().user_class.count(criterias)
users_cfg = get_cfg('users', {})
include_name_column = not (users_cfg.get('field_name'))
include_email_column = not (users_cfg.get('field_email'))
r += htmltext('<div id="listing">')
r += htmltext('<table class="main">')
r += htmltext('<thead>')
r += htmltext('<tr>')
if include_name_column:
r += htmltext('<th data-field-sort-key="name"><span>%s</span></th>') % _('Name')
if include_email_column:
r += htmltext('<th data-field-sort-key="email"><span>%s</span></th>') % _('Email')
for field in formdef.get_all_fields():
if field.include_in_listing:
r += htmltext('<th data-field-sort-key="f%s"><span>%s</span></th>') % (field.id, field.label)
r += htmltext('</tr>')
r += htmltext('</thead>')
r += htmltext('<tbody>')
for user in users:
r += htmltext('<tr data-link="%s/">') % user.id
if include_name_column:
r += htmltext('<td>%s</td>') % (user.name or '')
if include_email_column:
r += htmltext('<td>%s</td>') % (user.email or '')
for field in formdef.get_all_fields():
if field.include_in_listing:
r += htmltext('<td>%s</td>') % (user.form_data.get(field.id) or '')
r += htmltext('</tr>')
r += htmltext('</tbody>')
r += htmltext('</table>')
if get_publisher().is_using_postgresql():
r += pagination_links(offset, limit, total_count)
r += htmltext('</div>')
if get_request().form.get('ajax') == 'true':
get_response().filter = {'raw': True}
return r.getvalue()
return r.getvalue()
def _q_lookup(self, component):
try:
user = get_publisher().user_class.get(component)
return UserViewDirectory(user)
except KeyError:
pass
raise errors.TraversalError()
class ManagementDirectory(Directory):
_q_exports = ['', 'forms', 'listing', 'statistics', 'lookup', 'count', 'users', 'geojson', 'map']
users = UsersViewDirectory()
_q_exports = ['', 'forms', 'listing', 'statistics', 'lookup', 'count', 'geojson', 'map']
def add_breadcrumb(self):
get_response().breadcrumb.append(('management/', _('Management')))
@ -544,8 +208,6 @@ class ManagementDirectory(Directory):
and get_publisher().get_site_option('postgresql_views') != 'false'
):
r += htmltext('<a href="listing">%s</a>') % _('Global View')
if get_publisher().has_site_option('per-user-view'):
r += htmltext(' <a href="users/">%s</a>') % _('Per User View')
for formdef in formdefs:
if formdef.geolocations:
r += htmltext(' <a href="map">%s</a>') % _('Map View')