This commit is contained in:
shamoon 2024-10-23 21:43:17 -07:00
parent 3cb3bad862
commit 2c05247d84
No known key found for this signature in database
12 changed files with 106 additions and 116 deletions

View File

@ -26,7 +26,6 @@ celery = {extras = ["redis"], version = "*"}
channels = "~=4.1" channels = "~=4.1"
channels-redis = "*" channels-redis = "*"
concurrent-log-handler = "*" concurrent-log-handler = "*"
duration-parser = "*"
filelock = "*" filelock = "*"
flower = "*" flower = "*"
gotenberg-client = "*" gotenberg-client = "*"

11
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "3b615d0801cbb9ede6809ff8ecdd1ce1166bfefbc9e87b6fbfd26518b3425ec9" "sha256": "584249cbeaf29659c975000b5e02b12e45d768d795e4a8ac36118e73bd7c0b8a"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},
@ -576,15 +576,6 @@
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==0.7.0" "version": "==0.7.0"
}, },
"duration-parser": {
"hashes": [
"sha256:2d5c465aeccd467f5c981fa78d69edd13459d6cc8ce3c751e12cbe40742163f3",
"sha256:aecbb05af545f688f3f6277ab7720e538a8ab834e22c443e2a912f6c7ab6ec5c"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
"version": "==1.0.1"
},
"exceptiongroup": { "exceptiongroup": {
"hashes": [ "hashes": [
"sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b",

View File

@ -4380,22 +4380,22 @@
<context context-type="linenumber">121</context> <context context-type="linenumber">121</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7581051779822096189" datatype="html"> <trans-unit id="5337452276818111131" datatype="html">
<source>Set scheduled trigger delay and which field to use.</source> <source>Set scheduled trigger offset and which field to use.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">123</context> <context context-type="linenumber">123</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1418101411356139094" datatype="html"> <trans-unit id="3676850495488145564" datatype="html">
<source>Delay</source> <source>Offset</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">126</context> <context context-type="linenumber">126</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2695696137978565586" datatype="html"> <trans-unit id="1053165610360608412" datatype="html">
<source>Delay time string such as &apos;3months&apos;, &apos;1y&apos;.</source> <source>Offset in days. Use 0 for immediate.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">126</context> <context context-type="linenumber">126</context>
@ -4429,8 +4429,8 @@
<context context-type="linenumber">134</context> <context context-type="linenumber">134</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2364111057675998268" datatype="html"> <trans-unit id="1088170562604583291" datatype="html">
<source>Custom field to use for delay.</source> <source>Custom field to use for date.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">134</context> <context context-type="linenumber">134</context>

View File

@ -120,18 +120,18 @@
<input type="hidden" formControlName="id" /> <input type="hidden" formControlName="id" />
<pngx-input-select i18n-title title="Trigger type" [horizontal]="true" [items]="triggerTypeOptions" formControlName="type"></pngx-input-select> <pngx-input-select i18n-title title="Trigger type" [horizontal]="true" [items]="triggerTypeOptions" formControlName="type"></pngx-input-select>
@if (formGroup.get('type').value === WorkflowTriggerType.Scheduled) { @if (formGroup.get('type').value === WorkflowTriggerType.Scheduled) {
<p class="small" i18n>Set scheduled trigger delay and which field to use.</p> <p class="small" i18n>Set scheduled trigger offset and which field to use.</p>
<div class="row"> <div class="row">
<div class="col-4"> <div class="col-4">
<pngx-input-text i18n-title title="Delay" formControlName="schedule_delay" i18n-hint hint="Delay time string such as '3months', '1y'." [error]="error?.schedule_delay"></pngx-input-text> <pngx-input-number i18n-title title="Offset" formControlName="schedule_offset_days" i18n-hint hint="Offset in days. Use 0 for immediate." [error]="error?.schedule_offset_days"></pngx-input-number>
<pngx-input-check i18n-title title="Repeat" formControlName="schedule_is_recurring" i18n-hint hint="Repeat the trigger after the delay." [error]="error?.schedule_is_recurring"></pngx-input-check> <pngx-input-check i18n-title title="Repeat" formControlName="schedule_is_recurring" i18n-hint hint="Repeat the trigger after the delay." [error]="error?.schedule_is_recurring"></pngx-input-check>
</div> </div>
<div class="col-4"> <div class="col-4">
<pngx-input-select i18n-title title="Relative to" formControlName="schedule_delay_field" [items]="scheduleDelayFieldOptions" [error]="error?.schedule_delay_field"></pngx-input-select> <pngx-input-select i18n-title title="Relative to" formControlName="schedule_date_field" [items]="scheduleDateFieldOptions" [error]="error?.schedule_date_field"></pngx-input-select>
</div> </div>
@if (formGroup.get('schedule_delay_field').value === 'custom_field') { @if (formGroup.get('schedule_date_field').value === 'custom_field') {
<div class="col-4"> <div class="col-4">
<pngx-input-select i18n-title title="Delay custom field" formControlName="schedule_delay_custom_field" [items]="dateCustomFields" i18n-hint hint="Custom field to use for delay." [error]="error?.schedule_delay_custom_field"></pngx-input-select> <pngx-input-select i18n-title title="Delay custom field" formControlName="schedule_date_custom_field" [items]="dateCustomFields" i18n-hint hint="Custom field to use for date." [error]="error?.schedule_date_custom_field"></pngx-input-select>
</div> </div>
} }
</div> </div>

View File

@ -19,7 +19,7 @@ import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field' import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
import { import {
DocumentSource, DocumentSource,
ScheduleDelayField, ScheduleDateField,
WorkflowTrigger, WorkflowTrigger,
WorkflowTriggerType, WorkflowTriggerType,
} from 'src/app/data/workflow-trigger' } from 'src/app/data/workflow-trigger'
@ -49,21 +49,21 @@ export const DOCUMENT_SOURCE_OPTIONS = [
}, },
] ]
export const SCHEDULE_DELAY_FIELD_OPTIONS = [ export const SCHEDULE_DATE_FIELD_OPTIONS = [
{ {
id: ScheduleDelayField.Added, id: ScheduleDateField.Added,
name: $localize`Added`, name: $localize`Added`,
}, },
{ {
id: ScheduleDelayField.Created, id: ScheduleDateField.Created,
name: $localize`Created`, name: $localize`Created`,
}, },
{ {
id: ScheduleDelayField.Modified, id: ScheduleDateField.Modified,
name: $localize`Modified`, name: $localize`Modified`,
}, },
{ {
id: ScheduleDelayField.CustomField, id: ScheduleDateField.CustomField,
name: $localize`Custom Field`, name: $localize`Custom Field`,
}, },
] ]
@ -338,11 +338,11 @@ export class WorkflowEditDialogComponent
filter_has_document_type: new FormControl( filter_has_document_type: new FormControl(
trigger.filter_has_document_type trigger.filter_has_document_type
), ),
schedule_delay: new FormControl(trigger.schedule_delay), schedule_offset_days: new FormControl(trigger.schedule_offset_days),
schedule_is_recurring: new FormControl(trigger.schedule_is_recurring), schedule_is_recurring: new FormControl(trigger.schedule_is_recurring),
schedule_delay_field: new FormControl(trigger.schedule_delay_field), schedule_date_field: new FormControl(trigger.schedule_date_field),
schedule_delay_custom_field: new FormControl( schedule_date_custom_field: new FormControl(
trigger.schedule_delay_custom_field trigger.schedule_date_custom_field
), ),
}), }),
{ emitEvent } { emitEvent }
@ -418,8 +418,8 @@ export class WorkflowEditDialogComponent
return WORKFLOW_TYPE_OPTIONS return WORKFLOW_TYPE_OPTIONS
} }
get scheduleDelayFieldOptions() { get scheduleDateFieldOptions() {
return SCHEDULE_DELAY_FIELD_OPTIONS return SCHEDULE_DATE_FIELD_OPTIONS
} }
get dateCustomFields() { get dateCustomFields() {
@ -448,10 +448,10 @@ export class WorkflowEditDialogComponent
matching_algorithm: MATCH_NONE, matching_algorithm: MATCH_NONE,
match: '', match: '',
is_insensitive: true, is_insensitive: true,
schedule_delay: null, schedule_offset_days: null,
schedule_is_recurring: false, schedule_is_recurring: false,
schedule_delay_field: ScheduleDelayField.Added, schedule_date_field: ScheduleDateField.Added,
schedule_delay_custom_field: null, schedule_date_custom_field: null,
} }
this.object.triggers.push(trigger) this.object.triggers.push(trigger)
this.createTriggerField(trigger) this.createTriggerField(trigger)

