WIP: general: run workflow as after job (#81260) #1365
|
@ -386,7 +386,11 @@ class FormFillPage(PublicFormFillPage):
|
|||
self.clean_submission_context()
|
||||
filled.refresh_from_storage()
|
||||
filled.record_workflow_event('backoffice-created')
|
||||
url = filled.perform_workflow()
|
||||
if get_publisher().has_site_option('perform-workflow-as-job'):
|
||||
url = None
|
||||
filled.perform_workflow_as_job()
|
||||
else:
|
||||
url = filled.perform_workflow()
|
||||
return self.redirect_after_submitted(url, filled)
|
||||
|
||||
def redirect_after_submitted(self, url, filled):
|
||||
|
|
|
@ -27,11 +27,12 @@ import urllib.parse
|
|||
import unidecode
|
||||
from django.utils.html import strip_tags
|
||||
from django.utils.timezone import localtime, make_naive
|
||||
from quixote import get_publisher, get_request, get_session
|
||||
from quixote import get_publisher, get_request, get_response, get_session
|
||||
from quixote.errors import RequestError
|
||||
from quixote.html import htmltext
|
||||
from quixote.http_request import Upload
|
||||
|
||||
from wcs.qommon.afterjobs import AfterJob
|
||||
from wcs.sql_criterias import And, Contains, Equal, Intersects, Null, StrictNotEqual
|
||||
|
||||
from .qommon import _, misc
|
||||
|
@ -306,6 +307,7 @@ class FormData(StorableObject):
|
|||
geolocations = None
|
||||
statistics_data = None
|
||||
relations_data = None
|
||||
workflow_processing_afterjob_id = None
|
||||
|
||||
_formdef = None
|
||||
|
||||
|
@ -731,6 +733,13 @@ class FormData(StorableObject):
|
|||
with push_perform_workflow(self):
|
||||
return perform_items(wf_status.items, self)
|
||||
|
||||
def perform_workflow_as_job(self):
|
||||
job = PerformWorkflowJob(label=_('Processing'), formdata=self)
|
||||
job.store()
|
||||
self.workflow_processing_afterjob_id = job.id
|
||||
self.store()
|
||||
get_response().add_after_job(job)
|
||||
|
||||
def perform_global_action(self, action_id, user):
|
||||
from wcs.workflows import perform_items, push_perform_workflow
|
||||
|
||||
|
@ -2078,6 +2087,33 @@ class FormData(StorableObject):
|
|||
raise AttributeError(attr)
|
||||
|
||||
|
||||
class PerformWorkflowJob(AfterJob):
|
||||
url = None
|
||||
|
||||
def __init__(self, formdata, **kwargs):
|
||||
formdef = formdata._formdef
|
||||
super().__init__(
|
||||
formdef_class=formdef.__class__,
|
||||
formdef_id=formdef.id,
|
||||
formdata_id=formdata.id,
|
||||
session_user_id=get_session().get_user_id() if get_session() else None,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def execute(self):
|
||||
formdef = self.kwargs['formdef_class'].get(self.kwargs['formdef_id'])
|
||||
formdata = formdef.data_class().get(self.kwargs['formdata_id'])
|
||||
if self.kwargs['user_id']:
|
||||
user = get_publisher().user_class.get(self.kwargs['user_id'], ignore_errors=True)
|
||||
with get_publisher().substitutions.freeze():
|
||||
get_publisher().substitutions.feed(user)
|
||||
try:
|
||||
self.url = formdata.perform_workflow()
|
||||
finally:
|
||||
formdata.workflow_processing_afterjob_id = None
|
||||
formdata.store()
|
||||
|
||||
|
||||
class FormDetails:
|
||||
# lazy object compatibility to keep form_details as part of static variables while
|
||||
# generating it only if/when accessed.
|
||||
|
|
|
@ -33,6 +33,7 @@ from wcs.api_utils import get_query_flag, get_user_from_api_query_string, is_url
|
|||
from wcs.blocks import BlockSubWidget, BlockWidget
|
||||
from wcs.fields import FileField
|
||||
from wcs.qommon.admin.texts import TextsDirectory
|
||||
from wcs.qommon.afterjobs import AfterJob
|
||||
from wcs.qommon.upload_storage import get_storage_object
|
||||
from wcs.utils import record_timings
|
||||
from wcs.wf.editable import EditableWorkflowStatusItem
|
||||
|
@ -161,7 +162,16 @@ class FormTemplateMixin:
|
|||
|
||||
|
||||
class FormStatusPage(Directory, FormTemplateMixin):
|
||||
_q_exports_orig = ['', 'download', 'json', 'action', 'live', 'tempfile', 'tsupdate']
|
||||
_q_exports_orig = [
|
||||
'',
|
||||
'download',
|
||||
'json',
|
||||
'action',
|
||||
'live',
|
||||
'tempfile',
|
||||
'tsupdate',
|
||||
('check-workflow-progress', 'check_workflow_progress'),
|
||||
]
|
||||
_q_extra_exports = []
|
||||
form_page_class = None
|
||||
|
||||
|
@ -254,6 +264,16 @@ class FormStatusPage(Directory, FormTemplateMixin):
|
|||
get_response().set_content_type('application/json')
|
||||
return json.dumps({'ts': str(self.filled.last_update_time.timestamp())})
|
||||
|
||||
def check_workflow_progress(self):
|
||||
self.check_auth()
|
||||
get_request().ignore_session = True
|
||||
get_response().set_content_type('application/json')
|
||||
try:
|
||||
afterjob = AfterJob.get(get_request().form.get('id') or '-')
|
||||
except KeyError:
|
||||
return json.dumps({'err': 1})
|
||||
return json.dumps({'err': 0, 'status': afterjob.status, 'url': afterjob.url})
|
||||
|
||||
def tempfile(self):
|
||||
# allow for file uploaded via a file widget in a workflow form
|
||||
# to be downloaded back from widget
|
||||
|
@ -676,6 +696,13 @@ class FormStatusPage(Directory, FormTemplateMixin):
|
|||
|
||||
r += htmltext(bottom_workflow_messages)
|
||||
|
||||
if self.filled.workflow_processing_afterjob_id:
|
||||
form = None
|
||||
locked = True
|
||||
r += htmltext('<div class="infonotice"><p>')
|
||||
r += str(_('Currently processing.'))
|
||||
r += htmltext('</p>')
|
||||
|
||||
locked = False
|
||||
if form:
|
||||
all_visitors = get_session().get_object_visitors(self.filled)
|
||||
|
@ -755,6 +782,9 @@ class FormStatusPage(Directory, FormTemplateMixin):
|
|||
},
|
||||
)
|
||||
|
||||
if isinstance(next_url, AfterJob):
|
||||
return
|
||||
|
||||
if next_url:
|
||||
return next_url
|
||||
if form.has_errors():
|
||||
|
|
|
@ -1930,7 +1930,10 @@ class FormPage(Directory, TempfileDirectoryMixin, FormTemplateMixin):
|
|||
self.clean_submission_context()
|
||||
filled.refresh_from_storage()
|
||||
filled.record_workflow_event('frontoffice-created')
|
||||
url = filled.perform_workflow()
|
||||
if get_publisher().has_site_option('perform-workflow-as-job'):
|
||||
filled.perform_workflow_as_job()
|
||||
else:
|
||||
url = filled.perform_workflow()
|
||||
|
||||
if not filled.user_id:
|
||||
get_session().mark_anonymous_formdata(filled)
|
||||
|
@ -2454,7 +2457,15 @@ class RootDirectory(AccessControlled, Directory):
|
|||
|
||||
|
||||
class PublicFormStatusPage(FormStatusPage):
|
||||
_q_exports_orig = ['', 'download', 'status', 'live', 'tempfile', 'tsupdate']
|
||||
_q_exports_orig = [
|
||||
'',
|
||||
'download',
|
||||
'status',
|
||||
'live',
|
||||
'tempfile',
|
||||
'tsupdate',
|
||||
('check-workflow-progress', 'check_workflow_progress'),
|
||||
]
|
||||
form_page_class = FormPage
|
||||
history_templates = ['wcs/front/formdata_history.html', 'wcs/formdata_history.html']
|
||||
status_templates = ['wcs/front/formdata_status.html', 'wcs/formdata_status.html']
|
||||
|
|
|
@ -148,8 +148,11 @@ class Session(QommonSession, CaptchaSession, StorableObject):
|
|||
user_id = QuixoteSession.get_user(self)
|
||||
return bool(user_id)
|
||||
|
||||
def get_user_id(self):
|
||||
return super().get_user()
|
||||
|
||||
def get_user(self):
|
||||
user_id = QuixoteSession.get_user(self)
|
||||
user_id = self.get_user_id()
|
||||
if user_id:
|
||||
try:
|
||||
user = get_publisher().user_class.get(user_id)
|
||||
|
|
|
@ -1118,3 +1118,43 @@ document.addEventListener('DOMContentLoaded', function(){
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const formdata_page_div = document.getElementById('formdata-page')
|
||||
if (! formdata_page_div) return
|
||||
const afterjob_id = formdata_page_div.dataset.workflowProcessingAfterjobId
|
||||
if (! afterjob_id) return
|
||||
|
||||
var wait_count = 0
|
||||
var in_call = false
|
||||
|
||||
var wait_id = setInterval(function() {
|
||||
wait_count += 1
|
||||
if (! in_call && (wait_count < 20 || wait_count % 10 == 0)) {
|
||||
in_call = true
|
||||
fetch(`${window.location.pathname}check-workflow-progress?id=${afterjob_id}`).then((response) => {
|
||||
if (! response.ok) {
|
||||
in_call = false
|
||||
clearInterval(wait_id)
|
||||
return
|
||||
}
|
||||
return response.json()
|
||||
}).then((json) => {
|
||||
console.log('got json:', json)
|
||||
if (json && json.status == 'completed') {
|
||||
clearInterval(wait_id)
|
||||
if (json.url) {
|
||||
window.location = json.url
|
||||
} else {
|
||||
window.location.reload()
|
||||
}
|
||||
} else {
|
||||
// continue
|
||||
}
|
||||
in_call = false
|
||||
})
|
||||
}
|
||||
}, 200)
|
||||
|
||||
|
||||
})
|
||||
|
|
|
@ -2289,6 +2289,7 @@ class SqlDataMixin(SqlMixin):
|
|||
('auto_geoloc', 'point'),
|
||||
('statistics_data', 'jsonb'),
|
||||
('relations_data', 'jsonb'),
|
||||
('workflow_processing_afterjob_id', 'varchar'),
|
||||
]
|
||||
|
||||
def __init__(self, id=None):
|
||||
|
@ -2474,6 +2475,7 @@ class SqlDataMixin(SqlMixin):
|
|||
'workflow_merged_roles_dict': self.workflow_merged_roles_dict,
|
||||
'statistics_data': self.statistics_data or {},
|
||||
'relations_data': self.relations_data or {},
|
||||
'workflow_processing_afterjob_id': self.workflow_processing_afterjob_id,
|
||||
}
|
||||
if self._evolution is not None and hasattr(self, '_last_update_time'):
|
||||
# if evolution was loaded it may have been been modified, and last update time
|
||||
|
@ -5148,7 +5150,7 @@ def get_period_total(
|
|||
# latest migration, number + description (description is not used
|
||||
# programmaticaly but will make sure git conflicts if two migrations are
|
||||
# separately added with the same number)
|
||||
SQL_LEVEL = (106, 'add context column to logged_errors table')
|
||||
SQL_LEVEL = (107, 'add workflow_processing_afterjob_id to card/form data')
|
||||
|
||||
|
||||
def migrate_global_views(conn, cur):
|
||||
|
@ -5426,13 +5428,14 @@ def migrate():
|
|||
continue
|
||||
for formdata in formdef.data_class().select_iterator():
|
||||
formdata._set_auto_fields(cur) # build digests
|
||||
if sql_level < 102:
|
||||
if sql_level < 107:
|
||||
# 58: add workflow_merged_roles_dict as a jsonb column with
|
||||
# combined formdef and formdata value.
|
||||
# 69: add auto_geoloc field to form/card tables
|
||||
# 80: add jsonb column to hold statistics data
|
||||
# 91: add jsonb column to hold relations data
|
||||
# 102: switch formdata datetime columns to timestamptz
|
||||
# 107: add workflow_processing_afterjob_id to card/form data
|
||||
drop_views(None, conn, cur)
|
||||
for formdef in FormDef.select() + CardDef.select():
|
||||
do_formdef_tables(formdef, rebuild_views=False, rebuild_global_views=False)
|
||||
|
|
|
@ -2,38 +2,52 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% block body %}
|
||||
{% block session-message %}
|
||||
{{session_message|safe}}
|
||||
{% endblock %}
|
||||
<div id="formdata-page" {% if formdata.workflow_processing_afterjob_id %}data-workflow-processing-afterjob-id="{{ formdata.workflow_processing_afterjob_id }}"{% endif %}>
|
||||
{% block session-message %}
|
||||
{{session_message|safe}}
|
||||
{% endblock %}
|
||||
|
||||
{% with workflow_messages=view.workflow_messages %}
|
||||
{% if workflow_messages %}
|
||||
{{ workflow_messages|safe }}
|
||||
{% else %}
|
||||
<div id="receipt-intro">
|
||||
{{ view.recorded_message|safe }}
|
||||
{% if mine %}
|
||||
{{ view.get_handling_role_info_text|safe }}
|
||||
{% endif %}
|
||||
{% if mine and formdata.formdef.enable_tracking_codes and formdata.tracking_code %}
|
||||
<p id="tracking-code">
|
||||
{% trans "You can get back to this page using the following tracking code:" %}
|
||||
<a href="../code/{{ formdata.tracking_code }}/" data-popup>{{ formdata.tracking_code }}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if formdata.workflow_processing_afterjob_id %}
|
||||
<div style="background: #eee; padding: 1em;"><div class="loader"></div><p style="text-align: center;">{% trans "Processing..." %}</p></div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{{ view.receipt|safe }}
|
||||
{{ view.history|safe }}
|
||||
{% with workflow_messages=view.workflow_messages %}
|
||||
{% if workflow_messages %}
|
||||
{{ workflow_messages|safe }}
|
||||
{% else %}
|
||||
<div id="receipt-intro">
|
||||
{{ view.recorded_message|safe }}
|
||||
{% if mine %}
|
||||
{{ view.get_handling_role_info_text|safe }}
|
||||
{% endif %}
|
||||
{% if mine and formdata.formdef.enable_tracking_codes and formdata.tracking_code %}
|
||||
<p id="tracking-code">
|
||||
{% trans "You can get back to this page using the following tracking code:" %}
|
||||
<a href="../code/{{ formdata.tracking_code }}/" data-popup>{{ formdata.tracking_code }}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with workflow_messages=view.bottom_workflow_messages %}
|
||||
{% if workflow_messages or workflow_form %}<span id="action-zone"></span>{% endif %}
|
||||
{{ workflow_messages|safe }}
|
||||
{% endwith %}
|
||||
{% if workflow_form %}
|
||||
{{ view.actions_workflow_messages|safe }}
|
||||
{{ workflow_form.render|safe }}
|
||||
{% endif %}
|
||||
{{ view.receipt|safe }}
|
||||
{% if formdata.evolution %}
|
||||
{{ view.history|safe }}
|
||||
{% endif %}
|
||||
|
||||
{% with workflow_messages=view.bottom_workflow_messages %}
|
||||
{% if workflow_messages or workflow_form %}<span id="action-zone"></span>{% endif %}
|
||||
{{ workflow_messages|safe }}
|
||||
{% endwith %}
|
||||
|
||||
{% if workflow_form and not formdata.workflow_processing_afterjob_id %}
|
||||
{{ view.actions_workflow_messages|safe }}
|
||||
{{ workflow_form.render|safe }}
|
||||
{% endif %}
|
||||
|
||||
{% if formdata.workflow_processing_afterjob_id %}
|
||||
<div style="background: #eee; padding: 1em;"><div class="loader"></div><p style="text-align: center;">{% trans "Processing..." %}</p></div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -2779,7 +2779,10 @@ class WorkflowStatus(SerieOfActionsMixin):
|
|||
if evo.status:
|
||||
filled.status = evo.status
|
||||
filled.store()
|
||||
return filled.perform_workflow()
|
||||
if get_publisher().has_site_option('perform-workflow-as-job'):
|
||||
return filled.perform_workflow_as_job()
|
||||
else:
|
||||
return filled.perform_workflow()
|
||||
|
||||
def get_subdirectories(self, formdata):
|
||||
subdirectories = []
|
||||
|
|
Loading…
Reference in New Issue