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.
- {labull_framework-0.1.2/labull_framework.egg-info → labull_framework-0.1.3}/PKG-INFO +1 -1
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/canvas/data.py +30 -2
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/canvas/static/canvas.css +45 -13
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/canvas/static/canvas.js +1902 -1365
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/canvas/static/page.html +53 -52
- {labull_framework-0.1.2 → labull_framework-0.1.3/labull_framework.egg-info}/PKG-INFO +1 -1
- {labull_framework-0.1.2 → labull_framework-0.1.3}/pyproject.toml +1 -1
- {labull_framework-0.1.2 → labull_framework-0.1.3}/LICENSE +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/MANIFEST.in +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/README-pypi.md +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/__init__.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/admin.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/apps.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/auth/__init__.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/auth/backend.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/auth/bootstrap.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/auth/sso.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/auth/views.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/canvas/__init__.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/canvas/gitinfo.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/canvas/views.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/env.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/jsonio.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/__init__.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/deepcheck.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/devkit.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/dispatch.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/export.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/health.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/registry.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/sdk.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/service.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/validate.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/magic_block/views.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/management/__init__.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/management/commands/__init__.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/management/commands/labull_contract.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/migrations/0001_initial.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/migrations/__init__.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/models.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/settings.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework/urls.py +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework.egg-info/SOURCES.txt +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework.egg-info/dependency_links.txt +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework.egg-info/requires.txt +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/labull_framework.egg-info/top_level.txt +0 -0
- {labull_framework-0.1.2 → labull_framework-0.1.3}/setup.cfg +0 -0
|
@@ -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 =
|
|
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
|
|
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
|
-
|
|
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(--
|
|
219
|
+
.cardinality circle { fill: var(--card); stroke: var(--ink); stroke-width: 1.8; }
|
|
215
220
|
.cardinality text {
|
|
216
|
-
fill: var(--
|
|
221
|
+
fill: var(--ink);
|
|
217
222
|
font-size: 10px;
|
|
218
223
|
font-weight: 700;
|
|
219
224
|
text-anchor: middle;
|
|
220
|
-
|
|
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
|
-
*
|
|
234
|
-
*
|
|
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
|
-
|
|
299
|
-
.title
|
|
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); }
|