Move RenderMixin to extras.models.mixins, Rename RenderMixin to RenderTemplateMixin

This commit is contained in:
Renato Almeida de Oliveira Zaroubin 2025-04-06 19:27:48 +00:00
parent fea7926197
commit 517c8016c3
4 changed files with 101 additions and 93 deletions

View File

@ -6,9 +6,9 @@ from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from extras.querysets import ConfigContextQuerySet
from extras.models.mixins import RenderTemplateMixin
from netbox.models import ChangeLoggedModel
from netbox.models.features import (
CloningMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin, RenderMixin)
from netbox.models.features import CloningMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin
from netbox.registry import registry
from utilities.data import deepmerge
@ -208,7 +208,7 @@ class ConfigContextModel(models.Model):
#
class ConfigTemplate(
SyncedDataMixin, CustomLinksMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedModel, RenderMixin):
SyncedDataMixin, CustomLinksMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedModel, RenderTemplateMixin):
name = models.CharField(
verbose_name=_('name'),
max_length=100

View File

@ -3,9 +3,18 @@ import importlib.util
import os
import sys
from django.core.files.storage import storages
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.http import HttpResponse
from extras.constants import DEFAULT_MIME_TYPE
from extras.utils import filename_from_model, filename_from_object
from utilities.jinja2 import render_jinja2
__all__ = (
'PythonModuleMixin',
'RenderTemplateMixin',
)
@ -66,3 +75,87 @@ class PythonModuleMixin:
loader.exec_module(module)
return module
class RenderTemplateMixin(models.Model):
"""
Enables support for rendering templates.
"""
template_code = models.TextField(
verbose_name=_('template code'),
help_text=_('Jinja template code.')
)
environment_params = models.JSONField(
verbose_name=_('environment parameters'),
blank=True,
null=True,
default=dict,
help_text=_(
'Any <a href="{url}">additional parameters</a> to pass when constructing the Jinja2 environment.'
).format(url='https://jinja.palletsprojects.com/en/stable/api/#jinja2.Environment')
)
mime_type = models.CharField(
max_length=50,
blank=True,
verbose_name=_('MIME type'),
help_text=_('Defaults to <code>{default}</code>').format(
default=DEFAULT_MIME_TYPE
),
)
file_name = models.CharField(
max_length=200,
blank=True,
help_text=_('Filename to give to the rendered export file')
)
file_extension = models.CharField(
verbose_name=_('file extension'),
max_length=15,
blank=True,
help_text=_('Extension to append to the rendered filename')
)
as_attachment = models.BooleanField(
verbose_name=_('as attachment'),
default=True,
help_text=_("Download file as attachment")
)
class Meta:
abstract = True
def get_context(self, context=None, queryset=None):
raise NotImplementedError(_("{class_name} must implement a get_context() method.").format(
class_name=self.__class__
))
def render(self, context=None, queryset=None):
"""
Render the template with the provided context. The context is passed to the Jinja2 environment as a dictionary.
"""
context = self.get_context(context=context, queryset=queryset)
env_params = self.environment_params or {}
output = render_jinja2(self.template_code, context, env_params)
# Replace CRLF-style line terminators
output = output.replace('\r\n', '\n')
return output
def render_to_response(self, context=None, queryset=None):
output = self.render(context=context, queryset=queryset)
mime_type = self.mime_type or DEFAULT_MIME_TYPE
# Build the response
response = HttpResponse(output, content_type=mime_type)
if self.as_attachment:
extension = f'.{self.file_extension}' if self.file_extension else ''
if queryset:
filename = self.file_name or filename_from_model(queryset.model)
elif context:
filename = self.file_name or filename_from_object(context)
else:
filename = self.file_name or "template"
full_filename = f'{filename}{extension}'
response['Content-Disposition'] = f'attachment; filename="{full_filename}"'
return response

View File

@ -16,11 +16,12 @@ from extras.choices import *
from extras.conditions import ConditionSet
from extras.constants import *
from extras.utils import image_upload
from extras.models.mixins import RenderTemplateMixin
from netbox.config import get_config
from netbox.events import get_event_type_choices
from netbox.models import ChangeLoggedModel
from netbox.models.features import (
CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin, RenderMixin
CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin
)
from utilities.html import clean_html
from utilities.jinja2 import render_jinja2
@ -381,7 +382,7 @@ class CustomLink(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
}
class ExportTemplate(SyncedDataMixin, CloningMixin, ExportTemplatesMixin, ChangeLoggedModel, RenderMixin):
class ExportTemplate(SyncedDataMixin, CloningMixin, ExportTemplatesMixin, ChangeLoggedModel, RenderTemplateMixin):
object_types = models.ManyToManyField(
to='core.ObjectType',
related_name='export_templates',

View File

@ -6,7 +6,6 @@ from django.contrib.contenttypes.fields import GenericRelation
from django.core.validators import ValidationError
from django.db import models
from django.db.models import Q
from django.http import HttpResponse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from taggit.managers import TaggableManager
@ -14,12 +13,11 @@ from taggit.managers import TaggableManager
from core.choices import JobStatusChoices, ObjectChangeActionChoices
from core.models import ObjectType
from extras.choices import *
from extras.constants import CUSTOMFIELD_EMPTY_VALUES, DEFAULT_MIME_TYPE
from extras.utils import is_taggable, filename_from_model, filename_from_object
from extras.constants import CUSTOMFIELD_EMPTY_VALUES
from extras.utils import is_taggable
from netbox.config import get_config
from netbox.registry import registry
from netbox.signals import post_clean
from utilities.jinja2 import render_jinja2
from utilities.json import CustomFieldJSONEncoder
from utilities.serialization import serialize_object
from utilities.views import register_model_view
@ -39,7 +37,6 @@ __all__ = (
'JournalingMixin',
'NotificationsMixin',
'SyncedDataMixin',
'RenderMixin',
'TagsMixin',
'register_models',
)
@ -340,89 +337,6 @@ class ExportTemplatesMixin(models.Model):
abstract = True
class RenderMixin(models.Model):
"""
Enables support for rendering templates.
"""
template_code = models.TextField(
verbose_name=_('template code'),
help_text=_('Jinja2 template code.')
)
environment_params = models.JSONField(
verbose_name=_('environment parameters'),
blank=True,
null=True,
default=dict,
help_text=_(
'Any <a href="https://jinja.palletsprojects.com/en/3.1.x/api/#jinja2.Environment">additional parameters</a>'
' to pass when constructing the Jinja2 environment.'
)
)
mime_type = models.CharField(
max_length=50,
blank=True,
verbose_name=_('MIME type'),
help_text=_('Defaults to <code>{default_mime_type}<code>').format(
default_mime_type=DEFAULT_MIME_TYPE
),
)
file_name = models.CharField(
max_length=200,
blank=True,
help_text=_('Filename to give to the rendered export file')
)
file_extension = models.CharField(
verbose_name=_('file extension'),
max_length=15,
blank=True,
help_text=_('Extension to append to the rendered filename')
)
as_attachment = models.BooleanField(
verbose_name=_('as attachment'),
default=True,
help_text=_("Download file as attachment")
)
class Meta:
abstract = True
def get_context(self, context=None, queryset=None):
raise NotImplementedError(_("{class_name} must implement a get_context() method.").format(
class_name=self.__class__
))
def render(self, context=None, queryset=None):
"""
Render the template with the provided context. The context is passed to the Jinja2 environment as a dictionary.
"""
context = self.get_context(context=context, queryset=queryset)
env_params = self.environment_params or {}
output = render_jinja2(self.template_code, context, env_params)
# Replace CRLF-style line terminators
output = output.replace('\r\n', '\n')
return output
def render_to_response(self, context=None, queryset=None):
output = self.render(context=context, queryset=queryset)
mime_type = self.mime_type or DEFAULT_MIME_TYPE
# Build the response
response = HttpResponse(output, content_type=mime_type)
if self.as_attachment:
extension = f'.{self.file_extension}' if self.file_extension else ''
if queryset:
filename = self.file_name or filename_from_model(queryset.model)
elif context:
filename = self.file_name or filename_from_object(context)
full_filename = f'{filename}{extension}'
response['Content-Disposition'] = f'attachment; filename="{full_filename}"'
return response
class ImageAttachmentsMixin(models.Model):
"""
Enables the assignments of ImageAttachments.