PR#4387: repo requests web ui
Merges #4387 https://pagure.io/koji/pull-request/4387 Fixes: #4289 https://pagure.io/koji/issue/4289 repo requests web ui
This commit is contained in:
commit
45c290dd74
13 changed files with 1422 additions and 27 deletions
|
|
@ -44,6 +44,8 @@ class FakeClientSession(BaseFakeClientSession):
|
|||
super(FakeClientSession, self).__init__(*a, **kw)
|
||||
self._calldata = {}
|
||||
self._offsets = {}
|
||||
self._missing_rsession = None
|
||||
# caller can set _missing_rsession to a Recording session to handle missing calls
|
||||
|
||||
def load_calls(self, data):
|
||||
"""Load call data
|
||||
|
|
@ -75,6 +77,9 @@ class FakeClientSession(BaseFakeClientSession):
|
|||
# we may have a series of calls for each key
|
||||
calls = self._calldata.get(key)
|
||||
ofs = self._offsets.get(key, 0)
|
||||
if calls is None:
|
||||
# we don't have it
|
||||
return self._handle_missing(name, args, kwargs)
|
||||
call = calls[ofs]
|
||||
ofs += 1
|
||||
if ofs < len(calls):
|
||||
|
|
@ -90,6 +95,15 @@ class FakeClientSession(BaseFakeClientSession):
|
|||
else:
|
||||
return mock.MagicMock()
|
||||
|
||||
def _handle_missing(self, name, args, kwargs):
|
||||
print('Missing call data for: %s %r %r' % (name, args, kwargs))
|
||||
rsession = self._missing_rsession
|
||||
if rsession is None:
|
||||
return mock.MagicMock()
|
||||
|
||||
# otherwise use the recording session
|
||||
return rsession._callMethod(name, args, kwargs)
|
||||
|
||||
def _munge(self, data):
|
||||
def callback(value):
|
||||
if isinstance(value, list):
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -27,7 +27,9 @@ class TestPages(unittest.TestCase):
|
|||
def setUpClass(cls):
|
||||
# recording session used across tests in recording mode
|
||||
cls.cfile = os.path.dirname(__file__) + f'/data/pages_calls.json'
|
||||
cls.cfile2 = os.path.dirname(__file__) + f'/data/pages_calls_updates.json'
|
||||
cls.recording = False
|
||||
cls.updating = False
|
||||
cls.rsession = RecordingClientSession('http://localhost/kojihub', {})
|
||||
|
||||
@classmethod
|
||||
|
|
@ -35,6 +37,8 @@ class TestPages(unittest.TestCase):
|
|||
if cls.recording:
|
||||
# save recorded calls
|
||||
cls.rsession.dump(cls.cfile)
|
||||
elif cls.updating:
|
||||
cls.rsession.dump(cls.cfile2)
|
||||
|
||||
def setUp(self):
|
||||
self.environ = {
|
||||
|
|
@ -66,6 +70,8 @@ class TestPages(unittest.TestCase):
|
|||
self.time.return_value = 1735707600.0
|
||||
|
||||
def __get_server(env):
|
||||
# this is replacing the call to _getServer
|
||||
env['koji.session'] = self.server
|
||||
return self.server
|
||||
|
||||
self.get_server.side_effect = __get_server
|
||||
|
|
@ -77,6 +83,8 @@ class TestPages(unittest.TestCase):
|
|||
else:
|
||||
self.server = FakeClientSession('SERVER', {})
|
||||
self.server.load(self.cfile)
|
||||
if self.updating:
|
||||
self.server._missing_rsession = self.rsession
|
||||
return self.server
|
||||
|
||||
def tearDown(self):
|
||||
|
|
@ -166,17 +174,27 @@ class TestPages(unittest.TestCase):
|
|||
['buildroots', ''],
|
||||
['buildroots', 'start=50&order=id'],
|
||||
#['builds', 'start=50&order=id'],
|
||||
['reporequests', ''],
|
||||
['reporequests', 'active=all&order=tag_name'],
|
||||
['reporequests', 'active=false&order=-id'],
|
||||
['reporequests', 'tag=1&active=all'],
|
||||
['reporequest', 'reqID=127'],
|
||||
['reporequest', 'reqID=128'],
|
||||
['reporequest', 'reqID=132'],
|
||||
['reporequest', 'reqID=133'],
|
||||
['reporequest', 'reqID=134'],
|
||||
]
|
||||
|
||||
def prep_handler(self, method, query):
|
||||
"""Takes method name and query string, returns handler and data"""
|
||||
# based loosely on publisher prep_handler
|
||||
self.environ['QUERY_STRING'] = query
|
||||
self.environ['koji.method'] = method
|
||||
self.environ['SCRIPT_NAME'] = method
|
||||
environ = self.environ.copy()
|
||||
environ['QUERY_STRING'] = query
|
||||
environ['koji.method'] = method
|
||||
environ['SCRIPT_NAME'] = method
|
||||
handler = getattr(webidx, method)
|
||||
fs = FieldStorageCompat(self.environ)
|
||||
self.environ['koji.form'] = fs
|
||||
fs = FieldStorageCompat(environ)
|
||||
environ['koji.form'] = fs
|
||||
# even though we have curated urls, we need to filter args for some cases, e.g. search
|
||||
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \
|
||||
inspect.getfullargspec(handler)
|
||||
|
|
@ -184,14 +202,14 @@ class TestPages(unittest.TestCase):
|
|||
data = dslice(fs.data, args, strict=False)
|
||||
else:
|
||||
data = fs.data.copy()
|
||||
return handler, data
|
||||
return handler, data, environ
|
||||
|
||||
def test_web_handlers(self):
|
||||
"""Test a bunch of web handlers"""
|
||||
for method, query in self.CALLS:
|
||||
handler, data = self.prep_handler(method, query)
|
||||
handler, data, environ = self.prep_handler(method, query)
|
||||
|
||||
result = handler(self.environ, **data)
|
||||
result = handler(environ, **data)
|
||||
|
||||
# result should be a string containing the rendered template
|
||||
self.assertIsInstance(result, str)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class TestRepoInfo(unittest.TestCase):
|
|||
|
||||
webidx.repoinfo(self.environ, self.repo_id)
|
||||
self.server.repoInfo.assert_called_once_with(int(self.repo_id), strict=False)
|
||||
self.server.listBuildroots.assert_called_once_with(repoID=int(self.repo_id))
|
||||
self.server.listBuildroots.assert_called_once_with(repoID=int(self.repo_id), queryOpts={'countOnly': True})
|
||||
|
||||
def test_repoinfo_not_dist(self):
|
||||
"""Test repoinfo function - not dist repo"""
|
||||
|
|
@ -46,4 +46,7 @@ class TestRepoInfo(unittest.TestCase):
|
|||
|
||||
webidx.repoinfo(self.environ, self.repo_id)
|
||||
self.server.repoInfo.assert_called_once_with(int(self.repo_id), strict=False)
|
||||
self.server.listBuildroots.assert_called_once_with(repoID=int(self.repo_id))
|
||||
self.server.listBuildroots.assert_called_once_with(repoID=int(self.repo_id), queryOpts={'countOnly': True})
|
||||
|
||||
|
||||
# the end
|
||||
|
|
|
|||
|
|
@ -797,6 +797,10 @@ def taskinfo(environ, taskID):
|
|||
else:
|
||||
values['perms'] = []
|
||||
|
||||
values['requests'] = []
|
||||
if task['method'] == 'newRepo':
|
||||
values['requests'] = server.repo.queryQueue([['task_id', '=', task['id']]], ['id'])
|
||||
|
||||
values['koji'] = koji
|
||||
values['S'] = SafeValue
|
||||
|
||||
|
|
@ -1051,8 +1055,10 @@ def taginfo(environ, tagID, all='0', packageOrder='package_name', packageStart=N
|
|||
values['srcTargets'] = srcTargets
|
||||
values['destTargets'] = destTargets
|
||||
values['all'] = all
|
||||
values['repo'] = server.getRepo(tag['id'], state=koji.REPO_READY)
|
||||
values['repo'] = server.repo.get(tag['id'])
|
||||
values['external_repos'] = server.getExternalRepoList(tag['id'])
|
||||
values['request_count'] = server.repo.queryQueue([['tag_id', '=', tag['id']]],
|
||||
opts={'countOnly': True})
|
||||
|
||||
child = None
|
||||
if childID is not None:
|
||||
|
|
@ -2696,13 +2702,72 @@ def repoinfo(environ, repoID):
|
|||
else:
|
||||
values['repo_json'] = os.path.join(
|
||||
pathinfo.repo(repo_info['id'], repo_info['tag_name']), 'repo.json')
|
||||
num_buildroots = len(server.listBuildroots(repoID=repoID)) or 0
|
||||
values['numBuildroots'] = num_buildroots
|
||||
num_buildroots = server.listBuildroots(repoID=repoID, queryOpts={'countOnly': True})
|
||||
values['numBuildroots'] = num_buildroots
|
||||
values['requests'] = server.repo.queryQueue([['repo_id', '=', repoID]], ['id'])
|
||||
|
||||
values['state_name'] = kojiweb.util.repoState(repo_info['state'])
|
||||
values['create_time'] = kojiweb.util.formatTimeLong(repo_info['create_ts'])
|
||||
return _genHTML(environ, 'repoinfo.html.j2')
|
||||
|
||||
|
||||
def reporequest(environ, reqID):
|
||||
values = _initValues(environ, 'Repo Request', 'tags')
|
||||
server = _getServer(environ)
|
||||
|
||||
req_id = int(reqID)
|
||||
values['req_id'] = req_id
|
||||
rows = server.repo.queryQueue([['id', '=', req_id]], '**')
|
||||
if not rows:
|
||||
raise koji.GenericError("No such repo request: %s" % req_id)
|
||||
req = rows[0]
|
||||
if req['at_event']:
|
||||
values['at_event'] = server.getEvent(req['at_event'])
|
||||
elif req['min_event']:
|
||||
values['min_event'] = server.getEvent(req['min_event'])
|
||||
else:
|
||||
# invalid, but technically not blocked in db
|
||||
values['min_event'] = None
|
||||
values['req'] = req
|
||||
return _genHTML(environ, 'reporequest.html.j2')
|
||||
|
||||
|
||||
def reporequests(environ, active="true", tag=None, start=None, order=None):
|
||||
values = _initValues(environ, 'Repo Requests', 'tags')
|
||||
server = _getServer(environ)
|
||||
|
||||
clauses = []
|
||||
desc_parts = []
|
||||
if active.lower() == 'all':
|
||||
desc_parts.append('Recent repo requests')
|
||||
elif active.lower() in ('false', 'no', '0'):
|
||||
desc_parts.append('Inactive repo requests')
|
||||
clauses.append(["active", False])
|
||||
active = 'false'
|
||||
else:
|
||||
desc_parts.append('Active repo requests')
|
||||
clauses.append(["active", True])
|
||||
active = 'true'
|
||||
if tag:
|
||||
taginfo = server.getTag(_convert_if_int(tag), strict=True, event='auto')
|
||||
clauses.append(["tag_id", taginfo['id']])
|
||||
desc_parts.append('for tag %(name)s' % taginfo)
|
||||
tag = taginfo['name']
|
||||
else:
|
||||
tag = None
|
||||
if order is None:
|
||||
order = '-id'
|
||||
values['desc'] = ' '.join(desc_parts)
|
||||
values['order'] = order
|
||||
values['active'] = active
|
||||
values['tag'] = tag
|
||||
kojiweb.util.paginateMethod(server, values, 'repo.queryQueue',
|
||||
args=(clauses, '**'),
|
||||
start=start, dataName='reqs', prefix='req', order=order,
|
||||
optsarg='opts')
|
||||
return _genHTML(environ, 'reporequests.html.j2')
|
||||
|
||||
|
||||
def activesession(environ, start=None, order=None):
|
||||
values = _initValues(environ, 'Active sessions', 'activesession')
|
||||
server = _getServer(environ)
|
||||
|
|
|
|||
|
|
@ -7,16 +7,35 @@
|
|||
<tr><th>ID</th><td>{{ repo.id }}</td><th></tr>
|
||||
<tr><th>Tag</th><td><a href="taginfo?tagID={{ repo.tag_id }}">{{ repo.tag_name }}</a></td></tr>
|
||||
{% if repo.task_id %}
|
||||
<tr><th>Task ID</th><td><a href="taskinfo?taskID={{ repo.task_id }}">{{ repo.task_id }}</a></td></tr>
|
||||
<tr><th>Task ID</th><td><a href="taskinfo?taskID={{ repo.task_id }}">{{ repo.task_id }}</a> ({{ util.taskState(repo.task_state) }})</td></tr>
|
||||
{% endif %}
|
||||
<tr><th>State</th><td class="repo{{ state_name }}">{{ state_name }}</td></tr>
|
||||
<tr><th>Event</th><td>{{ repo.create_event }} ({{ create_time }})</td></tr>
|
||||
<tr><th>Created</th><td>{{ util.formatTimeLong(repo.creation_ts) }}</td></tr>
|
||||
<tr><th>State changed</th><td>{{ util.formatTimeLong(repo.state_ts) }}</td></tr>
|
||||
<tr><th>Created from Event</th><td>{{ repo.create_event }} ({{ util.formatTimeLong(repo.create_ts) }})</td></tr>
|
||||
{%- if repo.begin_event %}
|
||||
{%- if repo.end_event %}
|
||||
<tr><th>Event range</th><td>{{ repo.begin_event }} ... {{ repo.end_event }}</td></tr>
|
||||
{%- else %}
|
||||
<tr><th>Event range</th><td>{{ repo.begin_event }} ... </td></tr>
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- if state_name != 'deleted' %}
|
||||
<tr><th>URL</th><td><a href="{{ url }}">repodata</a></td></tr>
|
||||
<tr><th>Repo json</th><td><a href="{{ repo_json }}">repo.json</a></td></tr>
|
||||
{%- endif %}
|
||||
#if repo.custom_opts
|
||||
<th>Custom Opts</th><td class="usertext">{{ json.dumps(repo.custom_opts, indent=4) }}</td>
|
||||
#endif
|
||||
<tr><th>Dist repo?</th><td class="{{ repo.dist | lower }}">{{ 'yes' if repo.dist else 'no' }}</td></tr>
|
||||
<tr><th>Number of buildroots: </th><td><a href="buildroots?repoID={{ repo.id }}">{{ numBuildroots }}</a></td></tr>
|
||||
#if requests
|
||||
<tr><th>Fulfills requests:</th><td>
|
||||
#for req in requests
|
||||
<a href="reporequest?reqID={{ req.id }}">{{ req.id }}</a>
|
||||
#endfor
|
||||
</td/></tr>
|
||||
#endif
|
||||
</table>
|
||||
{% else %}
|
||||
Repo {{ repo_id }} not found.
|
||||
|
|
|
|||
38
www/kojiweb/templates/reporequest.html.j2
Normal file
38
www/kojiweb/templates/reporequest.html.j2
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
#include "header.html.j2"
|
||||
|
||||
<h4>Information for repo request {{ req_id }}</h4>
|
||||
|
||||
#if not req
|
||||
Repo request {{ req_id }} not found.
|
||||
#else
|
||||
<table>
|
||||
<tr><th>ID</th><td>{{ req.id }}</td></tr>
|
||||
<tr><th>Active</th><td>{{ req.active }}</td></tr>
|
||||
<tr><th>Priority</th><td>{{ req.priority }}</td></tr>
|
||||
<tr><th>Tag</th><td><a href="taginfo?tagID={{ req.tag_id }}">{{ req.tag_name }}</a></td></tr>
|
||||
#if req.at_event
|
||||
<tr><th>At specific event</th><td>{{ at_event.id }} ({{ util.formatTimeLong(at_event.ts) }})</td></tr>
|
||||
#elif req.min_event
|
||||
<tr><th>Minimum event</th><td>{{ min_event.id }} ({{ util.formatTimeLong(min_event.ts) }})</td></tr>
|
||||
#else
|
||||
<tr><th>Invalid event</th><td>Unable to determine event for request</td></tr>
|
||||
#endif
|
||||
#if req.opts
|
||||
<th>Options</th><td class="usertext">{{ req.opts | tojson(indent=4) }}</td>
|
||||
#endif
|
||||
#if req.repo_id
|
||||
<tr><th>Fulfilled by repo</th><td><a href="repoinfo?repoID={{ req.repo_id }}">{{ req.repo_id }}</a></td></tr>
|
||||
#endif
|
||||
#if req.task_id
|
||||
<tr><th>Task ID</th><td><a href="taskinfo?taskID={{ req.task_id }}">{{ req.task_id }}</a> ({{ util.taskState(req.task_state) }})</td></tr>
|
||||
<tr><th>Tries</th><td>{{ req.tries }}</td></tr>
|
||||
#endif
|
||||
<tr><th>Owner</th><td><a href="userinfo?userID={{ req.owner }}">{{ req.owner_name }}</a></td></tr>
|
||||
<tr><th>Created</th><td>{{ util.formatTimeLong(req.create_ts) }}</td></tr>
|
||||
<tr><th>Updated</th><td>{{ util.formatTimeLong(req.update_ts) }}</td></tr>
|
||||
</table>
|
||||
#endif
|
||||
|
||||
|
||||
#include "footer.html.j2"
|
||||
127
www/kojiweb/templates/reporequests.html.j2
Normal file
127
www/kojiweb/templates/reporequests.html.j2
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
#include "header.html.j2"
|
||||
|
||||
# from "macros.html.j2" import rowToggle
|
||||
|
||||
#set Pvars = ('active', 'tag', 'order')
|
||||
#set P = util.passthrough
|
||||
|
||||
<h4>{{ desc }}</h4>
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td colspan="6">
|
||||
<form action="">
|
||||
<table class="nested">
|
||||
<tr><td>
|
||||
<strong>Active</strong>:
|
||||
</td><td>
|
||||
<select name="active" class="filterlist" onchange="javascript: window.location = 'reporequests?active=' + this.value + '{{ P('tag', 'order') }}';">
|
||||
<option value="true" {{ 'selected' if active == 'true' else '' }}>true</option>
|
||||
<option value="false" {{ 'selected' if active == 'false' else '' }}>false</option>
|
||||
<option value="all" {{ 'selected' if active == 'all' else '' }}>all</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<strong>Tag</strong>:
|
||||
</td><td>
|
||||
<input type="text" name="tag" value="{{ tag if tag else '' }}"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paginate" colspan="6">
|
||||
#if (reqPages |length) > 1
|
||||
<form class="pageJump" action="">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'reporequests?start=' + this.value * {{ reqRange }} + '{{ P(*Pvars) }}';">
|
||||
#for pageNum in reqPages
|
||||
<option value="{{ pageNum }}"{{ ' selected' if pageNum == reqCurrentPage else '' }}>{{ pageNum + 1 }}</option>
|
||||
#endfor
|
||||
</select>
|
||||
</form>
|
||||
#endif
|
||||
#if reqStart > 0
|
||||
<a href="reporequests?start={{ reqStart - reqRange }}{{ P(*Pvars) }}"><<<</a>
|
||||
#endif
|
||||
#if totalReqs != 0
|
||||
<strong>Requests {{ reqStart + 1 }} through {{ reqStart + reqCount }} of {{ totalReqs }}</strong>
|
||||
#endif
|
||||
#if reqStart + reqCount < totalReqs
|
||||
<a href="reporequests?start={{ reqStart + reqRange }}{{ P(*Pvars) }}">>>></a>
|
||||
#endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="reporequests?{{ P(*Pvars, toggleOrder='id', prefix='') }}">ID</a> {{ util.sortImage('id') }}</th>
|
||||
<th><a href="reporequests?{{ P(*Pvars, toggleOrder='priority', prefix='') }}">Priority</a> {{ util.sortImage('priority') }}</th>
|
||||
<th><a href="reporequests?{{ P(*Pvars, toggleOrder='tag_name', prefix='') }}">Tag</a> {{ util.sortImage('tag_name') }}</th>
|
||||
<th>Task</th>
|
||||
<th>Repo</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
#if (reqs |length) > 0
|
||||
#for req in reqs
|
||||
<tr class="{{ rowToggle(loop) }}">
|
||||
<td><a href="reporequest?reqID={{ req.id }}">{{ req.id }}</a></td>
|
||||
<td>{{ req.priority }}</td>
|
||||
<td>
|
||||
<a href="taginfo?tagID={{ req.tag_id }}">{{ req.tag_name }}</a>
|
||||
#if not tag
|
||||
<a href="reporequests?tag={{ req.tag_id }}{{ P('active', 'order') }}" title="Filter by tag"><img src="{{ util.themePath('images/funnel.svg') }}" alt="^" /></a>
|
||||
#endif
|
||||
</td>
|
||||
#if req.task_id
|
||||
<td><a href="taskinfo?taskID={{ req.task_id }}">{{ req.task_id }}</a></td>
|
||||
#else
|
||||
<td>...</td>
|
||||
#endif
|
||||
#if req.repo_id
|
||||
<td><a href="repoinfo?repoID={{ req.repo_id }}">{{ req.repo_id }}</a></td>
|
||||
#else
|
||||
<td>...</td>
|
||||
#endif
|
||||
<td>
|
||||
## simulate a more helpful status
|
||||
#if req.active
|
||||
{{ util.imageTag('waiting') }}
|
||||
#elif req.repo_id
|
||||
{{ util.imageTag('yes') }}
|
||||
#else
|
||||
{{ util.imageTag('no') }}
|
||||
#endif
|
||||
</td>
|
||||
</tr>
|
||||
#endfor
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="2">No repo requests</td>
|
||||
</tr>
|
||||
#endif
|
||||
<tr>
|
||||
<td class="paginate" colspan="2">
|
||||
#if (reqPages |length) > 1
|
||||
<form class="pageJump" action="">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'reporequests?start=' + this.value * {{ reqRange }} + '{{ P(*Pvars) }}';">
|
||||
#for pageNum in reqPages
|
||||
<option value="{{ pageNum }}"{{ ' selected' if pageNum == reqCurrentPage else '' }}>{{ pageNum + 1 }}</option>
|
||||
#endfor
|
||||
</select>
|
||||
</form>
|
||||
#endif
|
||||
#if reqStart > 0
|
||||
<a href="reporequests?start={{ reqStart - reqRange }}{{ P(*Pvars) }}"><<<</a>
|
||||
#endif
|
||||
#if totalReqs != 0
|
||||
<strong>Reqs {{ reqStart + 1 }} through {{ reqStart + reqCount }} of {{ totalReqs }}</strong>
|
||||
#endif
|
||||
#if reqStart + reqCount < totalReqs
|
||||
<a href="reporequests?start={{ reqStart + reqRange }}{{ P(*Pvars) }}">>>></a>
|
||||
#endif
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "footer.html.j2"
|
||||
|
|
@ -117,6 +117,10 @@
|
|||
#endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Repo requests</th>
|
||||
<td><a href="reporequests?active=all&tag={{ tag.id }}">{{ request_count }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Packages</th>
|
||||
<td><a href="packages?blocked=0&tagID={{ tag.id }}">{{ numPackages }}</a></td>
|
||||
|
|
|
|||
|
|
@ -140,6 +140,14 @@ None
|
|||
</tr>
|
||||
#endfor
|
||||
#endif
|
||||
#if requests
|
||||
<tr><th>For request:</th><td>
|
||||
## we only expect one, but if we get more print them all
|
||||
#for req in requests
|
||||
<a href="reporequest?reqID={{ req.id }}">{{ req.id }}</a>
|
||||
#endfor
|
||||
</td/></tr>
|
||||
#endif
|
||||
<tr>
|
||||
<th>Created</th><td>{{ util.formatTimeLong(task.create_ts) }}</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -353,17 +353,17 @@ def _sortImage(orderVal, sortKey, orderVar):
|
|||
|
||||
@safe_return
|
||||
@pass_context
|
||||
def passthrough(context, *varnames, prefix='&', invert=False):
|
||||
def passthrough(context, *varnames, prefix='&', invert=False, toggleOrder=None):
|
||||
if invert:
|
||||
_PASSTHROUGH = context.get('_PASSTHROUGH', None)
|
||||
if _PASSTHROUGH is None:
|
||||
raise Exception('template does not define _PASSTHROUGH')
|
||||
varnames = {n for n in _PASSTHROUGH if n not in varnames}
|
||||
data = {n: context.get(n, default=None) for n in varnames}
|
||||
return _passthrough(data, prefix)
|
||||
return _passthrough(data, prefix, toggleOrder)
|
||||
|
||||
|
||||
def _passthrough(data, prefix='&'):
|
||||
def _passthrough(data, prefix='&', toggleOrder=None):
|
||||
"""
|
||||
Construct a url parameter string from template vars
|
||||
|
||||
|
|
@ -380,6 +380,11 @@ def _passthrough(data, prefix='&'):
|
|||
result = []
|
||||
for var in sorted(data):
|
||||
value = data[var]
|
||||
if var == 'order' and toggleOrder is not None:
|
||||
if value == toggleOrder:
|
||||
value = '-' + value
|
||||
else:
|
||||
value = toggleOrder
|
||||
if value is not None:
|
||||
if isinstance(value, str):
|
||||
if value.isdigit():
|
||||
|
|
@ -396,7 +401,7 @@ def _passthrough(data, prefix='&'):
|
|||
|
||||
|
||||
@pass_context
|
||||
def passthrough_except(context, *exclude, prefix='&'):
|
||||
def passthrough_except(context, *exclude, prefix='&', toggleOrder=None):
|
||||
"""
|
||||
Construct a string suitable for use as URL
|
||||
parameters. The template calling this method must have
|
||||
|
|
@ -408,7 +413,7 @@ def passthrough_except(context, *exclude, prefix='&'):
|
|||
"""
|
||||
# note that we have to pass context ourselves here
|
||||
# the decorator only works when called directly from the template
|
||||
return passthrough(context, *exclude, prefix=prefix, invert=True)
|
||||
return passthrough(context, *exclude, prefix=prefix, invert=True, toggleOrder=toggleOrder)
|
||||
|
||||
|
||||
def sortByKeyFuncNoneGreatest(key):
|
||||
|
|
@ -459,7 +464,7 @@ def paginateList(values, data, start, dataName, prefix=None, order=None, noneGre
|
|||
|
||||
def paginateMethod(server, values, methodName, args=None, kw=None,
|
||||
start=None, dataName=None, prefix=None, order=None, pageSize=50,
|
||||
first_page_count=True):
|
||||
first_page_count=True, optsarg='queryOpts'):
|
||||
"""Paginate the results of the method with the given name when called with the given args and
|
||||
kws. The method must support the queryOpts keyword parameter, and pagination is done in the
|
||||
database.
|
||||
|
|
@ -483,12 +488,12 @@ def paginateMethod(server, values, methodName, args=None, kw=None,
|
|||
if start == 0 and not first_page_count:
|
||||
totalRows = None
|
||||
else:
|
||||
kw['queryOpts'] = {'countOnly': True}
|
||||
kw[optsarg] = {'countOnly': True}
|
||||
totalRows = getattr(server, methodName)(*args, **kw)
|
||||
|
||||
kw['queryOpts'] = {'order': order,
|
||||
'offset': start,
|
||||
'limit': pageSize}
|
||||
kw[optsarg] = {'order': order,
|
||||
'offset': start,
|
||||
'limit': pageSize}
|
||||
data = getattr(server, methodName)(*args, **kw)
|
||||
count = len(data)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
SERVERDIR = /images
|
||||
FILES = $(wildcard *.gif *.png *.ico)
|
||||
FILES = $(wildcard *.gif *.png *.ico *.svg)
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
|
|
|||
12
www/static/images/funnel.svg
Normal file
12
www/static/images/funnel.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" height="16px" width="16px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 485.008 485.008" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M171.501,464.698v-237.9l-166.3-192.6c-8.9-10.9-7.9-33.3,15.1-33.3h443.6c21.6,0,26.6,19.8,15.1,33.3l-162.3,187.5v147.2
|
||||
c0,6-2,11.1-7.1,15.1l-103.8,95.8C193.801,488.698,171.501,483.898,171.501,464.698z M64.701,41.298l142.2,164.3c3,4,5,8.1,5,13.1
|
||||
v200.6l64.5-58.5v-146.1c0-5,2-9.1,5-13.1l138.1-160.3L64.701,41.298L64.701,41.298z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 716 B |
Loading…
Add table
Add a link
Reference in a new issue