app_label.codename
"
82 |
83 | #: models.py:161
84 | msgid "Message"
85 | msgstr "Nachricht"
86 |
87 | #: models.py:165
88 | msgid "Result"
89 | msgstr "Ergebnis"
90 |
91 | #: settings.py:22
92 | msgid "Default template"
93 | msgstr "Standardvorlage"
94 |
95 | #: templates/automations/dashboard.html:3
96 | #: templates/automations/task_list.html:12
97 | msgid "Automations dashboard"
98 | msgstr "Automatisierungs-Dashboard"
99 |
100 | #: templates/automations/error_report.html:3
101 | #, fuzzy
102 | #| msgid "Automations"
103 | msgid "Automations error report"
104 | msgstr "Automatisierung"
105 |
106 | #: templates/automations/error_report.html:7
107 | msgid "No automations stopped with an error."
108 | msgstr ""
109 |
110 | #: templates/automations/history.html:9
111 |
112 | msgid "Automation id:"
113 | msgstr "Automatisierungs-ID:"
114 |
115 | #: templates/automations/history.html:10
116 | msgid "Updated:"
117 | msgstr "Aktualisiert:"
118 |
119 | #: templates/automations/history.html:11
120 | msgid "Paused until:"
121 | msgstr "Pausiert bis:"
122 |
123 | #: templates/automations/history.html:12
124 | msgid "Created:"
125 | msgstr "Erstellt:"
126 |
127 | #: templates/automations/includes/dashboard_item.html:5
128 | msgid "Running"
129 | msgstr "Laufend"
130 |
131 | #: templates/automations/includes/form_view.html:8
132 | msgid "Back"
133 | msgstr "Zurück"
134 |
135 | #: templates/automations/includes/form_view.html:10
136 | msgid "OK"
137 | msgstr "OK"
138 |
139 | #: templates/automations/includes/history_item.html:4
140 | msgid "running"
141 | msgstr "läuft"
142 |
143 | #: templates/automations/includes/task_item.html:6
144 | msgid "View"
145 | msgstr "Ansehen"
146 |
147 | #: templates/automations/preformatted_traceback.html:4
148 | msgid "Traceback"
149 | msgstr "Aufrufhierarchie beim Fehler"
150 |
151 | #: templates/automations/preformatted_traceback.html:7
152 | msgid "No traceback available"
153 | msgstr "Keine Aufrufhierarchie verfügbar"
154 |
155 | #: templates/automations/task_list.html:3
156 | msgid "Open tasks for"
157 | msgstr "Offene Aufgaben für"
158 |
159 | #: templates/automations/task_list.html:6
160 | msgid "Currently no tasks for you"
161 | msgstr "Derzeit keine offenen Aufgaben"
162 |
163 | #: tests/models.py:7
164 | msgid "Test Group Name"
165 | msgstr ""
166 |
167 | #: tests/models.py:10
168 | msgid "test group"
169 | msgstr ""
170 |
171 | #: tests/models.py:11
172 | msgid "test groups"
173 | msgstr ""
174 |
175 | #: tests/models.py:15
176 | msgid "Test User Username"
177 | msgstr ""
178 |
179 | #: tests/models.py:16
180 | #, fuzzy
181 | #| msgid "Your e-mail address"
182 | msgid "email address"
183 | msgstr "Deine E-Mail-Adresse"
184 |
185 | #: tests/models.py:25 tests/models.py:29
186 | msgid "superuser status"
187 | msgstr ""
188 |
189 | #: tests/models.py:46
190 | msgid "test user"
191 | msgstr ""
192 |
193 | #: tests/models.py:47
194 | msgid "test users"
195 | msgstr ""
196 |
197 | #: tests/models.py:51
198 | msgid "Test Permission Slug"
199 | msgstr ""
200 |
201 | #: tests/models.py:58
202 | #, fuzzy
203 | #| msgid "Required permissions"
204 | msgid "test permission"
205 | msgstr "Benötigte Permissions"
206 |
207 | #: tests/models.py:59
208 | #, fuzzy
209 | #| msgid "Required permissions"
210 | msgid "test permissions"
211 | msgstr "Benötigte Permissions"
212 |
213 | #: tests/test_automations.py:69
214 | msgid "First name"
215 | msgstr "Vorname"
216 |
217 | #: tests/test_automations.py:73
218 | msgid "Your e-mail address"
219 | msgstr "Deine E-Mail-Adresse"
220 |
221 | #: tests/test_automations.py:77
222 | msgid "Chose session"
223 | msgstr "Sitzung wählen"
224 |
225 | #: views.py:136
226 | msgid "Obsolete automation %s"
227 | msgstr "Nicht mehr gültige Automatisierung %s"
228 |
229 | #: views.py:140
230 | #, python-format
231 | msgid "Obsolete automations %s"
232 | msgstr "Nicht mehr gültige Automatisierungen %s"
233 |
234 | #: views.py:157
235 | #, python-format
236 | msgid "Last %d days"
237 | msgstr "Vergangene %d Tage"
238 |
--------------------------------------------------------------------------------
/src/automations/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsbraun/django-automations/3fde447de8032f0ad1d76eb706fd521174d05415/src/automations/management/__init__.py
--------------------------------------------------------------------------------
/src/automations/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsbraun/django-automations/3fde447de8032f0ad1d76eb706fd521174d05415/src/automations/management/commands/__init__.py
--------------------------------------------------------------------------------
/src/automations/management/commands/automation_delete_history.py:
--------------------------------------------------------------------------------
1 | from logging import getLogger
2 |
3 | from django.core.management.base import BaseCommand
4 |
5 | from automations.models import AutomationModel
6 |
7 | logger = getLogger(__name__)
8 |
9 |
10 | class Command(BaseCommand):
11 | help = "Delete Automations older than the specified number of days (default=30)"
12 |
13 | def add_arguments(self, parser):
14 | parser.add_argument(
15 | "days_old",
16 | type=int,
17 | nargs="?",
18 | help="The minumum age of an Automation (in days) before it is deleted",
19 | default=30,
20 | )
21 |
22 | def handle(self, *args, **kwargs):
23 | days_old = kwargs["days_old"]
24 | total, info_dict = AutomationModel.delete_history(days_old)
25 |
26 | automation_count = info_dict.get("automations.AutomationModel", 0)
27 | task_count = info_dict.get("automations.AutomationTaskModel", 0)
28 |
29 | self.stdout.write(
30 | f"{total} total objects deleted, including {automation_count} AutomationModel "
31 | f"instances, and {task_count} AutomationTaskModel instances"
32 | )
33 |
--------------------------------------------------------------------------------
/src/automations/management/commands/automation_step.py:
--------------------------------------------------------------------------------
1 | from logging import getLogger
2 |
3 | from django.core.management import BaseCommand
4 |
5 | from automations.models import AutomationModel
6 |
7 | logger = getLogger(__name__)
8 |
9 |
10 | class Command(BaseCommand):
11 | help = "Touch every automation to proceed."
12 |
13 | def handle(self, *args, **options):
14 | AutomationModel.run()
15 |
--------------------------------------------------------------------------------
/src/automations/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.8 on 2021-05-02 08:56
2 |
3 | import django.db.models.deletion
4 | from django.conf import settings
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14 | ("auth", "0012_alter_user_first_name_max_length"),
15 | ]
16 |
17 | operations = [
18 | migrations.CreateModel(
19 | name="AutomationModel",
20 | fields=[
21 | (
22 | "id",
23 | models.AutoField(
24 | auto_created=True,
25 | primary_key=True,
26 | serialize=False,
27 | verbose_name="ID",
28 | ),
29 | ),
30 | (
31 | "automation_class",
32 | models.CharField(max_length=256, verbose_name="Process class"),
33 | ),
34 | (
35 | "finished",
36 | models.BooleanField(default=False, verbose_name="Finished"),
37 | ),
38 | ("data", models.JSONField(default=dict, verbose_name="Data")),
39 | (
40 | "paused_until",
41 | models.DateTimeField(null=True, verbose_name="Paused until"),
42 | ),
43 | ("created", models.DateTimeField(auto_now_add=True)),
44 | ("updated", models.DateTimeField(auto_now=True)),
45 | ],
46 | ),
47 | migrations.CreateModel(
48 | name="AutomationTaskModel",
49 | fields=[
50 | (
51 | "id",
52 | models.AutoField(
53 | auto_created=True,
54 | primary_key=True,
55 | serialize=False,
56 | verbose_name="ID",
57 | ),
58 | ),
59 | (
60 | "status",
61 | models.CharField(blank=True, max_length=256, verbose_name="Status"),
62 | ),
63 | ("locked", models.IntegerField(default=0, verbose_name="Locked")),
64 | (
65 | "interaction_permissions",
66 | models.JSONField(
67 | default=list,
68 | help_text="List of permissions of the form app_label.codename",
69 | verbose_name="Required permissions",
70 | ),
71 | ),
72 | ("created", models.DateTimeField(auto_now_add=True)),
73 | ("finished", models.DateTimeField(null=True)),
74 | (
75 | "message",
76 | models.CharField(
77 | blank=True, max_length=128, verbose_name="Message"
78 | ),
79 | ),
80 | (
81 | "result",
82 | models.JSONField(
83 | blank=True, default=dict, null=True, verbose_name="Result"
84 | ),
85 | ),
86 | (
87 | "automation",
88 | models.ForeignKey(
89 | on_delete=django.db.models.deletion.CASCADE,
90 | to="automations.automationmodel",
91 | ),
92 | ),
93 | (
94 | "interaction_group",
95 | models.ForeignKey(
96 | null=True,
97 | on_delete=django.db.models.deletion.PROTECT,
98 | to="auth.group",
99 | verbose_name="Assigned group",
100 | ),
101 | ),
102 | (
103 | "interaction_user",
104 | models.ForeignKey(
105 | null=True,
106 | on_delete=django.db.models.deletion.PROTECT,
107 | to=settings.AUTH_USER_MODEL,
108 | verbose_name="Assigned user",
109 | ),
110 | ),
111 | (
112 | "previous",
113 | models.ForeignKey(
114 | null=True,
115 | on_delete=django.db.models.deletion.SET_NULL,
116 | to="automations.automationtaskmodel",
117 | verbose_name="Previous task",
118 | ),
119 | ),
120 | ],
121 | ),
122 | ]
123 |
--------------------------------------------------------------------------------
/src/automations/migrations/0002_auto_20210506_1957.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.8 on 2021-05-06 17:57
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("automations", "0001_initial"),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name="automationmodel",
16 | name="automation_class",
17 | field=models.CharField(max_length=256, verbose_name="Prezess-Klasse"),
18 | ),
19 | migrations.AlterField(
20 | model_name="automationmodel",
21 | name="data",
22 | field=models.JSONField(default=dict, verbose_name="Daten"),
23 | ),
24 | migrations.AlterField(
25 | model_name="automationmodel",
26 | name="finished",
27 | field=models.BooleanField(default=False, verbose_name="Beendet"),
28 | ),
29 | migrations.AlterField(
30 | model_name="automationmodel",
31 | name="paused_until",
32 | field=models.DateTimeField(null=True, verbose_name="Pausiert bis"),
33 | ),
34 | migrations.AlterField(
35 | model_name="automationtaskmodel",
36 | name="locked",
37 | field=models.IntegerField(default=0, verbose_name="Gesperrt"),
38 | ),
39 | migrations.AlterField(
40 | model_name="automationtaskmodel",
41 | name="message",
42 | field=models.CharField(
43 | blank=True, max_length=128, verbose_name="Nachricht"
44 | ),
45 | ),
46 | migrations.AlterField(
47 | model_name="automationtaskmodel",
48 | name="previous",
49 | field=models.ForeignKey(
50 | null=True,
51 | on_delete=django.db.models.deletion.SET_NULL,
52 | to="automations.automationtaskmodel",
53 | verbose_name="Vorherige Aufgabe",
54 | ),
55 | ),
56 | migrations.AlterField(
57 | model_name="automationtaskmodel",
58 | name="result",
59 | field=models.JSONField(
60 | blank=True, default=dict, null=True, verbose_name="Ergebnis"
61 | ),
62 | ),
63 | ]
64 |
--------------------------------------------------------------------------------
/src/automations/migrations/0003_auto_20210511_0825.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.8 on 2021-05-11 08:25
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("automations", "0002_auto_20210506_1957"),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name="automationtaskmodel",
16 | name="requires_interaction",
17 | field=models.BooleanField(
18 | default=False, verbose_name="Requires interaction"
19 | ),
20 | ),
21 | migrations.AlterField(
22 | model_name="automationmodel",
23 | name="automation_class",
24 | field=models.CharField(max_length=256, verbose_name="Process class"),
25 | ),
26 | migrations.AlterField(
27 | model_name="automationmodel",
28 | name="data",
29 | field=models.JSONField(default=dict, verbose_name="Data"),
30 | ),
31 | migrations.AlterField(
32 | model_name="automationmodel",
33 | name="finished",
34 | field=models.BooleanField(default=False, verbose_name="Finished"),
35 | ),
36 | migrations.AlterField(
37 | model_name="automationmodel",
38 | name="paused_until",
39 | field=models.DateTimeField(null=True, verbose_name="Paused until"),
40 | ),
41 | migrations.AlterField(
42 | model_name="automationtaskmodel",
43 | name="locked",
44 | field=models.IntegerField(default=0, verbose_name="Locked"),
45 | ),
46 | migrations.AlterField(
47 | model_name="automationtaskmodel",
48 | name="message",
49 | field=models.CharField(blank=True, max_length=128, verbose_name="Message"),
50 | ),
51 | migrations.AlterField(
52 | model_name="automationtaskmodel",
53 | name="previous",
54 | field=models.ForeignKey(
55 | null=True,
56 | on_delete=django.db.models.deletion.SET_NULL,
57 | to="automations.automationtaskmodel",
58 | verbose_name="Previous task",
59 | ),
60 | ),
61 | migrations.AlterField(
62 | model_name="automationtaskmodel",
63 | name="result",
64 | field=models.JSONField(
65 | blank=True, default=dict, null=True, verbose_name="Result"
66 | ),
67 | ),
68 | ]
69 |
--------------------------------------------------------------------------------
/src/automations/migrations/0004_auto_20210511_1042.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.8 on 2021-05-11 08:42
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("automations", "0003_auto_20210511_0825"),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name="automationmodel",
16 | name="automation_class",
17 | field=models.CharField(max_length=256, verbose_name="Prezess-Klasse"),
18 | ),
19 | migrations.AlterField(
20 | model_name="automationmodel",
21 | name="data",
22 | field=models.JSONField(default=dict, verbose_name="Daten"),
23 | ),
24 | migrations.AlterField(
25 | model_name="automationmodel",
26 | name="finished",
27 | field=models.BooleanField(default=False, verbose_name="Beendet"),
28 | ),
29 | migrations.AlterField(
30 | model_name="automationmodel",
31 | name="paused_until",
32 | field=models.DateTimeField(null=True, verbose_name="Pausiert bis"),
33 | ),
34 | migrations.AlterField(
35 | model_name="automationtaskmodel",
36 | name="locked",
37 | field=models.IntegerField(default=0, verbose_name="Gesperrt"),
38 | ),
39 | migrations.AlterField(
40 | model_name="automationtaskmodel",
41 | name="message",
42 | field=models.CharField(
43 | blank=True, max_length=128, verbose_name="Nachricht"
44 | ),
45 | ),
46 | migrations.AlterField(
47 | model_name="automationtaskmodel",
48 | name="previous",
49 | field=models.ForeignKey(
50 | null=True,
51 | on_delete=django.db.models.deletion.SET_NULL,
52 | to="automations.automationtaskmodel",
53 | verbose_name="Vorherige Aufgabe",
54 | ),
55 | ),
56 | migrations.AlterField(
57 | model_name="automationtaskmodel",
58 | name="result",
59 | field=models.JSONField(
60 | blank=True, default=dict, null=True, verbose_name="Ergebnis"
61 | ),
62 | ),
63 | ]
64 |
--------------------------------------------------------------------------------
/src/automations/migrations/0005_automationmodel_key.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.8 on 2021-05-11 19:45
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("automations", "0004_auto_20210511_1042"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="automationmodel",
15 | name="key",
16 | field=models.CharField(
17 | default="", max_length=64, verbose_name="_Unique hash"
18 | ),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/src/automations/migrations/0006_auto_20211121_1357.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.9 on 2021-11-21 13:57
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("automations", "0005_automationmodel_key"),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name="automationmodel",
16 | name="automation_class",
17 | field=models.CharField(max_length=256, verbose_name="Process class"),
18 | ),
19 | migrations.AlterField(
20 | model_name="automationmodel",
21 | name="data",
22 | field=models.JSONField(default=dict, verbose_name="Data"),
23 | ),
24 | migrations.AlterField(
25 | model_name="automationmodel",
26 | name="finished",
27 | field=models.BooleanField(default=False, verbose_name="Finished"),
28 | ),
29 | migrations.AlterField(
30 | model_name="automationmodel",
31 | name="key",
32 | field=models.CharField(
33 | default="", max_length=64, verbose_name="Unique hash"
34 | ),
35 | ),
36 | migrations.AlterField(
37 | model_name="automationmodel",
38 | name="paused_until",
39 | field=models.DateTimeField(null=True, verbose_name="Paused until"),
40 | ),
41 | migrations.AlterField(
42 | model_name="automationtaskmodel",
43 | name="locked",
44 | field=models.IntegerField(default=0, verbose_name="Locked"),
45 | ),
46 | migrations.AlterField(
47 | model_name="automationtaskmodel",
48 | name="message",
49 | field=models.CharField(blank=True, max_length=128, verbose_name="Message"),
50 | ),
51 | migrations.AlterField(
52 | model_name="automationtaskmodel",
53 | name="previous",
54 | field=models.ForeignKey(
55 | null=True,
56 | on_delete=django.db.models.deletion.SET_NULL,
57 | to="automations.automationtaskmodel",
58 | verbose_name="Previous task",
59 | ),
60 | ),
61 | migrations.AlterField(
62 | model_name="automationtaskmodel",
63 | name="result",
64 | field=models.JSONField(
65 | blank=True, default=dict, null=True, verbose_name="Result"
66 | ),
67 | ),
68 | ]
69 |
--------------------------------------------------------------------------------
/src/automations/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsbraun/django-automations/3fde447de8032f0ad1d76eb706fd521174d05415/src/automations/migrations/__init__.py
--------------------------------------------------------------------------------
/src/automations/models.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 | import datetime
3 | import hashlib
4 | import sys
5 | from logging import getLogger
6 | from types import MethodType
7 |
8 | from django.conf import settings as project_settings
9 | from django.contrib.auth import get_user_model
10 | from django.db import models
11 | from django.db.models import Q
12 | from django.utils.module_loading import import_string
13 | from django.utils.timezone import now
14 | from django.utils.translation import gettext as _
15 |
16 | from . import settings
17 |
18 | # Create your models here.
19 |
20 | logger = getLogger(__name__)
21 |
22 | User = get_user_model()
23 | Group = settings.get_group_model()
24 |
25 |
26 | def get_automation_class(dotted_name):
27 | components = dotted_name.rsplit(".", 1)
28 | cls = __import__(components[0], fromlist=[components[-1]])
29 | cls = getattr(cls, components[-1])
30 | return cls
31 |
32 |
33 | class AutomationModel(models.Model):
34 | automation_class = models.CharField(
35 | max_length=256,
36 | blank=False,
37 | verbose_name=_("Process class"),
38 | )
39 | finished = models.BooleanField(
40 | default=False,
41 | verbose_name=_("Finished"),
42 | )
43 | data = models.JSONField(
44 | verbose_name=_("Data"),
45 | default=dict,
46 | )
47 | key = models.CharField(
48 | verbose_name=_("Unique hash"),
49 | default="",
50 | max_length=64,
51 | )
52 | paused_until = models.DateTimeField(
53 | null=True,
54 | verbose_name=_("Paused until"),
55 | )
56 | created = models.DateTimeField(
57 | auto_now_add=True,
58 | )
59 | updated = models.DateTimeField(
60 | auto_now=True,
61 | )
62 |
63 | _automation_class = None
64 |
65 | def save(self, *args, **kwargs):
66 | self.key = self.get_key()
67 | return super().save(*args, **kwargs)
68 |
69 | def get_automation_class(self):
70 | if self._automation_class is None:
71 | self._automation_class = get_automation_class(self.automation_class)
72 | return self._automation_class
73 |
74 | @property
75 | def instance(self):
76 | return self.get_automation_class()(automation=self)
77 |
78 | @classmethod
79 | def run(cls, timestamp=None):
80 | if timestamp is None:
81 | timestamp = now()
82 | automations = cls.objects.filter(
83 | finished=False,
84 | ).filter(Q(paused_until__lte=timestamp) | Q(paused_until=None))
85 |
86 | for automation in automations:
87 | klass = import_string(automation.automation_class)
88 | instance = klass(automation_id=automation.id, autorun=False)
89 | logger.info(f"Running automation {automation.automation_class}")
90 | try:
91 | instance.run()
92 | except Exception as e: # pragma: no cover
93 | automation.finished = True
94 | automation.save()
95 | logger.error(f"Error: {repr(e)}", exc_info=sys.exc_info())
96 |
97 | def get_key(self):
98 | return hashlib.sha1(
99 | f"{self.automation_class}-{self.id}".encode("utf-8")
100 | ).hexdigest()
101 |
102 | @classmethod
103 | def delete_history(cls, days=30):
104 | automations = cls.objects.filter(
105 | finished=True, updated__lt=now() - datetime.timedelta(days=days)
106 | )
107 | return automations.delete()
108 |
109 | def __str__(self):
110 | return f"{{ automation.data }}18 |
{{ error.data|truncatechars:40 }}
18 | {% endif %}
19 | {{ node.description }}
{% endif %} 8 | {% if task.message == "OK" and task.result %} 9 |{{ task.result }}10 | {% elif "Error" in task.message %} 11 | 12 |
{{ task.message }}
13 |
14 | {% else %}
15 | {{ task.message }}16 | {% endif %} 17 |
{{ task.get_node.description }}
6 | {% trans "View" %} 7 |{{ error }}6 | {% else %} 7 |