Repository: netbox-community/netbox

Issue:
Add support for jinja2 custom filters for webhooks
### NetBox version

v3.2.3

### Feature type

Change to existing functionality

### Proposed functionality

When using webhooks, I'd like to have the ability to add some custom jinja2 filters. 
Ansible comes with some nice ones, such as `to_nice_yaml` etc. however, I'd like to have the ability to add filters where you see fit. 

Maybe something like https://github.com/tbotnz/netpalm-admin would work, where you can just paste the filter into the GUI and it'll be uploaded to the netbox instance, failing that, we could also add a `jinja2_filters` folder somewhere, where the user could just add the filters there. Similarly to the `scripts` design in Netbox. 

### Use case

It would allow users to customise the payload better when using webhooks. 

### Database changes

Not sure.

### External dependencies

Jinja2


Discussion:
I think it makes sense to allow injecting custom jinja2 filters. I think the only implementation that would make sense for a feature is this is that we just add a configuration option similar to what we have for Custom Validation (see. [Direct Class Reference](https://docs.netbox.dev/en/stable/customization/custom-validation/)).

Allowing you to have something like this in your configuration.py (the function can of course be included from wherever).

```
def to_lower(input):
    return input.lower()

JINJA2_FILTERS = [to_lower]
````

Would that be fine for your usecase @tom0010 ? Having a field to paste code is a little icky for a feature that will be used by few.
I think that would work.
Thanks @kkthxbye-code 🙂

Edit: also, would this allow to use these filters elsewhere, and not just in webhooks, maybe scripts too? 
@tom0010 - It will be avaliable where jinja2 is used which is: 

* Custom Links (url and label)
* Webhooks (url, headers, body)
* Export templates 

Resolving patch:
diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py
--- a/netbox/netbox/settings.py
+++ b/netbox/netbox/settings.py
@@ -96,6 +96,7 @@
 FIELD_CHOICES = getattr(configuration, 'FIELD_CHOICES', {})
 HTTP_PROXIES = getattr(configuration, 'HTTP_PROXIES', None)
 INTERNAL_IPS = getattr(configuration, 'INTERNAL_IPS', ('127.0.0.1', '::1'))
+JINJA2_FILTERS = getattr(configuration, 'JINJA2_FILTERS', {})
 LOGGING = getattr(configuration, 'LOGGING', {})
 LOGIN_PERSISTENCE = getattr(configuration, 'LOGIN_PERSISTENCE', False)
 LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', False)
diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py
--- a/netbox/utilities/utils.py
+++ b/netbox/utilities/utils.py
@@ -14,6 +14,7 @@
 from dcim.choices import CableLengthUnitChoices
 from extras.plugins import PluginConfig
 from extras.utils import is_taggable
+from netbox.config import get_config
 from utilities.constants import HTTP_REQUEST_META_SAFE_COPY
 
 
@@ -257,7 +258,9 @@ def render_jinja2(template_code, context):
     """
     Render a Jinja2 template with the provided context. Return the rendered content.
     """
-    return SandboxedEnvironment().from_string(source=template_code).render(**context)
+    environment = SandboxedEnvironment()
+    environment.filters.update(get_config().JINJA2_FILTERS)
+    return environment.from_string(source=template_code).render(**context)
 
 
 def prepare_cloned_fields(instance):


Test patch: