Script classes silently fail validation when inherited required fields are omitted from fieldsets #11378

Closed
opened 2025-12-29 21:44:23 +01:00 by adam · 5 comments
Owner

Originally created by @rizlas on GitHub (Jul 14, 2025).

Originally assigned to: @jnovinger on GitHub.

Deployment Type

Self-hosted

NetBox Version

v4.2.9

Python Version

3.12

Steps to Reproduce

When a script class inherits required fields (e.g. via a shared mixin), but those fields are not listed in its Meta.fieldsets, the UI form fails validation silently. The form is not rendered properly, the user sees no error, and the script cannot be executed—making the issue hard to debug.

Use case

  • Common logic and script variables are reused via mixins.
  • Some scripts intentionally exclude certain inherited fields from the UI by omitting them from fieldsets.
  • However, if an inherited field is defined as required=True, form validation fails even if that field is hidden, and no feedback is presented to the user.

Script code example:

Base mixin (note: bar field is required by default)

class MyScriptMixin:
    class Meta:
        commit_default = False
        fieldsets = (
            (
                "Section Foo",
                (
                    "bar",
                ),
            ),
            (
                "Logging",
                ("debug_mode",),
            ),
        )

    bar = IntegerVar(
        0,
        30,
        description="Bar Integer Field",
        default=30,
    )

Actual NetBox Script

class MyScript(MyScriptMixin, Script):
    class Meta:
        name = "My script"
        description = "My script descpription"
        commit_default = False
        fieldsets = (
            (
                "Limit",
                (
                    "vrf_selector",
                    "tag_selector",
                ),
            ),
            (
                "Logging",
                ("debug_mode",),
            ),
        )

    vrf_selector = MultiObjectVar(
        label="VRF selector",
        description="Limit script to only selected VRFs",
        model=VRF,
        required=False,
    )

    tag_selector = MultiObjectVar(
        label="Tag selector",
        description="Limit script to only IP addresses tagged with selected tags",
        model=Tag,
        required=False,
    )

Expected Behavior

Form validation errors should be visible in the UI (e.g. via Django messages or inline form field errors).

Observed Behavior

When clicking "Run Script", the page is simply reloaded with no visible error or feedback to the user. The script form appears to reset, and no indication is given that validation failed. This creates confusion, as it appears that the script did not run but provides no explanation why.

I would suggest adding the following condition at this line to improve form error visibility:

         elif not form.is_valid():
            messages.error(request, form.errors.as_text())

This would provide immediate feedback when a script form fails validation, rather than silently reloading the page without explanation.
Would you be open to a PR implementing this?

Current behavior:

https://github.com/user-attachments/assets/9a144e7b-7fdc-4ddc-8efa-953ca776b9f9

Expected:

https://github.com/user-attachments/assets/665998ac-83f8-4f07-9cac-f7b95b1ec9da