View File

@ -13,7 +13,7 @@ export enum WorkflowTriggerType {
Scheduled = 4, Scheduled = 4,
} }
export enum ScheduleDelayField { export enum ScheduleDateField {
Added = 'added', Added = 'added',
Created = 'created', Created = 'created',
Modified = 'modified', Modified = 'modified',
@ -43,11 +43,11 @@ export interface WorkflowTrigger extends ObjectWithId {
filter_has_document_type?: number // DocumentType.id filter_has_document_type?: number // DocumentType.id
schedule_delay?: string schedule_offset_days?: number
schedule_is_recurring?: boolean schedule_is_recurring?: boolean
schedule_delay_field?: ScheduleDelayField schedule_date_field?: ScheduleDateField
schedule_delay_custom_field?: number // CustomField.id schedule_date_custom_field?: number // CustomField.id
} }

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.1 on 2024-10-24 04:03 # Generated by Django 5.1.1 on 2024-10-24 04:15
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
@ -14,29 +14,18 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name="workflowtrigger", model_name="workflowtrigger",
name="schedule_delay", name="schedule_date_custom_field",
field=models.CharField(
blank=True,
help_text="The delay before the scheduled trigger is activated.",
max_length=256,
null=True,
verbose_name="schedule delay",
),
),
migrations.AddField(
model_name="workflowtrigger",
name="schedule_delay_custom_field",
field=models.ForeignKey( field=models.ForeignKey(
blank=True, blank=True,
null=True, null=True,
on_delete=django.db.models.deletion.SET_NULL, on_delete=django.db.models.deletion.SET_NULL,
to="documents.customfield", to="documents.customfield",
verbose_name="schedule delay custom field", verbose_name="schedule date custom field",
), ),
), ),
migrations.AddField( migrations.AddField(
model_name="workflowtrigger", model_name="workflowtrigger",
name="schedule_delay_field", name="schedule_date_field",
field=models.CharField( field=models.CharField(
choices=[ choices=[
("added", "Added"), ("added", "Added"),
@ -45,9 +34,9 @@ class Migration(migrations.Migration):
("custom_field", "Custom Field"), ("custom_field", "Custom Field"),
], ],
default="added", default="added",
help_text="The field to use for the delay.", help_text="The field to check for a schedule trigger.",
max_length=20, max_length=20,
verbose_name="schedule delay field", verbose_name="schedule date field",
), ),
), ),
migrations.AddField( migrations.AddField(
@ -59,6 +48,15 @@ class Migration(migrations.Migration):
verbose_name="schedule is recurring", verbose_name="schedule is recurring",
), ),
), ),
migrations.AddField(
model_name="workflowtrigger",
name="schedule_offset_days",
field=models.PositiveIntegerField(
default=0,
help_text="The number of days to offset the schedule trigger by.",
verbose_name="schedule offset days",
),
),
migrations.AlterField( migrations.AlterField(
model_name="workflowtrigger", model_name="workflowtrigger",
name="type", name="type",
@ -85,6 +83,19 @@ class Migration(migrations.Migration):
verbose_name="ID", verbose_name="ID",
), ),
), ),
(
"type",
models.PositiveIntegerField(
choices=[
(1, "Consumption Started"),
(2, "Document Added"),
(3, "Document Updated"),
(4, "Scheduled"),
],
null=True,
verbose_name="workflow trigger type",
),
),
( (
"run_at", "run_at",
models.DateTimeField( models.DateTimeField(

View File

@ -1023,7 +1023,7 @@ class WorkflowTrigger(models.Model):
API_UPLOAD = DocumentSource.ApiUpload.value, _("Api Upload") API_UPLOAD = DocumentSource.ApiUpload.value, _("Api Upload")
MAIL_FETCH = DocumentSource.MailFetch.value, _("Mail Fetch") MAIL_FETCH = DocumentSource.MailFetch.value, _("Mail Fetch")
class ScheduleDelayField(models.TextChoices): class ScheduleDateField(models.TextChoices):
ADDED = "added", _("Added") ADDED = "added", _("Added")
CREATED = "created", _("Created") CREATED = "created", _("Created")
MODIFIED = "modified", _("Modified") MODIFIED = "modified", _("Modified")
@ -1105,13 +1105,11 @@ class WorkflowTrigger(models.Model):
verbose_name=_("has this correspondent"), verbose_name=_("has this correspondent"),
) )
schedule_delay = models.CharField( schedule_offset_days = models.PositiveIntegerField(
_("schedule delay"), _("schedule offset days"),
max_length=256, default=0,
null=True,
blank=True,
help_text=_( help_text=_(
"The delay before the scheduled trigger is activated.", "The number of days to offset the schedule trigger by.",
), ),
) )
@ -1123,22 +1121,22 @@ class WorkflowTrigger(models.Model):
), ),
) )
schedule_delay_field = models.CharField( schedule_date_field = models.CharField(
_("schedule delay field"), _("schedule date field"),
max_length=20, max_length=20,
choices=ScheduleDelayField.choices, choices=ScheduleDateField.choices,
default=ScheduleDelayField.ADDED, default=ScheduleDateField.ADDED,
help_text=_( help_text=_(
"The field to use for the delay.", "The field to check for a schedule trigger.",
), ),
) )
schedule_delay_custom_field = models.ForeignKey( schedule_date_custom_field = models.ForeignKey(
CustomField, CustomField,
null=True, null=True,
blank=True, blank=True,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
verbose_name=_("schedule delay custom field"), verbose_name=_("schedule date custom field"),
) )
class Meta: class Meta:
@ -1401,6 +1399,12 @@ class WorkflowRun(models.Model):
verbose_name=_("workflow"), verbose_name=_("workflow"),
) )
type = models.PositiveIntegerField(
_("workflow trigger type"),
choices=WorkflowTrigger.WorkflowTriggerType.choices,
null=True,
)
document = models.ForeignKey( document = models.ForeignKey(
Document, Document,
null=True, null=True,

View File

@ -21,7 +21,6 @@ from django.utils.crypto import get_random_string
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from drf_writable_nested.serializers import NestedUpdateMixin from drf_writable_nested.serializers import NestedUpdateMixin
from duration_parser import parse_timedelta
from guardian.core import ObjectPermissionChecker from guardian.core import ObjectPermissionChecker
from guardian.shortcuts import get_users_with_perms from guardian.shortcuts import get_users_with_perms
from guardian.utils import get_group_obj_perms_model from guardian.utils import get_group_obj_perms_model
@ -1753,22 +1752,12 @@ class WorkflowTriggerSerializer(serializers.ModelSerializer):
"filter_has_tags", "filter_has_tags",
"filter_has_correspondent", "filter_has_correspondent",
"filter_has_document_type", "filter_has_document_type",
"schedule_delay", "schedule_offset_days",
"schedule_is_recurring", "schedule_is_recurring",
"schedule_delay_field", "schedule_date_field",
"schedule_delay_custom_field", "schedule_date_custom_field",
] ]
def validate_schedule_delay(self, value):
if value is not None:
try:
parse_timedelta(value)
except Exception as e:
raise serializers.ValidationError(
f"Invalid schedule delay format: {e}",
)
return value
def validate(self, attrs): def validate(self, attrs):
# Empty strings treated as None to avoid unexpected behavior # Empty strings treated as None to avoid unexpected behavior
if ( if (
@ -1794,11 +1783,6 @@ class WorkflowTriggerSerializer(serializers.ModelSerializer):
"File name, path or mail rule filter are required", "File name, path or mail rule filter are required",
) )
if attrs["type"] == WorkflowTrigger.WorkflowTriggerType.SCHEDULED and (
"schedule_delay" not in attrs or attrs["schedule_delay"] is None
):
raise serializers.ValidationError("Schedule delay is required")
return attrs return attrs

