labull-framework 0.1.34__tar.gz → 0.1.35__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 (55) hide show
  1. {labull_framework-0.1.34/labull_framework.egg-info → labull_framework-0.1.35}/PKG-INFO +1 -1
  2. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/canvas/data.py +73 -6
  3. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/canvas/gitinfo.py +414 -69
  4. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/canvas/shape_ast.py +1880 -1581
  5. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/canvas/static/canvas.css +64 -23
  6. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/canvas/static/canvas.js +269 -57
  7. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/canvas/static/page.html +18 -6
  8. {labull_framework-0.1.34 → labull_framework-0.1.35/labull_framework.egg-info}/PKG-INFO +1 -1
  9. {labull_framework-0.1.34 → labull_framework-0.1.35}/pyproject.toml +1 -1
  10. {labull_framework-0.1.34 → labull_framework-0.1.35}/LICENSE +0 -0
  11. {labull_framework-0.1.34 → labull_framework-0.1.35}/MANIFEST.in +0 -0
  12. {labull_framework-0.1.34 → labull_framework-0.1.35}/README-pypi.md +0 -0
  13. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/__init__.py +0 -0
  14. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/admin.py +0 -0
  15. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/apps.py +0 -0
  16. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/auth/__init__.py +0 -0
  17. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/auth/backend.py +0 -0
  18. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/auth/bootstrap.py +0 -0
  19. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/auth/sso.py +0 -0
  20. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/auth/views.py +0 -0
  21. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/canvas/__init__.py +0 -0
  22. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/canvas/views.py +0 -0
  23. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/env.py +0 -0
  24. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/jsonio.py +0 -0
  25. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/magic_block/__init__.py +0 -0
  26. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/magic_block/blockpackage.py +0 -0
  27. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/magic_block/deepcheck.py +0 -0
  28. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/magic_block/devkit.py +0 -0
  29. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/magic_block/dispatch.py +0 -0
  30. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/magic_block/export.py +0 -0
  31. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/magic_block/health.py +0 -0
  32. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/magic_block/parallel.py +0 -0
  33. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/magic_block/registry.py +0 -0
  34. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/magic_block/sdk.py +0 -0
  35. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/magic_block/service.py +0 -0
  36. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/magic_block/subproc.py +0 -0
  37. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/magic_block/validate.py +0 -0
  38. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/magic_block/views.py +0 -0
  39. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/management/__init__.py +0 -0
  40. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/management/commands/__init__.py +0 -0
  41. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/management/commands/labull_contract.py +0 -0
  42. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/migrations/0001_initial.py +0 -0
  43. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/migrations/0002_remove_magicblock_prev_files_and_more.py +0 -0
  44. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/migrations/0003_magicblock_tags.py +0 -0
  45. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/migrations/0004_magicblock_detail.py +0 -0
  46. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/migrations/__init__.py +0 -0
  47. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/models.py +0 -0
  48. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/settings.py +0 -0
  49. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/urls.py +0 -0
  50. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework/virtual.py +0 -0
  51. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework.egg-info/SOURCES.txt +0 -0
  52. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework.egg-info/dependency_links.txt +0 -0
  53. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework.egg-info/requires.txt +0 -0
  54. {labull_framework-0.1.34 → labull_framework-0.1.35}/labull_framework.egg-info/top_level.txt +0 -0
  55. {labull_framework-0.1.34 → labull_framework-0.1.35}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labull-framework
3
- Version: 0.1.34
3
+ Version: 0.1.35
4
4
  Summary: Labull Framework: a pluggable Django app with Authing OIDC login and magic blocks
5
5
  Author: Labull Framework
6
6
  License-Expression: MIT
@@ -450,10 +450,32 @@ def _fields_of(model: Any) -> tuple[list[dict[str, Any]], list[dict[str, Any]],
450
450
  )
451
451
  )
452
452
  continue
453
- if getattr(field, 'one_to_many', False) and not getattr(field, 'one_to_one', False):
454
- # 🔴 **反向那一侧也要画**:从 `Customer` 看不到"它被多少订单引用",
455
- # 图就是半个 —— 老项目那套连线两边都连。反向是**只读**的(库里没有这一列)。
456
- # ⚠️ 反向的一对一**跳过**:它的正向那一边已经画了一条线,两边都画就成了两条重合的线。
453
+ # 🔴 **反向那一侧也要画**:从 `Customer` 看不到"它被多少订单引用",
454
+ # 图就是半个 —— 老项目那套连线两边都连。反向是**只读**的(库里没有这一列)。
455
+ # 🔴 **反向的一对一必须并进来**(`one_to_one` 那一档)——老代码写的是
456
+ # `one_to_many and not one_to_one`,而 Django 的反向一对一是一个
457
+ # `OneToOneRel`:它的 `one_to_many` 是 **False**、`one_to_one` 是 **True**
458
+ # ⇒ 老判据把它整个漏掉,**一条反向一对一都不画**。
459
+ # 代价不是"少画一根线"而是**一片假差异**:基线那一侧是 `shape_ast._reverses()`
460
+ # 算的,它对一对一**照画不误**(`name_back = related or model.name.lower()`)
461
+ # ⇒ 每次打开画布都报"删除了 `supplier.bank_account` / `labull_user.extra`"
462
+ # (在 `_relation_diff()` 那里成了 `deleted` 一档),而那两张表根本没动过。
463
+ # 🔴 "正向那边已经画了一条线"这件事**不成立**:反向一对一指向的是**子表**,
464
+ # 正向那条线指向的是**父表**,两张卡片、两条线,不是重合。
465
+ # 口径与 `shape_ast._reverses()` 那一段**逐字对齐**(那边的 `elif kind == 'oneToOne'`)。
466
+ # 🔴 **前向的一对一不能被这一档吃掉**(这一条是我修上面那一档时当场踩出来的,
467
+ # 而且报文一模一样是"改过"):前向的 `OneToOneField` 在 `_meta` 上
468
+ # `one_to_one` 也是 **True** —— 拿它当选判据,`ReceiveBankAccount.supplier`
469
+ # 这条**真列**会被当成"反向那一侧",于是 `kind` 记成 `reverse`、`column` 记成
470
+ # `None`、`null`/`unique` 记成 `None` ⇒ 基线里那条 `('supplier','oneToOne','1',True,True)`
471
+ # 与它一比就是"改过"(实测:`receive_bank_account.supplier` / `user_extra_info.user`
472
+ # 两张表各报一处假的修改)。
473
+ # 判据:**反向的那几个都是 `ForeignObjectRel`**(`auto_created` 为真),
474
+ # 前向字段是 `Field`(`auto_created` 为假)—— 只有前一种才走这一档。
475
+ reverse_rel = getattr(field, 'one_to_many', False) or (
476
+ bool(getattr(field, 'one_to_one', False)) and bool(getattr(field, 'auto_created', False))
477
+ )
478
+ if reverse_rel:
457
479
  target = field.related_model
