flask-admin-autolinks 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,3 @@
1
+ from .mixin import AutoLinksMixin
2
+
3
+ __all__ = ["AutoLinksMixin"]
@@ -0,0 +1,69 @@
1
+ """Hyperlink relationship columns in Flask-Admin SQLAlchemy list views."""
2
+
3
+ import typing as t
4
+
5
+ from flask import url_for
6
+ from flask_admin.contrib.sqla.tools import is_relationship
7
+ from markupsafe import Markup, escape
8
+
9
+
10
+ class AutoLinksMixin:
11
+ """Put before ModelView in the bases. True = auto-detect; dict = per-column endpoint override."""
12
+
13
+ column_relationship_links: bool | dict[str, str] = False
14
+
15
+ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
16
+ super().__init__(*args, **kwargs)
17
+ self._autolinks = self._scaffold_autolinks()
18
+ if self._autolinks: # eager-load link targets: no N+1 on the list page
19
+ self._auto_joins = list({*self._auto_joins, *self._autolinks.values()})
20
+
21
+ def _scaffold_autolinks(self) -> dict[str, t.Any]:
22
+ if not self.column_relationship_links:
23
+ return {}
24
+ found = {}
25
+ for name, _label in self._list_columns:
26
+ if name in self.column_formatters: # explicit formatters always win
27
+ continue
28
+ attr = getattr(self.model, name, None)
29
+ if attr is not None and is_relationship(attr):
30
+ found[name] = attr
31
+ return found
32
+
33
+ def _target_view(self, name: str, related_cls: type) -> t.Any:
34
+ links = self.column_relationship_links
35
+ endpoint = links.get(name) if isinstance(links, dict) else None
36
+ for view in self.admin._views:
37
+ if endpoint is not None:
38
+ if view.endpoint == endpoint:
39
+ return view
40
+ elif getattr(view, "model", None) is related_cls:
41
+ return view
42
+ return None
43
+
44
+ @staticmethod
45
+ def _href(view: t.Any, target: t.Any) -> str | None:
46
+ if view is None or not view.is_accessible():
47
+ return None
48
+ pk = view.get_pk_value(target)
49
+ if view.can_edit:
50
+ return url_for(f"{view.endpoint}.edit_view", id=pk)
51
+ if view.can_view_details:
52
+ return url_for(f"{view.endpoint}.details_view", id=pk)
53
+ return None
54
+
55
+ def get_list_value(self, context: t.Any, model: t.Any, name: str) -> t.Any:
56
+ attr = self._autolinks.get(name)
57
+ if attr is None:
58
+ return super().get_list_value(context, model, name)
59
+ value = getattr(model, name)
60
+ if value is None:
61
+ return ""
62
+ prop = attr.property
63
+ view = self._target_view(name, prop.mapper.class_)
64
+ out = []
65
+ for target in value if prop.uselist else [value]:
66
+ label = escape(str(target))
67
+ href = self._href(view, target)
68
+ out.append(Markup('<a href="{}">{}</a>').format(href, label) if href else label)
69
+ return Markup(", ").join(out)
File without changes
@@ -0,0 +1,101 @@
1
+ Metadata-Version: 2.5
2
+ Name: flask-admin-autolinks
3
+ Version: 0.1.0
4
+ Summary: Implements autolinks for related models in ModelView from Flask-Admin
5
+ Author-email: Mario Hernandez <mariofix@proton.me>
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: flask-admin>=2.2.1
9
+ Description-Content-Type: text/markdown
10
+
11
+ # flask-admin-autolinks
12
+
13
+ Automatic hyperlinks for relationship columns in Flask-Admin's SQLAlchemy list views.
14
+
15
+ By default, a relationship column renders as plain text (`str()` of the related object). With this mixin, each related object becomes a link to its own edit (or details) page in the admin.
16
+
17
+ ## Features
18
+
19
+ - Opt-in per view. No effect unless `column_relationship_links` is set.
20
+ - Resolves the target view from the registered views by model. No endpoint naming convention required.
21
+ - Respects the target view's `can_edit`, `can_view_details` and `is_accessible()`. Falls back to plain text.
22
+ - Eager-loads link targets through the view's `_auto_joins`, so the list page does not issue N+1 queries.
23
+ - Explicit `column_formatters` entries always win.
24
+ - Output is escaped.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ uv add flask-admin-autolinks
30
+ # or
31
+ pip install flask-admin-autolinks
32
+ ```
33
+
34
+ Requires Flask-Admin with SQLAlchemy support (`flask-admin[sqlalchemy]`). Supported versions are declared in `pyproject.toml`.
35
+
36
+ ## Usage
37
+
38
+ Place `AutoLinksMixin` before `ModelView` in the bases:
39
+
40
+ ```python
41
+ from flask_admin.contrib.sqla import ModelView
42
+ from flask_admin_autolinks import AutoLinksMixin
43
+
44
+
45
+ class OrderView(AutoLinksMixin, ModelView):
46
+ column_list = ("id", "customer", "items")
47
+ column_relationship_links = True
48
+ ```
49
+
50
+ For project-wide use, define a shared base once:
51
+
52
+ ```python
53
+ class BaseView(AutoLinksMixin, ModelView):
54
+ column_relationship_links = True
55
+ ```
56
+
57
+ Only relationship columns listed in `column_list` are linked.
58
+
59
+ ## Configuration
60
+
61
+ `column_relationship_links` accepts:
62
+
63
+ | Value | Behavior |
64
+ | --- | --- |
65
+ | `False` (default) | Disabled. |
66
+ | `True` | Link every relationship column in `column_list` to the registered view for the related model. |
67
+ | `dict[str, str]` | Same as `True`, with per-column overrides mapping a relationship name to a view endpoint. |
68
+
69
+ ```python
70
+ class OrderView(AutoLinksMixin, ModelView):
71
+ column_list = ("id", "customer", "items")
72
+ column_relationship_links = {"customer": "client"} # "client" is the target view's endpoint
73
+ ```
74
+
75
+ An empty dict is falsy and leaves the feature disabled.
76
+
77
+ ## Behavior
78
+
79
+ For each related object, the link target is chosen in this order:
80
+
81
+ 1. Target view has `can_edit` and is accessible: link to `edit_view`.
82
+ 2. Otherwise, target view has `can_view_details` and is accessible: link to `details_view`.
83
+ 3. Otherwise: escaped plain text.
84
+
85
+ If no view is registered for the related model (or the override endpoint is not found), the cell renders as plain text. The list page is never broken by a missing target.
86
+
87
+ Collections render as comma-separated links. `None` renders as an empty cell.
88
+
89
+ A column that already has an entry in `column_formatters` is left untouched and is not eager-loaded by this package.
90
+
91
+ ## To Do
92
+
93
+ - SQLAlchemy backend only (`flask_admin.contrib.sqla`), add others.
94
+
95
+ ## License
96
+
97
+ See `LICENSE` for MIT License.
98
+
99
+ ## IA
100
+
101
+ Documentation done with Claude.
@@ -0,0 +1,7 @@
1
+ flask_admin_autolinks/__init__.py,sha256=8efM6HJ6nyXm4eCv12_6dHMAImwaT2ye1BJ2aRPjLhw,64
2
+ flask_admin_autolinks/mixin.py,sha256=vCEizpf3hZhD5a4lfNPcltal333r-x0XSL9x9p88flM,2722
3
+ flask_admin_autolinks/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ flask_admin_autolinks-0.1.0.dist-info/METADATA,sha256=jsWeOUntAVr5OEQnicZARGmhbKz8DQrr7oRQ_4AIlM4,3248
5
+ flask_admin_autolinks-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
6
+ flask_admin_autolinks-0.1.0.dist-info/licenses/LICENSE,sha256=_McTnrwzSZTy3mRFU9bF65PXsF9zLOPbJbKWetXuR7M,1072
7
+ flask_admin_autolinks-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mario Hernandez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.