labull-framework 0.1.2__tar.gz → 0.1.3__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 (47) hide show
  1. {labull_framework-0.1.2/labull_framework.egg-info → labull_framework-0.1.3}/PKG-INFO +1 -1
  2. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/canvas/data.py +30 -2
  3. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/canvas/static/canvas.css +45 -13
  4. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/canvas/static/canvas.js +1902 -1365
  5. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/canvas/static/page.html +53 -52
  6. {labull_framework-0.1.2 → labull_framework-0.1.3/labull_framework.egg-info}/PKG-INFO +1 -1
  7. {labull_framework-0.1.2 → labull_framework-0.1.3}/pyproject.toml +1 -1
  8. {labull_framework-0.1.2 → labull_framework-0.1.3}/LICENSE +0 -0
  9. {labull_framework-0.1.2 → labull_framework-0.1.3}/MANIFEST.in +0 -0
  10. {labull_framework-0.1.2 → labull_framework-0.1.3}/README-pypi.md +0 -0
  11. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/__init__.py +0 -0
  12. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/admin.py +0 -0
  13. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/apps.py +0 -0
  14. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/auth/__init__.py +0 -0
  15. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/auth/backend.py +0 -0
  16. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/auth/bootstrap.py +0 -0
  17. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/auth/sso.py +0 -0
  18. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/auth/views.py +0 -0
  19. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/canvas/__init__.py +0 -0
  20. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/canvas/gitinfo.py +0 -0
  21. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/canvas/views.py +0 -0
  22. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/env.py +0 -0
  23. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/jsonio.py +0 -0
  24. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/__init__.py +0 -0
  25. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/deepcheck.py +0 -0
  26. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/devkit.py +0 -0
  27. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/dispatch.py +0 -0
  28. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/export.py +0 -0
  29. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/health.py +0 -0
  30. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/registry.py +0 -0
  31. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/sdk.py +0 -0
  32. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/service.py +0 -0
  33. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/validate.py +0 -0
  34. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/views.py +0 -0
  35. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/management/__init__.py +0 -0
  36. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/management/commands/__init__.py +0 -0
  37. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/management/commands/labull_contract.py +0 -0
  38. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/migrations/0001_initial.py +0 -0
  39. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/migrations/__init__.py +0 -0
  40. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/models.py +0 -0
  41. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/settings.py +0 -0
  42. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/urls.py +0 -0
  43. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework.egg-info/SOURCES.txt +0 -0
  44. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework.egg-info/dependency_links.txt +0 -0
  45. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework.egg-info/requires.txt +0 -0
  46. {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework.egg-info/top_level.txt +0 -0
  47. {labull_framework-0.1.2 → labull_framework-0.1.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labull-framework
3
- Version: 0.1.2
3
+ Version: 0.1.3
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
@@ -253,6 +253,27 @@ def _verbose(model: Any) -> str:
253
253
  return str(getattr(model._meta, 'verbose_name', '') or '')
254
254
 
255
255
 
256
+ def m2m_through(field: Any) -> Any:
257
+ """多对多那条关系**中间表**是哪个模型。
258
+
259
+ 🔴 正反两侧拿中间表的方式**不一样**,只认一边就会炸(真踩过:`canvasdemo` 的演示表
260
+ `Course.students` 一加进来,`/canvas` 直接 500):
261
+ · **正向**(`ManyToManyField`):中间表挂在 `field.remote_field.through` 上;
262
+ · **反向**(`ManyToManyRel`,例如 `Student.courses`):`field.remote_field` 是**正向那个
263
+ field 本身**,而它在 Django 里**没有 `through` 属性**(不是 None,是 AttributeError),
264
+ 中间表挂在 `field.through` 上。
265
+ ⚠️ 老代码写的是 `getattr(field.remote_field, 'through', None)`:反向那一侧拿到 `None`,
266
+ 紧接着 `None._meta` 抛 `AttributeError` —— 而 `auth` 的那几张表恰好在 `SKIP_TABLES` 里、
267
+ 平时又没人写过普通多对多,所以这个坑一直没露出来。`getattr` 的默认值把 AttributeError
268
+ 一起吞了,连报错都指不到真正的原因。
269
+ """
270
+ for owner in (getattr(field, 'remote_field', None), field):
271
+ through = getattr(owner, 'through', None)
272
+ if through is not None:
273
+ return through
274
+ return None
275
+
276
+
256
277
  def shape() -> dict[str, list[dict[str, Any]]]:
257
278
  """当前的模型形状(进程内现算,不需要任何文件)。
258
279
 
@@ -270,14 +291,21 @@ def shape() -> dict[str, list[dict[str, Any]]]:
270
291
  for field in model._meta.get_fields():
271
292
  if getattr(field, 'many_to_many', False):
272
293
  target = field.related_model
273
- through = getattr(getattr(field, 'remote_field', None), 'through', None)
294
+ through = m2m_through(field)
295
+ # 🔴 "反向那一侧"必须标成 `readonly`:多对多的正反两条都走这个分支,
296
+ # 而前端是**按"两端表名排序"分组、每组只画正向那条**的。
297
+ # 老代码这里没给 `readonly` ⇒ 反向那条也进了 forward 组 ⇒ **同一个关系画了两条
298
+ # 方向相反的线、必然交叉**(用户报的「关联键故意交叉了」就是这个,
299
+ # 实测 `course -> student` 与 `student -> course` 各画了一条)。
300
+ # 判定:反向的多对多是一个 `ManyToManyRel`,`auto_created` 为真。
274
301
  relations.append(
275
302
  _relation_of(
276
303
  field,
277
304
  'manyToMany',
278
305
  '*',
279
306
  target,
280
- via=str(getattr(through._meta, 'db_table', '') or '') or None,
307
+ via=str(getattr(getattr(through, '_meta', None), 'db_table', '') or '') or None,
308
+ readonly=bool(getattr(field, 'auto_created', False)),
281
309
  )
282
310
  )
283
311
  continue
@@ -179,7 +179,11 @@ body {
179
179
  transition: stroke-width .12s;
180
180
  }
181
181
 
182
- .edge.violet { stroke: var(--tone-violet); }
182
+ /* 🔴 **按"关联种类"上的色已经全部去掉**(这里原来有一条 `.edge.violet { stroke: var(--tone-violet) }`)。
183
+ * 理由就是用户定的那条规矩:**整页默认全黑,只有"相对基线动过"才上色**。
184
+ * 多对多那条线原来默认是紫的(它排在 `.edge` 的黑色**后面**,所以盖掉了默认色)——
185
+ * 用户一眼就看出来了(「为什么是紫色?」)。
186
+ * ⚠️ 老判据只验了普通外键是黑的、没验多对多,所以这条一直漏着;现在 CSS 里不再有种类色。 */
183
187
  /* 反向(只读)那一侧画虚一点:库里没有这一列,不该看起来跟真外键一模一样 */
184
188
  .edge.is-readonly { stroke-dasharray: 5 4; }
185
189
  /* 🔴 关联的增 / 改 / 删:颜色与卡片、字段行的三档完全一致(同一份 token)。
@@ -207,42 +211,65 @@ body {
207
211
  .arrow.modified { stroke: var(--modified); }
208
212
  .arrow.deleted { stroke: var(--deleted); }
209
213
 
210
- /* 基数:**空心圆里一个 `1` / `*`**,落在方块边界附近(线的两端)。
211
- * 🔴 圆填白、描边走线的颜色:它压在白卡 / 绿卡 / 黄卡上都读得出来 ——
212
- * 卡片底色恰恰是"变了没有"的判据,标记不能靠底色来分辨。 */
214
+ /* 基数:**空心圆里一个 `1` / `*`**,落在方块边界内侧(线的两端)。
215
+ * 🔴 **默认是黑的**(用户明确要的:整页默认全黑,只有"相对基线动过"的才上色)——
216
+ * 和线一个口径:没动过的关联,线黑、两端的圈也黑;动了才换色(下面那三条)。
217
+ * ⚠️ 圆填卡片色(白底把线遮住),所以字上再加一圈卡片色描边才读得清。 */
213
218
  .cardinality { pointer-events: none; }
214
- .cardinality circle { fill: var(--card); stroke: var(--tone-green); stroke-width: 1.8; }
219
+ .cardinality circle { fill: var(--card); stroke: var(--ink); stroke-width: 1.8; }
215
220
  .cardinality text {
216
- fill: var(--tone-green);
221
+ fill: var(--ink);
217
222
  font-size: 10px;
218
223
  font-weight: 700;
219
224
  text-anchor: middle;
220
- dominant-baseline: middle;
225
+ /* 🔴 `central` 而不是 `middle`:`middle` 对齐的是**x-height 的中线**,数字会略微偏下;
226
+ * `central` 对齐的是**整个 em 框的中线**,`1` 与 `*` 都正好落在圆心上(用户要的"正中间")。
227
+ * ⚠️ 别再往 `y` 上加偏移补偿 —— 那样和这里叠起来就偏了(真踩过)。 */
228
+ dominant-baseline: central;
221
229
  font-family: ui-monospace, Consolas, monospace;
222
230
  paint-order: stroke;
223
231
  stroke: var(--card);
224
232
  stroke-width: 2.5px;
225
233
  stroke-linejoin: round;
226
234
  }
235
+ /* 只有"这条关联相对基线动过"时才上色 —— 与 `.edge.is-*` / 卡片 / 字段行同一套 token。 */
236
+ .cardinality.is-added circle { stroke: var(--added); }
237
+ .cardinality.is-added text { fill: var(--added); }
238
+ .cardinality.is-modified circle { stroke: var(--modified); }
239
+ .cardinality.is-modified text { fill: var(--modified); }
240
+ .cardinality.is-deleted circle { stroke: var(--deleted); }
241
+ .cardinality.is-deleted text { fill: var(--deleted); }
227
242
  /* `*` 那个字比 `1` 宽,给大一号才在同一个圆里好看。
228
243
  * ⚠️ 两个圆**都是实线**:一开始给 `*` 画了虚线想区分"多",用户明确要求实心圆,就去掉了
229
244
  * (`1` 与 `*` 的字形本身已经够区分;实线圆看起来也更干净)。 */
230
245
  .cardinality.is-many text { font-size: 16px; }
231
246
 
232
- /* 关联键的名字:标在**线的正中间**。
233
- * 🔴 `dominant-baseline: middle` 是必须的:`y` 默认是**基线**,配合上面那句旋转,
234
- * 不写它文字就会整体偏到线的一侧。`paint-order: stroke` 再描一圈卡片色,
235
- * 线从字底下穿过时也读得清。 */
247
+ /* 🔴 关联名:**一律水平、框高统一**(参考 Mendix 的关联标签)。
248
+ * · 字**不按线的角度转**了:斜框有两个毛病 —— 框高会随角度变(AABB 撑大),
249
+ * 而且斜着不像"钉在线上";
250
+ * · `dominant-baseline: middle` 仍然必须有:`y` 默认是**基线**,不写它字就偏到线的一侧;
251
+ * · 那圈 `paint-order: stroke` 的描边**去掉了** —— 现在有框的底色遮线,描边只会在框里多一圈边。
252
+ * 自关联那个圈上的名字没有框,描边只留给了它(见下面 `.edge-name--self`)。 */
236
253
  .edge-name {
237
254
  fill: var(--muted);
238
255
  font-size: 10.5px;
239
256
  font-family: ui-monospace, Consolas, monospace;
240
257
  text-anchor: middle;
241
258
  dominant-baseline: middle;
259
+ pointer-events: none;
260
+ }
261
+ /* 自关联那个圈上的名字**没有框**(它挂在圈顶,不在线上),所以这唯一一处保留描边好读。 */
262
+ .edge-name--self {
242
263
  paint-order: stroke;
243
264
  stroke: var(--card);
244
265
  stroke-width: 3.5px;
245
266
  stroke-linejoin: round;
267
+ }
268
+ /* 关联标签的黑框(参考 Mendix):细黑边 + 卡片色底 —— 底把线遮住,字就干净了。 */
269
+ .edge-name-box {
270
+ fill: var(--card);
271
+ stroke: var(--ink);
272
+ stroke-width: 1;
246
273
  pointer-events: none;
247
274
  }
248
275
  /* 有关联备注时才可点(点开看备注)—— 鼠标形状是唯一的提示,所以只给能点的那些加。 */
@@ -295,9 +322,14 @@ body {
295
322
  .head:hover { background: #e9eff6; }
296
323
 
297
324
  .title { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
298
- .title b { font-size: 14px; font-family: ui-monospace, Consolas, monospace; }
299
- .title .cn { color: var(--text); font-size: 12px; }
325
+ /* 🔴 主名字要**大**(用户明确要的):卡片上第一眼要能认出这是哪张表,14px 在缩到 40% 之后太小。 */
326
+ .title b { font-size: 16px; font-family: ui-monospace, Consolas, monospace; }
327
+ .title .cn { color: var(--text); font-size: 13px; }
300
328
  .title .mod { color: var(--muted); font-size: 11px; }
329
+ /* 🔴 备注模式下两个名字**对调**(见 canvas.js 里算 `primary` / `secondary` 那两行):
330
+ * 主位那时是**中文备注名** —— 中文不该走等宽;技术表名退到次位,它才是该等宽的那个。 */
331
+ #canvas.is-note-first .title b { font-family: inherit; }
332
+ #canvas.is-note-first .title .cn { font-family: ui-monospace, Consolas, monospace; }
301
333
 
302
334
  .caret { color: var(--muted); font-size: 11px; transition: transform .15s; }
303
335
  .table.is-open .caret { transform: rotate(180deg); }