Răsfoiți Sursa

Fixes #23652: Resolve event type choices lazily in event rule forms

Arthur 19 ore în urmă
părinte
comite
cd5acdedba

+ 1 - 1
netbox/extras/forms/bulk_edit.py

@@ -302,7 +302,7 @@ class EventRuleBulkEditForm(OwnerMixin, NetBoxModelBulkEditForm):
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
     event_types = forms.MultipleChoiceField(
     event_types = forms.MultipleChoiceField(
-        choices=get_event_type_choices(),
+        choices=get_event_type_choices,
         required=False,
         required=False,
         label=_('Event types')
         label=_('Event types')
     )
     )

+ 1 - 1
netbox/extras/forms/bulk_import.py

@@ -317,7 +317,7 @@ class EventRuleImportForm(OwnerCSVMixin, NetBoxModelImportForm):
         help_text=_("One or more assigned object types")
         help_text=_("One or more assigned object types")
     )
     )
     event_types = CSVMultipleChoiceField(
     event_types = CSVMultipleChoiceField(
-        choices=get_event_type_choices(),
+        choices=get_event_type_choices,
         label=_('Event types'),
         label=_('Event types'),
         help_text=_('The event type(s) which will trigger this rule')
         help_text=_('The event type(s) which will trigger this rule')
     )
     )

+ 1 - 1
netbox/extras/forms/model_forms.py

@@ -607,7 +607,7 @@ class EventRuleForm(OwnerMixin, NetBoxModelForm):
         queryset=ObjectType.objects.with_feature('event_rules'),
         queryset=ObjectType.objects.with_feature('event_rules'),
     )
     )
     event_types = MultipleChoiceField(
     event_types = MultipleChoiceField(
-        choices=get_event_type_choices(),
+        choices=get_event_type_choices,
         label=_('Event types')
         label=_('Event types')
     )
     )
     action_choice = ChoiceField(
     action_choice = ChoiceField(

+ 37 - 0
netbox/extras/tests/test_forms.py

@@ -12,12 +12,14 @@ from dcim.forms import SiteForm
 from dcim.models import Site
 from dcim.models import Site
 from extras.choices import CustomFieldTypeChoices, EventRuleActionChoices
 from extras.choices import CustomFieldTypeChoices, EventRuleActionChoices
 from extras.forms import SavedFilterForm, TableConfigBulkEditForm, TableConfigForm
 from extras.forms import SavedFilterForm, TableConfigBulkEditForm, TableConfigForm
+from extras.forms.bulk_edit import EventRuleBulkEditForm
 from extras.forms.bulk_import import EventRuleImportForm
 from extras.forms.bulk_import import EventRuleImportForm
 from extras.forms.filtersets import EventRuleFilterForm
 from extras.forms.filtersets import EventRuleFilterForm
 from extras.forms.model_forms import CustomFieldChoiceSetForm, EventRuleForm
 from extras.forms.model_forms import CustomFieldChoiceSetForm, EventRuleForm
 from extras.forms.scripts import ScriptFileForm
 from extras.forms.scripts import ScriptFileForm
 from extras.models import CustomField, CustomFieldChoiceSet, EventRule, NotificationGroup, Script, ScriptModule, Webhook
 from extras.models import CustomField, CustomFieldChoiceSet, EventRule, NotificationGroup, Script, ScriptModule, Webhook
 from netbox.event_rules import EventRuleAction, register_event_rule_action
 from netbox.event_rules import EventRuleAction, register_event_rule_action
+from netbox.events import EventType
 from netbox.registry import registry
 from netbox.registry import registry
 from utilities.forms.widgets import HTMXSelect
 from utilities.forms.widgets import HTMXSelect
 
 
@@ -522,6 +524,41 @@ class EventRuleFilterFormTestCase(TestCase):
         self.assertIn(None, choices)  # The blank choice is retained
         self.assertIn(None, choices)  # The blank choice is retained
 
 
 
 
+class EventRuleEventTypeChoicesTestCase(TestCase):
+    """
+    Event types registered after the forms module is imported (e.g. in a plugin's ready()) must be
+    offered by every event rule form.
+    """
+
+    def setUp(self):
+        EventType('test_late_event', 'Late event').register()
+        self.addCleanup(registry['event_types'].pop, 'test_late_event', None)
+
+    def test_model_form_offers_late_event_type(self):
+        choices = dict(EventRuleForm().fields['event_types'].choices)
+        self.assertEqual(choices.get('test_late_event'), 'Late event')
+
+    def test_bulk_edit_form_offers_late_event_type(self):
+        choices = dict(EventRuleBulkEditForm().fields['event_types'].choices)
+        self.assertEqual(choices.get('test_late_event'), 'Late event')
+
+    def test_filter_form_offers_late_event_type(self):
+        choices = dict(EventRuleFilterForm().fields['event_type'].choices)
+        self.assertEqual(choices.get('test_late_event'), 'Late event')
+
+    def test_import_form_accepts_late_event_type(self):
+        webhook = Webhook.objects.create(name='Late Event Webhook', payload_url='http://localhost:9000/')
+        form = EventRuleImportForm(data={
+            'name': 'Late Event Rule',
+            'object_types': 'dcim.site',
+            'event_types': 'test_late_event',
+            'action_type': EventRuleActionChoices.WEBHOOK,
+            'action_object': webhook.name,
+        })
+        self.assertTrue(form.is_valid(), form.errors)
+        self.assertEqual(form.cleaned_data['event_types'], ['test_late_event'])
+
+
 class EventRuleImportFormTestCase(TestCase):
 class EventRuleImportFormTestCase(TestCase):
     """
     """
     EventRuleImportForm resolves action_object via each registered action's resolve_import_object()
     EventRuleImportForm resolves action_object via each registered action's resolve_import_object()

+ 5 - 1
netbox/utilities/forms/fields/csv.py

@@ -37,7 +37,11 @@ class CSVChoicesMixin:
 
 
     def __init__(self, *, choices=(), **kwargs):
     def __init__(self, *, choices=(), **kwargs):
         super().__init__(choices=choices, **kwargs)
         super().__init__(choices=choices, **kwargs)
-        self.choices = unpack_grouped_choices(choices)
+        # Keep callable choices lazy so they're resolved on each access
+        if callable(choices):
+            self.choices = lambda: unpack_grouped_choices(choices())
+        else:
+            self.choices = unpack_grouped_choices(choices)
 
 
 
 
 class CSVChoiceField(CSVChoicesMixin, forms.ChoiceField):
 class CSVChoiceField(CSVChoicesMixin, forms.ChoiceField):