View File

@ -918,6 +918,7 @@ def run_workflows(
WorkflowRun.objects.create( WorkflowRun.objects.create(
workflow=workflow, workflow=workflow,
type=trigger_type,
document=document if not use_overrides else None, document=document if not use_overrides else None,
) )

View File

@ -14,7 +14,6 @@ from django.db import models
from django.db import transaction from django.db import transaction
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.utils import timezone from django.utils import timezone
from duration_parser import parse_timedelta
from filelock import FileLock from filelock import FileLock
from whoosh.writing import AsyncWriter from whoosh.writing import AsyncWriter
@ -342,38 +341,38 @@ def check_scheduled_workflows():
trigger: WorkflowTrigger trigger: WorkflowTrigger
for trigger in schedule_triggers: for trigger in schedule_triggers:
documents = Document.objects.none() documents = Document.objects.none()
delay_td = parse_timedelta(trigger.schedule_delay) offset_td = timedelta(days=trigger.schedule_offset_days)
logger.debug( logger.debug(
f"Checking trigger {trigger} with delay {delay_td} against field: {trigger.schedule_delay_field}", f"Checking trigger {trigger} with offset {offset_td} against field: {trigger.schedule_date_field}",
) )
if ( if (
trigger.schedule_delay_field trigger.schedule_date_field
== WorkflowTrigger.ScheduleDelayField.ADDED == WorkflowTrigger.ScheduleDateField.ADDED
): ):
documents = Document.objects.filter( documents = Document.objects.filter(
added__lt=timezone.now() - delay_td, added__lt=timezone.now() - offset_td,
) )
elif ( elif (
trigger.schedule_delay_field trigger.schedule_date_field
== WorkflowTrigger.ScheduleDelayField.CREATED == WorkflowTrigger.ScheduleDateField.CREATED
): ):
documents = Document.objects.filter( documents = Document.objects.filter(
created__lt=timezone.now() - delay_td, created__lt=timezone.now() - offset_td,
) )
elif ( elif (
trigger.schedule_delay_field trigger.schedule_date_field
== WorkflowTrigger.ScheduleDelayField.MODIFIED == WorkflowTrigger.ScheduleDateField.MODIFIED
): ):
documents = Document.objects.filter( documents = Document.objects.filter(
modified__lt=timezone.now() - delay_td, modified__lt=timezone.now() - offset_td,
) )
elif ( elif (
trigger.schedule_delay_field trigger.schedule_date_field
== WorkflowTrigger.ScheduleDelayField.CUSTOM_FIELD == WorkflowTrigger.ScheduleDateField.CUSTOM_FIELD
): ):
cf_instances = CustomFieldInstance.objects.filter( cf_instances = CustomFieldInstance.objects.filter(
field=trigger.schedule_delay_custom_field, field=trigger.schedule_date_custom_field,
value_date__lt=timezone.now() - delay_td, value_date__lt=timezone.now() - offset_td,
) )
documents = Document.objects.filter( documents = Document.objects.filter(
id__in=cf_instances.values_list("document", flat=True), id__in=cf_instances.values_list("document", flat=True),
@ -385,6 +384,7 @@ def check_scheduled_workflows():
for document in documents: for document in documents:
workflow_runs = WorkflowRun.objects.filter( workflow_runs = WorkflowRun.objects.filter(
document=document, document=document,
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
workflow=workflow, workflow=workflow,
) )
if not trigger.schedule_is_recurring and workflow_runs.exists(): if not trigger.schedule_is_recurring and workflow_runs.exists():
@ -396,7 +396,7 @@ def check_scheduled_workflows():
elif ( elif (
trigger.schedule_is_recurring trigger.schedule_is_recurring
and workflow_runs.exists() and workflow_runs.exists()
and workflow_runs.last().run_at > timezone.now() - delay_td and workflow_runs.last().run_at > timezone.now() - offset_td
): ):
# schedule is recurring but the last run was within the delay # schedule is recurring but the last run was within the delay
logger.debug( logger.debug(

View File

@ -1318,8 +1318,8 @@ class TestWorkflows(DirectoriesMixin, FileSystemAssertsMixin, APITestCase):
""" """
trigger = WorkflowTrigger.objects.create( trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED, type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
schedule_delay="1 day", schedule_offset_days=1,
schedule_delay_field="created", schedule_date_field="created",
) )
action = WorkflowAction.objects.create( action = WorkflowAction.objects.create(
assign_title="Doc assign owner", assign_title="Doc assign owner",
@ -1359,8 +1359,8 @@ class TestWorkflows(DirectoriesMixin, FileSystemAssertsMixin, APITestCase):
""" """
trigger = WorkflowTrigger.objects.create( trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED, type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
schedule_delay="1 day", schedule_offset_days=1,
schedule_delay_field=WorkflowTrigger.ScheduleDelayField.ADDED, schedule_date_field=WorkflowTrigger.ScheduleDateField.ADDED,
) )
action = WorkflowAction.objects.create( action = WorkflowAction.objects.create(
assign_title="Doc assign owner", assign_title="Doc assign owner",
@ -1402,8 +1402,8 @@ class TestWorkflows(DirectoriesMixin, FileSystemAssertsMixin, APITestCase):
mock_filter.return_value = Document.objects.all() mock_filter.return_value = Document.objects.all()
trigger = WorkflowTrigger.objects.create( trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED, type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
schedule_delay="1 day", schedule_offset_days=1,
schedule_delay_field=WorkflowTrigger.ScheduleDelayField.MODIFIED, schedule_date_field=WorkflowTrigger.ScheduleDateField.MODIFIED,
) )
action = WorkflowAction.objects.create( action = WorkflowAction.objects.create(
assign_title="Doc assign owner", assign_title="Doc assign owner",