mirror of
https://github.com/viewflow/viewflow.git
synced 2026-03-13 10:32:34 +08:00
Changes sync
This commit is contained in:
@@ -2,6 +2,26 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
2.2.4 2024-07-12
|
||||
-----------------
|
||||
|
||||
- Clone data, seed, and artifacts from canceled tasks to revived tasks.
|
||||
- Enhance error handling for celery.Job.
|
||||
- Improve the process cancellation template.
|
||||
- Redirect to the task detail page after canceling or undoing actions, instead
|
||||
of redirecting to the process detail page.
|
||||
- Added links to parent subprocess and parent task on the subprocess process and
|
||||
task details pages.
|
||||
- Updated the Process.parent_task field to use related_name='subprocess',
|
||||
allowing access to subprocesses via task.subprocess
|
||||
- Enhanced CreateProcessView and UpdateProcessView to set process_seed and
|
||||
artifact_generic_foreign_key fields based on form.cleaned_data, as Django
|
||||
model forms do not handle this automatically.
|
||||
- Added tasks with an ERROR status to the process dashboard for better visibility and tracking.
|
||||
- Added tooltip hover titles to nodes without text labels in the SVG workflow graph.
|
||||
- Marked StartHandler nodes as BPMN Start Message events on the SVG graph.
|
||||
- Fixed rendering of hidden field errors in forms.
|
||||
|
||||
2.2.3 2024-07-09
|
||||
-----------------
|
||||
|
||||
|
||||
20
README.md
20
README.md
@@ -185,6 +185,26 @@ modifications of Viewflow. You can find the commercial license terms in
|
||||
|
||||
## Changelog
|
||||
|
||||
### 2.2.4 2024-07-12
|
||||
|
||||
- Clone data, seed, and artifacts from canceled tasks to revived tasks.
|
||||
- Enhance error handling for celery.Job.
|
||||
- Improve the process cancellation template.
|
||||
- Redirect to the task detail page after canceling or undoing actions, instead
|
||||
of redirecting to the process detail page.
|
||||
- Added links to parent subprocess and parent task on the subprocess process and
|
||||
task details pages.
|
||||
- Updated the Process.parent_task field to use related_name='subprocess',
|
||||
allowing access to subprocesses via task.subprocess
|
||||
- Enhanced CreateProcessView and UpdateProcessView to set process_seed and
|
||||
artifact_generic_foreign_key fields based on form.cleaned_data, as Django
|
||||
model forms do not handle this automatically.
|
||||
- Added tasks with an ERROR status to the process dashboard for better visibility and tracking.
|
||||
- Added tooltip hover titles to nodes without text labels in the SVG workflow graph.
|
||||
- Marked StartHandler nodes as BPMN Start Message events on the SVG graph.
|
||||
- Fixed rendering of hidden field errors in forms.
|
||||
|
||||
|
||||
### 2.2.3 2024-07-09
|
||||
|
||||
- Fixed issue with Split/Join operations when an immediate split to join
|
||||
|
||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -7,6 +7,7 @@
|
||||
"name": "viewflow",
|
||||
"dependencies": {
|
||||
"@hotwired/turbo": "^7.3.0",
|
||||
"@iconfu/svg-inject": "^1.2.3",
|
||||
"@material/textfield": "^14.0.0",
|
||||
"@material/web": "^1.2.0",
|
||||
"@open-wc/lit-helpers": "^0.6.0",
|
||||
@@ -1135,6 +1136,11 @@
|
||||
"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@iconfu/svg-inject": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@iconfu/svg-inject/-/svg-inject-1.2.3.tgz",
|
||||
"integrity": "sha512-3v1MUAJqmJS4jmhHoCkSxt+EdJrjPHlLXrWocCT25kCxnxJto8028Z6CC406EL11KA53SDZgI/QQA5GEJAoiRw=="
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
|
||||
@@ -8451,6 +8457,11 @@
|
||||
"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
|
||||
"dev": true
|
||||
},
|
||||
"@iconfu/svg-inject": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@iconfu/svg-inject/-/svg-inject-1.2.3.tgz",
|
||||
"integrity": "sha512-3v1MUAJqmJS4jmhHoCkSxt+EdJrjPHlLXrWocCT25kCxnxJto8028Z6CC406EL11KA53SDZgI/QQA5GEJAoiRw=="
|
||||
},
|
||||
"@jridgewell/gen-mapping": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hotwired/turbo": "^7.3.0",
|
||||
"@iconfu/svg-inject": "^1.2.3",
|
||||
"@material/textfield": "^14.0.0",
|
||||
"@material/web": "^1.2.0",
|
||||
"@open-wc/lit-helpers": "^0.6.0",
|
||||
|
||||
2
setup.py
2
setup.py
@@ -4,7 +4,7 @@ README = open("README.md", "r", encoding="utf-8").read()
|
||||
|
||||
setuptools.setup(
|
||||
name="django-viewflow",
|
||||
version="2.2.3",
|
||||
version="2.2.4",
|
||||
author_email="kmmbvnr@gmail.com",
|
||||
author="Mikhail Podgurskiy",
|
||||
description="Reusable library to build business applications fast",
|
||||
|
||||
@@ -12,28 +12,55 @@ class Test(TestCase):
|
||||
self.assertEqual(
|
||||
content,
|
||||
'<div class="vf-form mdc-layout-grid">'
|
||||
'<div class="vf-form__hiddenfields"><input id="id_promocode" name="promocode"'
|
||||
' label="Promocode" type="hidden"></div>'
|
||||
'<div class="vf-form__visiblefields mdc-layout-grid__inner">'
|
||||
'<div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-12">'
|
||||
'<vf-field-input required="required" id="id_username" name="username" label="Username"'
|
||||
' type="text"></vf-field-input></div></div></div>',
|
||||
)
|
||||
|
||||
def _test_render_form_error(self):
|
||||
def test_render_form_field_error(self):
|
||||
renderer = FormLayout()
|
||||
content = renderer.render(TestForm(data={}))
|
||||
self.assertEqual(
|
||||
content,
|
||||
'<div class="vf-form mdc-layout-grid"><div class="vf-form__errors">'
|
||||
'<div class="vf-form__error">Form error</div></div>'
|
||||
'<div class="vf-form__hiddenfields"><input id="id_promocode" name="promocode"'
|
||||
' label="Promocode" type="hidden"></div>'
|
||||
'<div class="vf-form__visiblefields mdc-layout-grid__inner">'
|
||||
'<div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-12">'
|
||||
'<vf-field-input required="required" id="id_username" name="username" label="Username"'
|
||||
' error="This field is required." type="text"></vf-field-input></div></div></div>',
|
||||
'<vf-field-input required="required" aria-invalid="true" id="id_username" name="username"'
|
||||
' label="Username" error="This field is required." type="text"></vf-field-input></div></div></div>',
|
||||
)
|
||||
|
||||
def test_render_form_hidden_field_error(self):
|
||||
renderer = FormLayout()
|
||||
content = renderer.render(TestForm(data={"promocode": "invalid"}))
|
||||
self.assertEqual(
|
||||
content,
|
||||
'<div class="vf-form mdc-layout-grid"><div class="vf-form__errors">'
|
||||
'<div class="vf-form__error">Form error</div>'
|
||||
'<div class="vf-form__error">Promocode must be empty</div>'
|
||||
"</div>"
|
||||
'<div class="vf-form__hiddenfields"><input id="id_promocode" name="promocode"'
|
||||
' value="invalid" label="Promocode" error="Promocode must be empty" type="hidden"></div>'
|
||||
'<div class="vf-form__visiblefields mdc-layout-grid__inner">'
|
||||
'<div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-12">'
|
||||
'<vf-field-input required="required" aria-invalid="true" id="id_username" name="username"'
|
||||
' label="Username" error="This field is required." type="text"></vf-field-input></div></div></div>',
|
||||
)
|
||||
|
||||
|
||||
class TestForm(forms.Form):
|
||||
username = forms.CharField()
|
||||
promocode = forms.CharField(widget=forms.HiddenInput, required=False)
|
||||
|
||||
def clean_promocode(self):
|
||||
promo = self.cleaned_data.get("promocode", "")
|
||||
if promo:
|
||||
raise forms.ValidationError("Promocode must be empty")
|
||||
|
||||
def clean(self):
|
||||
raise forms.ValidationError("Form error")
|
||||
|
||||
@@ -510,9 +510,10 @@ class FormLayout:
|
||||
def append_non_field_errors(self, form: forms.Form, root: ElementTree.Element):
|
||||
errors = form.non_field_errors()
|
||||
errors.extend(
|
||||
form.error_class(bound_field.errors)
|
||||
error
|
||||
for bound_field in form.hidden_fields()
|
||||
if bound_field.errors
|
||||
for error in bound_field.errors
|
||||
)
|
||||
|
||||
if errors:
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
|
||||
circle.event,
|
||||
path.event {
|
||||
fill: none;
|
||||
fill: transparent;
|
||||
stroke: #000;
|
||||
}
|
||||
|
||||
@@ -90,12 +90,12 @@
|
||||
}
|
||||
|
||||
path.event-marker {
|
||||
fill: none;
|
||||
fill: transparent;
|
||||
stroke: #000;
|
||||
}
|
||||
|
||||
path.gateway {
|
||||
fill: none;
|
||||
fill: transparent;
|
||||
stroke: #000;
|
||||
stroke-width: 1.1;
|
||||
}
|
||||
@@ -108,7 +108,7 @@
|
||||
}
|
||||
|
||||
rect.task {
|
||||
fill: none;
|
||||
fill: transparent;
|
||||
stroke: #000;
|
||||
stroke-width: 1.1;
|
||||
}
|
||||
@@ -120,7 +120,7 @@
|
||||
}
|
||||
|
||||
path.task-marker {
|
||||
fill: none;
|
||||
fill: transparent;
|
||||
stroke: #000;
|
||||
stroke-width: 1.1;
|
||||
}
|
||||
@@ -128,14 +128,14 @@
|
||||
</style>
|
||||
{% for cell in cells %}
|
||||
<g id="{{ cell.node.name }}" transform="translate({{ cell.shape.x }}, {{ cell.shape.y }})"{% if cell.status %} class="{{ cell.status|lower }}"{% endif %}>
|
||||
{% if cell.status %}<title>{{ cell.status }}</title>{% endif %}
|
||||
{% if cell.title %}<title>{{ cell.title }}</title>{% endif %}
|
||||
{{ cell.shape.svg|safe }}
|
||||
{% for text, class, font_size, x, y in cell.shape.text %}
|
||||
<text class="{{ class }}" font-size="{{ font_size }}px" x="{{ x }}" y="{{ y }}">{% if forloop.first %}{{ text|title }}{% else %}{{ text }}{% endif %}</text>
|
||||
{% endfor %}
|
||||
</g>
|
||||
{% endfor %}{% for edge in edges %}
|
||||
<path fill="none" stroke="#000000" stroke-width="2" id="{{ edge.src.name}}__{{ edge.dst.name }}" marker-end="url(#end-marker)"
|
||||
<path fill="transparent" stroke="#000000" stroke-width="2" id="{{ edge.src.name}}__{{ edge.dst.name }}" marker-end="url(#end-marker)"
|
||||
d="{% for x,y in edge.segments %}{% if forloop.first %}M{% else %} L{% endif %}{{ x }}, {{ y }}{% endfor %}"/>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="mdc-layout-grid vf-page__grid">
|
||||
<div class="mdc-layout-grid__inner">
|
||||
<div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-9-desktop mdc-layout-grid__cell--span-8-tablet mdc-layout-grid__cell--span-4-phone">
|
||||
<div class="mdc-layout-grid__inner vf-page__grid-inner">
|
||||
<div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-8-desktop mdc-layout-grid__cell--span-8-tablet mdc-layout-grid__cell--span-4-phone">
|
||||
<div class="mdc-card vf-card">
|
||||
<section class="vf-card__header">
|
||||
<h1 class="vf-card__title">
|
||||
@@ -90,7 +90,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% block panel-cell %}
|
||||
<div class="mdc-layout-grid__cell {% block panel-cell-span %}mdc-layout-grid__cell--span-3-desktop mdc-layout-grid__cell--span-8-tablet mdc-layout-grid__cell--span-4-phone{% endblock %}">
|
||||
<div class="mdc-layout-grid__cell {% block panel-cell-span %}mdc-layout-grid__cell--span-4-desktop mdc-layout-grid__cell--span-8-tablet mdc-layout-grid__cell--span-4-phone{% endblock %}">
|
||||
{% include_process_data process %}
|
||||
</div>
|
||||
{% endblock panel-cell %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% load viewflow %}
|
||||
{% load viewflow i18n %}
|
||||
<div class="mdc-card vf-card">
|
||||
<section class="vf-card__header">
|
||||
<h1 class="vf-card__title">{{ process }}</h1>
|
||||
@@ -20,13 +20,33 @@
|
||||
</td>
|
||||
</tr>
|
||||
{% for field, field_name, value in process_data %}
|
||||
{% if field.name != 'data' and field.name != 'flow_class' and field.name != 'artifact_object_id' and field.name != 'artifact_content_type'%}
|
||||
{% if field.name != 'parent_task' and field.name != 'data' and field.name != 'flow_class' and field.name != 'artifact_object_id' and field.name != 'artifact_content_type' and field.name != 'seed_object_id' and field.name != 'seed_content_type'%}
|
||||
<tr>
|
||||
<th class="vf-list__table-header vf-list__table-header-text">{{ field_name }}</th>
|
||||
<td>{{ value }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if process.parent_task %}
|
||||
<tr>
|
||||
<th class="vf-list__table-header vf-list__table-header-text">{% translate 'Parent Task' %}</th>
|
||||
<td>
|
||||
<a href="{% reverse process.parent_task.flow_task 'index' process.parent_task.process_id process.parent_task.pk %}">{{ process.parent_task.flow_task }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if process.seed %}
|
||||
<tr>
|
||||
<th class="vf-list__table-header vf-list__table-header-text">{% translate 'Seed' %}</th>
|
||||
<td>{{ process.seed }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if process.artifact %}
|
||||
<tr>
|
||||
<th class="vf-list__table-header vf-list__table-header-text">{% translate 'Artifact' %}</th>
|
||||
<td>{{ process.artifact }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
@@ -20,6 +20,28 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if seed_data %}
|
||||
<div class="mdc-card vf-card">
|
||||
<section class="vf-card__header">
|
||||
<h1 class="vf-card__title">{{ task.seed }}</h1>
|
||||
</section>
|
||||
<section class="vf-card__body">
|
||||
<table class="vf-list__table">
|
||||
<tbody>
|
||||
{% for field, field_name, value in seed_data %}
|
||||
{% if field.name != 'process' and field.name != 'task' %}
|
||||
<tr>
|
||||
<th class="vf-list__table-header vf-list__table-header-text">{{ field_name }}</th>
|
||||
<td>{{ value }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if data %}
|
||||
{{ data }}
|
||||
|
||||
@@ -37,6 +37,16 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% with subprocesses=task.subprocesses.all %}{% if subprocesses %}
|
||||
<div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-12">
|
||||
<h4 class="mdc-typography mdc-typography--headline5" style="margin-bottom:10px">{% trans "Subprocess" %}</h4>
|
||||
<div>
|
||||
{% for subprocess in subprocesses %}
|
||||
<a class="mdc-typography mdc-typography--subtitle1" href="{% reverse subprocess.flow_class 'process_detail' subprocess.pk %}">{{ subprocess }}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}{% endwith %}
|
||||
{% endblock %}
|
||||
|
||||
{% block task_actions %}
|
||||
|
||||
@@ -51,6 +51,8 @@ def include_task_data(context, task):
|
||||
try:
|
||||
if task.artifact_object_id:
|
||||
context["artifact_data"] = get_object_data(task.artifact)
|
||||
if task.seed_object_id:
|
||||
context["seed_data"] = get_object_data(task.seed)
|
||||
context["data"] = task.data
|
||||
context["task"] = task
|
||||
return template.render(
|
||||
|
||||
@@ -47,7 +47,18 @@ class Shape(object):
|
||||
|
||||
|
||||
class Cell(object):
|
||||
__slots__ = ["col", "row", "x", "y", "width", "height", "node", "shape", "status"]
|
||||
__slots__ = [
|
||||
"col",
|
||||
"row",
|
||||
"x",
|
||||
"y",
|
||||
"width",
|
||||
"height",
|
||||
"node",
|
||||
"shape",
|
||||
"title",
|
||||
"status",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -59,6 +70,7 @@ class Cell(object):
|
||||
width=-1,
|
||||
height=-1,
|
||||
shape=None,
|
||||
title=None,
|
||||
status=None,
|
||||
):
|
||||
self.node = node
|
||||
@@ -69,6 +81,7 @@ class Cell(object):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.shape = shape if shape is not None else Shape()
|
||||
self.title = title
|
||||
self.status = status
|
||||
|
||||
def incoming(self):
|
||||
@@ -536,11 +549,13 @@ def calc_text(grid):
|
||||
shape = getattr(cell.node, "shape", DEFAULT_SHAPE)
|
||||
text_align = shape.get("text-align")
|
||||
font_size = shape.get("font-size", 12)
|
||||
|
||||
if cell.node.task_title:
|
||||
title = force_str(cell.node.task_title)
|
||||
else:
|
||||
title = " ".join(cell.node.name.capitalize().split("_"))
|
||||
|
||||
if text_align == "middle":
|
||||
if cell.node.task_title:
|
||||
title = force_str(cell.node.task_title)
|
||||
else:
|
||||
title = " ".join(cell.node.name.capitalize().split("_"))
|
||||
segments = wrap(title, 20)
|
||||
block_height = max(len(segments) - 1, 0) * font_size * 1.2
|
||||
|
||||
@@ -557,6 +572,8 @@ def calc_text(grid):
|
||||
y_start + (n * font_size * 1.2),
|
||||
)
|
||||
)
|
||||
else:
|
||||
cell.title = title
|
||||
|
||||
|
||||
def calc_cell_status(flow_class, grid, process_pk):
|
||||
|
||||
@@ -58,7 +58,6 @@ class UnassignTaskView(
|
||||
|
||||
class CancelTaskView(
|
||||
mixins.SuccessMessageMixin,
|
||||
mixins.TaskSuccessUrlMixin,
|
||||
mixins.TaskViewTemplateNames,
|
||||
generic.FormView,
|
||||
):
|
||||
@@ -77,10 +76,16 @@ class CancelTaskView(
|
||||
self.request.activation.cancel()
|
||||
return super().form_valid(*args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
"""Continue on task or redirect back to task list."""
|
||||
activation = self.request.activation
|
||||
return activation.flow_task.reverse(
|
||||
"detail", args=[activation.process.pk, activation.task.pk]
|
||||
)
|
||||
|
||||
|
||||
class UndoTaskView(
|
||||
mixins.SuccessMessageMixin,
|
||||
mixins.TaskSuccessUrlMixin,
|
||||
mixins.TaskViewTemplateNames,
|
||||
generic.FormView,
|
||||
):
|
||||
@@ -99,10 +104,16 @@ class UndoTaskView(
|
||||
self.request.activation.undo()
|
||||
return super().form_valid(*args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
"""Continue on task or redirect back to task list."""
|
||||
activation = self.request.activation
|
||||
return activation.flow_task.reverse(
|
||||
"detail", args=[activation.process.pk, activation.task.pk]
|
||||
)
|
||||
|
||||
|
||||
class ReviveTaskView(
|
||||
mixins.SuccessMessageMixin,
|
||||
mixins.TaskSuccessUrlMixin,
|
||||
mixins.TaskViewTemplateNames,
|
||||
generic.FormView,
|
||||
):
|
||||
|
||||
@@ -25,6 +25,10 @@ class CreateProcessView(
|
||||
def form_valid(self, form):
|
||||
"""If the form is valid, save the associated model and finish the task."""
|
||||
self.object = form.save()
|
||||
if "seed" in form.cleaned_data:
|
||||
self.object.seed = form.cleaned_data["seed"]
|
||||
if "artifact" in form.cleaned_data:
|
||||
self.object.artifact = form.cleaned_data["artifact"]
|
||||
self.request.activation.execute()
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Q
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -7,7 +8,7 @@ from django.views import generic
|
||||
|
||||
from viewflow.views import ListModelView
|
||||
from viewflow.utils import viewprop, has_object_perm
|
||||
from viewflow.workflow import chart
|
||||
from viewflow.workflow import chart, STATUS
|
||||
from viewflow.workflow.fields import get_task_ref
|
||||
from . import mixins, filters
|
||||
|
||||
@@ -56,7 +57,8 @@ class DashboardView(
|
||||
"tasks": self.flow_class.task_class._default_manager.filter_available(
|
||||
[self.flow_class], self.request.user
|
||||
).filter(
|
||||
finished__isnull=True, flow_task=node
|
||||
Q(finished__isnull=True) | Q(status=STATUS.ERROR),
|
||||
flow_task=node,
|
||||
)[
|
||||
: self.MAX_ROWS
|
||||
],
|
||||
|
||||
@@ -24,6 +24,10 @@ class UpdateProcessView(
|
||||
def form_valid(self, form):
|
||||
"""If the form is valid, save the associated model and finish the task."""
|
||||
self.object = form.save()
|
||||
if "seed" in form.cleaned_data:
|
||||
self.object.seed = form.cleaned_data["seed"]
|
||||
if "artifact" in form.cleaned_data:
|
||||
self.object.artifact = form.cleaned_data["artifact"]
|
||||
self.request.activation.execute()
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.0.1 on 2024-07-11 05:14
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("viewflow", "0013_process_seed_content_type_process_seed_object_id_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="process",
|
||||
name="parent_task",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="subprocesses",
|
||||
to="viewflow.task",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -236,7 +236,7 @@ class Process(AbstractProcess):
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="+",
|
||||
related_name="subprocesses",
|
||||
to="Task",
|
||||
)
|
||||
|
||||
|
||||
@@ -55,9 +55,13 @@ class AbstractJobActivation(mixins.NextNodeActivationMixin, Activation):
|
||||
tb = exception.__traceback__
|
||||
while tb.tb_next:
|
||||
tb = tb.tb_next
|
||||
serialized_locals = json.dumps(
|
||||
tb.tb_frame.f_locals, default=lambda obj: str(obj)
|
||||
)
|
||||
|
||||
try:
|
||||
serialized_locals = json.dumps(
|
||||
tb.tb_frame.f_locals, default=lambda obj: str(obj)
|
||||
)
|
||||
except Exception as ex:
|
||||
serialized_locals = json.dumps({"_serialization_exception": str(ex)})
|
||||
|
||||
self.task.data["_exception"] = {
|
||||
"title": str(exception),
|
||||
|
||||
@@ -170,6 +170,8 @@ class StartHandle(mixins.NextNodeMixin, Node):
|
||||
"height": 50,
|
||||
"svg": """
|
||||
<circle class="event" cx="25" cy="25" r="25"/>
|
||||
<rect xmlns="http://www.w3.org/2000/svg" x="7.5" y="15" width="35" height="20" fill="transparent" stroke="rgb(0, 0, 0)"/>
|
||||
<path xmlns="http://www.w3.org/2000/svg" d="M 7.5 15 L 25 25 L 42.5 15" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10"/>
|
||||
""",
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user