django-simpletask5 0.2.7__tar.gz → 0.2.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/PKG-INFO +41 -4
  2. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/README.md +40 -3
  3. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/publisher.py +12 -4
  4. django_simpletask5-0.2.8/django_simpletask5/migrations/0013_alter_taskexecution_task_id.py +24 -0
  5. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/models.py +50 -9
  6. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5.egg-info/PKG-INFO +41 -4
  7. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5.egg-info/SOURCES.txt +1 -0
  8. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/pyproject.toml +1 -1
  9. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/LICENSE +0 -0
  10. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/__init__.py +0 -0
  11. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/admin.py +0 -0
  12. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/apps.py +0 -0
  13. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/__init__.py +0 -0
  14. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/cronjob_registry.py +0 -0
  15. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/defaults.py +0 -0
  16. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/executor_scanner.py +0 -0
  17. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/lock.py +0 -0
  18. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/logging_utils.py +0 -0
  19. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/message_queue.py +0 -0
  20. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/reclaim.py +0 -0
  21. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/security.py +0 -0
  22. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/signals.py +0 -0
  23. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/worker_registry.py +0 -0
  24. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/dashboards.py +0 -0
  25. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/__init__.py +0 -0
  26. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/archive.py +0 -0
  27. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/base.py +0 -0
  28. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/bash_script.py +0 -0
  29. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/exceptions.py +0 -0
  30. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/loader.py +0 -0
  31. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/ping_pong.py +0 -0
  32. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/python_script.py +0 -0
  33. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/retry_timeout.py +0 -0
  34. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/simple_request.py +0 -0
  35. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/status_check.py +0 -0
  36. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  37. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.po +0 -0
  38. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/management/__init__.py +0 -0
  39. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/management/commands/__init__.py +0 -0
  40. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/management/commands/django_simpletask_archive.py +0 -0
  41. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/management/commands/django_simpletask_crontab.py +0 -0
  42. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/management/commands/django_simpletask_executor.py +0 -0
  43. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/management/commands/django_simpletask_queues.py +0 -0
  44. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/management/commands/django_simpletask_sync_cronjobs.py +0 -0
  45. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0001_initial.py +0 -0
  46. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0002_taskexecution_done_tasks_count_and_more.py +0 -0
  47. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0003_alter_cronjob_options_remove_cronjob_name_and_more.py +0 -0
  48. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0004_alter_cronjob_is_modified_by_user.py +0 -0
  49. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0005_alter_cronjob_is_modified_by_user_help.py +0 -0
  50. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0006_alter_cronjob_options_alter_taskexecution_created_at_and_more.py +0 -0
  51. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0007_rename_task_model_to_executor_class_in_stat.py +0 -0
  52. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0008_taskexecution_pickup_timeout_seconds.py +0 -0
  53. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0009_add_cronjob_app_label.py +0 -0
  54. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0010_taskexecution_id_alter_taskexecution_execution_id.py +0 -0
  55. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0011_taskexecutionstat_canceled_count.py +0 -0
  56. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0012_alter_taskexecution_status.py +0 -0
  57. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/__init__.py +0 -0
  58. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/services/__init__.py +0 -0
  59. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/services/archive.py +0 -0
  60. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/static/django_simpletask5/css/dashboard.css +0 -0
  61. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/static/django_simpletask5/js/select2_executor.js +0 -0
  62. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5.egg-info/dependency_links.txt +0 -0
  63. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5.egg-info/requires.txt +0 -0
  64. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5.egg-info/top_level.txt +0 -0
  65. {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-simpletask5
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: Lightweight async task execution framework for Django
5
5
  Author-email: rRR0VrFP <rrr0vrfp@qq.com>
6
6
  Maintainer-email: rRR0VrFP <rrr0vrfp@qq.com>
@@ -278,7 +278,7 @@ class OrderTask(Task):
278
278
  ```
279
279
 
280
280
  要点:
281
- - 主键为 Django 默认自增 `id`;内置 `task_id`(UUID,`unique=True`)作为业务唯一标识,**不是**主键
281
+ - 主键为 Django 默认自增 `id`。`Task` 不再内置 `task_id` 字段,业务标识由业务模型自行定义(自定义主键或自然键均可)
282
282
  - 保存 / 删除 / 批量创建自动触发事件,详见「事件处理机制」
283
283
  - 事件上下文、update 字段判定、delete 拦截等行为的完整说明见对应章节
284
284
 
@@ -354,7 +354,7 @@ OrderTask.objects.create(order_id='ORD-001', customer_name='Test', amount=Decima
354
354
  | `update` | 字段值发生变化后保存 | `task_id`、`trigger_event`、`data`、`old_data`、`new_data`、`changed_fields` |
355
355
  | `delete` | 调用 `instance.delete()` | `task_id`、`trigger_event`、`data` |
356
356
 
357
- 执行器通过 `execution.get_context_dict()` 获取以上上下文。
357
+ 执行器通过 `execution.get_context_dict()` 获取以上上下文。其中 `task_id` 是业务对象主键的字符串表示(如 `"42"`、UUID 字符串或自定义键);业务对象反查见「模型与生命周期」。
358
358
 
359
359
  #### update 事件的字段变更判定
360
360
 
@@ -393,6 +393,8 @@ class OrderDeleteExecutor(BaseExecutor):
393
393
 
394
394
  这样实现删除操作与外部系统清理的解耦,且支持失败重试。
395
395
 
396
+ `confirm_delete()` 与 `execution.get_task()` 都会通过 `TaskExecution.task_id`(业务主键字符串)反查业务对象,反查逻辑由业务模型上的 `get_task_instance()` 提供(见「模型与生命周期」)。
397
+
396
398
  ### 事件 → 执行器映射
397
399
 
398
400
  每个事件消息最终要由一个执行器处理。`executor_class` 声明「事件名 → 执行器类路径」的分派表,可以是字符串(所有事件共用同一个执行器)或字典(按事件分派):
@@ -568,10 +570,38 @@ class OrderCreateExecutor(BaseExecutor):
568
570
 
569
571
  所有业务任务模型继承 `Task`:
570
572
 
571
- - 内置 `task_id`(UUID,`unique=True`);主键为 Django 默认自增 `id`,无需自定义
573
+ - 主键为 Django 默认自增 `id`,无需自定义;**不内置** `task_id` 字段,业务标识由业务模型按需定义
572
574
  - 提供 `trigger(event, extra_context)`、`skip_publish()`、`get_executor_class_path(event)`、`get_queue_name(event)` 等方法
573
575
  - 保存、删除、批量创建自动触发事件(见「事件处理机制」)
574
576
 
577
+ #### 业务对象反查:`get_task_instance()`
578
+
579
+ `TaskExecution.task_id` 存的是业务对象主键的字符串表示(`CharField`)。需要反查业务对象时(`execution.get_task()` / `execution.confirm_delete()`),框架先通过 `task_model` 解析出模型类,再调用该类的 `get_task_instance(task_id)` 完成查询。
580
+
581
+ 默认实现在 `Task` 基类中按主键 `pk` 查询,并通过内置的 `_coerce_task_pk()` 兼容常见主键类型:
582
+
583
+ - `AutoField` / `BigAutoField` / `SmallAutoField` — 字符串转为 `int`
584
+ - `UUIDField` — 字符串转为 `uuid.UUID`
585
+ - `CharField` / `TextField` 等字符串主键 — 原样使用
586
+
587
+ ```python
588
+ task = execution.get_task() # 返回业务对象(反查不到返回 None)
589
+ execution.confirm_delete() # 反查并真实删除业务对象
590
+ ```
591
+
592
+ 业务模型使用自定义主键或非主键自然键时,**重载 `get_task_instance()`** 即可,框架会自动改走你的实现:
593
+
594
+ ```python
595
+ class OrderTask(Task):
596
+ code = models.CharField(max_length=64, unique=True) # 非主键自然键
597
+
598
+ @classmethod
599
+ def get_task_instance(cls, task_id):
600
+ if task_id is None:
601
+ return None
602
+ return cls.objects.filter(code=task_id).first()
603
+ ```
604
+
575
605
  ### TaskExecution 状态机
576
606
 
577
607
  每条事件消息对应一条 `TaskExecution` 记录,状态流转如下:
@@ -702,6 +732,13 @@ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保
702
732
 
703
733
  ## Releases
704
734
 
735
+ ### 0.2.8
736
+
737
+ - **移除 `Task` 冗余的 `task_id` 字段** — `TaskExecution.task_id` 由 UUID 改为 `CharField(max_length=255)`,直接用于存放业务对象主键字符串(`str(instance.pk)`),不再维护一份与业务主键重复的 UUID。消息上下文中的 `task_id` 字段名不变,值语义从 UUID 变为业务主键字符串(如 `"42"`)
738
+ - **业务对象反查下沉到模型层** — 新增 `Task.get_task_instance(task_id)` 类方法作为默认反查实现(按 `pk` 查询,内置 `_coerce_task_pk()` 兼容 `int` / `UUID` / 字符串主键);`TaskExecution.get_task()` / `confirm_delete()` 改为解析 `task_model` 后委托给该方法。业务模型使用自定义主键或自然键时重载 `get_task_instance()` 即可
739
+ - **新增迁移** — `django_simpletask5.0013_alter_taskexecution_task_id`(字段改 CharField)、`django_simpletask5_example.0003_remove_ordertask_task_id`
740
+ - **破坏性变更**: 依赖 `TaskExecution.task_id` 为 UUID 类型的外部消费方需适配为业务主键字符串;业务 `Task` 子类若曾使用内置 `task_id` 字段需移除
741
+
705
742
  ### 0.2.7
706
743
 
707
744
  - **运行中任务回收机制重构** — 取消原先按 `expire_time` 将 running 直接判为超时的语义;`StatusCheckExecutor` 仅兜底回收长期未被 Worker 获取的 pending。新增 `core/reclaim`,由 crontab 每 ~5 秒基于执行锁探测运行中任务:锁已过期(执行器异常退出)即快速重投或直接置失败,降低孤儿任务滞留时间
@@ -238,7 +238,7 @@ class OrderTask(Task):
238
238
  ```
239
239
 
240
240
  要点:
241
- - 主键为 Django 默认自增 `id`;内置 `task_id`(UUID,`unique=True`)作为业务唯一标识,**不是**主键
241
+ - 主键为 Django 默认自增 `id`。`Task` 不再内置 `task_id` 字段,业务标识由业务模型自行定义(自定义主键或自然键均可)
242
242
  - 保存 / 删除 / 批量创建自动触发事件,详见「事件处理机制」
243
243
  - 事件上下文、update 字段判定、delete 拦截等行为的完整说明见对应章节
244
244
 
@@ -314,7 +314,7 @@ OrderTask.objects.create(order_id='ORD-001', customer_name='Test', amount=Decima
314
314
  | `update` | 字段值发生变化后保存 | `task_id`、`trigger_event`、`data`、`old_data`、`new_data`、`changed_fields` |
315
315
  | `delete` | 调用 `instance.delete()` | `task_id`、`trigger_event`、`data` |
316
316
 
317
- 执行器通过 `execution.get_context_dict()` 获取以上上下文。
317
+ 执行器通过 `execution.get_context_dict()` 获取以上上下文。其中 `task_id` 是业务对象主键的字符串表示(如 `"42"`、UUID 字符串或自定义键);业务对象反查见「模型与生命周期」。
318
318
 
319
319
  #### update 事件的字段变更判定
320
320
 
@@ -353,6 +353,8 @@ class OrderDeleteExecutor(BaseExecutor):
353
353
 
354
354
  这样实现删除操作与外部系统清理的解耦,且支持失败重试。
355
355
 
356
+ `confirm_delete()` 与 `execution.get_task()` 都会通过 `TaskExecution.task_id`(业务主键字符串)反查业务对象,反查逻辑由业务模型上的 `get_task_instance()` 提供(见「模型与生命周期」)。
357
+
356
358
  ### 事件 → 执行器映射
357
359
 
358
360
  每个事件消息最终要由一个执行器处理。`executor_class` 声明「事件名 → 执行器类路径」的分派表,可以是字符串(所有事件共用同一个执行器)或字典(按事件分派):
@@ -528,10 +530,38 @@ class OrderCreateExecutor(BaseExecutor):
528
530
 
529
531
  所有业务任务模型继承 `Task`:
530
532
 
531
- - 内置 `task_id`(UUID,`unique=True`);主键为 Django 默认自增 `id`,无需自定义
533
+ - 主键为 Django 默认自增 `id`,无需自定义;**不内置** `task_id` 字段,业务标识由业务模型按需定义
532
534
  - 提供 `trigger(event, extra_context)`、`skip_publish()`、`get_executor_class_path(event)`、`get_queue_name(event)` 等方法
533
535
  - 保存、删除、批量创建自动触发事件(见「事件处理机制」)
534
536
 
537
+ #### 业务对象反查:`get_task_instance()`
538
+
539
+ `TaskExecution.task_id` 存的是业务对象主键的字符串表示(`CharField`)。需要反查业务对象时(`execution.get_task()` / `execution.confirm_delete()`),框架先通过 `task_model` 解析出模型类,再调用该类的 `get_task_instance(task_id)` 完成查询。
540
+
541
+ 默认实现在 `Task` 基类中按主键 `pk` 查询,并通过内置的 `_coerce_task_pk()` 兼容常见主键类型:
542
+
543
+ - `AutoField` / `BigAutoField` / `SmallAutoField` — 字符串转为 `int`
544
+ - `UUIDField` — 字符串转为 `uuid.UUID`
545
+ - `CharField` / `TextField` 等字符串主键 — 原样使用
546
+
547
+ ```python
548
+ task = execution.get_task() # 返回业务对象(反查不到返回 None)
549
+ execution.confirm_delete() # 反查并真实删除业务对象
550
+ ```
551
+
552
+ 业务模型使用自定义主键或非主键自然键时,**重载 `get_task_instance()`** 即可,框架会自动改走你的实现:
553
+
554
+ ```python
555
+ class OrderTask(Task):
556
+ code = models.CharField(max_length=64, unique=True) # 非主键自然键
557
+
558
+ @classmethod
559
+ def get_task_instance(cls, task_id):
560
+ if task_id is None:
561
+ return None
562
+ return cls.objects.filter(code=task_id).first()
563
+ ```
564
+
535
565
  ### TaskExecution 状态机
536
566
 
537
567
  每条事件消息对应一条 `TaskExecution` 记录,状态流转如下:
@@ -662,6 +692,13 @@ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保
662
692
 
663
693
  ## Releases
664
694
 
695
+ ### 0.2.8
696
+
697
+ - **移除 `Task` 冗余的 `task_id` 字段** — `TaskExecution.task_id` 由 UUID 改为 `CharField(max_length=255)`,直接用于存放业务对象主键字符串(`str(instance.pk)`),不再维护一份与业务主键重复的 UUID。消息上下文中的 `task_id` 字段名不变,值语义从 UUID 变为业务主键字符串(如 `"42"`)
698
+ - **业务对象反查下沉到模型层** — 新增 `Task.get_task_instance(task_id)` 类方法作为默认反查实现(按 `pk` 查询,内置 `_coerce_task_pk()` 兼容 `int` / `UUID` / 字符串主键);`TaskExecution.get_task()` / `confirm_delete()` 改为解析 `task_model` 后委托给该方法。业务模型使用自定义主键或自然键时重载 `get_task_instance()` 即可
699
+ - **新增迁移** — `django_simpletask5.0013_alter_taskexecution_task_id`(字段改 CharField)、`django_simpletask5_example.0003_remove_ordertask_task_id`
700
+ - **破坏性变更**: 依赖 `TaskExecution.task_id` 为 UUID 类型的外部消费方需适配为业务主键字符串;业务 `Task` 子类若曾使用内置 `task_id` 字段需移除
701
+
665
702
  ### 0.2.7
666
703
 
667
704
  - **运行中任务回收机制重构** — 取消原先按 `expire_time` 将 running 直接判为超时的语义;`StatusCheckExecutor` 仅兜底回收长期未被 Worker 获取的 pending。新增 `core/reclaim`,由 crontab 每 ~5 秒基于执行锁探测运行中任务:锁已过期(执行器异常退出)即快速重投或直接置失败,降低孤儿任务滞留时间
@@ -39,7 +39,7 @@ def create_and_publish(task, trigger_event, extra_context=None):
39
39
  queue_name = task.get_queue_name(event=trigger_event)
40
40
 
41
41
  context = {
42
- 'task_id': str(task.task_id),
42
+ 'task_id': _task_identity(task),
43
43
  'trigger_event': trigger_event,
44
44
  'data': _model_to_dict(task),
45
45
  }
@@ -49,7 +49,7 @@ def create_and_publish(task, trigger_event, extra_context=None):
49
49
  serialized_context = _serialize_context(context)
50
50
 
51
51
  execution = TaskExecution(
52
- task_id=task.task_id,
52
+ task_id=_task_identity(task),
53
53
  task_model=task._meta.label,
54
54
  trigger_event=trigger_event,
55
55
  executor_class=executor_class_path,
@@ -74,7 +74,7 @@ def bulk_create_and_publish(tasks, trigger_event, extra_context=None):
74
74
  queue_name = task.get_queue_name(event=trigger_event)
75
75
 
76
76
  context = {
77
- 'task_id': str(task.task_id),
77
+ 'task_id': _task_identity(task),
78
78
  'trigger_event': trigger_event,
79
79
  'data': _model_to_dict(task),
80
80
  }
@@ -84,7 +84,7 @@ def bulk_create_and_publish(tasks, trigger_event, extra_context=None):
84
84
  serialized_context = _serialize_context(context)
85
85
 
86
86
  execution = TaskExecution(
87
- task_id=task.task_id,
87
+ task_id=_task_identity(task),
88
88
  task_model=task._meta.label,
89
89
  trigger_event=trigger_event,
90
90
  executor_class=executor_class_path,
@@ -165,6 +165,14 @@ def _serialize_value(value):
165
165
  return str(value)
166
166
 
167
167
 
168
+ def _task_identity(instance):
169
+ """返回业务对象唯一标识字符串,用作 TaskExecution.task_id。
170
+
171
+ 业务实例唯一性由其真实主键承载,而非额外的 task_id UUID。
172
+ """
173
+ return str(instance.pk) if instance.pk is not None else None
174
+
175
+
168
176
  def _model_to_dict(instance):
169
177
  data = {}
170
178
  for field in instance._meta.fields:
@@ -0,0 +1,24 @@
1
+ # Generated by Django 5.2.16 on 2026-09-03 06:32
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("django_simpletask5", "0012_alter_taskexecution_status"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name="taskexecution",
15
+ name="task_id",
16
+ field=models.CharField(
17
+ blank=True,
18
+ db_index=True,
19
+ max_length=255,
20
+ null=True,
21
+ verbose_name="Task ID",
22
+ ),
23
+ ),
24
+ ]
@@ -42,8 +42,6 @@ class TaskManager(models.Manager):
42
42
 
43
43
 
44
44
  class Task(models.Model):
45
- task_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
46
-
47
45
  objects = TaskManager()
48
46
 
49
47
  class Meta:
@@ -114,6 +112,23 @@ class Task(models.Model):
114
112
  return self._derive_executor_class_path(event)
115
113
  return self.executor_class
116
114
 
115
+ @classmethod
116
+ def get_task_instance(cls, task_id):
117
+ """根据 Execution 记录的 task_id(业务主键字符串)反查业务实例。
118
+
119
+ 默认实现按主键(pk)查询,并兼容 int / UUID / 字符串主键;
120
+ 业务子类可在自定义主键查询逻辑时重载此方法。
121
+ """
122
+ if task_id is None:
123
+ return None
124
+ pk = _coerce_task_pk(cls, task_id)
125
+ if pk is None:
126
+ return None
127
+ try:
128
+ return cls.objects.get(pk=pk)
129
+ except cls.DoesNotExist:
130
+ return None
131
+
117
132
  def _derive_executor_class_path(self, event=None):
118
133
  app_label = self._meta.app_label
119
134
  class_name = self.__class__.__name__
@@ -142,6 +157,30 @@ class Task(models.Model):
142
157
  raise _DeleteIntercepted(self)
143
158
 
144
159
 
160
+ def _coerce_task_pk(model, task_id):
161
+ """将 TaskExecution.task_id 字符串转换为业务模型主键的实际类型。
162
+
163
+ 业务模型的主键可能是 int(AutoField/BigAutoField)、UUID(UUIDField)
164
+ 或字符串(CharField/TextField 等)。无法正确转换时返回 None。
165
+ """
166
+ if task_id is None:
167
+ return None
168
+ pk_field = model._meta.pk
169
+ if isinstance(pk_field, models.AutoField) \
170
+ or isinstance(pk_field, models.BigAutoField) \
171
+ or isinstance(pk_field, models.SmallAutoField):
172
+ try:
173
+ return int(task_id)
174
+ except (TypeError, ValueError):
175
+ return None
176
+ if isinstance(pk_field, models.UUIDField):
177
+ try:
178
+ return uuid.UUID(str(task_id))
179
+ except (ValueError, AttributeError, TypeError):
180
+ return None
181
+ return task_id
182
+
183
+
145
184
  class TaskExecution(models.Model):
146
185
  STATUS_CHOICES = [
147
186
  ('pending', _('Pending')),
@@ -154,7 +193,7 @@ class TaskExecution(models.Model):
154
193
  ]
155
194
 
156
195
  execution_id = models.UUIDField(verbose_name=_('Execution ID'), unique=True, default=uuid.uuid4, editable=False)
157
- task_id = models.UUIDField(verbose_name=_('Task ID'), null=True, blank=True, db_index=True)
196
+ task_id = models.CharField(verbose_name=_('Task ID'), max_length=255, null=True, blank=True, db_index=True)
158
197
  task_model = models.CharField(verbose_name=_('Task model'), max_length=256, null=True, blank=True, help_text='app_label.ModelName')
159
198
  trigger_event = models.CharField(verbose_name=_('Trigger event'), max_length=64, db_index=True)
160
199
  executor_class = models.CharField(verbose_name=_('Executor class'), max_length=512, db_index=True)
@@ -191,20 +230,22 @@ class TaskExecution(models.Model):
191
230
  if task_model is None:
192
231
  return
193
232
  try:
194
- instance = task_model.objects.get(task_id=self.task_id)
233
+ instance = task_model.get_task_instance(self.task_id)
234
+ if instance is None:
235
+ return
195
236
  instance._force_delete = True
196
237
  instance.delete()
197
- except task_model.DoesNotExist:
238
+ except Exception:
198
239
  pass
199
240
 
200
241
  def get_task(self):
201
242
  if not self.task_id or not self.task_model:
202
243
  return None
203
244
  try:
204
- from django.apps import apps as django_apps
205
- app_label, model_name = self.task_model.rsplit('.', 1)
206
- model = django_apps.get_model(app_label, model_name)
207
- return model.objects.get(task_id=self.task_id)
245
+ model = self.get_task_model()
246
+ if model is None:
247
+ return None
248
+ return model.get_task_instance(self.task_id)
208
249
  except Exception:
209
250
  return None
210
251
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-simpletask5
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: Lightweight async task execution framework for Django
5
5
  Author-email: rRR0VrFP <rrr0vrfp@qq.com>
6
6
  Maintainer-email: rRR0VrFP <rrr0vrfp@qq.com>
@@ -278,7 +278,7 @@ class OrderTask(Task):
278
278
  ```
279
279
 
280
280
  要点:
281
- - 主键为 Django 默认自增 `id`;内置 `task_id`(UUID,`unique=True`)作为业务唯一标识,**不是**主键
281
+ - 主键为 Django 默认自增 `id`。`Task` 不再内置 `task_id` 字段,业务标识由业务模型自行定义(自定义主键或自然键均可)
282
282
  - 保存 / 删除 / 批量创建自动触发事件,详见「事件处理机制」
283
283
  - 事件上下文、update 字段判定、delete 拦截等行为的完整说明见对应章节
284
284
 
@@ -354,7 +354,7 @@ OrderTask.objects.create(order_id='ORD-001', customer_name='Test', amount=Decima
354
354
  | `update` | 字段值发生变化后保存 | `task_id`、`trigger_event`、`data`、`old_data`、`new_data`、`changed_fields` |
355
355
  | `delete` | 调用 `instance.delete()` | `task_id`、`trigger_event`、`data` |
356
356
 
357
- 执行器通过 `execution.get_context_dict()` 获取以上上下文。
357
+ 执行器通过 `execution.get_context_dict()` 获取以上上下文。其中 `task_id` 是业务对象主键的字符串表示(如 `"42"`、UUID 字符串或自定义键);业务对象反查见「模型与生命周期」。
358
358
 
359
359
  #### update 事件的字段变更判定
360
360
 
@@ -393,6 +393,8 @@ class OrderDeleteExecutor(BaseExecutor):
393
393
 
394
394
  这样实现删除操作与外部系统清理的解耦,且支持失败重试。
395
395
 
396
+ `confirm_delete()` 与 `execution.get_task()` 都会通过 `TaskExecution.task_id`(业务主键字符串)反查业务对象,反查逻辑由业务模型上的 `get_task_instance()` 提供(见「模型与生命周期」)。
397
+
396
398
  ### 事件 → 执行器映射
397
399
 
398
400
  每个事件消息最终要由一个执行器处理。`executor_class` 声明「事件名 → 执行器类路径」的分派表,可以是字符串(所有事件共用同一个执行器)或字典(按事件分派):
@@ -568,10 +570,38 @@ class OrderCreateExecutor(BaseExecutor):
568
570
 
569
571
  所有业务任务模型继承 `Task`:
570
572
 
571
- - 内置 `task_id`(UUID,`unique=True`);主键为 Django 默认自增 `id`,无需自定义
573
+ - 主键为 Django 默认自增 `id`,无需自定义;**不内置** `task_id` 字段,业务标识由业务模型按需定义
572
574
  - 提供 `trigger(event, extra_context)`、`skip_publish()`、`get_executor_class_path(event)`、`get_queue_name(event)` 等方法
573
575
  - 保存、删除、批量创建自动触发事件(见「事件处理机制」)
574
576
 
577
+ #### 业务对象反查:`get_task_instance()`
578
+
579
+ `TaskExecution.task_id` 存的是业务对象主键的字符串表示(`CharField`)。需要反查业务对象时(`execution.get_task()` / `execution.confirm_delete()`),框架先通过 `task_model` 解析出模型类,再调用该类的 `get_task_instance(task_id)` 完成查询。
580
+
581
+ 默认实现在 `Task` 基类中按主键 `pk` 查询,并通过内置的 `_coerce_task_pk()` 兼容常见主键类型:
582
+
583
+ - `AutoField` / `BigAutoField` / `SmallAutoField` — 字符串转为 `int`
584
+ - `UUIDField` — 字符串转为 `uuid.UUID`
585
+ - `CharField` / `TextField` 等字符串主键 — 原样使用
586
+
587
+ ```python
588
+ task = execution.get_task() # 返回业务对象(反查不到返回 None)
589
+ execution.confirm_delete() # 反查并真实删除业务对象
590
+ ```
591
+
592
+ 业务模型使用自定义主键或非主键自然键时,**重载 `get_task_instance()`** 即可,框架会自动改走你的实现:
593
+
594
+ ```python
595
+ class OrderTask(Task):
596
+ code = models.CharField(max_length=64, unique=True) # 非主键自然键
597
+
598
+ @classmethod
599
+ def get_task_instance(cls, task_id):
600
+ if task_id is None:
601
+ return None
602
+ return cls.objects.filter(code=task_id).first()
603
+ ```
604
+
575
605
  ### TaskExecution 状态机
576
606
 
577
607
  每条事件消息对应一条 `TaskExecution` 记录,状态流转如下:
@@ -702,6 +732,13 @@ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保
702
732
 
703
733
  ## Releases
704
734
 
735
+ ### 0.2.8
736
+
737
+ - **移除 `Task` 冗余的 `task_id` 字段** — `TaskExecution.task_id` 由 UUID 改为 `CharField(max_length=255)`,直接用于存放业务对象主键字符串(`str(instance.pk)`),不再维护一份与业务主键重复的 UUID。消息上下文中的 `task_id` 字段名不变,值语义从 UUID 变为业务主键字符串(如 `"42"`)
738
+ - **业务对象反查下沉到模型层** — 新增 `Task.get_task_instance(task_id)` 类方法作为默认反查实现(按 `pk` 查询,内置 `_coerce_task_pk()` 兼容 `int` / `UUID` / 字符串主键);`TaskExecution.get_task()` / `confirm_delete()` 改为解析 `task_model` 后委托给该方法。业务模型使用自定义主键或自然键时重载 `get_task_instance()` 即可
739
+ - **新增迁移** — `django_simpletask5.0013_alter_taskexecution_task_id`(字段改 CharField)、`django_simpletask5_example.0003_remove_ordertask_task_id`
740
+ - **破坏性变更**: 依赖 `TaskExecution.task_id` 为 UUID 类型的外部消费方需适配为业务主键字符串;业务 `Task` 子类若曾使用内置 `task_id` 字段需移除
741
+
705
742
  ### 0.2.7
706
743
 
707
744
  - **运行中任务回收机制重构** — 取消原先按 `expire_time` 将 running 直接判为超时的语义;`StatusCheckExecutor` 仅兜底回收长期未被 Worker 获取的 pending。新增 `core/reclaim`,由 crontab 每 ~5 秒基于执行锁探测运行中任务:锁已过期(执行器异常退出)即快速重投或直接置失败,降低孤儿任务滞留时间
@@ -55,6 +55,7 @@ django_simpletask5/migrations/0009_add_cronjob_app_label.py
55
55
  django_simpletask5/migrations/0010_taskexecution_id_alter_taskexecution_execution_id.py
56
56
  django_simpletask5/migrations/0011_taskexecutionstat_canceled_count.py
57
57
  django_simpletask5/migrations/0012_alter_taskexecution_status.py
58
+ django_simpletask5/migrations/0013_alter_taskexecution_task_id.py
58
59
  django_simpletask5/migrations/__init__.py
59
60
  django_simpletask5/services/__init__.py
60
61
  django_simpletask5/services/archive.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "django-simpletask5"
7
- version = "0.2.7"
7
+ version = "0.2.8"
8
8
  description = "Lightweight async task execution framework for Django"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}