Originally created by @rizlas on GitHub (Jul 14, 2025). Originally assigned to: @jnovinger on GitHub. ### Deployment Type Self-hosted ### NetBox Version v4.2.9 ### Python Version 3.12 ### Steps to Reproduce When a script class inherits required fields (e.g. via a shared mixin), but those fields are **not listed** in its `Meta.fieldsets`, the UI form fails validation silently. The form is not rendered properly, the user sees no error, and the script cannot be executed—making the issue hard to debug. ### Use case - Common logic and script variables are reused via mixins. - Some scripts intentionally exclude certain inherited fields from the UI by omitting them from `fieldsets`. - However, if an inherited field is defined as `required=True`, form validation fails even if that field is hidden, and no feedback is presented to the user. Script code example: Base mixin (note: `bar` field is required by default) ```python class MyScriptMixin: class Meta: commit_default = False fieldsets = ( ( "Section Foo", ( "bar", ), ), ( "Logging", ("debug_mode",), ), ) bar = IntegerVar( 0, 30, description="Bar Integer Field", default=30, ) ``` Actual NetBox Script ```python class MyScript(MyScriptMixin, Script): class Meta: name = "My script" description = "My script descpription" commit_default = False fieldsets = ( ( "Limit", ( "vrf_selector", "tag_selector", ), ), ( "Logging", ("debug_mode",), ), ) vrf_selector = MultiObjectVar( label="VRF selector", description="Limit script to only selected VRFs", model=VRF, required=False, ) tag_selector = MultiObjectVar( label="Tag selector", description="Limit script to only IP addresses tagged with selected tags", model=Tag, required=False, ) ``` ### Expected Behavior Form validation errors should be **visible** in the UI (e.g. via Django messages or inline form field errors). ### Observed Behavior When clicking "Run Script", the page is simply reloaded with no visible error or feedback to the user. The script form appears to reset, and no indication is given that validation failed. This creates confusion, as it appears that the script did not run but provides no explanation why. I would suggest adding the following condition [at this line](https://github.com/netbox-community/netbox/blob/main/netbox/extras/views.py#L1364) to improve form error visibility: ```python elif not form.is_valid(): messages.error(request, form.errors.as_text()) ``` This would provide immediate feedback when a script form fails validation, rather than silently reloading the page without explanation. Would you be open to a PR implementing this? Current behavior: https://github.com/user-attachments/assets/9a144e7b-7fdc-4ddc-8efa-953ca776b9f9 Expected: https://github.com/user-attachments/assets/665998ac-83f8-4f07-9cac-f7b95b1ec9da
adam added the type: bugstatus: acceptedseverity: low labels 2025-12-29 21:44:23 +01:00
adam closed this issue 2025-12-29 21:44:23 +01:00
Author
Owner

@jnovinger commented on GitHub (Jul 14, 2025):

@rizlas , please provide a full and complete set of steps to reproduce. Remember, each bug report must include detailed steps that someone else can follow on a clean, empty NetBox installation to reproduce the exact problem you're experiencing. These instructions should include the creation of any involved objects (including the full script module), any configuration changes, and complete accounting of the actions being taken.

When I make a fairly naive attempt to reverse engineer your script, trying to run it results in AttributeError: 'NoneType' object has no attribute 'label'.

@jnovinger commented on GitHub (Jul 14, 2025): @rizlas , please provide a full and complete set of steps to reproduce. Remember, each bug report must include detailed steps that someone else can follow on a clean, empty NetBox installation to reproduce the exact problem you're experiencing. These instructions should include the creation of any involved objects (including the full script module), any configuration changes, and complete accounting of the actions being taken. When I make a fairly naive attempt to reverse engineer your script, trying to run it results in `AttributeError: 'NoneType' object has no attribute 'label'`.
Author
Owner

@rizlas commented on GitHub (Jul 15, 2025):

Ok:

  1. Create test_base.py script
from extras.scripts import BooleanVar, IntegerVar


class MyScriptMixin:
    class Meta:
        commit_default = False
        fieldsets = (
            (
                "Section Foo",
                ("bar",),
            ),
            (
                "Logging",
                ("debug_mode",),
            ),
        )

    bar = IntegerVar(
        0,
        30,
        description="Bar Integer Field",
        default=30,
    )

    debug_mode = BooleanVar("Activate debug mode", default=False)
  1. Create test_script.py
from extras.models.tags import Tag
from extras.scripts import MultiObjectVar, Script
from ipam.models import VRF

from scripts.test_base import MyScriptMixin


class MyScript(MyScriptMixin, Script):
    class Meta:
        name = "My script"
        description = "My script descpription"
        commit_default = False
        fieldsets = (
            (
                "Limit",
                (
                    "vrf_selector",
                    "tag_selector",
                ),
            ),
            (
                "Logging",
                ("debug_mode",),
            ),
        )

    vrf_selector = MultiObjectVar(
        label="VRF selector",
        description="Limit script to only selected VRFs",
        model=VRF,
        required=False,
    )

    tag_selector = MultiObjectVar(
        label="Tag selector",
        description="Limit script to only IP addresses tagged with selected tags",
        model=Tag,
        required=False,
    )

    def run(self, data, commit):
        """Script entry point.

        Parameters
        ----------
        data : dict
            input from the user as dictionary
        commit : bool
            Flag that indicate if changes are commited to the database

        """
        self.log_info("Script is running")
  1. Add test_base.py to NetBox script

test_base.py appears like this:

Image

