backoffice: add "current forms" view (#8227)

This commit is contained in:
Frédéric Péters 2015-09-10 14:21:51 +02:00
parent 1e124b0cf2
commit 9404eb65ee
4 changed files with 163 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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