modified: netbox_services/engine/__init__.py
modified: netbox_services/engine/allocators.py modified: netbox_services/engine/context.py new file: netbox_services/engine/mappers.py modified: netbox_services/engine/resolver.py new file: netbox_services/engine/templating.py modified: netbox_services/forms.py modified: netbox_services/models.py modified: netbox_services/tests/test_allocators.py new file: netbox_services/tests/test_mappers.py modified: netbox_services/tests/test_resolver.py new file: netbox_services/tests/test_templating.py Simplify layout to template and service instances
This commit is contained in:
1 parent
4ff9eac274
commit
b1c9c7a40b
12 files changed
+1133
-33
No files matched your search
@@ -0,0 +1,80 @@
|
||||
"""Jinja2-based attribute templating with ``..`` / ``...`` parent access.
|
||||
|
||||
Syntax inside ``{{ }}`` expressions:
|
||||
- ``..`` → immediate parent (alias ``p1``)
|
||||
- ``...`` → parent of parent (alias ``p2``)
|
||||
- ``....`` → great-grandparent (alias ``p3``)
|
||||
- ``service`` → the Service instance
|
||||
- ``index`` → 0-based index within a ``quantity`` loop
|
||||
|
||||
Example::
|
||||
|
||||
attrs:
|
||||
name: "rtr-{{ service.tenant.slug }}-{{ index + 1 }}"
|
||||
site: "{{ ..site.pk }}" # parent device's site PK
|
||||
notes: "{{ ...region.slug }}" # grandparent region slug
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
|
||||
_DOT_RE = re.compile(r"(?<![.\w])\.{2,}")
|
||||
|
||||
_env = SandboxedEnvironment(
|
||||
variable_start_string="{{",
|
||||
variable_end_string="}}",
|
||||
autoescape=False,
|
||||
)
|
||||
|
||||
|
||||
def preprocess_dots(s):
|
||||
"""Replace ``..`` → ``p1.``, ``...`` → ``p2.``, etc.
|
||||
|
||||
Runs of 2+ dots not preceded by a word char or dot are replaced.
|
||||
This preserves single dots (attribute access, IP addresses like
|
||||
``10.0.0.1``) and literal strings without ``{{ }}``.
|
||||
"""
|
||||
def replacer(m):
|
||||
depth = len(m.group(0)) - 1
|
||||
return f"p{depth}."
|
||||
|
||||
return _DOT_RE.sub(replacer, s)
|
||||
|
||||
|
||||
def _build_jinja_ctx(service, ancestors, index):
|
||||
ctx = {"service": service, "index": index}
|
||||
for i, obj in enumerate(ancestors):
|
||||
ctx[f"p{i + 1}"] = obj
|
||||
return ctx
|
||||
|
||||
|
||||
def render_value(value, service, ancestors, index):
|
||||
"""Render a single attribute value.
|
||||
|
||||
Strings are preprocessed (``..`` → ``p1.``) then Jinja2-rendered.
|
||||
Non-strings (int, bool, list, dict) are returned as-is.
|
||||
"""
|
||||
if not isinstance(value, str):
|
||||
return value
|
||||
|
||||
if "{{" not in value:
|
||||
return value
|
||||
|
||||
processed = preprocess_dots(value)
|
||||
template = _env.from_string(processed)
|
||||
return template.render(**_build_jinja_ctx(service, ancestors, index))
|
||||
|
||||
|
||||
def render_attrs(attrs, service, ancestors, index):
|
||||
"""Render all values in an attrs dict.
|
||||
|
||||
*ancestors* is a list where ``ancestors[0]`` is the immediate parent,
|
||||
``ancestors[1]`` is the grandparent, etc.
|
||||
"""
|
||||
if not attrs:
|
||||
return {}
|
||||
return {
|
||||
key: render_value(val, service, ancestors, index)
|
||||
for key, val in attrs.items()
|
||||
}
|
||||
Reference in new issue
Block a user