Skip to content

Commit 0c6726d

Browse files
authored
Merge pull request #19570 from netbox-community/19490-jinja-template-fails-with-empty-include
Fixes #19490: restores nesting behavior of DataSource-based ConfigTemplate
2 parents cc099e8 + d7672ab commit 0c6726d

File tree

3 files changed

+90
-8
lines changed

3 files changed

+90
-8
lines changed

netbox/extras/models/mixins.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def render(self, context=None, queryset=None):
131131
"""
132132
context = self.get_context(context=context, queryset=queryset)
133133
env_params = self.environment_params or {}
134-
output = render_jinja2(self.template_code, context, env_params)
134+
output = render_jinja2(self.template_code, context, env_params, getattr(self, 'data_file', None))
135135

136136
# Replace CRLF-style line terminators
137137
output = output.replace('\r\n', '\n')

netbox/extras/tests/test_models.py

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import tempfile
2+
from pathlib import Path
3+
14
from django.forms import ValidationError
2-
from django.test import TestCase
5+
from django.test import tag, TestCase
36

4-
from core.models import ObjectType
7+
from core.models import DataSource, ObjectType
58
from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Platform, Region, Site, SiteGroup
6-
from extras.models import ConfigContext, Tag
9+
from extras.models import ConfigContext, ConfigTemplate, Tag
710
from tenancy.models import Tenant, TenantGroup
811
from utilities.exceptions import AbortRequest
912
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
@@ -33,8 +36,8 @@ def test_tag_related_manager_ordering_weight_then_name(self):
3336
]
3437

3538
site = Site.objects.create(name='Site 1')
36-
for tag in tags:
37-
site.tags.add(tag)
39+
for _tag in tags:
40+
site.tags.add(_tag)
3841
site.save()
3942

4043
site = Site.objects.first()
@@ -540,3 +543,66 @@ def test_invalid_local_context_data(self):
540543
device.local_context_data = 'foo'
541544
with self.assertRaises(ValidationError):
542545
device.clean()
546+
547+
548+
class ConfigTemplateTest(TestCase):
549+
"""
550+
TODO: These test cases deal with the weighting, ordering, and deep merge logic of config context data.
551+
"""
552+
MAIN_TEMPLATE = """
553+
{%- include 'base.j2' %}
554+
""".strip()
555+
BASE_TEMPLATE = """
556+
Hi
557+
""".strip()
558+
559+
@classmethod
560+
def _create_template_file(cls, templates_dir, file_name, content):
561+
template_file_name = file_name
562+
if not template_file_name.endswith('j2'):
563+
template_file_name += '.j2'
564+
temp_file_path = templates_dir / template_file_name
565+
566+
with open(temp_file_path, 'w') as f:
567+
f.write(content)
568+
569+
@classmethod
570+
def setUpTestData(cls):
571+
temp_dir = tempfile.TemporaryDirectory()
572+
templates_dir = Path(temp_dir.name) / "templates"
573+
templates_dir.mkdir(parents=True, exist_ok=True)
574+
575+
cls._create_template_file(templates_dir, 'base.j2', cls.BASE_TEMPLATE)
576+
cls._create_template_file(templates_dir, 'main.j2', cls.MAIN_TEMPLATE)
577+
578+
data_source = DataSource(
579+
name="Test DataSource",
580+
type="local",
581+
source_url=str(templates_dir),
582+
)
583+
data_source.save()
584+
data_source.sync()
585+
586+
base_config_template = ConfigTemplate(
587+
name="BaseTemplate",
588+
data_file=data_source.datafiles.filter(path__endswith='base.j2').first()
589+
)
590+
base_config_template.clean()
591+
base_config_template.save()
592+
cls.base_config_template = base_config_template
593+
594+
main_config_template = ConfigTemplate(
595+
name="MainTemplate",
596+
data_file=data_source.datafiles.filter(path__endswith='main.j2').first()
597+
)
598+
main_config_template.clean()
599+
main_config_template.save()
600+
cls.main_config_template = main_config_template
601+
602+
@tag('regression')
603+
def test_config_template_with_data_source(self):
604+
self.assertEqual(self.BASE_TEMPLATE, self.base_config_template.render({}))
605+
606+
@tag('regression')
607+
def test_config_template_with_data_source_nested_templates(self):
608+
self.assertEqual(self.BASE_TEMPLATE, self.main_config_template.render({}))

netbox/utilities/jinja2.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,27 @@ def cache_templates(self, templates):
4949
# Utility functions
5050
#
5151

52-
def render_jinja2(template_code, context, environment_params=None):
52+
def render_jinja2(template_code, context, environment_params=None, data_file=None):
5353
"""
5454
Render a Jinja2 template with the provided context. Return the rendered content.
5555
"""
5656
environment_params = environment_params or {}
57+
58+
if 'loader' not in environment_params:
59+
if data_file:
60+
loader = DataFileLoader(data_file.source)
61+
loader.cache_templates({
62+
data_file.path: template_code
63+
})
64+
else:
65+
loader = BaseLoader()
66+
environment_params['loader'] = loader
67+
5768
environment = SandboxedEnvironment(**environment_params)
5869
environment.filters.update(get_config().JINJA2_FILTERS)
59-
return environment.from_string(source=template_code).render(**context)
70+
71+
if data_file:
72+
template = environment.get_template(data_file.path)
73+
else:
74+
template = environment.from_string(source=template_code)
75+
return template.render(**context)

0 commit comments

Comments
 (0)