458
480
  # 🔴 **对端是"我们不画的那几张表"时不补这一条**(`django.contrib.*` / 会话 / 迁移):
459
481
  # 它的正向声明住在 **Django 自己的源码**里,而"上一版"那一份只扫项目与装着的
@@ -464,7 +486,13 @@ def _fields_of(model: Any) -> tuple[list[dict[str, Any]], list[dict[str, Any]],
464
486
  # **用同一条判据**就能对齐:不画的表,两边都不挂它的反向关联)。
465
487
  if _table_of_model(target) in SKIP_TABLES:
466
488
  continue
467
- relations.append(_relation_of(field, 'reverse', '*', target, readonly=True))
489
+ # 🔴 一对一反过来的**基数也是 `1`**:这一头只对应一行(`OneToOneRel.one_to_one`)。
490
+ # 报成 `*` 会让前端画出"一对多"那种端口,也与基线那一份(`back_card = '1'`)对不上
491
+ # ⇒ `_relation_key()` 一比就是"改过",又一处假差异。
492
+ once = bool(getattr(field, 'one_to_one', False))
493
+ relations.append(
494
+ _relation_of(field, 'reverse', '1' if once else '*', target, readonly=True)
495
+ )
468
496
  continue
469
497
  if not hasattr(field, 'attname'):
470
498
  continue
@@ -477,7 +505,46 @@ def _fields_of(model: Any) -> tuple[list[dict[str, Any]], list[dict[str, Any]],
477
505
  _relation_of(field, kind, '1', target, column=str(field.attname))
478
506
  )
479
507
  fields.append(_shape_field(field, enums))
480
- return fields, relations, enums
508
+ return fields, _dedupe_relation_names(relations), enums
509
+
510
+
511
+ def _dedupe_relation_names(relations: list[dict[str, Any]]) -> list[dict[str, Any]]:
512
+ """按**关联名**去重:真列(前向那条)优先,说不出名字的反向那条让位。
513
+
514
+ 🔴 为什么必须有这一步(修完"反向一对一也要画"那一档才露出来的):差异比对
515
+ (`_relations_of()` 与 `_relation_diff()`)是拿 `row['field']` 当键的,
516
+ **同名就互相盖**。而反向一对一在**没有 `related_name`** 时,Django 给它的访问器名是
517
+ **子模型名的小写**(`UserExtraInfo` ⇒ `extra`、`ReceiveBankAccount` ⇒ `bank_account`)——
518
+ 这一档本身没问题;问题是那 13 条前向一对一/外键里,只要有一条的**字段名**与
519
+ 另一条反向关系的名字撞上,两条里必然被盖掉一条,于是 `_relation_diff()` 拿着同名键
520
+ 比出**两条不同的关系**,报出"`receive_bank_account.supplier` 改过"这种假差异
521
+ (基线里那一条是 `('supplier','oneToOne')`,现在这一份被盖成了
522
+ `('supplier','reverse')` —— 实测就是这个)。
523
+ 🔴 **前向那条(`readonly` 不为真)一定留下**:它是库里真有的那一列,信息量更大;
524
+ 而名字冲突时 Django 本来也不会给反向那一侧生成访问器属性。
525
+ ⚠️ 所以判据是"先收前向、再补反向",**不是**"先来后到"——`_meta.get_fields()`
526
+ 今天恰好是"先列、后反向",靠顺序等于靠一个没写进契约的实现细节。
527
+ """
528
+ forward = {
529
+ str(row.get('field') or '')
530
+ for row in relations
531
+ if not row.get('readonly')
532
+ }
533
+ out: list[dict[str, Any]] = []
534
+ seen: set[str] = set()
535
+ for row in relations:
536
+ name = str(row.get('field') or '')
537
+ if not name:
538
+ out.append(row)
539
+ continue
540
+ if name in seen:
541
+ continue
542
+ # 反向那条与某个真列同名 ⇒ 让位(它没有自己的访问器,见 docstring)。
543
+ if row.get('readonly') and name in forward:
544
+ continue
545
+ seen.add(name)
546
+ out.append(row)
547
+ return out
481
548
 
482
549
 
483
550
  def _type_of(field: Any) -> str: