Upload 22 files
Browse files- core/migrations/0001_initial.py +425 -0
- core/migrations/0002_add_scheduling_models.py +254 -0
- core/migrations/0003_add_package_operation.py +98 -0
- core/migrations/0004_secret.py +67 -0
- core/migrations/0005_add_webhook_token.py +25 -0
- core/migrations/0006_notification_settings.py +128 -0
- core/migrations/0007_phase7_settings.py +103 -0
- core/migrations/0008_worker_heartbeat.py +20 -0
- core/migrations/0009_script_archive.py +35 -0
- core/migrations/0010_globalsettings_setup_completed.py +27 -0
- core/migrations/0011_registration_and_invites.py +85 -0
- core/migrations/0012_password_reset_token.py +63 -0
- core/migrations/0013_add_tags.py +83 -0
- core/migrations/0014_worker_settings.py +50 -0
- core/migrations/0015_datastore.py +107 -0
- core/migrations/0016_globalsettings_admin_url_slug.py +19 -0
- core/migrations/0017_datastore_api_token.py +97 -0
- core/migrations/0018_add_weekly_monthly_schedule.py +69 -0
- core/migrations/0019_s3_storage_settings.py +90 -0
- core/migrations/0020_s3_backup_schedule.py +129 -0
- core/migrations/0021_field_updates.py +41 -0
- core/migrations/__init__.py +0 -0
core/migrations/0001_initial.py
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 6.0.1 on 2026-01-18 17:49
|
| 2 |
+
|
| 3 |
+
import django.contrib.auth.models
|
| 4 |
+
import django.contrib.auth.validators
|
| 5 |
+
import django.db.models.deletion
|
| 6 |
+
import django.utils.timezone
|
| 7 |
+
import uuid
|
| 8 |
+
from django.conf import settings
|
| 9 |
+
from django.db import migrations, models
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class Migration(migrations.Migration):
|
| 13 |
+
|
| 14 |
+
initial = True
|
| 15 |
+
|
| 16 |
+
dependencies = [
|
| 17 |
+
("auth", "0012_alter_user_first_name_max_length"),
|
| 18 |
+
]
|
| 19 |
+
|
| 20 |
+
operations = [
|
| 21 |
+
migrations.CreateModel(
|
| 22 |
+
name="User",
|
| 23 |
+
fields=[
|
| 24 |
+
(
|
| 25 |
+
"id",
|
| 26 |
+
models.BigAutoField(
|
| 27 |
+
auto_created=True,
|
| 28 |
+
primary_key=True,
|
| 29 |
+
serialize=False,
|
| 30 |
+
verbose_name="ID",
|
| 31 |
+
),
|
| 32 |
+
),
|
| 33 |
+
("password", models.CharField(max_length=128, verbose_name="password")),
|
| 34 |
+
(
|
| 35 |
+
"last_login",
|
| 36 |
+
models.DateTimeField(
|
| 37 |
+
blank=True, null=True, verbose_name="last login"
|
| 38 |
+
),
|
| 39 |
+
),
|
| 40 |
+
(
|
| 41 |
+
"is_superuser",
|
| 42 |
+
models.BooleanField(
|
| 43 |
+
default=False,
|
| 44 |
+
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
| 45 |
+
verbose_name="superuser status",
|
| 46 |
+
),
|
| 47 |
+
),
|
| 48 |
+
(
|
| 49 |
+
"username",
|
| 50 |
+
models.CharField(
|
| 51 |
+
error_messages={
|
| 52 |
+
"unique": "A user with that username already exists."
|
| 53 |
+
},
|
| 54 |
+
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
| 55 |
+
max_length=150,
|
| 56 |
+
unique=True,
|
| 57 |
+
validators=[
|
| 58 |
+
django.contrib.auth.validators.UnicodeUsernameValidator()
|
| 59 |
+
],
|
| 60 |
+
verbose_name="username",
|
| 61 |
+
),
|
| 62 |
+
),
|
| 63 |
+
(
|
| 64 |
+
"first_name",
|
| 65 |
+
models.CharField(
|
| 66 |
+
blank=True, max_length=150, verbose_name="first name"
|
| 67 |
+
),
|
| 68 |
+
),
|
| 69 |
+
(
|
| 70 |
+
"last_name",
|
| 71 |
+
models.CharField(
|
| 72 |
+
blank=True, max_length=150, verbose_name="last name"
|
| 73 |
+
),
|
| 74 |
+
),
|
| 75 |
+
(
|
| 76 |
+
"is_staff",
|
| 77 |
+
models.BooleanField(
|
| 78 |
+
default=False,
|
| 79 |
+
help_text="Designates whether the user can log into this admin site.",
|
| 80 |
+
verbose_name="staff status",
|
| 81 |
+
),
|
| 82 |
+
),
|
| 83 |
+
(
|
| 84 |
+
"is_active",
|
| 85 |
+
models.BooleanField(
|
| 86 |
+
default=True,
|
| 87 |
+
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
|
| 88 |
+
verbose_name="active",
|
| 89 |
+
),
|
| 90 |
+
),
|
| 91 |
+
(
|
| 92 |
+
"date_joined",
|
| 93 |
+
models.DateTimeField(
|
| 94 |
+
default=django.utils.timezone.now, verbose_name="date joined"
|
| 95 |
+
),
|
| 96 |
+
),
|
| 97 |
+
("email", models.EmailField(max_length=254, unique=True)),
|
| 98 |
+
(
|
| 99 |
+
"is_verified",
|
| 100 |
+
models.BooleanField(
|
| 101 |
+
default=False,
|
| 102 |
+
help_text="Whether the user has verified their email via magic link",
|
| 103 |
+
),
|
| 104 |
+
),
|
| 105 |
+
(
|
| 106 |
+
"groups",
|
| 107 |
+
models.ManyToManyField(
|
| 108 |
+
blank=True,
|
| 109 |
+
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
| 110 |
+
related_name="user_set",
|
| 111 |
+
related_query_name="user",
|
| 112 |
+
to="auth.group",
|
| 113 |
+
verbose_name="groups",
|
| 114 |
+
),
|
| 115 |
+
),
|
| 116 |
+
(
|
| 117 |
+
"user_permissions",
|
| 118 |
+
models.ManyToManyField(
|
| 119 |
+
blank=True,
|
| 120 |
+
help_text="Specific permissions for this user.",
|
| 121 |
+
related_name="user_set",
|
| 122 |
+
related_query_name="user",
|
| 123 |
+
to="auth.permission",
|
| 124 |
+
verbose_name="user permissions",
|
| 125 |
+
),
|
| 126 |
+
),
|
| 127 |
+
],
|
| 128 |
+
options={
|
| 129 |
+
"verbose_name": "user",
|
| 130 |
+
"verbose_name_plural": "users",
|
| 131 |
+
"db_table": "users",
|
| 132 |
+
},
|
| 133 |
+
managers=[
|
| 134 |
+
("objects", django.contrib.auth.models.UserManager()),
|
| 135 |
+
],
|
| 136 |
+
),
|
| 137 |
+
migrations.CreateModel(
|
| 138 |
+
name="Environment",
|
| 139 |
+
fields=[
|
| 140 |
+
(
|
| 141 |
+
"id",
|
| 142 |
+
models.UUIDField(
|
| 143 |
+
default=uuid.uuid4,
|
| 144 |
+
editable=False,
|
| 145 |
+
primary_key=True,
|
| 146 |
+
serialize=False,
|
| 147 |
+
),
|
| 148 |
+
),
|
| 149 |
+
("name", models.CharField(max_length=100)),
|
| 150 |
+
("description", models.TextField(blank=True)),
|
| 151 |
+
(
|
| 152 |
+
"path",
|
| 153 |
+
models.CharField(
|
| 154 |
+
help_text="Directory name within the environments folder",
|
| 155 |
+
max_length=255,
|
| 156 |
+
unique=True,
|
| 157 |
+
),
|
| 158 |
+
),
|
| 159 |
+
("python_version", models.CharField(blank=True, max_length=20)),
|
| 160 |
+
(
|
| 161 |
+
"requirements",
|
| 162 |
+
models.TextField(
|
| 163 |
+
blank=True,
|
| 164 |
+
help_text="Installed packages in pip requirements format",
|
| 165 |
+
),
|
| 166 |
+
),
|
| 167 |
+
(
|
| 168 |
+
"is_default",
|
| 169 |
+
models.BooleanField(
|
| 170 |
+
default=False,
|
| 171 |
+
help_text="Whether this is the default environment for new scripts",
|
| 172 |
+
),
|
| 173 |
+
),
|
| 174 |
+
(
|
| 175 |
+
"is_active",
|
| 176 |
+
models.BooleanField(
|
| 177 |
+
default=True,
|
| 178 |
+
help_text="Whether this environment is available for use",
|
| 179 |
+
),
|
| 180 |
+
),
|
| 181 |
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
| 182 |
+
("updated_at", models.DateTimeField(auto_now=True)),
|
| 183 |
+
(
|
| 184 |
+
"created_by",
|
| 185 |
+
models.ForeignKey(
|
| 186 |
+
blank=True,
|
| 187 |
+
null=True,
|
| 188 |
+
on_delete=django.db.models.deletion.SET_NULL,
|
| 189 |
+
related_name="created_environments",
|
| 190 |
+
to=settings.AUTH_USER_MODEL,
|
| 191 |
+
),
|
| 192 |
+
),
|
| 193 |
+
],
|
| 194 |
+
options={
|
| 195 |
+
"verbose_name": "environment",
|
| 196 |
+
"verbose_name_plural": "environments",
|
| 197 |
+
"db_table": "environments",
|
| 198 |
+
"ordering": ["-is_default", "name"],
|
| 199 |
+
},
|
| 200 |
+
),
|
| 201 |
+
migrations.CreateModel(
|
| 202 |
+
name="Script",
|
| 203 |
+
fields=[
|
| 204 |
+
(
|
| 205 |
+
"id",
|
| 206 |
+
models.UUIDField(
|
| 207 |
+
default=uuid.uuid4,
|
| 208 |
+
editable=False,
|
| 209 |
+
primary_key=True,
|
| 210 |
+
serialize=False,
|
| 211 |
+
),
|
| 212 |
+
),
|
| 213 |
+
("name", models.CharField(max_length=200)),
|
| 214 |
+
("description", models.TextField(blank=True)),
|
| 215 |
+
("code", models.TextField(help_text="Python code to execute")),
|
| 216 |
+
(
|
| 217 |
+
"timeout_seconds",
|
| 218 |
+
models.PositiveIntegerField(
|
| 219 |
+
default=300,
|
| 220 |
+
help_text="Maximum execution time in seconds (default: 5 minutes)",
|
| 221 |
+
),
|
| 222 |
+
),
|
| 223 |
+
(
|
| 224 |
+
"is_enabled",
|
| 225 |
+
models.BooleanField(
|
| 226 |
+
default=True, help_text="Whether this script can be executed"
|
| 227 |
+
),
|
| 228 |
+
),
|
| 229 |
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
| 230 |
+
("updated_at", models.DateTimeField(auto_now=True)),
|
| 231 |
+
(
|
| 232 |
+
"created_by",
|
| 233 |
+
models.ForeignKey(
|
| 234 |
+
blank=True,
|
| 235 |
+
null=True,
|
| 236 |
+
on_delete=django.db.models.deletion.SET_NULL,
|
| 237 |
+
related_name="scripts",
|
| 238 |
+
to=settings.AUTH_USER_MODEL,
|
| 239 |
+
),
|
| 240 |
+
),
|
| 241 |
+
(
|
| 242 |
+
"environment",
|
| 243 |
+
models.ForeignKey(
|
| 244 |
+
help_text="Python environment to use for execution",
|
| 245 |
+
on_delete=django.db.models.deletion.PROTECT,
|
| 246 |
+
related_name="scripts",
|
| 247 |
+
to="core.environment",
|
| 248 |
+
),
|
| 249 |
+
),
|
| 250 |
+
],
|
| 251 |
+
options={
|
| 252 |
+
"verbose_name": "script",
|
| 253 |
+
"verbose_name_plural": "scripts",
|
| 254 |
+
"db_table": "scripts",
|
| 255 |
+
"ordering": ["-updated_at"],
|
| 256 |
+
},
|
| 257 |
+
),
|
| 258 |
+
migrations.CreateModel(
|
| 259 |
+
name="MagicToken",
|
| 260 |
+
fields=[
|
| 261 |
+
(
|
| 262 |
+
"id",
|
| 263 |
+
models.BigAutoField(
|
| 264 |
+
auto_created=True,
|
| 265 |
+
primary_key=True,
|
| 266 |
+
serialize=False,
|
| 267 |
+
verbose_name="ID",
|
| 268 |
+
),
|
| 269 |
+
),
|
| 270 |
+
("token", models.CharField(db_index=True, max_length=64, unique=True)),
|
| 271 |
+
(
|
| 272 |
+
"email",
|
| 273 |
+
models.EmailField(
|
| 274 |
+
help_text="Email address this token was sent to", max_length=254
|
| 275 |
+
),
|
| 276 |
+
),
|
| 277 |
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
| 278 |
+
("expires_at", models.DateTimeField()),
|
| 279 |
+
("used_at", models.DateTimeField(blank=True, null=True)),
|
| 280 |
+
(
|
| 281 |
+
"ip_address",
|
| 282 |
+
models.GenericIPAddressField(
|
| 283 |
+
blank=True,
|
| 284 |
+
help_text="IP address that requested this token",
|
| 285 |
+
null=True,
|
| 286 |
+
),
|
| 287 |
+
),
|
| 288 |
+
(
|
| 289 |
+
"user",
|
| 290 |
+
models.ForeignKey(
|
| 291 |
+
blank=True,
|
| 292 |
+
null=True,
|
| 293 |
+
on_delete=django.db.models.deletion.CASCADE,
|
| 294 |
+
related_name="magic_tokens",
|
| 295 |
+
to=settings.AUTH_USER_MODEL,
|
| 296 |
+
),
|
| 297 |
+
),
|
| 298 |
+
],
|
| 299 |
+
options={
|
| 300 |
+
"verbose_name": "magic token",
|
| 301 |
+
"verbose_name_plural": "magic tokens",
|
| 302 |
+
"db_table": "magic_tokens",
|
| 303 |
+
"indexes": [
|
| 304 |
+
models.Index(
|
| 305 |
+
fields=["token", "expires_at"],
|
| 306 |
+
name="magic_token_token_b6602a_idx",
|
| 307 |
+
),
|
| 308 |
+
models.Index(
|
| 309 |
+
fields=["email", "created_at"],
|
| 310 |
+
name="magic_token_email_302c69_idx",
|
| 311 |
+
),
|
| 312 |
+
],
|
| 313 |
+
},
|
| 314 |
+
),
|
| 315 |
+
migrations.CreateModel(
|
| 316 |
+
name="Run",
|
| 317 |
+
fields=[
|
| 318 |
+
(
|
| 319 |
+
"id",
|
| 320 |
+
models.UUIDField(
|
| 321 |
+
default=uuid.uuid4,
|
| 322 |
+
editable=False,
|
| 323 |
+
primary_key=True,
|
| 324 |
+
serialize=False,
|
| 325 |
+
),
|
| 326 |
+
),
|
| 327 |
+
(
|
| 328 |
+
"status",
|
| 329 |
+
models.CharField(
|
| 330 |
+
choices=[
|
| 331 |
+
("pending", "Pending"),
|
| 332 |
+
("running", "Running"),
|
| 333 |
+
("success", "Success"),
|
| 334 |
+
("failed", "Failed"),
|
| 335 |
+
("timeout", "Timeout"),
|
| 336 |
+
("cancelled", "Cancelled"),
|
| 337 |
+
],
|
| 338 |
+
db_index=True,
|
| 339 |
+
default="pending",
|
| 340 |
+
max_length=20,
|
| 341 |
+
),
|
| 342 |
+
),
|
| 343 |
+
(
|
| 344 |
+
"exit_code",
|
| 345 |
+
models.IntegerField(
|
| 346 |
+
blank=True,
|
| 347 |
+
help_text="Process exit code (0 = success)",
|
| 348 |
+
null=True,
|
| 349 |
+
),
|
| 350 |
+
),
|
| 351 |
+
(
|
| 352 |
+
"stdout",
|
| 353 |
+
models.TextField(
|
| 354 |
+
blank=True, help_text="Standard output from script execution"
|
| 355 |
+
),
|
| 356 |
+
),
|
| 357 |
+
(
|
| 358 |
+
"stderr",
|
| 359 |
+
models.TextField(
|
| 360 |
+
blank=True, help_text="Standard error from script execution"
|
| 361 |
+
),
|
| 362 |
+
),
|
| 363 |
+
(
|
| 364 |
+
"started_at",
|
| 365 |
+
models.DateTimeField(
|
| 366 |
+
blank=True, help_text="When execution started", null=True
|
| 367 |
+
),
|
| 368 |
+
),
|
| 369 |
+
(
|
| 370 |
+
"ended_at",
|
| 371 |
+
models.DateTimeField(
|
| 372 |
+
blank=True, help_text="When execution ended", null=True
|
| 373 |
+
),
|
| 374 |
+
),
|
| 375 |
+
(
|
| 376 |
+
"code_snapshot",
|
| 377 |
+
models.TextField(
|
| 378 |
+
blank=True, help_text="Copy of script code at time of execution"
|
| 379 |
+
),
|
| 380 |
+
),
|
| 381 |
+
(
|
| 382 |
+
"task_id",
|
| 383 |
+
models.CharField(
|
| 384 |
+
blank=True,
|
| 385 |
+
db_index=True,
|
| 386 |
+
help_text="django-q2 task ID for tracking async execution",
|
| 387 |
+
max_length=100,
|
| 388 |
+
),
|
| 389 |
+
),
|
| 390 |
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
| 391 |
+
(
|
| 392 |
+
"triggered_by",
|
| 393 |
+
models.ForeignKey(
|
| 394 |
+
blank=True,
|
| 395 |
+
null=True,
|
| 396 |
+
on_delete=django.db.models.deletion.SET_NULL,
|
| 397 |
+
related_name="triggered_runs",
|
| 398 |
+
to=settings.AUTH_USER_MODEL,
|
| 399 |
+
),
|
| 400 |
+
),
|
| 401 |
+
(
|
| 402 |
+
"script",
|
| 403 |
+
models.ForeignKey(
|
| 404 |
+
on_delete=django.db.models.deletion.CASCADE,
|
| 405 |
+
related_name="runs",
|
| 406 |
+
to="core.script",
|
| 407 |
+
),
|
| 408 |
+
),
|
| 409 |
+
],
|
| 410 |
+
options={
|
| 411 |
+
"verbose_name": "run",
|
| 412 |
+
"verbose_name_plural": "runs",
|
| 413 |
+
"db_table": "runs",
|
| 414 |
+
"ordering": ["-created_at"],
|
| 415 |
+
"indexes": [
|
| 416 |
+
models.Index(
|
| 417 |
+
fields=["script", "-created_at"], name="runs_script__1182ba_idx"
|
| 418 |
+
),
|
| 419 |
+
models.Index(
|
| 420 |
+
fields=["status", "-created_at"], name="runs_status_56f7d6_idx"
|
| 421 |
+
),
|
| 422 |
+
],
|
| 423 |
+
},
|
| 424 |
+
),
|
| 425 |
+
]
|
core/migrations/0002_add_scheduling_models.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.9 on 2026-01-19 15:56
|
| 2 |
+
|
| 3 |
+
import django.db.models.deletion
|
| 4 |
+
import uuid
|
| 5 |
+
from django.conf import settings
|
| 6 |
+
from django.db import migrations, models
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class Migration(migrations.Migration):
|
| 10 |
+
|
| 11 |
+
dependencies = [
|
| 12 |
+
("core", "0001_initial"),
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
operations = [
|
| 16 |
+
migrations.AddField(
|
| 17 |
+
model_name="run",
|
| 18 |
+
name="trigger_type",
|
| 19 |
+
field=models.CharField(
|
| 20 |
+
choices=[
|
| 21 |
+
("manual", "Manual"),
|
| 22 |
+
("scheduled", "Scheduled"),
|
| 23 |
+
("api", "API"),
|
| 24 |
+
],
|
| 25 |
+
default="manual",
|
| 26 |
+
help_text="How this run was triggered",
|
| 27 |
+
max_length=20,
|
| 28 |
+
),
|
| 29 |
+
),
|
| 30 |
+
migrations.CreateModel(
|
| 31 |
+
name="GlobalSettings",
|
| 32 |
+
fields=[
|
| 33 |
+
(
|
| 34 |
+
"id",
|
| 35 |
+
models.BigAutoField(
|
| 36 |
+
auto_created=True,
|
| 37 |
+
primary_key=True,
|
| 38 |
+
serialize=False,
|
| 39 |
+
verbose_name="ID",
|
| 40 |
+
),
|
| 41 |
+
),
|
| 42 |
+
(
|
| 43 |
+
"schedules_paused",
|
| 44 |
+
models.BooleanField(
|
| 45 |
+
default=False,
|
| 46 |
+
help_text="Global pause for all scheduled script executions",
|
| 47 |
+
),
|
| 48 |
+
),
|
| 49 |
+
(
|
| 50 |
+
"schedules_paused_at",
|
| 51 |
+
models.DateTimeField(
|
| 52 |
+
blank=True, help_text="When schedules were paused", null=True
|
| 53 |
+
),
|
| 54 |
+
),
|
| 55 |
+
("updated_at", models.DateTimeField(auto_now=True)),
|
| 56 |
+
(
|
| 57 |
+
"schedules_paused_by",
|
| 58 |
+
models.ForeignKey(
|
| 59 |
+
blank=True,
|
| 60 |
+
null=True,
|
| 61 |
+
on_delete=django.db.models.deletion.SET_NULL,
|
| 62 |
+
related_name="+",
|
| 63 |
+
to=settings.AUTH_USER_MODEL,
|
| 64 |
+
),
|
| 65 |
+
),
|
| 66 |
+
],
|
| 67 |
+
options={
|
| 68 |
+
"verbose_name": "global settings",
|
| 69 |
+
"verbose_name_plural": "global settings",
|
| 70 |
+
"db_table": "global_settings",
|
| 71 |
+
},
|
| 72 |
+
),
|
| 73 |
+
migrations.CreateModel(
|
| 74 |
+
name="ScriptSchedule",
|
| 75 |
+
fields=[
|
| 76 |
+
(
|
| 77 |
+
"id",
|
| 78 |
+
models.UUIDField(
|
| 79 |
+
default=uuid.uuid4,
|
| 80 |
+
editable=False,
|
| 81 |
+
primary_key=True,
|
| 82 |
+
serialize=False,
|
| 83 |
+
),
|
| 84 |
+
),
|
| 85 |
+
(
|
| 86 |
+
"run_mode",
|
| 87 |
+
models.CharField(
|
| 88 |
+
choices=[
|
| 89 |
+
("manual", "Manual"),
|
| 90 |
+
("interval", "Interval"),
|
| 91 |
+
("daily", "Daily"),
|
| 92 |
+
],
|
| 93 |
+
default="manual",
|
| 94 |
+
max_length=20,
|
| 95 |
+
),
|
| 96 |
+
),
|
| 97 |
+
(
|
| 98 |
+
"interval_minutes",
|
| 99 |
+
models.PositiveIntegerField(
|
| 100 |
+
blank=True,
|
| 101 |
+
choices=[
|
| 102 |
+
(5, "Every 5 minutes"),
|
| 103 |
+
(10, "Every 10 minutes"),
|
| 104 |
+
(15, "Every 15 minutes"),
|
| 105 |
+
(30, "Every 30 minutes"),
|
| 106 |
+
(60, "Every hour"),
|
| 107 |
+
(120, "Every 2 hours"),
|
| 108 |
+
(360, "Every 6 hours"),
|
| 109 |
+
(720, "Every 12 hours"),
|
| 110 |
+
],
|
| 111 |
+
help_text="Interval in minutes (for interval mode)",
|
| 112 |
+
null=True,
|
| 113 |
+
),
|
| 114 |
+
),
|
| 115 |
+
(
|
| 116 |
+
"daily_times",
|
| 117 |
+
models.JSONField(
|
| 118 |
+
blank=True,
|
| 119 |
+
default=list,
|
| 120 |
+
help_text='List of times in HH:MM format, e.g., ["09:00", "18:00"]',
|
| 121 |
+
),
|
| 122 |
+
),
|
| 123 |
+
(
|
| 124 |
+
"timezone",
|
| 125 |
+
models.CharField(
|
| 126 |
+
default="UTC",
|
| 127 |
+
help_text="Timezone for daily schedules (e.g., 'America/New_York')",
|
| 128 |
+
max_length=50,
|
| 129 |
+
),
|
| 130 |
+
),
|
| 131 |
+
(
|
| 132 |
+
"is_active",
|
| 133 |
+
models.BooleanField(
|
| 134 |
+
default=True,
|
| 135 |
+
help_text="Whether this schedule is active (can be paused without deleting)",
|
| 136 |
+
),
|
| 137 |
+
),
|
| 138 |
+
(
|
| 139 |
+
"q_schedule_ids",
|
| 140 |
+
models.JSONField(
|
| 141 |
+
blank=True,
|
| 142 |
+
default=list,
|
| 143 |
+
help_text="django-q2 Schedule object IDs",
|
| 144 |
+
),
|
| 145 |
+
),
|
| 146 |
+
(
|
| 147 |
+
"next_run",
|
| 148 |
+
models.DateTimeField(
|
| 149 |
+
blank=True,
|
| 150 |
+
help_text="Cached next scheduled run time",
|
| 151 |
+
null=True,
|
| 152 |
+
),
|
| 153 |
+
),
|
| 154 |
+
(
|
| 155 |
+
"last_scheduled_run",
|
| 156 |
+
models.DateTimeField(
|
| 157 |
+
blank=True,
|
| 158 |
+
help_text="When the schedule last triggered a run",
|
| 159 |
+
null=True,
|
| 160 |
+
),
|
| 161 |
+
),
|
| 162 |
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
| 163 |
+
("updated_at", models.DateTimeField(auto_now=True)),
|
| 164 |
+
(
|
| 165 |
+
"created_by",
|
| 166 |
+
models.ForeignKey(
|
| 167 |
+
blank=True,
|
| 168 |
+
null=True,
|
| 169 |
+
on_delete=django.db.models.deletion.SET_NULL,
|
| 170 |
+
related_name="created_schedules",
|
| 171 |
+
to=settings.AUTH_USER_MODEL,
|
| 172 |
+
),
|
| 173 |
+
),
|
| 174 |
+
(
|
| 175 |
+
"script",
|
| 176 |
+
models.OneToOneField(
|
| 177 |
+
on_delete=django.db.models.deletion.CASCADE,
|
| 178 |
+
related_name="schedule",
|
| 179 |
+
to="core.script",
|
| 180 |
+
),
|
| 181 |
+
),
|
| 182 |
+
],
|
| 183 |
+
options={
|
| 184 |
+
"verbose_name": "script schedule",
|
| 185 |
+
"verbose_name_plural": "script schedules",
|
| 186 |
+
"db_table": "script_schedules",
|
| 187 |
+
},
|
| 188 |
+
),
|
| 189 |
+
migrations.CreateModel(
|
| 190 |
+
name="ScheduleHistory",
|
| 191 |
+
fields=[
|
| 192 |
+
(
|
| 193 |
+
"id",
|
| 194 |
+
models.UUIDField(
|
| 195 |
+
default=uuid.uuid4,
|
| 196 |
+
editable=False,
|
| 197 |
+
primary_key=True,
|
| 198 |
+
serialize=False,
|
| 199 |
+
),
|
| 200 |
+
),
|
| 201 |
+
(
|
| 202 |
+
"change_type",
|
| 203 |
+
models.CharField(
|
| 204 |
+
choices=[
|
| 205 |
+
("created", "Created"),
|
| 206 |
+
("updated", "Updated"),
|
| 207 |
+
("enabled", "Enabled"),
|
| 208 |
+
("disabled", "Disabled"),
|
| 209 |
+
("deleted", "Deleted"),
|
| 210 |
+
],
|
| 211 |
+
max_length=20,
|
| 212 |
+
),
|
| 213 |
+
),
|
| 214 |
+
(
|
| 215 |
+
"previous_config",
|
| 216 |
+
models.JSONField(
|
| 217 |
+
blank=True,
|
| 218 |
+
help_text="Previous schedule configuration",
|
| 219 |
+
null=True,
|
| 220 |
+
),
|
| 221 |
+
),
|
| 222 |
+
(
|
| 223 |
+
"new_config",
|
| 224 |
+
models.JSONField(
|
| 225 |
+
blank=True, help_text="New schedule configuration", null=True
|
| 226 |
+
),
|
| 227 |
+
),
|
| 228 |
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
| 229 |
+
(
|
| 230 |
+
"changed_by",
|
| 231 |
+
models.ForeignKey(
|
| 232 |
+
blank=True,
|
| 233 |
+
null=True,
|
| 234 |
+
on_delete=django.db.models.deletion.SET_NULL,
|
| 235 |
+
to=settings.AUTH_USER_MODEL,
|
| 236 |
+
),
|
| 237 |
+
),
|
| 238 |
+
(
|
| 239 |
+
"schedule",
|
| 240 |
+
models.ForeignKey(
|
| 241 |
+
on_delete=django.db.models.deletion.CASCADE,
|
| 242 |
+
related_name="history",
|
| 243 |
+
to="core.scriptschedule",
|
| 244 |
+
),
|
| 245 |
+
),
|
| 246 |
+
],
|
| 247 |
+
options={
|
| 248 |
+
"verbose_name": "schedule history",
|
| 249 |
+
"verbose_name_plural": "schedule history entries",
|
| 250 |
+
"db_table": "schedule_history",
|
| 251 |
+
"ordering": ["-created_at"],
|
| 252 |
+
},
|
| 253 |
+
),
|
| 254 |
+
]
|
core/migrations/0003_add_package_operation.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.9 on 2026-01-20 07:30
|
| 2 |
+
|
| 3 |
+
import django.db.models.deletion
|
| 4 |
+
import uuid
|
| 5 |
+
from django.conf import settings
|
| 6 |
+
from django.db import migrations, models
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class Migration(migrations.Migration):
|
| 10 |
+
|
| 11 |
+
dependencies = [
|
| 12 |
+
("core", "0002_add_scheduling_models"),
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
operations = [
|
| 16 |
+
migrations.CreateModel(
|
| 17 |
+
name="PackageOperation",
|
| 18 |
+
fields=[
|
| 19 |
+
(
|
| 20 |
+
"id",
|
| 21 |
+
models.UUIDField(
|
| 22 |
+
default=uuid.uuid4,
|
| 23 |
+
editable=False,
|
| 24 |
+
primary_key=True,
|
| 25 |
+
serialize=False,
|
| 26 |
+
),
|
| 27 |
+
),
|
| 28 |
+
(
|
| 29 |
+
"operation",
|
| 30 |
+
models.CharField(
|
| 31 |
+
choices=[
|
| 32 |
+
("install", "Install"),
|
| 33 |
+
("uninstall", "Uninstall"),
|
| 34 |
+
("bulk_install", "Bulk Install"),
|
| 35 |
+
],
|
| 36 |
+
max_length=20,
|
| 37 |
+
),
|
| 38 |
+
),
|
| 39 |
+
(
|
| 40 |
+
"package_spec",
|
| 41 |
+
models.CharField(
|
| 42 |
+
help_text="Package specification (e.g., 'requests==2.31.0') or requirements content for bulk install",
|
| 43 |
+
max_length=500,
|
| 44 |
+
),
|
| 45 |
+
),
|
| 46 |
+
(
|
| 47 |
+
"status",
|
| 48 |
+
models.CharField(
|
| 49 |
+
choices=[
|
| 50 |
+
("pending", "Pending"),
|
| 51 |
+
("running", "Running"),
|
| 52 |
+
("success", "Success"),
|
| 53 |
+
("failed", "Failed"),
|
| 54 |
+
],
|
| 55 |
+
default="pending",
|
| 56 |
+
max_length=20,
|
| 57 |
+
),
|
| 58 |
+
),
|
| 59 |
+
("output", models.TextField(blank=True, help_text="pip stdout")),
|
| 60 |
+
("error", models.TextField(blank=True, help_text="pip stderr")),
|
| 61 |
+
(
|
| 62 |
+
"task_id",
|
| 63 |
+
models.CharField(
|
| 64 |
+
blank=True,
|
| 65 |
+
help_text="django-q2 task ID for tracking",
|
| 66 |
+
max_length=100,
|
| 67 |
+
),
|
| 68 |
+
),
|
| 69 |
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
| 70 |
+
("started_at", models.DateTimeField(blank=True, null=True)),
|
| 71 |
+
("completed_at", models.DateTimeField(blank=True, null=True)),
|
| 72 |
+
(
|
| 73 |
+
"created_by",
|
| 74 |
+
models.ForeignKey(
|
| 75 |
+
blank=True,
|
| 76 |
+
null=True,
|
| 77 |
+
on_delete=django.db.models.deletion.SET_NULL,
|
| 78 |
+
related_name="package_operations",
|
| 79 |
+
to=settings.AUTH_USER_MODEL,
|
| 80 |
+
),
|
| 81 |
+
),
|
| 82 |
+
(
|
| 83 |
+
"environment",
|
| 84 |
+
models.ForeignKey(
|
| 85 |
+
on_delete=django.db.models.deletion.CASCADE,
|
| 86 |
+
related_name="package_operations",
|
| 87 |
+
to="core.environment",
|
| 88 |
+
),
|
| 89 |
+
),
|
| 90 |
+
],
|
| 91 |
+
options={
|
| 92 |
+
"verbose_name": "package operation",
|
| 93 |
+
"verbose_name_plural": "package operations",
|
| 94 |
+
"db_table": "package_operations",
|
| 95 |
+
"ordering": ["-created_at"],
|
| 96 |
+
},
|
| 97 |
+
),
|
| 98 |
+
]
|
core/migrations/0004_secret.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.9 on 2026-01-20 08:26
|
| 2 |
+
|
| 3 |
+
import django.db.models.deletion
|
| 4 |
+
import uuid
|
| 5 |
+
from django.conf import settings
|
| 6 |
+
from django.db import migrations, models
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class Migration(migrations.Migration):
|
| 10 |
+
|
| 11 |
+
dependencies = [
|
| 12 |
+
("core", "0003_add_package_operation"),
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
operations = [
|
| 16 |
+
migrations.CreateModel(
|
| 17 |
+
name="Secret",
|
| 18 |
+
fields=[
|
| 19 |
+
(
|
| 20 |
+
"id",
|
| 21 |
+
models.UUIDField(
|
| 22 |
+
default=uuid.uuid4,
|
| 23 |
+
editable=False,
|
| 24 |
+
primary_key=True,
|
| 25 |
+
serialize=False,
|
| 26 |
+
),
|
| 27 |
+
),
|
| 28 |
+
(
|
| 29 |
+
"key",
|
| 30 |
+
models.CharField(
|
| 31 |
+
help_text="Environment variable name (uppercase, underscores allowed)",
|
| 32 |
+
max_length=100,
|
| 33 |
+
unique=True,
|
| 34 |
+
),
|
| 35 |
+
),
|
| 36 |
+
(
|
| 37 |
+
"encrypted_value",
|
| 38 |
+
models.TextField(help_text="Fernet-encrypted secret value"),
|
| 39 |
+
),
|
| 40 |
+
(
|
| 41 |
+
"description",
|
| 42 |
+
models.TextField(
|
| 43 |
+
blank=True,
|
| 44 |
+
help_text="Optional description of what this secret is used for",
|
| 45 |
+
),
|
| 46 |
+
),
|
| 47 |
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
| 48 |
+
("updated_at", models.DateTimeField(auto_now=True)),
|
| 49 |
+
(
|
| 50 |
+
"created_by",
|
| 51 |
+
models.ForeignKey(
|
| 52 |
+
blank=True,
|
| 53 |
+
null=True,
|
| 54 |
+
on_delete=django.db.models.deletion.SET_NULL,
|
| 55 |
+
related_name="created_secrets",
|
| 56 |
+
to=settings.AUTH_USER_MODEL,
|
| 57 |
+
),
|
| 58 |
+
),
|
| 59 |
+
],
|
| 60 |
+
options={
|
| 61 |
+
"verbose_name": "secret",
|
| 62 |
+
"verbose_name_plural": "secrets",
|
| 63 |
+
"db_table": "secrets",
|
| 64 |
+
"ordering": ["key"],
|
| 65 |
+
},
|
| 66 |
+
),
|
| 67 |
+
]
|
core/migrations/0005_add_webhook_token.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.9 on 2026-01-20 14:08
|
| 2 |
+
|
| 3 |
+
from django.db import migrations, models
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Migration(migrations.Migration):
|
| 7 |
+
|
| 8 |
+
dependencies = [
|
| 9 |
+
("core", "0004_secret"),
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
operations = [
|
| 13 |
+
migrations.AddField(
|
| 14 |
+
model_name="script",
|
| 15 |
+
name="webhook_token",
|
| 16 |
+
field=models.CharField(
|
| 17 |
+
blank=True,
|
| 18 |
+
db_index=True,
|
| 19 |
+
help_text="Unique token for webhook URL (auto-generated)",
|
| 20 |
+
max_length=64,
|
| 21 |
+
null=True,
|
| 22 |
+
unique=True,
|
| 23 |
+
),
|
| 24 |
+
),
|
| 25 |
+
]
|
core/migrations/0006_notification_settings.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.9 on 2026-01-20 14:33
|
| 2 |
+
|
| 3 |
+
from django.db import migrations, models
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Migration(migrations.Migration):
|
| 7 |
+
|
| 8 |
+
dependencies = [
|
| 9 |
+
("core", "0005_add_webhook_token"),
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
operations = [
|
| 13 |
+
migrations.AddField(
|
| 14 |
+
model_name="globalsettings",
|
| 15 |
+
name="default_notification_email",
|
| 16 |
+
field=models.EmailField(
|
| 17 |
+
blank=True,
|
| 18 |
+
help_text="Default email address for all notifications",
|
| 19 |
+
max_length=254,
|
| 20 |
+
),
|
| 21 |
+
),
|
| 22 |
+
migrations.AddField(
|
| 23 |
+
model_name="globalsettings",
|
| 24 |
+
name="email_backend",
|
| 25 |
+
field=models.CharField(
|
| 26 |
+
choices=[
|
| 27 |
+
("disabled", "Disabled"),
|
| 28 |
+
("smtp", "SMTP"),
|
| 29 |
+
("resend", "Resend API"),
|
| 30 |
+
],
|
| 31 |
+
default="disabled",
|
| 32 |
+
help_text="Email backend for notifications",
|
| 33 |
+
max_length=20,
|
| 34 |
+
),
|
| 35 |
+
),
|
| 36 |
+
migrations.AddField(
|
| 37 |
+
model_name="globalsettings",
|
| 38 |
+
name="resend_api_key_encrypted",
|
| 39 |
+
field=models.TextField(blank=True, help_text="Resend API key (encrypted)"),
|
| 40 |
+
),
|
| 41 |
+
migrations.AddField(
|
| 42 |
+
model_name="globalsettings",
|
| 43 |
+
name="resend_from_email",
|
| 44 |
+
field=models.EmailField(
|
| 45 |
+
blank=True, help_text="From email address for Resend", max_length=254
|
| 46 |
+
),
|
| 47 |
+
),
|
| 48 |
+
migrations.AddField(
|
| 49 |
+
model_name="globalsettings",
|
| 50 |
+
name="smtp_from_email",
|
| 51 |
+
field=models.EmailField(
|
| 52 |
+
blank=True, help_text="From email address for SMTP", max_length=254
|
| 53 |
+
),
|
| 54 |
+
),
|
| 55 |
+
migrations.AddField(
|
| 56 |
+
model_name="globalsettings",
|
| 57 |
+
name="smtp_host",
|
| 58 |
+
field=models.CharField(
|
| 59 |
+
blank=True, help_text="SMTP server hostname", max_length=255
|
| 60 |
+
),
|
| 61 |
+
),
|
| 62 |
+
migrations.AddField(
|
| 63 |
+
model_name="globalsettings",
|
| 64 |
+
name="smtp_password_encrypted",
|
| 65 |
+
field=models.TextField(blank=True, help_text="SMTP password (encrypted)"),
|
| 66 |
+
),
|
| 67 |
+
migrations.AddField(
|
| 68 |
+
model_name="globalsettings",
|
| 69 |
+
name="smtp_port",
|
| 70 |
+
field=models.PositiveIntegerField(
|
| 71 |
+
default=587, help_text="SMTP server port"
|
| 72 |
+
),
|
| 73 |
+
),
|
| 74 |
+
migrations.AddField(
|
| 75 |
+
model_name="globalsettings",
|
| 76 |
+
name="smtp_use_tls",
|
| 77 |
+
field=models.BooleanField(
|
| 78 |
+
default=True, help_text="Use TLS for SMTP connection"
|
| 79 |
+
),
|
| 80 |
+
),
|
| 81 |
+
migrations.AddField(
|
| 82 |
+
model_name="globalsettings",
|
| 83 |
+
name="smtp_username",
|
| 84 |
+
field=models.CharField(
|
| 85 |
+
blank=True, help_text="SMTP username", max_length=255
|
| 86 |
+
),
|
| 87 |
+
),
|
| 88 |
+
migrations.AddField(
|
| 89 |
+
model_name="script",
|
| 90 |
+
name="notify_email",
|
| 91 |
+
field=models.EmailField(
|
| 92 |
+
blank=True,
|
| 93 |
+
help_text="Override email for this script (uses global default if empty)",
|
| 94 |
+
max_length=254,
|
| 95 |
+
),
|
| 96 |
+
),
|
| 97 |
+
migrations.AddField(
|
| 98 |
+
model_name="script",
|
| 99 |
+
name="notify_on",
|
| 100 |
+
field=models.CharField(
|
| 101 |
+
choices=[
|
| 102 |
+
("never", "Never"),
|
| 103 |
+
("failure", "On Failure"),
|
| 104 |
+
("success", "On Success"),
|
| 105 |
+
("both", "On Success and Failure"),
|
| 106 |
+
],
|
| 107 |
+
default="never",
|
| 108 |
+
help_text="When to send notifications for this script",
|
| 109 |
+
max_length=20,
|
| 110 |
+
),
|
| 111 |
+
),
|
| 112 |
+
migrations.AddField(
|
| 113 |
+
model_name="script",
|
| 114 |
+
name="notify_webhook_enabled",
|
| 115 |
+
field=models.BooleanField(
|
| 116 |
+
default=False, help_text="Enable webhook notifications for this script"
|
| 117 |
+
),
|
| 118 |
+
),
|
| 119 |
+
migrations.AddField(
|
| 120 |
+
model_name="script",
|
| 121 |
+
name="notify_webhook_url",
|
| 122 |
+
field=models.URLField(
|
| 123 |
+
blank=True,
|
| 124 |
+
help_text="URL to POST notification webhooks to",
|
| 125 |
+
max_length=500,
|
| 126 |
+
),
|
| 127 |
+
),
|
| 128 |
+
]
|
core/migrations/0007_phase7_settings.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.9 on 2026-01-21 07:30
|
| 2 |
+
|
| 3 |
+
from django.db import migrations, models
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Migration(migrations.Migration):
|
| 7 |
+
|
| 8 |
+
dependencies = [
|
| 9 |
+
("core", "0006_notification_settings"),
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
operations = [
|
| 13 |
+
migrations.AddField(
|
| 14 |
+
model_name="globalsettings",
|
| 15 |
+
name="auto_cleanup_enabled",
|
| 16 |
+
field=models.BooleanField(
|
| 17 |
+
default=False, help_text="Automatically clean up old runs daily"
|
| 18 |
+
),
|
| 19 |
+
),
|
| 20 |
+
migrations.AddField(
|
| 21 |
+
model_name="globalsettings",
|
| 22 |
+
name="date_format",
|
| 23 |
+
field=models.CharField(
|
| 24 |
+
choices=[
|
| 25 |
+
("YYYY-MM-DD", "YYYY-MM-DD (ISO)"),
|
| 26 |
+
("MM/DD/YYYY", "MM/DD/YYYY (US)"),
|
| 27 |
+
("DD/MM/YYYY", "DD/MM/YYYY (EU)"),
|
| 28 |
+
("DD.MM.YYYY", "DD.MM.YYYY"),
|
| 29 |
+
],
|
| 30 |
+
default="YYYY-MM-DD",
|
| 31 |
+
help_text="Date display format",
|
| 32 |
+
max_length=20,
|
| 33 |
+
),
|
| 34 |
+
),
|
| 35 |
+
migrations.AddField(
|
| 36 |
+
model_name="globalsettings",
|
| 37 |
+
name="instance_name",
|
| 38 |
+
field=models.CharField(
|
| 39 |
+
blank=True,
|
| 40 |
+
default="PyRunner",
|
| 41 |
+
help_text="Instance name displayed in header and emails",
|
| 42 |
+
max_length=100,
|
| 43 |
+
),
|
| 44 |
+
),
|
| 45 |
+
migrations.AddField(
|
| 46 |
+
model_name="globalsettings",
|
| 47 |
+
name="last_cleanup_at",
|
| 48 |
+
field=models.DateTimeField(
|
| 49 |
+
blank=True, help_text="When the last cleanup was performed", null=True
|
| 50 |
+
),
|
| 51 |
+
),
|
| 52 |
+
migrations.AddField(
|
| 53 |
+
model_name="globalsettings",
|
| 54 |
+
name="retention_count",
|
| 55 |
+
field=models.PositiveIntegerField(
|
| 56 |
+
default=0, help_text="Keep last X runs per script (0 = unlimited)"
|
| 57 |
+
),
|
| 58 |
+
),
|
| 59 |
+
migrations.AddField(
|
| 60 |
+
model_name="globalsettings",
|
| 61 |
+
name="retention_days",
|
| 62 |
+
field=models.PositiveIntegerField(
|
| 63 |
+
default=0, help_text="Delete runs older than X days (0 = keep forever)"
|
| 64 |
+
),
|
| 65 |
+
),
|
| 66 |
+
migrations.AddField(
|
| 67 |
+
model_name="globalsettings",
|
| 68 |
+
name="time_format",
|
| 69 |
+
field=models.CharField(
|
| 70 |
+
choices=[("24h", "24-hour"), ("12h", "12-hour")],
|
| 71 |
+
default="24h",
|
| 72 |
+
help_text="Time display format",
|
| 73 |
+
max_length=10,
|
| 74 |
+
),
|
| 75 |
+
),
|
| 76 |
+
migrations.AddField(
|
| 77 |
+
model_name="globalsettings",
|
| 78 |
+
name="timezone",
|
| 79 |
+
field=models.CharField(
|
| 80 |
+
default="UTC",
|
| 81 |
+
help_text="Default timezone for the instance",
|
| 82 |
+
max_length=50,
|
| 83 |
+
),
|
| 84 |
+
),
|
| 85 |
+
migrations.AddField(
|
| 86 |
+
model_name="script",
|
| 87 |
+
name="retention_count_override",
|
| 88 |
+
field=models.PositiveIntegerField(
|
| 89 |
+
blank=True,
|
| 90 |
+
help_text="Override global retention count for this script",
|
| 91 |
+
null=True,
|
| 92 |
+
),
|
| 93 |
+
),
|
| 94 |
+
migrations.AddField(
|
| 95 |
+
model_name="script",
|
| 96 |
+
name="retention_days_override",
|
| 97 |
+
field=models.PositiveIntegerField(
|
| 98 |
+
blank=True,
|
| 99 |
+
help_text="Override global retention days for this script",
|
| 100 |
+
null=True,
|
| 101 |
+
),
|
| 102 |
+
),
|
| 103 |
+
]
|
core/migrations/0008_worker_heartbeat.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.9 on 2026-01-21 08:02
|
| 2 |
+
|
| 3 |
+
from django.db import migrations, models
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Migration(migrations.Migration):
|
| 7 |
+
|
| 8 |
+
dependencies = [
|
| 9 |
+
("core", "0007_phase7_settings"),
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
operations = [
|
| 13 |
+
migrations.AddField(
|
| 14 |
+
model_name="globalsettings",
|
| 15 |
+
name="worker_heartbeat_at",
|
| 16 |
+
field=models.DateTimeField(
|
| 17 |
+
blank=True, help_text="Last heartbeat from django-q workers", null=True
|
| 18 |
+
),
|
| 19 |
+
),
|
| 20 |
+
]
|
core/migrations/0009_script_archive.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.9 on 2026-01-24 08:53
|
| 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 |
+
dependencies = [
|
| 11 |
+
("core", "0008_worker_heartbeat"),
|
| 12 |
+
]
|
| 13 |
+
|
| 14 |
+
operations = [
|
| 15 |
+
migrations.AddField(
|
| 16 |
+
model_name="script",
|
| 17 |
+
name="archived_at",
|
| 18 |
+
field=models.DateTimeField(
|
| 19 |
+
blank=True,
|
| 20 |
+
help_text="When this script was archived (null = not archived)",
|
| 21 |
+
null=True,
|
| 22 |
+
),
|
| 23 |
+
),
|
| 24 |
+
migrations.AddField(
|
| 25 |
+
model_name="script",
|
| 26 |
+
name="archived_by",
|
| 27 |
+
field=models.ForeignKey(
|
| 28 |
+
blank=True,
|
| 29 |
+
null=True,
|
| 30 |
+
on_delete=django.db.models.deletion.SET_NULL,
|
| 31 |
+
related_name="archived_scripts",
|
| 32 |
+
to=settings.AUTH_USER_MODEL,
|
| 33 |
+
),
|
| 34 |
+
),
|
| 35 |
+
]
|
core/migrations/0010_globalsettings_setup_completed.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.9 on 2026-01-24 10:46
|
| 2 |
+
|
| 3 |
+
from django.db import migrations, models
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Migration(migrations.Migration):
|
| 7 |
+
|
| 8 |
+
dependencies = [
|
| 9 |
+
("core", "0009_script_archive"),
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
operations = [
|
| 13 |
+
migrations.AddField(
|
| 14 |
+
model_name="globalsettings",
|
| 15 |
+
name="setup_completed",
|
| 16 |
+
field=models.BooleanField(
|
| 17 |
+
default=False, help_text="Whether initial setup has been completed"
|
| 18 |
+
),
|
| 19 |
+
),
|
| 20 |
+
migrations.AddField(
|
| 21 |
+
model_name="globalsettings",
|
| 22 |
+
name="setup_completed_at",
|
| 23 |
+
field=models.DateTimeField(
|
| 24 |
+
blank=True, help_text="When the initial setup was completed", null=True
|
| 25 |
+
),
|
| 26 |
+
),
|
| 27 |
+
]
|
core/migrations/0011_registration_and_invites.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated migration for registration control and user invites
|
| 2 |
+
|
| 3 |
+
from django.conf import settings
|
| 4 |
+
from django.db import migrations, models
|
| 5 |
+
import django.db.models.deletion
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class Migration(migrations.Migration):
|
| 9 |
+
|
| 10 |
+
dependencies = [
|
| 11 |
+
("core", "0010_globalsettings_setup_completed"),
|
| 12 |
+
]
|
| 13 |
+
|
| 14 |
+
operations = [
|
| 15 |
+
# Add allow_registration to GlobalSettings
|
| 16 |
+
migrations.AddField(
|
| 17 |
+
model_name="globalsettings",
|
| 18 |
+
name="allow_registration",
|
| 19 |
+
field=models.BooleanField(
|
| 20 |
+
default=True,
|
| 21 |
+
help_text="Allow new users to register without an invite (auto-disabled after first user)",
|
| 22 |
+
),
|
| 23 |
+
),
|
| 24 |
+
# Create UserInvite model
|
| 25 |
+
migrations.CreateModel(
|
| 26 |
+
name="UserInvite",
|
| 27 |
+
fields=[
|
| 28 |
+
(
|
| 29 |
+
"id",
|
| 30 |
+
models.BigAutoField(
|
| 31 |
+
auto_created=True,
|
| 32 |
+
primary_key=True,
|
| 33 |
+
serialize=False,
|
| 34 |
+
verbose_name="ID",
|
| 35 |
+
),
|
| 36 |
+
),
|
| 37 |
+
(
|
| 38 |
+
"email",
|
| 39 |
+
models.EmailField(
|
| 40 |
+
help_text="Email address of the invited user",
|
| 41 |
+
max_length=254,
|
| 42 |
+
unique=True,
|
| 43 |
+
),
|
| 44 |
+
),
|
| 45 |
+
(
|
| 46 |
+
"token",
|
| 47 |
+
models.CharField(db_index=True, max_length=64, unique=True),
|
| 48 |
+
),
|
| 49 |
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
| 50 |
+
("expires_at", models.DateTimeField()),
|
| 51 |
+
("used_at", models.DateTimeField(blank=True, null=True)),
|
| 52 |
+
(
|
| 53 |
+
"created_by",
|
| 54 |
+
models.ForeignKey(
|
| 55 |
+
on_delete=django.db.models.deletion.CASCADE,
|
| 56 |
+
related_name="sent_invites",
|
| 57 |
+
to=settings.AUTH_USER_MODEL,
|
| 58 |
+
),
|
| 59 |
+
),
|
| 60 |
+
(
|
| 61 |
+
"used_by",
|
| 62 |
+
models.ForeignKey(
|
| 63 |
+
blank=True,
|
| 64 |
+
null=True,
|
| 65 |
+
on_delete=django.db.models.deletion.SET_NULL,
|
| 66 |
+
related_name="received_invite",
|
| 67 |
+
to=settings.AUTH_USER_MODEL,
|
| 68 |
+
),
|
| 69 |
+
),
|
| 70 |
+
],
|
| 71 |
+
options={
|
| 72 |
+
"verbose_name": "user invite",
|
| 73 |
+
"verbose_name_plural": "user invites",
|
| 74 |
+
"db_table": "user_invites",
|
| 75 |
+
},
|
| 76 |
+
),
|
| 77 |
+
migrations.AddIndex(
|
| 78 |
+
model_name="userinvite",
|
| 79 |
+
index=models.Index(fields=["token"], name="user_invite_token_idx"),
|
| 80 |
+
),
|
| 81 |
+
migrations.AddIndex(
|
| 82 |
+
model_name="userinvite",
|
| 83 |
+
index=models.Index(fields=["email"], name="user_invite_email_idx"),
|
| 84 |
+
),
|
| 85 |
+
]
|
core/migrations/0012_password_reset_token.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.9 on 2026-01-24 12:17
|
| 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 |
+
dependencies = [
|
| 11 |
+
("core", "0011_registration_and_invites"),
|
| 12 |
+
]
|
| 13 |
+
|
| 14 |
+
operations = [
|
| 15 |
+
migrations.CreateModel(
|
| 16 |
+
name="PasswordResetToken",
|
| 17 |
+
fields=[
|
| 18 |
+
(
|
| 19 |
+
"id",
|
| 20 |
+
models.BigAutoField(
|
| 21 |
+
auto_created=True,
|
| 22 |
+
primary_key=True,
|
| 23 |
+
serialize=False,
|
| 24 |
+
verbose_name="ID",
|
| 25 |
+
),
|
| 26 |
+
),
|
| 27 |
+
("token", models.CharField(db_index=True, max_length=64, unique=True)),
|
| 28 |
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
| 29 |
+
("expires_at", models.DateTimeField()),
|
| 30 |
+
("used_at", models.DateTimeField(blank=True, null=True)),
|
| 31 |
+
],
|
| 32 |
+
options={
|
| 33 |
+
"verbose_name": "password reset token",
|
| 34 |
+
"verbose_name_plural": "password reset tokens",
|
| 35 |
+
"db_table": "password_reset_tokens",
|
| 36 |
+
},
|
| 37 |
+
),
|
| 38 |
+
migrations.RenameIndex(
|
| 39 |
+
model_name="userinvite",
|
| 40 |
+
new_name="user_invite_token_e617ee_idx",
|
| 41 |
+
old_name="user_invite_token_idx",
|
| 42 |
+
),
|
| 43 |
+
migrations.RenameIndex(
|
| 44 |
+
model_name="userinvite",
|
| 45 |
+
new_name="user_invite_email_cf6347_idx",
|
| 46 |
+
old_name="user_invite_email_idx",
|
| 47 |
+
),
|
| 48 |
+
migrations.AddField(
|
| 49 |
+
model_name="passwordresettoken",
|
| 50 |
+
name="user",
|
| 51 |
+
field=models.ForeignKey(
|
| 52 |
+
on_delete=django.db.models.deletion.CASCADE,
|
| 53 |
+
related_name="password_reset_tokens",
|
| 54 |
+
to=settings.AUTH_USER_MODEL,
|
| 55 |
+
),
|
| 56 |
+
),
|
| 57 |
+
migrations.AddIndex(
|
| 58 |
+
model_name="passwordresettoken",
|
| 59 |
+
index=models.Index(
|
| 60 |
+
fields=["token", "expires_at"], name="password_re_token_9adc88_idx"
|
| 61 |
+
),
|
| 62 |
+
),
|
| 63 |
+
]
|
core/migrations/0013_add_tags.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.9 on 2026-01-24 13:07
|
| 2 |
+
|
| 3 |
+
import django.db.models.deletion
|
| 4 |
+
import uuid
|
| 5 |
+
from django.conf import settings
|
| 6 |
+
from django.db import migrations, models
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class Migration(migrations.Migration):
|
| 10 |
+
|
| 11 |
+
dependencies = [
|
| 12 |
+
("core", "0012_password_reset_token"),
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
operations = [
|
| 16 |
+
migrations.CreateModel(
|
| 17 |
+
name="Tag",
|
| 18 |
+
fields=[
|
| 19 |
+
(
|
| 20 |
+
"id",
|
| 21 |
+
models.UUIDField(
|
| 22 |
+
default=uuid.uuid4,
|
| 23 |
+
editable=False,
|
| 24 |
+
primary_key=True,
|
| 25 |
+
serialize=False,
|
| 26 |
+
),
|
| 27 |
+
),
|
| 28 |
+
(
|
| 29 |
+
"name",
|
| 30 |
+
models.CharField(
|
| 31 |
+
help_text="Tag name (must be unique)",
|
| 32 |
+
max_length=50,
|
| 33 |
+
unique=True,
|
| 34 |
+
),
|
| 35 |
+
),
|
| 36 |
+
(
|
| 37 |
+
"color",
|
| 38 |
+
models.CharField(
|
| 39 |
+
choices=[
|
| 40 |
+
("gray", "Gray"),
|
| 41 |
+
("red", "Red"),
|
| 42 |
+
("orange", "Orange"),
|
| 43 |
+
("yellow", "Yellow"),
|
| 44 |
+
("green", "Green"),
|
| 45 |
+
("blue", "Blue"),
|
| 46 |
+
("purple", "Purple"),
|
| 47 |
+
("pink", "Pink"),
|
| 48 |
+
],
|
| 49 |
+
default="gray",
|
| 50 |
+
help_text="Tag color for visual distinction",
|
| 51 |
+
max_length=20,
|
| 52 |
+
),
|
| 53 |
+
),
|
| 54 |
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
| 55 |
+
(
|
| 56 |
+
"created_by",
|
| 57 |
+
models.ForeignKey(
|
| 58 |
+
blank=True,
|
| 59 |
+
null=True,
|
| 60 |
+
on_delete=django.db.models.deletion.SET_NULL,
|
| 61 |
+
related_name="created_tags",
|
| 62 |
+
to=settings.AUTH_USER_MODEL,
|
| 63 |
+
),
|
| 64 |
+
),
|
| 65 |
+
],
|
| 66 |
+
options={
|
| 67 |
+
"verbose_name": "tag",
|
| 68 |
+
"verbose_name_plural": "tags",
|
| 69 |
+
"db_table": "tags",
|
| 70 |
+
"ordering": ["name"],
|
| 71 |
+
},
|
| 72 |
+
),
|
| 73 |
+
migrations.AddField(
|
| 74 |
+
model_name="script",
|
| 75 |
+
name="tags",
|
| 76 |
+
field=models.ManyToManyField(
|
| 77 |
+
blank=True,
|
| 78 |
+
help_text="Tags for organizing and filtering scripts",
|
| 79 |
+
related_name="scripts",
|
| 80 |
+
to="core.tag",
|
| 81 |
+
),
|
| 82 |
+
),
|
| 83 |
+
]
|
core/migrations/0014_worker_settings.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.9 on 2026-01-24 14:37
|
| 2 |
+
|
| 3 |
+
from django.db import migrations, models
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Migration(migrations.Migration):
|
| 7 |
+
|
| 8 |
+
dependencies = [
|
| 9 |
+
("core", "0013_add_tags"),
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
operations = [
|
| 13 |
+
migrations.AddField(
|
| 14 |
+
model_name="globalsettings",
|
| 15 |
+
name="q_queue_limit",
|
| 16 |
+
field=models.PositiveIntegerField(
|
| 17 |
+
default=20, help_text="Maximum number of tasks in the queue"
|
| 18 |
+
),
|
| 19 |
+
),
|
| 20 |
+
migrations.AddField(
|
| 21 |
+
model_name="globalsettings",
|
| 22 |
+
name="q_retry",
|
| 23 |
+
field=models.PositiveIntegerField(
|
| 24 |
+
default=660, help_text="Seconds before a task is retried after timeout"
|
| 25 |
+
),
|
| 26 |
+
),
|
| 27 |
+
migrations.AddField(
|
| 28 |
+
model_name="globalsettings",
|
| 29 |
+
name="q_timeout",
|
| 30 |
+
field=models.PositiveIntegerField(
|
| 31 |
+
default=600, help_text="Task timeout in seconds (0 for no timeout)"
|
| 32 |
+
),
|
| 33 |
+
),
|
| 34 |
+
migrations.AddField(
|
| 35 |
+
model_name="globalsettings",
|
| 36 |
+
name="q_workers",
|
| 37 |
+
field=models.PositiveIntegerField(
|
| 38 |
+
default=2, help_text="Number of worker processes for task queue"
|
| 39 |
+
),
|
| 40 |
+
),
|
| 41 |
+
migrations.AddField(
|
| 42 |
+
model_name="globalsettings",
|
| 43 |
+
name="worker_settings_updated_at",
|
| 44 |
+
field=models.DateTimeField(
|
| 45 |
+
blank=True,
|
| 46 |
+
help_text="When worker settings were last updated (requires restart)",
|
| 47 |
+
null=True,
|
| 48 |
+
),
|
| 49 |
+
),
|
| 50 |
+
]
|
core/migrations/0015_datastore.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.9 on 2026-01-24 17:54
|
| 2 |
+
|
| 3 |
+
import django.db.models.deletion
|
| 4 |
+
import uuid
|
| 5 |
+
from django.conf import settings
|
| 6 |
+
from django.db import migrations, models
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class Migration(migrations.Migration):
|
| 10 |
+
|
| 11 |
+
dependencies = [
|
| 12 |
+
("core", "0014_worker_settings"),
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
operations = [
|
| 16 |
+
migrations.CreateModel(
|
| 17 |
+
name="DataStore",
|
| 18 |
+
fields=[
|
| 19 |
+
(
|
| 20 |
+
"id",
|
| 21 |
+
models.UUIDField(
|
| 22 |
+
default=uuid.uuid4,
|
| 23 |
+
editable=False,
|
| 24 |
+
primary_key=True,
|
| 25 |
+
serialize=False,
|
| 26 |
+
),
|
| 27 |
+
),
|
| 28 |
+
(
|
| 29 |
+
"name",
|
| 30 |
+
models.CharField(
|
| 31 |
+
help_text="Unique name for this data store (used in scripts)",
|
| 32 |
+
max_length=100,
|
| 33 |
+
unique=True,
|
| 34 |
+
),
|
| 35 |
+
),
|
| 36 |
+
(
|
| 37 |
+
"description",
|
| 38 |
+
models.TextField(
|
| 39 |
+
blank=True,
|
| 40 |
+
help_text="Optional description of what this data store is used for",
|
| 41 |
+
),
|
| 42 |
+
),
|
| 43 |
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
| 44 |
+
("updated_at", models.DateTimeField(auto_now=True)),
|
| 45 |
+
(
|
| 46 |
+
"created_by",
|
| 47 |
+
models.ForeignKey(
|
| 48 |
+
blank=True,
|
| 49 |
+
null=True,
|
| 50 |
+
on_delete=django.db.models.deletion.SET_NULL,
|
| 51 |
+
related_name="created_datastores",
|
| 52 |
+
to=settings.AUTH_USER_MODEL,
|
| 53 |
+
),
|
| 54 |
+
),
|
| 55 |
+
],
|
| 56 |
+
options={
|
| 57 |
+
"verbose_name": "data store",
|
| 58 |
+
"verbose_name_plural": "data stores",
|
| 59 |
+
"db_table": "datastores",
|
| 60 |
+
"ordering": ["name"],
|
| 61 |
+
},
|
| 62 |
+
),
|
| 63 |
+
migrations.CreateModel(
|
| 64 |
+
name="DataStoreEntry",
|
| 65 |
+
fields=[
|
| 66 |
+
(
|
| 67 |
+
"id",
|
| 68 |
+
models.UUIDField(
|
| 69 |
+
default=uuid.uuid4,
|
| 70 |
+
editable=False,
|
| 71 |
+
primary_key=True,
|
| 72 |
+
serialize=False,
|
| 73 |
+
),
|
| 74 |
+
),
|
| 75 |
+
(
|
| 76 |
+
"key",
|
| 77 |
+
models.CharField(
|
| 78 |
+
help_text="Unique key within this data store", max_length=255
|
| 79 |
+
),
|
| 80 |
+
),
|
| 81 |
+
("value_json", models.TextField(help_text="JSON-encoded value")),
|
| 82 |
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
| 83 |
+
("updated_at", models.DateTimeField(auto_now=True)),
|
| 84 |
+
(
|
| 85 |
+
"datastore",
|
| 86 |
+
models.ForeignKey(
|
| 87 |
+
on_delete=django.db.models.deletion.CASCADE,
|
| 88 |
+
related_name="entries",
|
| 89 |
+
to="core.datastore",
|
| 90 |
+
),
|
| 91 |
+
),
|
| 92 |
+
],
|
| 93 |
+
options={
|
| 94 |
+
"verbose_name": "data store entry",
|
| 95 |
+
"verbose_name_plural": "data store entries",
|
| 96 |
+
"db_table": "datastore_entries",
|
| 97 |
+
"ordering": ["key"],
|
| 98 |
+
"indexes": [
|
| 99 |
+
models.Index(
|
| 100 |
+
fields=["datastore", "key"],
|
| 101 |
+
name="datastore_e_datasto_6e5232_idx",
|
| 102 |
+
)
|
| 103 |
+
],
|
| 104 |
+
"unique_together": {("datastore", "key")},
|
| 105 |
+
},
|
| 106 |
+
),
|
| 107 |
+
]
|
core/migrations/0016_globalsettings_admin_url_slug.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.db import migrations, models
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class Migration(migrations.Migration):
|
| 5 |
+
dependencies = [
|
| 6 |
+
('core', '0015_datastore'),
|
| 7 |
+
]
|
| 8 |
+
|
| 9 |
+
operations = [
|
| 10 |
+
migrations.AddField(
|
| 11 |
+
model_name='globalsettings',
|
| 12 |
+
name='admin_url_slug',
|
| 13 |
+
field=models.CharField(
|
| 14 |
+
default='django-admin',
|
| 15 |
+
help_text='URL path for Django admin interface (requires restart)',
|
| 16 |
+
max_length=100,
|
| 17 |
+
),
|
| 18 |
+
),
|
| 19 |
+
]
|
core/migrations/0017_datastore_api_token.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated manually for DataStore API Token
|
| 2 |
+
|
| 3 |
+
import django.db.models.deletion
|
| 4 |
+
import uuid
|
| 5 |
+
from django.conf import settings
|
| 6 |
+
from django.db import migrations, models
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class Migration(migrations.Migration):
|
| 10 |
+
|
| 11 |
+
dependencies = [
|
| 12 |
+
("core", "0016_globalsettings_admin_url_slug"),
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
operations = [
|
| 16 |
+
migrations.CreateModel(
|
| 17 |
+
name="DataStoreAPIToken",
|
| 18 |
+
fields=[
|
| 19 |
+
(
|
| 20 |
+
"id",
|
| 21 |
+
models.UUIDField(
|
| 22 |
+
default=uuid.uuid4,
|
| 23 |
+
editable=False,
|
| 24 |
+
primary_key=True,
|
| 25 |
+
serialize=False,
|
| 26 |
+
),
|
| 27 |
+
),
|
| 28 |
+
(
|
| 29 |
+
"token",
|
| 30 |
+
models.CharField(
|
| 31 |
+
db_index=True,
|
| 32 |
+
help_text="API token value (auto-generated)",
|
| 33 |
+
max_length=64,
|
| 34 |
+
unique=True,
|
| 35 |
+
),
|
| 36 |
+
),
|
| 37 |
+
(
|
| 38 |
+
"name",
|
| 39 |
+
models.CharField(
|
| 40 |
+
help_text="Friendly name for this token",
|
| 41 |
+
max_length=100,
|
| 42 |
+
),
|
| 43 |
+
),
|
| 44 |
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
| 45 |
+
(
|
| 46 |
+
"last_used_at",
|
| 47 |
+
models.DateTimeField(
|
| 48 |
+
blank=True,
|
| 49 |
+
help_text="Last time this token was used",
|
| 50 |
+
null=True,
|
| 51 |
+
),
|
| 52 |
+
),
|
| 53 |
+
(
|
| 54 |
+
"expires_at",
|
| 55 |
+
models.DateTimeField(
|
| 56 |
+
blank=True,
|
| 57 |
+
help_text="Optional expiration date. Leave empty for no expiration.",
|
| 58 |
+
null=True,
|
| 59 |
+
),
|
| 60 |
+
),
|
| 61 |
+
(
|
| 62 |
+
"is_active",
|
| 63 |
+
models.BooleanField(
|
| 64 |
+
default=True,
|
| 65 |
+
help_text="Inactive tokens cannot be used for API access",
|
| 66 |
+
),
|
| 67 |
+
),
|
| 68 |
+
(
|
| 69 |
+
"datastore",
|
| 70 |
+
models.ForeignKey(
|
| 71 |
+
blank=True,
|
| 72 |
+
help_text="If set, token only grants access to this datastore. Leave empty for global access.",
|
| 73 |
+
null=True,
|
| 74 |
+
on_delete=django.db.models.deletion.CASCADE,
|
| 75 |
+
related_name="api_tokens",
|
| 76 |
+
to="core.datastore",
|
| 77 |
+
),
|
| 78 |
+
),
|
| 79 |
+
(
|
| 80 |
+
"created_by",
|
| 81 |
+
models.ForeignKey(
|
| 82 |
+
blank=True,
|
| 83 |
+
null=True,
|
| 84 |
+
on_delete=django.db.models.deletion.SET_NULL,
|
| 85 |
+
related_name="created_api_tokens",
|
| 86 |
+
to=settings.AUTH_USER_MODEL,
|
| 87 |
+
),
|
| 88 |
+
),
|
| 89 |
+
],
|
| 90 |
+
options={
|
| 91 |
+
"verbose_name": "API token",
|
| 92 |
+
"verbose_name_plural": "API tokens",
|
| 93 |
+
"db_table": "datastore_api_tokens",
|
| 94 |
+
"ordering": ["-created_at"],
|
| 95 |
+
},
|
| 96 |
+
),
|
| 97 |
+
]
|
core/migrations/0018_add_weekly_monthly_schedule.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated manually for weekly/monthly scheduling
|
| 2 |
+
|
| 3 |
+
from django.db import migrations, models
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Migration(migrations.Migration):
|
| 7 |
+
|
| 8 |
+
dependencies = [
|
| 9 |
+
("core", "0017_datastore_api_token"),
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
operations = [
|
| 13 |
+
# Add weekly_days field
|
| 14 |
+
migrations.AddField(
|
| 15 |
+
model_name="scriptschedule",
|
| 16 |
+
name="weekly_days",
|
| 17 |
+
field=models.JSONField(
|
| 18 |
+
blank=True,
|
| 19 |
+
default=list,
|
| 20 |
+
help_text="Days of week [0-6] where 0=Monday, e.g., [0, 2, 4] for Mon/Wed/Fri",
|
| 21 |
+
),
|
| 22 |
+
),
|
| 23 |
+
# Add weekly_times field
|
| 24 |
+
migrations.AddField(
|
| 25 |
+
model_name="scriptschedule",
|
| 26 |
+
name="weekly_times",
|
| 27 |
+
field=models.JSONField(
|
| 28 |
+
blank=True,
|
| 29 |
+
default=list,
|
| 30 |
+
help_text="List of times in HH:MM format for weekly mode",
|
| 31 |
+
),
|
| 32 |
+
),
|
| 33 |
+
# Add monthly_days field
|
| 34 |
+
migrations.AddField(
|
| 35 |
+
model_name="scriptschedule",
|
| 36 |
+
name="monthly_days",
|
| 37 |
+
field=models.JSONField(
|
| 38 |
+
blank=True,
|
| 39 |
+
default=list,
|
| 40 |
+
help_text="Days of month [1-31], e.g., [1, 15] for 1st and 15th",
|
| 41 |
+
),
|
| 42 |
+
),
|
| 43 |
+
# Add monthly_times field
|
| 44 |
+
migrations.AddField(
|
| 45 |
+
model_name="scriptschedule",
|
| 46 |
+
name="monthly_times",
|
| 47 |
+
field=models.JSONField(
|
| 48 |
+
blank=True,
|
| 49 |
+
default=list,
|
| 50 |
+
help_text="List of times in HH:MM format for monthly mode",
|
| 51 |
+
),
|
| 52 |
+
),
|
| 53 |
+
# Update run_mode choices to include weekly and monthly
|
| 54 |
+
migrations.AlterField(
|
| 55 |
+
model_name="scriptschedule",
|
| 56 |
+
name="run_mode",
|
| 57 |
+
field=models.CharField(
|
| 58 |
+
choices=[
|
| 59 |
+
("manual", "Manual"),
|
| 60 |
+
("interval", "Interval"),
|
| 61 |
+
("daily", "Daily"),
|
| 62 |
+
("weekly", "Weekly"),
|
| 63 |
+
("monthly", "Monthly"),
|
| 64 |
+
],
|
| 65 |
+
default="manual",
|
| 66 |
+
max_length=20,
|
| 67 |
+
),
|
| 68 |
+
),
|
| 69 |
+
]
|
core/migrations/0019_s3_storage_settings.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated migration for S3 storage settings
|
| 2 |
+
|
| 3 |
+
from django.db import migrations, models
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Migration(migrations.Migration):
|
| 7 |
+
|
| 8 |
+
dependencies = [
|
| 9 |
+
("core", "0018_add_weekly_monthly_schedule"),
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
operations = [
|
| 13 |
+
migrations.AddField(
|
| 14 |
+
model_name="globalsettings",
|
| 15 |
+
name="s3_enabled",
|
| 16 |
+
field=models.BooleanField(
|
| 17 |
+
default=False,
|
| 18 |
+
help_text="Enable S3-compatible storage for backups",
|
| 19 |
+
),
|
| 20 |
+
),
|
| 21 |
+
migrations.AddField(
|
| 22 |
+
model_name="globalsettings",
|
| 23 |
+
name="s3_endpoint_url",
|
| 24 |
+
field=models.CharField(
|
| 25 |
+
blank=True,
|
| 26 |
+
max_length=500,
|
| 27 |
+
help_text="S3 endpoint URL (leave empty for AWS S3)",
|
| 28 |
+
),
|
| 29 |
+
),
|
| 30 |
+
migrations.AddField(
|
| 31 |
+
model_name="globalsettings",
|
| 32 |
+
name="s3_region",
|
| 33 |
+
field=models.CharField(
|
| 34 |
+
blank=True,
|
| 35 |
+
default="us-east-1",
|
| 36 |
+
max_length=50,
|
| 37 |
+
help_text="S3 region",
|
| 38 |
+
),
|
| 39 |
+
),
|
| 40 |
+
migrations.AddField(
|
| 41 |
+
model_name="globalsettings",
|
| 42 |
+
name="s3_bucket_name",
|
| 43 |
+
field=models.CharField(
|
| 44 |
+
blank=True,
|
| 45 |
+
max_length=255,
|
| 46 |
+
help_text="S3 bucket name",
|
| 47 |
+
),
|
| 48 |
+
),
|
| 49 |
+
migrations.AddField(
|
| 50 |
+
model_name="globalsettings",
|
| 51 |
+
name="s3_access_key_encrypted",
|
| 52 |
+
field=models.TextField(
|
| 53 |
+
blank=True,
|
| 54 |
+
help_text="S3 access key (encrypted)",
|
| 55 |
+
),
|
| 56 |
+
),
|
| 57 |
+
migrations.AddField(
|
| 58 |
+
model_name="globalsettings",
|
| 59 |
+
name="s3_secret_key_encrypted",
|
| 60 |
+
field=models.TextField(
|
| 61 |
+
blank=True,
|
| 62 |
+
help_text="S3 secret key (encrypted)",
|
| 63 |
+
),
|
| 64 |
+
),
|
| 65 |
+
migrations.AddField(
|
| 66 |
+
model_name="globalsettings",
|
| 67 |
+
name="s3_use_ssl",
|
| 68 |
+
field=models.BooleanField(
|
| 69 |
+
default=True,
|
| 70 |
+
help_text="Use SSL/TLS for S3 connections",
|
| 71 |
+
),
|
| 72 |
+
),
|
| 73 |
+
migrations.AddField(
|
| 74 |
+
model_name="globalsettings",
|
| 75 |
+
name="s3_path_style",
|
| 76 |
+
field=models.BooleanField(
|
| 77 |
+
default=False,
|
| 78 |
+
help_text="Use path-style addressing (required for MinIO)",
|
| 79 |
+
),
|
| 80 |
+
),
|
| 81 |
+
migrations.AddField(
|
| 82 |
+
model_name="globalsettings",
|
| 83 |
+
name="s3_last_tested_at",
|
| 84 |
+
field=models.DateTimeField(
|
| 85 |
+
blank=True,
|
| 86 |
+
null=True,
|
| 87 |
+
help_text="When S3 connection was last successfully tested",
|
| 88 |
+
),
|
| 89 |
+
),
|
| 90 |
+
]
|
core/migrations/0020_s3_backup_schedule.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated migration for S3 scheduled backup settings
|
| 2 |
+
|
| 3 |
+
from django.db import migrations, models
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Migration(migrations.Migration):
|
| 7 |
+
|
| 8 |
+
dependencies = [
|
| 9 |
+
("core", "0019_s3_storage_settings"),
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
operations = [
|
| 13 |
+
migrations.AddField(
|
| 14 |
+
model_name="globalsettings",
|
| 15 |
+
name="s3_backup_enabled",
|
| 16 |
+
field=models.BooleanField(
|
| 17 |
+
default=False,
|
| 18 |
+
help_text="Enable scheduled backups to S3",
|
| 19 |
+
),
|
| 20 |
+
),
|
| 21 |
+
migrations.AddField(
|
| 22 |
+
model_name="globalsettings",
|
| 23 |
+
name="s3_backup_schedule",
|
| 24 |
+
field=models.CharField(
|
| 25 |
+
choices=[
|
| 26 |
+
("disabled", "Disabled"),
|
| 27 |
+
("daily", "Daily"),
|
| 28 |
+
("weekly", "Weekly"),
|
| 29 |
+
],
|
| 30 |
+
default="disabled",
|
| 31 |
+
max_length=20,
|
| 32 |
+
help_text="Backup schedule frequency",
|
| 33 |
+
),
|
| 34 |
+
),
|
| 35 |
+
migrations.AddField(
|
| 36 |
+
model_name="globalsettings",
|
| 37 |
+
name="s3_backup_time",
|
| 38 |
+
field=models.TimeField(
|
| 39 |
+
default="02:00",
|
| 40 |
+
help_text="Time of day to run backup (in instance timezone)",
|
| 41 |
+
),
|
| 42 |
+
),
|
| 43 |
+
migrations.AddField(
|
| 44 |
+
model_name="globalsettings",
|
| 45 |
+
name="s3_backup_day",
|
| 46 |
+
field=models.PositiveSmallIntegerField(
|
| 47 |
+
default=0,
|
| 48 |
+
help_text="Day of week for weekly backups (0=Monday, 6=Sunday)",
|
| 49 |
+
),
|
| 50 |
+
),
|
| 51 |
+
migrations.AddField(
|
| 52 |
+
model_name="globalsettings",
|
| 53 |
+
name="s3_backup_prefix",
|
| 54 |
+
field=models.CharField(
|
| 55 |
+
blank=True,
|
| 56 |
+
default="pyrunner-backups/",
|
| 57 |
+
max_length=255,
|
| 58 |
+
help_text="S3 key prefix for backup files",
|
| 59 |
+
),
|
| 60 |
+
),
|
| 61 |
+
migrations.AddField(
|
| 62 |
+
model_name="globalsettings",
|
| 63 |
+
name="s3_backup_retention_count",
|
| 64 |
+
field=models.PositiveIntegerField(
|
| 65 |
+
default=7,
|
| 66 |
+
help_text="Number of backups to keep in S3 (0 = keep all)",
|
| 67 |
+
),
|
| 68 |
+
),
|
| 69 |
+
migrations.AddField(
|
| 70 |
+
model_name="globalsettings",
|
| 71 |
+
name="s3_backup_include_runs",
|
| 72 |
+
field=models.BooleanField(
|
| 73 |
+
default=False,
|
| 74 |
+
help_text="Include run history in scheduled backups",
|
| 75 |
+
),
|
| 76 |
+
),
|
| 77 |
+
migrations.AddField(
|
| 78 |
+
model_name="globalsettings",
|
| 79 |
+
name="s3_backup_max_runs",
|
| 80 |
+
field=models.PositiveIntegerField(
|
| 81 |
+
default=1000,
|
| 82 |
+
help_text="Maximum runs to include in backup",
|
| 83 |
+
),
|
| 84 |
+
),
|
| 85 |
+
migrations.AddField(
|
| 86 |
+
model_name="globalsettings",
|
| 87 |
+
name="s3_backup_include_datastores",
|
| 88 |
+
field=models.BooleanField(
|
| 89 |
+
default=True,
|
| 90 |
+
help_text="Include datastores in scheduled backups",
|
| 91 |
+
),
|
| 92 |
+
),
|
| 93 |
+
migrations.AddField(
|
| 94 |
+
model_name="globalsettings",
|
| 95 |
+
name="s3_backup_last_run_at",
|
| 96 |
+
field=models.DateTimeField(
|
| 97 |
+
blank=True,
|
| 98 |
+
null=True,
|
| 99 |
+
help_text="When the last scheduled backup ran",
|
| 100 |
+
),
|
| 101 |
+
),
|
| 102 |
+
migrations.AddField(
|
| 103 |
+
model_name="globalsettings",
|
| 104 |
+
name="s3_backup_last_status",
|
| 105 |
+
field=models.CharField(
|
| 106 |
+
blank=True,
|
| 107 |
+
default="",
|
| 108 |
+
max_length=20,
|
| 109 |
+
help_text="Status of last backup (success/failed)",
|
| 110 |
+
),
|
| 111 |
+
),
|
| 112 |
+
migrations.AddField(
|
| 113 |
+
model_name="globalsettings",
|
| 114 |
+
name="s3_backup_last_error",
|
| 115 |
+
field=models.TextField(
|
| 116 |
+
blank=True,
|
| 117 |
+
default="",
|
| 118 |
+
help_text="Error message from last failed backup",
|
| 119 |
+
),
|
| 120 |
+
),
|
| 121 |
+
migrations.AddField(
|
| 122 |
+
model_name="globalsettings",
|
| 123 |
+
name="s3_backup_last_size",
|
| 124 |
+
field=models.PositiveIntegerField(
|
| 125 |
+
default=0,
|
| 126 |
+
help_text="Size of last backup in bytes",
|
| 127 |
+
),
|
| 128 |
+
),
|
| 129 |
+
]
|
core/migrations/0021_field_updates.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 6.0.1 on 2026-04-15 12:47
|
| 2 |
+
|
| 3 |
+
import core.models.environment
|
| 4 |
+
from django.db import migrations, models
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class Migration(migrations.Migration):
|
| 8 |
+
|
| 9 |
+
dependencies = [
|
| 10 |
+
("core", "0020_s3_backup_schedule"),
|
| 11 |
+
]
|
| 12 |
+
|
| 13 |
+
operations = [
|
| 14 |
+
migrations.AlterField(
|
| 15 |
+
model_name="environment",
|
| 16 |
+
name="path",
|
| 17 |
+
field=models.CharField(
|
| 18 |
+
help_text="Directory name within the environments folder",
|
| 19 |
+
max_length=255,
|
| 20 |
+
unique=True,
|
| 21 |
+
validators=[core.models.environment.validate_environment_path],
|
| 22 |
+
),
|
| 23 |
+
),
|
| 24 |
+
migrations.AlterField(
|
| 25 |
+
model_name="script",
|
| 26 |
+
name="timeout_seconds",
|
| 27 |
+
field=models.PositiveIntegerField(
|
| 28 |
+
default=3600,
|
| 29 |
+
help_text="Maximum execution time in seconds (default: 1 hour, max: 24 hours)",
|
| 30 |
+
),
|
| 31 |
+
),
|
| 32 |
+
migrations.AlterField(
|
| 33 |
+
model_name="scriptschedule",
|
| 34 |
+
name="timezone",
|
| 35 |
+
field=models.CharField(
|
| 36 |
+
default="UTC",
|
| 37 |
+
help_text="Timezone for scheduled runs (e.g., 'America/New_York')",
|
| 38 |
+
max_length=50,
|
| 39 |
+
),
|
| 40 |
+
),
|
| 41 |
+
]
|
core/migrations/__init__.py
ADDED
|
File without changes
|