and thats fine and expected.

  1. Add test_script.py to NetBox script. Test script is loaded correctly
Image
  1. Try to run test script
@rizlas commented on GitHub (Jul 15, 2025): Ok: 1. Create `test_base.py` script ```python from extras.scripts import BooleanVar, IntegerVar class MyScriptMixin: class Meta: commit_default = False fieldsets = ( ( "Section Foo", ("bar",), ), ( "Logging", ("debug_mode",), ), ) bar = IntegerVar( 0, 30, description="Bar Integer Field", default=30, ) debug_mode = BooleanVar("Activate debug mode", default=False) ``` 2. Create `test_script.py` ```python from extras.models.tags import Tag from extras.scripts import MultiObjectVar, Script from ipam.models import VRF from scripts.test_base import MyScriptMixin class MyScript(MyScriptMixin, Script): class Meta: name = "My script" description = "My script descpription" commit_default = False fieldsets = ( ( "Limit", ( "vrf_selector", "tag_selector", ), ), ( "Logging", ("debug_mode",), ), ) vrf_selector = MultiObjectVar( label="VRF selector", description="Limit script to only selected VRFs", model=VRF, required=False, ) tag_selector = MultiObjectVar( label="Tag selector", description="Limit script to only IP addresses tagged with selected tags", model=Tag, required=False, ) def run(self, data, commit): """Script entry point. Parameters ---------- data : dict input from the user as dictionary commit : bool Flag that indicate if changes are commited to the database """ self.log_info("Script is running") ``` 3. Add `test_base.py` to NetBox script `test_base.py` appears like this: <img width="583" height="118" alt="Image" src="https://github.com/user-attachments/assets/0b1f618f-fba4-4493-a1fe-6d9b5494814d" /> and thats fine and expected. 4. Add `test_script.py` to NetBox script. Test script is loaded correctly <img width="741" height="149" alt="Image" src="https://github.com/user-attachments/assets/500b85e5-93a0-47ab-ac42-086fd29e244b" /> 5. Try to run test script
Author
Owner

@jnovinger commented on GitHub (Sep 12, 2025):

Thanks for the extra details, @rizlas . My apologies for letting this set for too long.

@jnovinger commented on GitHub (Sep 12, 2025): Thanks for the extra details, @rizlas . My apologies for letting this set for too long.
Author
Owner

@jnovinger commented on GitHub (Oct 21, 2025):

I'm pushing a fix that displays form validation errors when the script form fails validation in cases like this, so users now see a clear error message (e.g., "bar: This field is required.") instead of a silent page reload.

The underlying issue is that excluding required fields from fieldsets isn't a supported pattern in NetBox. When fields aren't in fieldsets, they're not rendered in the form at all, so there's no way for the validation to pass even if the field has a default value. The documentation states "Any fields not included in this iterable will not be displayed in the form."

That is, in fact, the reason that the error was not shown in the first place. Because the field is not in fieldsets, it is not rendered, meaning there's no where to display the field error that is generated.

If you need the ability to have hidden required fields with auto-applied defaults when excluded from fieldsets, please open a separate feature request describing that use case.

With the fix in place, validation failures are now visible rather than silent.

@jnovinger commented on GitHub (Oct 21, 2025): I'm pushing a fix that displays form validation errors when the script form fails validation in cases like this, so users now see a clear error message (e.g., "bar: This field is required.") instead of a silent page reload. The underlying issue is that excluding required fields from fieldsets isn't a supported pattern in NetBox. When fields aren't in fieldsets, they're not rendered in the form at all, so there's no way for the validation to pass even if the field has a default value. The documentation states "Any fields not included in this iterable will not be displayed in the form." That is, in fact, the reason that the error was not shown in the first place. Because the field is not in fieldsets, it is not rendered, meaning there's no where to display the field error that is generated. If you need the ability to have hidden required fields with auto-applied defaults when excluded from fieldsets, please open a separate feature request describing that use case. With the fix in place, validation failures are now visible rather than silent.
Author
Owner

@rizlas commented on GitHub (Oct 21, 2025):

That's fine for now and thanks for the fix!

@rizlas commented on GitHub (Oct 21, 2025): That's fine for now and thanks for the fix!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#11378