backoffice: use a single page for all processing afterjob pages (#49772)
This commit is contained in:
parent
c006ee855c
commit
bbcda9aa66
|
@ -113,7 +113,7 @@ def test_settings_export_import(pub):
|
|||
resp = resp.form.submit('cancel')
|
||||
resp = app.get('/backoffice/settings/export')
|
||||
resp = resp.form.submit('submit')
|
||||
assert resp.location.startswith('http://example.net/backoffice/settings/export?job=')
|
||||
assert resp.location.startswith('http://example.net/backoffice/processing?job=')
|
||||
job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
|
||||
resp = resp.follow()
|
||||
assert 'completed' in resp.text
|
||||
|
@ -161,7 +161,7 @@ def test_settings_export_import(pub):
|
|||
|
||||
resp = app.get('/backoffice/settings/export')
|
||||
resp = resp.form.submit('submit')
|
||||
assert resp.location.startswith('http://example.net/backoffice/settings/export?job=')
|
||||
assert resp.location.startswith('http://example.net/backoffice/processing?job=')
|
||||
job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
|
||||
resp = resp.follow()
|
||||
resp = resp.click('Download Export')
|
||||
|
@ -241,7 +241,7 @@ def test_settings_export_import(pub):
|
|||
resp.form['datasources'] = False
|
||||
resp.form['wscalls'] = False
|
||||
resp = resp.form.submit('submit')
|
||||
assert resp.location.startswith('http://example.net/backoffice/settings/export?job=')
|
||||
assert resp.location.startswith('http://example.net/backoffice/processing?job=')
|
||||
job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
|
||||
resp = resp.follow()
|
||||
resp = resp.click('Download Export')
|
||||
|
@ -275,7 +275,7 @@ def test_settings_export_import(pub):
|
|||
pub.write_cfg()
|
||||
resp = app.get('/backoffice/settings/export')
|
||||
resp = resp.form.submit('submit')
|
||||
assert resp.location.startswith('http://example.net/backoffice/settings/export?job=')
|
||||
assert resp.location.startswith('http://example.net/backoffice/processing?job=')
|
||||
job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
|
||||
resp = resp.follow()
|
||||
resp = resp.click('Download Export')
|
||||
|
|
|
@ -356,7 +356,6 @@ def test_backoffice_cards_import_data_from_csv(pub):
|
|||
'text/csv')
|
||||
resp = resp.forms[0].submit().follow()
|
||||
assert 'Importing data into cards' in resp
|
||||
assert 'Column File will be ignored: type File Upload not supported.' in resp
|
||||
assert carddef.data_class().count() == 149
|
||||
card1, card2 = carddef.data_class().select(order_by='id')[:2]
|
||||
assert card1.data['1'] == '48.81;2.37'
|
||||
|
|
|
@ -187,7 +187,7 @@ def test_backoffice_export_long_listings(pub, threshold):
|
|||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/management/form-title/')
|
||||
resp = resp.click('Export as CSV File')
|
||||
assert resp.location.startswith('http://example.net/backoffice/management/form-title/export?job=')
|
||||
assert resp.location.startswith('http://example.net/backoffice/processing?job=')
|
||||
resp = resp.follow()
|
||||
assert 'completed' in resp.text
|
||||
resp = resp.click('Download Export')
|
||||
|
@ -201,7 +201,7 @@ def test_backoffice_export_long_listings(pub, threshold):
|
|||
|
||||
resp = app.get('/backoffice/management/form-title/')
|
||||
resp = resp.click('Export a Spreadsheet')
|
||||
assert resp.location.startswith('http://example.net/backoffice/management/form-title/export?job=')
|
||||
assert resp.location.startswith('http://example.net/backoffice/processing?job=')
|
||||
job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
|
||||
resp = resp.follow()
|
||||
assert 'completed' in resp.text
|
||||
|
|
|
@ -1251,8 +1251,6 @@ class FormDefPage(Directory):
|
|||
if get_publisher().is_using_postgresql():
|
||||
raise TraversalError()
|
||||
|
||||
if get_request().form.get('job'):
|
||||
return self.archive_processing()
|
||||
if get_request().form.get('download'):
|
||||
return self.archive_download()
|
||||
|
||||
|
@ -1319,7 +1317,10 @@ class FormDefPage(Directory):
|
|||
job = get_response().add_after_job(
|
||||
str(N_('Archiving forms')),
|
||||
archiver.archive)
|
||||
return redirect('archive?job=%s' % job.id)
|
||||
job.done_action_url = self.formdef.get_admin_url() + 'archive?job=%s' % job.id
|
||||
job.done_action_label = _('Download Archive')
|
||||
job.store()
|
||||
return redirect(job.get_processing_url())
|
||||
else:
|
||||
archiver.archive()
|
||||
|
||||
|
@ -1329,32 +1330,6 @@ class FormDefPage(Directory):
|
|||
'attachment; filename=%s-archive.wcs' % self.formdef.url_name)
|
||||
return archiver.fd.getvalue()
|
||||
|
||||
def archive_processing(self):
|
||||
try:
|
||||
job = AfterJob.get(get_request().form.get('job'))
|
||||
except KeyError:
|
||||
return redirect('.')
|
||||
|
||||
self.html_top(title=_('Archiving'))
|
||||
r = TemplateIO(html=True)
|
||||
r += get_session().display_message()
|
||||
get_response().add_javascript(['jquery.js', 'afterjob.js'])
|
||||
r += htmltext('<dl class="job-status">')
|
||||
r += htmltext('<dt>')
|
||||
r += _(job.label)
|
||||
r += htmltext('</dt>')
|
||||
r += htmltext('<dd>')
|
||||
r += htmltext('<span class="afterjob" id="%s">') % job.id
|
||||
r += _(job.status)
|
||||
r += htmltext('</span>')
|
||||
r += htmltext('</dd>')
|
||||
r += htmltext('</dl>')
|
||||
|
||||
r += htmltext('<div class="done">')
|
||||
r += htmltext('<a href="archive?download=%s">%s</a>') % (job.id, _('Download Archive'))
|
||||
r += htmltext('</div>')
|
||||
return r.getvalue()
|
||||
|
||||
def archive_download(self):
|
||||
try:
|
||||
job = AfterJob.get(get_request().form.get('download'))
|
||||
|
@ -1370,9 +1345,6 @@ class FormDefPage(Directory):
|
|||
return job.file_content
|
||||
|
||||
def anonymise(self):
|
||||
if get_request().form.get('job'):
|
||||
return self.anonymise_processing()
|
||||
|
||||
endpoints = []
|
||||
for status in self.formdef.workflow.get_endpoint_status():
|
||||
endpoints.append((str(status.id), status.name, str(status.id)))
|
||||
|
@ -1432,38 +1404,15 @@ class FormDefPage(Directory):
|
|||
job = get_response().add_after_job(
|
||||
str(N_('Anonymising forms')),
|
||||
anonymiser.anonymise)
|
||||
return redirect('anonymise?job=%s' % job.id)
|
||||
job.done_action_url = self.formdef.get_admin_url()
|
||||
job.done_action_label = _('Back')
|
||||
job.store()
|
||||
return redirect(job.get_processing_url())
|
||||
else:
|
||||
anonymiser.anonymise()
|
||||
|
||||
return redirect('.')
|
||||
|
||||
def anonymise_processing(self):
|
||||
try:
|
||||
job = AfterJob.get(get_request().form.get('job'))
|
||||
except KeyError:
|
||||
return redirect('.')
|
||||
|
||||
html_top('forms', title=_('Anonymising'))
|
||||
r = TemplateIO(html=True)
|
||||
r += get_session().display_message()
|
||||
get_response().add_javascript(['jquery.js', 'afterjob.js'])
|
||||
r += htmltext('<dl class="job-status">')
|
||||
r += htmltext('<dt>')
|
||||
r += _(job.label)
|
||||
r += htmltext('</dt>')
|
||||
r += htmltext('<dd>')
|
||||
r += htmltext('<span class="afterjob" id="%s">') % job.id
|
||||
r += _(job.status)
|
||||
r += htmltext('</span>')
|
||||
r += htmltext('</dd>')
|
||||
r += htmltext('</dl>')
|
||||
|
||||
r += htmltext('<div class="done">')
|
||||
r += htmltext('<a href="./">%s</a>') % _('Back')
|
||||
r += htmltext('</div>')
|
||||
return r.getvalue()
|
||||
|
||||
def enable(self):
|
||||
self.formdef.disabled = False
|
||||
self.formdef.store(comment=_('Enable'))
|
||||
|
|
|
@ -858,8 +858,8 @@ class SettingsDirectory(QommonSettingsDirectory):
|
|||
get_publisher().write_cfg()
|
||||
|
||||
def export(self):
|
||||
if get_request().form.get('job') or get_request().form.get('download'):
|
||||
return self.export_pending()
|
||||
if get_request().form.get('download'):
|
||||
return self.export_download()
|
||||
|
||||
form = Form(enctype="multipart/form-data")
|
||||
form.add(CheckboxWidget, 'formdefs', title = _('Forms'), value = True)
|
||||
|
@ -961,43 +961,23 @@ class SettingsDirectory(QommonSettingsDirectory):
|
|||
job = get_response().add_after_job(
|
||||
N_('Exporting site settings'),
|
||||
exporter.export)
|
||||
job.done_action_url = get_request().get_url() + '?download=%s' % job.id
|
||||
job.done_action_label = _('Download Export')
|
||||
job.done_button_attributes = {'download': 'export.wcs'}
|
||||
job.store()
|
||||
return redirect('export?job=%s' % job.id)
|
||||
return redirect(job.get_processing_url())
|
||||
|
||||
def export_pending(self):
|
||||
job_id = get_request().form.get('job') or get_request().form.get('download')
|
||||
def export_download(self):
|
||||
job_id = get_request().form.get('download')
|
||||
try:
|
||||
job = AfterJob.get(job_id)
|
||||
except KeyError:
|
||||
return redirect('.')
|
||||
|
||||
if get_request().form.get('download'):
|
||||
response = get_response()
|
||||
response.set_content_type('application/x-wcs')
|
||||
response.set_header('content-disposition', 'attachment; filename=export.wcs')
|
||||
return job.file_content
|
||||
|
||||
html_top('settings', title=_('Exporting'))
|
||||
r = TemplateIO(html=True)
|
||||
get_response().add_javascript(['jquery.js', 'afterjob.js'])
|
||||
r += htmltext('<h2>%s</h2>') % _('Export')
|
||||
r += htmltext('<div class="section"><dl class="job-status">')
|
||||
r += htmltext('<dt>')
|
||||
r += _(job.label)
|
||||
r += htmltext('</dt>')
|
||||
r += htmltext('<dd>')
|
||||
r += htmltext('<span class="afterjob" id="%s">') % job.id
|
||||
r += _(job.status)
|
||||
r += htmltext('</span>')
|
||||
r += htmltext('</dd>')
|
||||
r += htmltext('</dl>')
|
||||
|
||||
r += htmltext('<div class="done">')
|
||||
r += htmltext('<a download="export.wcs" href="export?download=%s">%s</a>') % (
|
||||
job.id, _('Download Export'))
|
||||
r += htmltext('</div>')
|
||||
r += htmltext('</div>')
|
||||
return r.getvalue()
|
||||
response = get_response()
|
||||
response.set_content_type('application/x-wcs')
|
||||
response.set_header('content-disposition', 'attachment; filename=export.wcs')
|
||||
return job.file_content
|
||||
|
||||
def p_import(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
|
|
|
@ -188,34 +188,27 @@ class CardPage(FormPage):
|
|||
if not self.can_user_add_cards():
|
||||
raise errors.AccessForbiddenError()
|
||||
context = {
|
||||
'unsupported_fields': [],
|
||||
'required_fields': []
|
||||
}
|
||||
try:
|
||||
job = AfterJob.get(get_request().form.get('job'))
|
||||
get_response().add_javascript(['jquery.js', 'afterjob.js'])
|
||||
context['job'] = job
|
||||
except KeyError:
|
||||
form = Form(enctype='multipart/form-data', use_tokens=False)
|
||||
form.add(FileWidget, 'file', title=_('File'), required=True)
|
||||
form.add_submit('submit', _('Submit'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('.')
|
||||
|
||||
if form.is_submitted() and not form.has_errors():
|
||||
try:
|
||||
return self.import_csv_submit(form.get_widget('file').parse().fp)
|
||||
except ValueError as e:
|
||||
form.set_error('file', e)
|
||||
context['form'] = form
|
||||
form = Form(enctype='multipart/form-data', use_tokens=False)
|
||||
form.add(FileWidget, 'file', title=_('File'), required=True)
|
||||
form.add_submit('submit', _('Submit'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('.')
|
||||
|
||||
if form.is_submitted() and not form.has_errors():
|
||||
try:
|
||||
return self.import_csv_submit(form.get_widget('file').parse().fp)
|
||||
except ValueError as e:
|
||||
form.set_error('file', e)
|
||||
|
||||
get_response().breadcrumb.append(('import_csv', _('Import CSV')))
|
||||
html_top('data_management', _('Import CSV'))
|
||||
context['form'] = form
|
||||
|
||||
for field in self.get_import_csv_fields():
|
||||
if field.convert_value_from_str is None:
|
||||
context['unsupported_fields'].append(field)
|
||||
if not hasattr(field, 'required'):
|
||||
continue
|
||||
if field.required and field.convert_value_from_str is None:
|
||||
|
@ -293,7 +286,7 @@ class CardPage(FormPage):
|
|||
job = ImportFromCsvAfterJob(carddef=self.formdef, data_lines=data_lines)
|
||||
if afterjob:
|
||||
get_response().add_after_job(job)
|
||||
return redirect('import-csv?job=%s' % job.id)
|
||||
return redirect(job.get_processing_url())
|
||||
else:
|
||||
job.execute()
|
||||
|
||||
|
@ -378,3 +371,13 @@ class ImportFromCsvAfterJob(AfterJob):
|
|||
data_instance.just_created()
|
||||
data_instance.store()
|
||||
data_instance.perform_workflow()
|
||||
|
||||
def done_action_url(self):
|
||||
carddef = self.kwargs['carddef_class'].get(self.kwargs['carddef_id'])
|
||||
return carddef.get_url()
|
||||
|
||||
def done_action_label(self):
|
||||
return _('Back to Listing')
|
||||
|
||||
def done_button_attributes(self):
|
||||
return {'data-redirect-auto': 'true'}
|
||||
|
|
|
@ -1963,33 +1963,7 @@ class FormPage(Directory):
|
|||
action_id=action['action'].id,
|
||||
item_ids=item_ids))
|
||||
job.store()
|
||||
return redirect('./?job=%s' % job.id)
|
||||
|
||||
def job_multi(self):
|
||||
try:
|
||||
job = AfterJob.get(get_request().form.get('job'))
|
||||
except KeyError:
|
||||
return redirect('.')
|
||||
|
||||
html_top('management', title=_('Executing Task'))
|
||||
r = TemplateIO(html=True)
|
||||
r += get_session().display_message()
|
||||
get_response().add_javascript(['jquery.js', 'afterjob.js'])
|
||||
r += htmltext('<dl class="job-status">')
|
||||
r += htmltext('<dt>')
|
||||
r += job.label
|
||||
r += htmltext('</dt>')
|
||||
r += htmltext('<dd>')
|
||||
r += htmltext('<span class="afterjob" id="%s">') % job.id
|
||||
r += _(job.status)
|
||||
r += htmltext('</span>')
|
||||
r += htmltext('</dd>')
|
||||
r += htmltext('</dl>')
|
||||
|
||||
r += htmltext('<div class="done">')
|
||||
r += htmltext('<a data-redirect-auto="true" href="./?%s">%s</a>') % (job.query_string, _('Back to Listing'))
|
||||
r += htmltext('</div>')
|
||||
return r.getvalue()
|
||||
return redirect(job.get_processing_url())
|
||||
|
||||
def csv(self):
|
||||
self.check_access()
|
||||
|
@ -2014,7 +1988,7 @@ class FormPage(Directory):
|
|||
if count > self.WCS_SYNC_EXPORT_LIMIT:
|
||||
job = get_response().add_after_job(job)
|
||||
job.store()
|
||||
return redirect('export?job=%s' % job.id)
|
||||
return redirect(job.get_processing_url())
|
||||
else:
|
||||
job.execute()
|
||||
|
||||
|
@ -2025,36 +1999,6 @@ class FormPage(Directory):
|
|||
|
||||
def export(self):
|
||||
self.check_access()
|
||||
if get_request().form.get('download'):
|
||||
return self.export_download()
|
||||
|
||||
try:
|
||||
job = AfterJob.get(get_request().form.get('job'))
|
||||
except KeyError:
|
||||
return redirect('.')
|
||||
|
||||
html_top('management', title=_('Exporting'))
|
||||
r = TemplateIO(html=True)
|
||||
r += get_session().display_message()
|
||||
get_response().add_javascript(['jquery.js', 'afterjob.js'])
|
||||
r += htmltext('<dl class="job-status">')
|
||||
r += htmltext('<dt>')
|
||||
r += _(job.label)
|
||||
r += htmltext('</dt>')
|
||||
r += htmltext('<dd>')
|
||||
r += htmltext('<span class="afterjob" id="%s">') % job.id
|
||||
r += _(job.status)
|
||||
r += htmltext('</span>')
|
||||
r += htmltext('</dd>')
|
||||
r += htmltext('</dl>')
|
||||
|
||||
r += htmltext('<div class="done">')
|
||||
r += htmltext('<a download="%s" href="export?download=%s">%s</a>') % (
|
||||
job.file_name, job.id, _('Download Export'))
|
||||
r += htmltext('</div>')
|
||||
return r.getvalue()
|
||||
|
||||
def export_download(self):
|
||||
try:
|
||||
job = AfterJob.get(get_request().form.get('download'))
|
||||
except KeyError:
|
||||
|
@ -2092,7 +2036,7 @@ class FormPage(Directory):
|
|||
if count > self.WCS_SYNC_EXPORT_LIMIT and not get_request().is_api_url():
|
||||
job = get_response().add_after_job(job)
|
||||
job.store()
|
||||
return redirect('export?job=%s' % job.id)
|
||||
return redirect(job.get_processing_url())
|
||||
else:
|
||||
job.execute()
|
||||
|
||||
|
@ -3449,6 +3393,16 @@ class MassActionAfterJob(AfterJob):
|
|||
self.completion_status = '{}/{}'.format(i + 1, len(formdatas))
|
||||
self.store()
|
||||
|
||||
def done_action_url(self):
|
||||
formdef = self.kwargs['formdef_class'].get(self.kwargs['formdef_id'])
|
||||
return formdef.get_url(backoffice=True)
|
||||
|
||||
def done_action_label(self):
|
||||
return _('Back to Listing')
|
||||
|
||||
def done_button_attributes(self):
|
||||
return {'data-redirect-auto': 'true'}
|
||||
|
||||
|
||||
class CsvExportAfterJob(AfterJob):
|
||||
label = N_('Exporting forms in CSV')
|
||||
|
@ -3506,6 +3460,16 @@ class CsvExportAfterJob(AfterJob):
|
|||
self.content_type = 'text/csv'
|
||||
self.store()
|
||||
|
||||
def done_action_url(self):
|
||||
formdef = self.kwargs['formdef_class'].get(self.kwargs['formdef_id'])
|
||||
return formdef.get_url(backoffice=True) + 'export?download=%s' % self.id
|
||||
|
||||
def done_action_label(self):
|
||||
return _('Download Export')
|
||||
|
||||
def done_button_attributes(self):
|
||||
return {'download': self.file_name}
|
||||
|
||||
|
||||
class OdsExportAfterJob(CsvExportAfterJob):
|
||||
def __init__(self, formdef, **kwargs):
|
||||
|
|
|
@ -25,6 +25,8 @@ from ..qommon.backoffice.menu import html_top
|
|||
|
||||
from ..qommon import misc, get_cfg
|
||||
from ..qommon import errors
|
||||
from ..qommon import template
|
||||
from ..qommon.afterjobs import AfterJob
|
||||
from ..qommon.form import *
|
||||
|
||||
from wcs.formdef import FormDef
|
||||
|
@ -44,7 +46,7 @@ from . import data_management
|
|||
|
||||
|
||||
class RootDirectory(BackofficeRootDirectory):
|
||||
_q_exports = ['', 'pending', 'statistics', ('menu.json', 'menu_json')]
|
||||
_q_exports = ['', 'pending', 'statistics', ('menu.json', 'menu_json'), 'processing']
|
||||
|
||||
forms = wcs.admin.forms.FormsDirectory()
|
||||
roles = wcs.admin.roles.RolesDirectory()
|
||||
|
@ -285,3 +287,15 @@ class RootDirectory(BackofficeRootDirectory):
|
|||
'studio', 'cards', 'data'):
|
||||
menu_items[-1]['icon'] = k.strip('/')
|
||||
return menu_items
|
||||
|
||||
def processing(self):
|
||||
html_top('/')
|
||||
try:
|
||||
job = AfterJob.get(get_request().form.get('job'))
|
||||
except KeyError:
|
||||
return redirect('.')
|
||||
|
||||
get_response().add_javascript(['jquery.js', 'afterjob.js'])
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/processing.html'],
|
||||
context={'job': job})
|
||||
|
|
|
@ -97,3 +97,6 @@ class AfterJob(StorableObject):
|
|||
obj_dict['job_cmd'] = None
|
||||
return obj_dict
|
||||
return self.__dict__
|
||||
|
||||
def get_processing_url(self):
|
||||
return '/backoffice/processing?job=%s' % self.id
|
||||
|
|
|
@ -1717,6 +1717,7 @@ ul#evolutions li div.msg li::after {
|
|||
|
||||
div.section > dl {
|
||||
padding-bottom: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
div.data-source-preview ul {
|
||||
|
|
|
@ -8,33 +8,6 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if job %}
|
||||
|
||||
{% if unsupported_fields %}
|
||||
{% for field in unsupported_fields %}
|
||||
<div class="warningnotice">
|
||||
{% blocktrans with label=field.label description=field.description %}
|
||||
Column {{ label }} will be ignored: type {{ description }} not supported.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<dl class="job-status">
|
||||
<dt>{{ job.label }}</dt>
|
||||
{% if warnings %}
|
||||
<dt>
|
||||
{% for warning in warnings %}
|
||||
<div class="warningnotice">{{ warning }}</div>
|
||||
{% endfor %}
|
||||
</dt>
|
||||
{% endif %}
|
||||
<dd><span class="afterjob" id="{{ job.id }}">{% trans job.status %}</span></dd>
|
||||
</dl>
|
||||
<div class="done">
|
||||
<a data-redirect-auto="true" href=".">{% trans "Back to Listing" %}</a>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
{% if required_fields %}
|
||||
<div class="errornotice">
|
||||
|
@ -51,5 +24,4 @@
|
|||
<p><a href="data-sample-csv">{% trans "Download sample file for this card" %}</a></p>
|
||||
{{ form.render|safe }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{% extends "wcs/backoffice/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar-title %}{% trans "Executing task..." %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="section">
|
||||
<dl class="job-status">
|
||||
<dt>{% trans job.label %}</dt>
|
||||
<dd><span class="afterjob" id="{{ job.id }}">{% trans job.status %}</span></dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="done" style="display: none">
|
||||
<a {% for attr in job.done_button_attributes.items %}{{ attr.0 }}="{{ attr.1 }}"{% endfor %}
|
||||
class="button" href="{{ job.done_action_url }}">{{ job.done_action_label }}</a>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue