backoffice: add "current forms" view (#8227)
This commit is contained in:
parent
1e124b0cf2
commit
9404eb65ee
|
@ -110,6 +110,8 @@ def create_environment(pub, set_receiver=True):
|
|||
formdata.store()
|
||||
|
||||
formdef = FormDef()
|
||||
if set_receiver:
|
||||
formdef.workflow_roles = {'_receiver': 1}
|
||||
formdef.name = 'other form'
|
||||
formdef.fields = []
|
||||
formdef.store()
|
||||
|
@ -118,6 +120,7 @@ def create_environment(pub, set_receiver=True):
|
|||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2014, 1, 1).timetuple()
|
||||
formdata.jump_status('new')
|
||||
formdata.store()
|
||||
|
||||
def teardown_module(module):
|
||||
|
@ -747,3 +750,30 @@ def test_backoffice_wscall_failure_display(pub):
|
|||
resp = app.get('/form-title/%s/' % number31)
|
||||
assert (' with the number %s.' % number31) in resp.body
|
||||
assert not 'Error during webservice call' in resp.body
|
||||
|
||||
def test_global_listing(pub):
|
||||
if not pub.is_using_postgresql():
|
||||
pytest.skip('this requires SQL')
|
||||
return
|
||||
|
||||
create_user(pub)
|
||||
create_environment(pub)
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/management/')
|
||||
assert 'General listing' in resp.body
|
||||
resp = resp.click('General listing')
|
||||
assert resp.body.count('<tr') == 20
|
||||
|
||||
resp = app.get('/backoffice/management/listing?limit=500')
|
||||
assert resp.body.count('<tr') == 37 # 17 formdef1 + 20 formdef2
|
||||
|
||||
resp = app.get('/backoffice/management/listing?offset=20&limit=20')
|
||||
assert resp.body.count('<tr') == 17
|
||||
|
||||
resp = app.get('/backoffice/management/listing')
|
||||
resp.form['end'] = '2014-02-01'
|
||||
resp = resp.form.submit()
|
||||
|
||||
assert resp.body.count('<tr') == 20
|
||||
assert 'http://example.net/backoffice/management/other-form/' in resp.body
|
||||
assert not 'http://example.net/backoffice/management/form-title/' in resp.body
|
||||
|
|
|
@ -30,22 +30,24 @@ from quixote.directory import Directory
|
|||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
from qommon.backoffice.menu import html_top
|
||||
from qommon.backoffice.listing import pagination_links
|
||||
from qommon import misc, get_logger
|
||||
from qommon.afterjobs import AfterJob
|
||||
from qommon import errors
|
||||
from qommon import ods
|
||||
from qommon.form import *
|
||||
from qommon.storage import Equal, NotEqual, LessOrEqual, GreaterOrEqual, Or
|
||||
from qommon.storage import Equal, NotEqual, LessOrEqual, GreaterOrEqual, Or, Intersects
|
||||
|
||||
from wcs.forms.backoffice import FormDefUI
|
||||
from wcs.forms.common import FormStatusPage
|
||||
|
||||
from wcs.categories import Category
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.roles import logged_users_role
|
||||
|
||||
|
||||
class ManagementDirectory(Directory):
|
||||
_q_exports = ['', 'statistics']
|
||||
_q_exports = ['', 'listing', 'statistics']
|
||||
|
||||
def is_accessible(self, user):
|
||||
return user.can_go_in_backoffice()
|
||||
|
@ -114,10 +116,39 @@ class ManagementDirectory(Directory):
|
|||
r += htmltext('<div class="bo-block">')
|
||||
r += htmltext('<ul id="sidebar-actions">')
|
||||
r += htmltext('<li><a href="statistics">%s</a></li>') % _('Global statistics')
|
||||
if get_publisher().is_using_postgresql() and \
|
||||
get_publisher().get_site_option('postgresql_views') != 'false':
|
||||
r += htmltext('<li><a href="listing">%s</a></li>') % _('General listing')
|
||||
r += htmltext('</ul>')
|
||||
r += htmltext('</div>')
|
||||
return r.getvalue()
|
||||
|
||||
def get_global_listing_sidebar(self, limit=None, offset=None):
|
||||
get_response().add_javascript(['jquery.js'])
|
||||
DateWidget.prepare_javascript()
|
||||
form = Form(use_tokens=False, id='listing-settings')
|
||||
form.add(CheckboxWidget, 'waiting', title=_('Waiting for an action'),
|
||||
value=True)
|
||||
form.add(DateWidget, 'start', title=_('Start Date'))
|
||||
form.add(DateWidget, 'end', title=_('End Date'))
|
||||
|
||||
if not offset:
|
||||
offset = 0
|
||||
if not limit:
|
||||
limit = int(get_publisher().get_site_option('default-page-size') or 20)
|
||||
form.add_hidden('offset', offset)
|
||||
form.add_hidden('limit', limit)
|
||||
|
||||
form.add_submit('submit', _('Submit'))
|
||||
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<div>')
|
||||
r += htmltext('<h3>%s</h3>') % _('Filters')
|
||||
r += form.render()
|
||||
r += htmltext('</div>')
|
||||
|
||||
return r.getvalue()
|
||||
|
||||
def get_stats_sidebar(self):
|
||||
get_response().add_javascript(['jquery.js'])
|
||||
DateWidget.prepare_javascript()
|
||||
|
@ -165,7 +196,7 @@ class ManagementDirectory(Directory):
|
|||
|
||||
counts = {}
|
||||
parsed_values = {}
|
||||
criterias = get_stats_criteria(get_request(), parsed_values)
|
||||
criterias = get_global_criteria(get_request(), parsed_values)
|
||||
for formdef in formdefs:
|
||||
values = formdef.data_class().select(criterias)
|
||||
counts[formdef.id] = len(values)
|
||||
|
@ -250,6 +281,91 @@ class ManagementDirectory(Directory):
|
|||
r += htmltext('</ul>')
|
||||
return r.getvalue()
|
||||
|
||||
def listing(self):
|
||||
if not get_publisher().is_using_postgresql():
|
||||
raise errors.TraversalError()
|
||||
|
||||
get_response().breadcrumb.append(('listing', _('List of Forms')))
|
||||
from wcs import sql
|
||||
html_top('management', _('Management'))
|
||||
|
||||
user_roles = [logged_users_role().id] + (get_request().user.roles or [])
|
||||
|
||||
limit = int(get_request().form.get('limit',
|
||||
get_publisher().get_site_option('default-page-size') or 20))
|
||||
offset = int(get_request().form.get('offset', 0))
|
||||
|
||||
parsed_values = {}
|
||||
criterias = get_global_criteria(get_request(), parsed_values)
|
||||
criterias.append(Equal('is_at_endpoint', False))
|
||||
if not get_request().form or get_request().form.get('waiting') == 'yes':
|
||||
criterias.append(Intersects('actions_roles_array', user_roles))
|
||||
else:
|
||||
criterias.append(Intersects('concerned_roles_array', user_roles))
|
||||
total_count = sql.AnyFormData.count(criterias)
|
||||
formdatas = sql.AnyFormData.select(criterias,
|
||||
order_by='receipt_time', limit=limit, offset=offset)
|
||||
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<table id="listing" class="main">')
|
||||
r += htmltext('<thead>')
|
||||
r += htmltext('<th>%s</th>') % _('Form')
|
||||
r += htmltext('<th>%s</th>') % _('Date')
|
||||
r += htmltext('<th>%s</th>') % _('User')
|
||||
r += htmltext('<th>%s</th>') % _('Status')
|
||||
r += htmltext('</thead>')
|
||||
r += htmltext('<tbody>')
|
||||
workflows = {}
|
||||
for formdata in formdatas:
|
||||
if not formdata.formdef.workflow_id in workflows:
|
||||
workflows[formdata.formdef.workflow_id] = formdata.formdef.workflow
|
||||
r += htmltext('<tr class="status-%s-%s" data-link="%s">' % (
|
||||
formdata.formdef.workflow.id,
|
||||
formdata.status,
|
||||
formdata.get_url(backoffice=True)))
|
||||
r += htmltext('<td>%s</td>') % formdata.formdef.name
|
||||
r += htmltext('<td class="cell-time">%s</td>') % misc.localstrftime(
|
||||
formdata.receipt_time)
|
||||
try:
|
||||
value = get_publisher().user_class.get(formdata.user_id).display_name
|
||||
r += htmltext('<td class="cell-user">%s</td>') % value
|
||||
except:
|
||||
r += htmltext('<td class="cell-user cell-no-user">-</td>')
|
||||
r += htmltext('<td class="cell-status">%s</td>') % formdata.get_status_label()
|
||||
r += htmltext('</tr>')
|
||||
|
||||
if workflows:
|
||||
colours = []
|
||||
for workflow in workflows.values():
|
||||
for status in workflow.possible_status:
|
||||
if status.colour and status.colour != 'FFFFFF':
|
||||
fg_colour = misc.get_foreground_colour(status.colour)
|
||||
colours.append((workflow.id, status.id, status.colour, fg_colour))
|
||||
if colours:
|
||||
r += htmltext('<style>')
|
||||
for workflow_id, status_id, bg_colour, fg_colour in colours:
|
||||
r += htmltext('tr.status-%s-wf-%s td.cell-status { '\
|
||||
'background-color: #%s !important; color: %s !important; }\n' % (
|
||||
workflow_id, status_id, bg_colour, fg_colour))
|
||||
r += htmltext('</style>')
|
||||
|
||||
r += htmltext('</tbody></table>')
|
||||
|
||||
if (offset > 0) or (total_count > limit > 0):
|
||||
r += pagination_links(offset, limit, total_count)
|
||||
|
||||
if get_request().form.get('ajax') == 'true':
|
||||
get_response().filter = None
|
||||
return r.getvalue()
|
||||
|
||||
get_response().filter['sidebar'] = self.get_global_listing_sidebar()
|
||||
rt = TemplateIO(html=True)
|
||||
rt += htmltext('<h2>%s</h2>') % _('List of Forms')
|
||||
rt += r.getvalue()
|
||||
r = rt
|
||||
return rt.getvalue()
|
||||
|
||||
|
||||
def _q_lookup(self, component):
|
||||
return FormPage(component)
|
||||
|
||||
|
@ -1233,7 +1349,7 @@ $(document).ready(function(){
|
|||
return r.getvalue()
|
||||
|
||||
|
||||
def get_stats_criteria(request, parsed_values=None):
|
||||
def get_global_criteria(request, parsed_values=None):
|
||||
"""
|
||||
Parses the request query string and returns a list of criterias suitable
|
||||
for select() usage. The parsed_values parameter can be given a dictionary,
|
||||
|
|
|
@ -27,6 +27,8 @@ def pagination_links(offset, limit, total_count):
|
|||
r = TemplateIO(html=True)
|
||||
r += htmltext('<div id="page-links">')
|
||||
query = get_request().form.copy()
|
||||
if 'ajax' in query:
|
||||
del query['ajax']
|
||||
if offset > 0:
|
||||
# link to previous page
|
||||
query['offset'] = max(offset-limit, 0)
|
||||
|
|
|
@ -351,6 +351,11 @@ table#listing.activity {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
#listing.main th,
|
||||
#listing.main td {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.letters-nav {
|
||||
text-align: center;
|
||||
border: 1px solid #888;
|
||||
|
@ -655,13 +660,18 @@ h3 span.change {
|
|||
}
|
||||
|
||||
div#page-links {
|
||||
margin: 1em -1ex;
|
||||
margin: 1em 0;
|
||||
padding: 2ex 1ex 0 1ex;
|
||||
}
|
||||
|
||||
div.bo-block div#page-links {
|
||||
margin: 1em -1ex;
|
||||
}
|
||||
|
||||
#page-links .previous-page,
|
||||
#page-links .next-page {
|
||||
display: inline-block;
|
||||
padding: 1ex 1.5ex;
|
||||
}
|
||||
|
||||
#page-links .previous-page:before {
|
||||
|
|
Loading…
Reference in New Issue