mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-08 08:38:16 -06:00
Initial work on #16886
This commit is contained in:
parent
b2c5a4639c
commit
8e2f459d10
3
docs/plugins/development/events.md
Normal file
3
docs/plugins/development/events.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Events
|
||||||
|
|
||||||
|
TODO
|
@ -142,6 +142,7 @@ nav:
|
|||||||
- Forms: 'plugins/development/forms.md'
|
- Forms: 'plugins/development/forms.md'
|
||||||
- Filters & Filter Sets: 'plugins/development/filtersets.md'
|
- Filters & Filter Sets: 'plugins/development/filtersets.md'
|
||||||
- Search: 'plugins/development/search.md'
|
- Search: 'plugins/development/search.md'
|
||||||
|
- Events: 'plugins/development/events.md'
|
||||||
- Data Backends: 'plugins/development/data-backends.md'
|
- Data Backends: 'plugins/development/data-backends.md'
|
||||||
- REST API: 'plugins/development/rest-api.md'
|
- REST API: 'plugins/development/rest-api.md'
|
||||||
- GraphQL API: 'plugins/development/graphql-api.md'
|
- GraphQL API: 'plugins/development/graphql-api.md'
|
||||||
|
@ -12,16 +12,18 @@ __all__ = (
|
|||||||
'OBJECT_UPDATED',
|
'OBJECT_UPDATED',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
NAMESPACE = 'netbox'
|
||||||
|
|
||||||
# Object events
|
# Object events
|
||||||
OBJECT_CREATED = 'object_created'
|
OBJECT_CREATED = f'{NAMESPACE}.object_created'
|
||||||
OBJECT_UPDATED = 'object_updated'
|
OBJECT_UPDATED = f'{NAMESPACE}.object_updated'
|
||||||
OBJECT_DELETED = 'object_deleted'
|
OBJECT_DELETED = f'{NAMESPACE}.object_deleted'
|
||||||
|
|
||||||
# Job events
|
# Job events
|
||||||
JOB_STARTED = 'job_started'
|
JOB_STARTED = f'{NAMESPACE}.job_started'
|
||||||
JOB_COMPLETED = 'job_completed'
|
JOB_COMPLETED = f'{NAMESPACE}.job_completed'
|
||||||
JOB_FAILED = 'job_failed'
|
JOB_FAILED = f'{NAMESPACE}.job_failed'
|
||||||
JOB_ERRORED = 'job_errored'
|
JOB_ERRORED = f'{NAMESPACE}.job_errored'
|
||||||
|
|
||||||
# Register core events
|
# Register core events
|
||||||
Event(name=OBJECT_CREATED, text=_('Object created')).register()
|
Event(name=OBJECT_CREATED, text=_('Object created')).register()
|
||||||
|
@ -34,9 +34,9 @@ class EventRuleSerializer(NetBoxModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = EventRule
|
model = EventRule
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'object_types', 'name', 'type_create', 'type_update', 'type_delete',
|
'id', 'url', 'display_url', 'display', 'object_types', 'name', 'enabled', 'event_types', 'conditions',
|
||||||
'type_job_start', 'type_job_end', 'enabled', 'conditions', 'action_type', 'action_object_type',
|
'action_type', 'action_object_type', 'action_object_id', 'action_object', 'description', 'custom_fields',
|
||||||
'action_object_id', 'action_object', 'description', 'custom_fields', 'tags', 'created', 'last_updated',
|
'tags', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ def process_event_queue(events):
|
|||||||
# Cache applicable Event Rules
|
# Cache applicable Event Rules
|
||||||
if object_type not in events_cache[action_flag]:
|
if object_type not in events_cache[action_flag]:
|
||||||
events_cache[action_flag][object_type] = EventRule.objects.filter(
|
events_cache[action_flag][object_type] = EventRule.objects.filter(
|
||||||
**{action_flag: True},
|
event_types__contains=[event['event_type']],
|
||||||
object_types=object_type,
|
object_types=object_type,
|
||||||
enabled=True
|
enabled=True
|
||||||
)
|
)
|
||||||
|
@ -99,6 +99,9 @@ class EventRuleFilterSet(NetBoxModelFilterSet):
|
|||||||
object_type = ContentTypeFilter(
|
object_type = ContentTypeFilter(
|
||||||
field_name='object_types'
|
field_name='object_types'
|
||||||
)
|
)
|
||||||
|
event_type = MultiValueCharFilter(
|
||||||
|
method='filter_event_type'
|
||||||
|
)
|
||||||
action_type = django_filters.MultipleChoiceFilter(
|
action_type = django_filters.MultipleChoiceFilter(
|
||||||
choices=EventRuleActionChoices
|
choices=EventRuleActionChoices
|
||||||
)
|
)
|
||||||
@ -108,8 +111,7 @@ class EventRuleFilterSet(NetBoxModelFilterSet):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = EventRule
|
model = EventRule
|
||||||
fields = (
|
fields = (
|
||||||
'id', 'name', 'type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end', 'enabled',
|
'id', 'name', 'enabled', 'action_type', 'description',
|
||||||
'action_type', 'description',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
@ -121,6 +123,9 @@ class EventRuleFilterSet(NetBoxModelFilterSet):
|
|||||||
Q(comments__icontains=value)
|
Q(comments__icontains=value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def filter_event_type(self, queryset, name, value):
|
||||||
|
return queryset.filter(event_types__overlap=value)
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldFilterSet(ChangeLoggedModelFilterSet):
|
class CustomFieldFilterSet(ChangeLoggedModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
from django.contrib.postgres.forms import SimpleArrayField
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
@ -248,33 +249,18 @@ class EventRuleBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
required=False,
|
required=False,
|
||||||
widget=BulkEditNullBooleanSelect()
|
widget=BulkEditNullBooleanSelect()
|
||||||
)
|
)
|
||||||
type_create = forms.NullBooleanField(
|
event_types = SimpleArrayField(
|
||||||
label=_('On create'),
|
label=_('Event types'),
|
||||||
required=False,
|
base_field=forms.CharField(),
|
||||||
widget=BulkEditNullBooleanSelect()
|
required=False
|
||||||
)
|
)
|
||||||
type_update = forms.NullBooleanField(
|
description = forms.CharField(
|
||||||
label=_('On update'),
|
label=_('Description'),
|
||||||
required=False,
|
max_length=200,
|
||||||
widget=BulkEditNullBooleanSelect()
|
required=False
|
||||||
)
|
|
||||||
type_delete = forms.NullBooleanField(
|
|
||||||
label=_('On delete'),
|
|
||||||
required=False,
|
|
||||||
widget=BulkEditNullBooleanSelect()
|
|
||||||
)
|
|
||||||
type_job_start = forms.NullBooleanField(
|
|
||||||
label=_('On job start'),
|
|
||||||
required=False,
|
|
||||||
widget=BulkEditNullBooleanSelect()
|
|
||||||
)
|
|
||||||
type_job_end = forms.NullBooleanField(
|
|
||||||
label=_('On job end'),
|
|
||||||
required=False,
|
|
||||||
widget=BulkEditNullBooleanSelect()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
nullable_fields = ('description', 'conditions',)
|
nullable_fields = ('description', 'conditions')
|
||||||
|
|
||||||
|
|
||||||
class TagBulkEditForm(BulkEditForm):
|
class TagBulkEditForm(BulkEditForm):
|
||||||
|
@ -196,8 +196,8 @@ class EventRuleImportForm(NetBoxModelImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = EventRule
|
model = EventRule
|
||||||
fields = (
|
fields = (
|
||||||
'name', 'description', 'enabled', 'conditions', 'object_types', 'type_create', 'type_update',
|
'name', 'description', 'enabled', 'conditions', 'object_types', 'event_types', 'action_type',
|
||||||
'type_delete', 'type_job_start', 'type_job_end', 'action_type', 'action_object', 'comments', 'tags'
|
'action_object', 'comments', 'tags'
|
||||||
)
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
@ -6,6 +6,7 @@ from core.models import ObjectType, DataFile, DataSource
|
|||||||
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
|
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
|
from netbox.events import get_event_type_choices
|
||||||
from netbox.forms.base import NetBoxModelFilterSetForm
|
from netbox.forms.base import NetBoxModelFilterSetForm
|
||||||
from netbox.forms.mixins import SavedFiltersMixin
|
from netbox.forms.mixins import SavedFiltersMixin
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
@ -274,14 +275,18 @@ class EventRuleFilterForm(NetBoxModelFilterSetForm):
|
|||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('q', 'filter_id', 'tag'),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
FieldSet('object_type_id', 'action_type', 'enabled', name=_('Attributes')),
|
FieldSet('object_type_id', 'event_type', 'action_type', 'enabled', name=_('Attributes')),
|
||||||
FieldSet('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end', name=_('Events')),
|
|
||||||
)
|
)
|
||||||
object_type_id = ContentTypeMultipleChoiceField(
|
object_type_id = ContentTypeMultipleChoiceField(
|
||||||
queryset=ObjectType.objects.with_feature('event_rules'),
|
queryset=ObjectType.objects.with_feature('event_rules'),
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Object type')
|
label=_('Object type')
|
||||||
)
|
)
|
||||||
|
event_type = forms.MultipleChoiceField(
|
||||||
|
choices=get_event_type_choices,
|
||||||
|
required=False,
|
||||||
|
label=_('Event type')
|
||||||
|
)
|
||||||
action_type = forms.ChoiceField(
|
action_type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(EventRuleActionChoices),
|
choices=add_blank_choice(EventRuleActionChoices),
|
||||||
required=False,
|
required=False,
|
||||||
@ -294,41 +299,6 @@ class EventRuleFilterForm(NetBoxModelFilterSetForm):
|
|||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
type_create = forms.NullBooleanField(
|
|
||||||
required=False,
|
|
||||||
widget=forms.Select(
|
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
|
||||||
),
|
|
||||||
label=_('Object creations')
|
|
||||||
)
|
|
||||||
type_update = forms.NullBooleanField(
|
|
||||||
required=False,
|
|
||||||
widget=forms.Select(
|
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
|
||||||
),
|
|
||||||
label=_('Object updates')
|
|
||||||
)
|
|
||||||
type_delete = forms.NullBooleanField(
|
|
||||||
required=False,
|
|
||||||
widget=forms.Select(
|
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
|
||||||
),
|
|
||||||
label=_('Object deletions')
|
|
||||||
)
|
|
||||||
type_job_start = forms.NullBooleanField(
|
|
||||||
required=False,
|
|
||||||
widget=forms.Select(
|
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
|
||||||
),
|
|
||||||
label=_('Job starts')
|
|
||||||
)
|
|
||||||
type_job_end = forms.NullBooleanField(
|
|
||||||
required=False,
|
|
||||||
widget=forms.Select(
|
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
|
||||||
),
|
|
||||||
label=_('Job terminations')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TagFilterForm(SavedFiltersMixin, FilterForm):
|
class TagFilterForm(SavedFiltersMixin, FilterForm):
|
||||||
|
@ -2,6 +2,7 @@ import json
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.contrib.postgres.forms import SimpleArrayField
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
@ -303,6 +304,10 @@ class EventRuleForm(NetBoxModelForm):
|
|||||||
label=_('Object types'),
|
label=_('Object types'),
|
||||||
queryset=ObjectType.objects.with_feature('event_rules'),
|
queryset=ObjectType.objects.with_feature('event_rules'),
|
||||||
)
|
)
|
||||||
|
event_types = SimpleArrayField(
|
||||||
|
label=_('Event types'),
|
||||||
|
base_field=forms.CharField()
|
||||||
|
)
|
||||||
action_choice = forms.ChoiceField(
|
action_choice = forms.ChoiceField(
|
||||||
label=_('Action choice'),
|
label=_('Action choice'),
|
||||||
choices=[]
|
choices=[]
|
||||||
@ -319,7 +324,7 @@ class EventRuleForm(NetBoxModelForm):
|
|||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('name', 'description', 'object_types', 'enabled', 'tags', name=_('Event Rule')),
|
FieldSet('name', 'description', 'object_types', 'enabled', 'tags', name=_('Event Rule')),
|
||||||
FieldSet('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end', name=_('Events')),
|
FieldSet('event_types', name=_('Event Types')),
|
||||||
FieldSet('conditions', name=_('Conditions')),
|
FieldSet('conditions', name=_('Conditions')),
|
||||||
FieldSet('action_type', 'action_choice', 'action_data', name=_('Action')),
|
FieldSet('action_type', 'action_choice', 'action_data', name=_('Action')),
|
||||||
)
|
)
|
||||||
@ -327,17 +332,9 @@ class EventRuleForm(NetBoxModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = EventRule
|
model = EventRule
|
||||||
fields = (
|
fields = (
|
||||||
'object_types', 'name', 'description', 'type_create', 'type_update', 'type_delete', 'type_job_start',
|
'object_types', 'name', 'description', 'enabled', 'event_types', 'conditions', 'action_type',
|
||||||
'type_job_end', 'enabled', 'conditions', 'action_type', 'action_object_type', 'action_object_id',
|
'action_object_type', 'action_object_id', 'action_data', 'comments', 'tags'
|
||||||
'action_data', 'comments', 'tags'
|
|
||||||
)
|
)
|
||||||
labels = {
|
|
||||||
'type_create': _('Creations'),
|
|
||||||
'type_update': _('Updates'),
|
|
||||||
'type_delete': _('Deletions'),
|
|
||||||
'type_job_start': _('Job executions'),
|
|
||||||
'type_job_end': _('Job terminations'),
|
|
||||||
}
|
|
||||||
widgets = {
|
widgets = {
|
||||||
'conditions': forms.Textarea(attrs={'class': 'font-monospace'}),
|
'conditions': forms.Textarea(attrs={'class': 'font-monospace'}),
|
||||||
'action_type': HTMXSelect(),
|
'action_type': HTMXSelect(),
|
||||||
|
@ -9,7 +9,7 @@ __all__ = (
|
|||||||
'CustomFieldFilter',
|
'CustomFieldFilter',
|
||||||
'CustomFieldChoiceSetFilter',
|
'CustomFieldChoiceSetFilter',
|
||||||
'CustomLinkFilter',
|
'CustomLinkFilter',
|
||||||
'EventRuleFilter',
|
# 'EventRuleFilter',
|
||||||
'ExportTemplateFilter',
|
'ExportTemplateFilter',
|
||||||
'ImageAttachmentFilter',
|
'ImageAttachmentFilter',
|
||||||
'JournalEntryFilter',
|
'JournalEntryFilter',
|
||||||
@ -92,7 +92,7 @@ class WebhookFilter(BaseFilterMixin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.filter(models.EventRule, lookups=True)
|
# @strawberry_django.filter(models.EventRule, lookups=True)
|
||||||
@autotype_decorator(filtersets.EventRuleFilterSet)
|
# @autotype_decorator(filtersets.EventRuleFilterSet)
|
||||||
class EventRuleFilter(BaseFilterMixin):
|
# class EventRuleFilter(BaseFilterMixin):
|
||||||
pass
|
# pass
|
||||||
|
@ -182,7 +182,8 @@ class WebhookType(OrganizationalObjectType):
|
|||||||
@strawberry_django.type(
|
@strawberry_django.type(
|
||||||
models.EventRule,
|
models.EventRule,
|
||||||
exclude=['content_types',],
|
exclude=['content_types',],
|
||||||
filters=EventRuleFilter
|
# TODO: Fix GraphQL filter
|
||||||
|
# filters=EventRuleFilter
|
||||||
)
|
)
|
||||||
class EventRuleType(OrganizationalObjectType):
|
class EventRuleType(OrganizationalObjectType):
|
||||||
action_object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
|
action_object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
|
||||||
|
75
netbox/extras/migrations/0119_eventrule_event_types.py
Normal file
75
netbox/extras/migrations/0119_eventrule_event_types.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import django.contrib.postgres.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
from core.events import *
|
||||||
|
|
||||||
|
|
||||||
|
def set_event_types(apps, schema_editor):
|
||||||
|
EventRule = apps.get_model('extras', 'EventRule')
|
||||||
|
event_rules = EventRule.objects.all()
|
||||||
|
|
||||||
|
for event_rule in event_rules:
|
||||||
|
event_rule.event_types = []
|
||||||
|
if event_rule.type_create:
|
||||||
|
event_rule.event_types.append(OBJECT_CREATED)
|
||||||
|
if event_rule.type_update:
|
||||||
|
event_rule.event_types.append(OBJECT_UPDATED)
|
||||||
|
if event_rule.type_delete:
|
||||||
|
event_rule.event_types.append(OBJECT_DELETED)
|
||||||
|
if event_rule.type_job_start:
|
||||||
|
event_rule.event_types.append(JOB_STARTED)
|
||||||
|
if event_rule.type_job_end:
|
||||||
|
# Map type_job_end to all job termination events
|
||||||
|
event_rule.event_types.extend([JOB_COMPLETED, JOB_ERRORED, JOB_FAILED])
|
||||||
|
|
||||||
|
EventRule.objects.bulk_update(event_rules, ['event_types'])
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('extras', '0118_notifications'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='eventrule',
|
||||||
|
name='event_types',
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.CharField(max_length=50),
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
size=None
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(
|
||||||
|
code=set_event_types,
|
||||||
|
reverse_code=migrations.RunPython.noop
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventrule',
|
||||||
|
name='event_types',
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=50), size=None),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='eventrule',
|
||||||
|
name='type_create',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='eventrule',
|
||||||
|
name='type_delete',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='eventrule',
|
||||||
|
name='type_job_end',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='eventrule',
|
||||||
|
name='type_job_start',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='eventrule',
|
||||||
|
name='type_update',
|
||||||
|
),
|
||||||
|
]
|
@ -3,6 +3,7 @@ import urllib.parse
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.core.validators import ValidationError
|
from django.core.validators import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
@ -60,30 +61,9 @@ class EventRule(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, ChangeLogged
|
|||||||
max_length=200,
|
max_length=200,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
type_create = models.BooleanField(
|
event_types = ArrayField(
|
||||||
verbose_name=_('on create'),
|
base_field=models.CharField(max_length=50),
|
||||||
default=False,
|
help_text=_("The types of event which will trigger this rule.")
|
||||||
help_text=_("Triggers when a matching object is created.")
|
|
||||||
)
|
|
||||||
type_update = models.BooleanField(
|
|
||||||
verbose_name=_('on update'),
|
|
||||||
default=False,
|
|
||||||
help_text=_("Triggers when a matching object is updated.")
|
|
||||||
)
|
|
||||||
type_delete = models.BooleanField(
|
|
||||||
verbose_name=_('on delete'),
|
|
||||||
default=False,
|
|
||||||
help_text=_("Triggers when a matching object is deleted.")
|
|
||||||
)
|
|
||||||
type_job_start = models.BooleanField(
|
|
||||||
verbose_name=_('on job start'),
|
|
||||||
default=False,
|
|
||||||
help_text=_("Triggers when a job for a matching object is started.")
|
|
||||||
)
|
|
||||||
type_job_end = models.BooleanField(
|
|
||||||
verbose_name=_('on job end'),
|
|
||||||
default=False,
|
|
||||||
help_text=_("Triggers when a job for a matching object terminates.")
|
|
||||||
)
|
)
|
||||||
enabled = models.BooleanField(
|
enabled = models.BooleanField(
|
||||||
verbose_name=_('enabled'),
|
verbose_name=_('enabled'),
|
||||||
@ -145,9 +125,7 @@ class EventRule(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, ChangeLogged
|
|||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
# At least one action type must be selected
|
# At least one action type must be selected
|
||||||
if not any([
|
if not self.event_types:
|
||||||
self.type_create, self.type_update, self.type_delete, self.type_job_start, self.type_job_end
|
|
||||||
]):
|
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("At least one event type must be selected: create, update, delete, job start, and/or job end.")
|
_("At least one event type must be selected: create, update, delete, job start, and/or job end.")
|
||||||
)
|
)
|
||||||
|
@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
from netbox.constants import EMPTY_TABLE_TEXT
|
from netbox.constants import EMPTY_TABLE_TEXT
|
||||||
|
from netbox.events import get_event_text
|
||||||
from netbox.tables import BaseTable, NetBoxTable, columns
|
from netbox.tables import BaseTable, NetBoxTable, columns
|
||||||
from .columns import NotificationActionsColumn
|
from .columns import NotificationActionsColumn
|
||||||
|
|
||||||
@ -399,20 +400,10 @@ class EventRuleTable(NetBoxTable):
|
|||||||
enabled = columns.BooleanColumn(
|
enabled = columns.BooleanColumn(
|
||||||
verbose_name=_('Enabled'),
|
verbose_name=_('Enabled'),
|
||||||
)
|
)
|
||||||
type_create = columns.BooleanColumn(
|
event_types = columns.ArrayColumn(
|
||||||
verbose_name=_('Create')
|
verbose_name=_('Event Types'),
|
||||||
)
|
func=get_event_text,
|
||||||
type_update = columns.BooleanColumn(
|
orderable=False
|
||||||
verbose_name=_('Update')
|
|
||||||
)
|
|
||||||
type_delete = columns.BooleanColumn(
|
|
||||||
verbose_name=_('Delete')
|
|
||||||
)
|
|
||||||
type_job_start = columns.BooleanColumn(
|
|
||||||
verbose_name=_('Job Start')
|
|
||||||
)
|
|
||||||
type_job_end = columns.BooleanColumn(
|
|
||||||
verbose_name=_('Job End')
|
|
||||||
)
|
)
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='extras:webhook_list'
|
url_name='extras:webhook_list'
|
||||||
@ -422,12 +413,10 @@ class EventRuleTable(NetBoxTable):
|
|||||||
model = EventRule
|
model = EventRule
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'enabled', 'description', 'action_type', 'action_object', 'object_types',
|
'pk', 'id', 'name', 'enabled', 'description', 'action_type', 'action_object', 'object_types',
|
||||||
'type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end', 'tags', 'created',
|
'event_types', 'tags', 'created', 'last_updated',
|
||||||
'last_updated',
|
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'enabled', 'action_type', 'action_object', 'object_types', 'type_create', 'type_update',
|
'pk', 'name', 'enabled', 'action_type', 'action_object', 'object_types', 'event_types',
|
||||||
'type_delete', 'type_job_start', 'type_job_end',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.timezone import make_aware
|
from django.utils.timezone import make_aware
|
||||||
@ -13,6 +12,7 @@ from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Loca
|
|||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
from extras.scripts import BooleanVar, IntegerVar, Script as PythonClass, StringVar
|
from extras.scripts import BooleanVar, IntegerVar, Script as PythonClass, StringVar
|
||||||
|
from netbox.events import *
|
||||||
from users.models import Group, User
|
from users.models import Group, User
|
||||||
from utilities.testing import APITestCase, APIViewTestCases
|
from utilities.testing import APITestCase, APIViewTestCases
|
||||||
|
|
||||||
@ -113,9 +113,9 @@ class EventRuleTest(APIViewTestCases.APIViewTestCase):
|
|||||||
Webhook.objects.bulk_create(webhooks)
|
Webhook.objects.bulk_create(webhooks)
|
||||||
|
|
||||||
event_rules = (
|
event_rules = (
|
||||||
EventRule(name='EventRule 1', type_create=True, action_object=webhooks[0]),
|
EventRule(name='EventRule 1', event_types=[OBJECT_CREATED], action_object=webhooks[0]),
|
||||||
EventRule(name='EventRule 2', type_create=True, action_object=webhooks[1]),
|
EventRule(name='EventRule 2', event_types=[OBJECT_CREATED], action_object=webhooks[1]),
|
||||||
EventRule(name='EventRule 3', type_create=True, action_object=webhooks[2]),
|
EventRule(name='EventRule 3', event_types=[OBJECT_CREATED], action_object=webhooks[2]),
|
||||||
)
|
)
|
||||||
EventRule.objects.bulk_create(event_rules)
|
EventRule.objects.bulk_create(event_rules)
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ class EventRuleTest(APIViewTestCases.APIViewTestCase):
|
|||||||
{
|
{
|
||||||
'name': 'EventRule 4',
|
'name': 'EventRule 4',
|
||||||
'object_types': ['dcim.device', 'dcim.devicetype'],
|
'object_types': ['dcim.device', 'dcim.devicetype'],
|
||||||
'type_create': True,
|
'event_types': [OBJECT_CREATED],
|
||||||
'action_type': EventRuleActionChoices.WEBHOOK,
|
'action_type': EventRuleActionChoices.WEBHOOK,
|
||||||
'action_object_type': 'extras.webhook',
|
'action_object_type': 'extras.webhook',
|
||||||
'action_object_id': webhooks[3].pk,
|
'action_object_id': webhooks[3].pk,
|
||||||
@ -131,7 +131,7 @@ class EventRuleTest(APIViewTestCases.APIViewTestCase):
|
|||||||
{
|
{
|
||||||
'name': 'EventRule 5',
|
'name': 'EventRule 5',
|
||||||
'object_types': ['dcim.device', 'dcim.devicetype'],
|
'object_types': ['dcim.device', 'dcim.devicetype'],
|
||||||
'type_create': True,
|
'event_types': [OBJECT_CREATED],
|
||||||
'action_type': EventRuleActionChoices.WEBHOOK,
|
'action_type': EventRuleActionChoices.WEBHOOK,
|
||||||
'action_object_type': 'extras.webhook',
|
'action_object_type': 'extras.webhook',
|
||||||
'action_object_id': webhooks[4].pk,
|
'action_object_id': webhooks[4].pk,
|
||||||
@ -139,7 +139,7 @@ class EventRuleTest(APIViewTestCases.APIViewTestCase):
|
|||||||
{
|
{
|
||||||
'name': 'EventRule 6',
|
'name': 'EventRule 6',
|
||||||
'object_types': ['dcim.device', 'dcim.devicetype'],
|
'object_types': ['dcim.device', 'dcim.devicetype'],
|
||||||
'type_create': True,
|
'event_types': [OBJECT_CREATED],
|
||||||
'action_type': EventRuleActionChoices.WEBHOOK,
|
'action_type': EventRuleActionChoices.WEBHOOK,
|
||||||
'action_object_type': 'extras.webhook',
|
'action_object_type': 'extras.webhook',
|
||||||
'action_object_id': webhooks[5].pk,
|
'action_object_id': webhooks[5].pk,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from core.events import *
|
||||||
from dcim.choices import SiteStatusChoices
|
from dcim.choices import SiteStatusChoices
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from extras.conditions import Condition, ConditionSet
|
from extras.conditions import Condition, ConditionSet
|
||||||
@ -230,8 +231,7 @@ class ConditionSetTest(TestCase):
|
|||||||
"""
|
"""
|
||||||
event_rule = EventRule(
|
event_rule = EventRule(
|
||||||
name='Event Rule 1',
|
name='Event Rule 1',
|
||||||
type_create=True,
|
event_types=[OBJECT_CREATED, OBJECT_UPDATED],
|
||||||
type_update=True,
|
|
||||||
conditions={
|
conditions={
|
||||||
'attr': 'status.value',
|
'attr': 'status.value',
|
||||||
'value': 'active',
|
'value': 'active',
|
||||||
@ -251,8 +251,7 @@ class ConditionSetTest(TestCase):
|
|||||||
"""
|
"""
|
||||||
event_rule = EventRule(
|
event_rule = EventRule(
|
||||||
name='Event Rule 1',
|
name='Event Rule 1',
|
||||||
type_create=True,
|
event_types=[OBJECT_CREATED, OBJECT_UPDATED],
|
||||||
type_update=True,
|
|
||||||
conditions={
|
conditions={
|
||||||
"attr": "status.value",
|
"attr": "status.value",
|
||||||
"value": ["planned", "staging"],
|
"value": ["planned", "staging"],
|
||||||
@ -273,8 +272,7 @@ class ConditionSetTest(TestCase):
|
|||||||
"""
|
"""
|
||||||
event_rule = EventRule(
|
event_rule = EventRule(
|
||||||
name='Event Rule 1',
|
name='Event Rule 1',
|
||||||
type_create=True,
|
event_types=[OBJECT_CREATED, OBJECT_UPDATED],
|
||||||
type_update=True,
|
|
||||||
conditions={
|
conditions={
|
||||||
"attr": "status.value",
|
"attr": "status.value",
|
||||||
"value": ["planned", "staging"],
|
"value": ["planned", "staging"],
|
||||||
@ -300,8 +298,7 @@ class ConditionSetTest(TestCase):
|
|||||||
webhook = Webhook.objects.create(name='Webhook 100', payload_url='http://example.com/?1', http_method='POST')
|
webhook = Webhook.objects.create(name='Webhook 100', payload_url='http://example.com/?1', http_method='POST')
|
||||||
form = EventRuleForm({
|
form = EventRuleForm({
|
||||||
"name": "Event Rule 1",
|
"name": "Event Rule 1",
|
||||||
"type_create": True,
|
'event_types': [OBJECT_CREATED, OBJECT_UPDATED],
|
||||||
"type_update": True,
|
|
||||||
"action_object_type": ct.pk,
|
"action_object_type": ct.pk,
|
||||||
"action_type": "webhook",
|
"action_type": "webhook",
|
||||||
"action_choice": webhook.pk,
|
"action_choice": webhook.pk,
|
||||||
|
@ -46,22 +46,22 @@ class EventRuleTest(APITestCase):
|
|||||||
webhook_type = ObjectType.objects.get(app_label='extras', model='webhook')
|
webhook_type = ObjectType.objects.get(app_label='extras', model='webhook')
|
||||||
event_rules = EventRule.objects.bulk_create((
|
event_rules = EventRule.objects.bulk_create((
|
||||||
EventRule(
|
EventRule(
|
||||||
name='Webhook Event 1',
|
name='Event Rule 1',
|
||||||
type_create=True,
|
event_types=[OBJECT_CREATED],
|
||||||
action_type=EventRuleActionChoices.WEBHOOK,
|
action_type=EventRuleActionChoices.WEBHOOK,
|
||||||
action_object_type=webhook_type,
|
action_object_type=webhook_type,
|
||||||
action_object_id=webhooks[0].id
|
action_object_id=webhooks[0].id
|
||||||
),
|
),
|
||||||
EventRule(
|
EventRule(
|
||||||
name='Webhook Event 2',
|
name='Event Rule 2',
|
||||||
type_update=True,
|
event_types=[OBJECT_UPDATED],
|
||||||
action_type=EventRuleActionChoices.WEBHOOK,
|
action_type=EventRuleActionChoices.WEBHOOK,
|
||||||
action_object_type=webhook_type,
|
action_object_type=webhook_type,
|
||||||
action_object_id=webhooks[0].id
|
action_object_id=webhooks[0].id
|
||||||
),
|
),
|
||||||
EventRule(
|
EventRule(
|
||||||
name='Webhook Event 3',
|
name='Event Rule 3',
|
||||||
type_delete=True,
|
event_types=[OBJECT_DELETED],
|
||||||
action_type=EventRuleActionChoices.WEBHOOK,
|
action_type=EventRuleActionChoices.WEBHOOK,
|
||||||
action_object_type=webhook_type,
|
action_object_type=webhook_type,
|
||||||
action_object_id=webhooks[0].id
|
action_object_id=webhooks[0].id
|
||||||
@ -82,8 +82,7 @@ class EventRuleTest(APITestCase):
|
|||||||
"""
|
"""
|
||||||
event_rule = EventRule(
|
event_rule = EventRule(
|
||||||
name='Event Rule 1',
|
name='Event Rule 1',
|
||||||
type_create=True,
|
event_types=[OBJECT_CREATED, OBJECT_UPDATED],
|
||||||
type_update=True,
|
|
||||||
conditions={
|
conditions={
|
||||||
'and': [
|
'and': [
|
||||||
{
|
{
|
||||||
@ -131,7 +130,7 @@ class EventRuleTest(APITestCase):
|
|||||||
# Verify that a background task was queued for the new object
|
# Verify that a background task was queued for the new object
|
||||||
self.assertEqual(self.queue.count, 1)
|
self.assertEqual(self.queue.count, 1)
|
||||||
job = self.queue.jobs[0]
|
job = self.queue.jobs[0]
|
||||||
self.assertEqual(job.kwargs['event_rule'], EventRule.objects.get(type_create=True))
|
self.assertEqual(job.kwargs['event_rule'], EventRule.objects.get(name='Event Rule 1'))
|
||||||
self.assertEqual(job.kwargs['event_type'], OBJECT_CREATED)
|
self.assertEqual(job.kwargs['event_type'], OBJECT_CREATED)
|
||||||
self.assertEqual(job.kwargs['model_name'], 'site')
|
self.assertEqual(job.kwargs['model_name'], 'site')
|
||||||
self.assertEqual(job.kwargs['data']['id'], response.data['id'])
|
self.assertEqual(job.kwargs['data']['id'], response.data['id'])
|
||||||
@ -181,7 +180,7 @@ class EventRuleTest(APITestCase):
|
|||||||
# Verify that a background task was queued for each new object
|
# Verify that a background task was queued for each new object
|
||||||
self.assertEqual(self.queue.count, 3)
|
self.assertEqual(self.queue.count, 3)
|
||||||
for i, job in enumerate(self.queue.jobs):
|
for i, job in enumerate(self.queue.jobs):
|
||||||
self.assertEqual(job.kwargs['event_rule'], EventRule.objects.get(type_create=True))
|
self.assertEqual(job.kwargs['event_rule'], EventRule.objects.get(name='Event Rule 1'))
|
||||||
self.assertEqual(job.kwargs['event_type'], OBJECT_CREATED)
|
self.assertEqual(job.kwargs['event_type'], OBJECT_CREATED)
|
||||||
self.assertEqual(job.kwargs['model_name'], 'site')
|
self.assertEqual(job.kwargs['model_name'], 'site')
|
||||||
self.assertEqual(job.kwargs['data']['id'], response.data[i]['id'])
|
self.assertEqual(job.kwargs['data']['id'], response.data[i]['id'])
|
||||||
@ -212,7 +211,7 @@ class EventRuleTest(APITestCase):
|
|||||||
# Verify that a background task was queued for the updated object
|
# Verify that a background task was queued for the updated object
|
||||||
self.assertEqual(self.queue.count, 1)
|
self.assertEqual(self.queue.count, 1)
|
||||||
job = self.queue.jobs[0]
|
job = self.queue.jobs[0]
|
||||||
self.assertEqual(job.kwargs['event_rule'], EventRule.objects.get(type_update=True))
|
self.assertEqual(job.kwargs['event_rule'], EventRule.objects.get(name='Event Rule 2'))
|
||||||
self.assertEqual(job.kwargs['event_type'], OBJECT_UPDATED)
|
self.assertEqual(job.kwargs['event_type'], OBJECT_UPDATED)
|
||||||
self.assertEqual(job.kwargs['model_name'], 'site')
|
self.assertEqual(job.kwargs['model_name'], 'site')
|
||||||
self.assertEqual(job.kwargs['data']['id'], site.pk)
|
self.assertEqual(job.kwargs['data']['id'], site.pk)
|
||||||
@ -268,7 +267,7 @@ class EventRuleTest(APITestCase):
|
|||||||
# Verify that a background task was queued for each updated object
|
# Verify that a background task was queued for each updated object
|
||||||
self.assertEqual(self.queue.count, 3)
|
self.assertEqual(self.queue.count, 3)
|
||||||
for i, job in enumerate(self.queue.jobs):
|
for i, job in enumerate(self.queue.jobs):
|
||||||
self.assertEqual(job.kwargs['event_rule'], EventRule.objects.get(type_update=True))
|
self.assertEqual(job.kwargs['event_rule'], EventRule.objects.get(name='Event Rule 2'))
|
||||||
self.assertEqual(job.kwargs['event_type'], OBJECT_UPDATED)
|
self.assertEqual(job.kwargs['event_type'], OBJECT_UPDATED)
|
||||||
self.assertEqual(job.kwargs['model_name'], 'site')
|
self.assertEqual(job.kwargs['model_name'], 'site')
|
||||||
self.assertEqual(job.kwargs['data']['id'], data[i]['id'])
|
self.assertEqual(job.kwargs['data']['id'], data[i]['id'])
|
||||||
@ -294,7 +293,7 @@ class EventRuleTest(APITestCase):
|
|||||||
# Verify that a task was queued for the deleted object
|
# Verify that a task was queued for the deleted object
|
||||||
self.assertEqual(self.queue.count, 1)
|
self.assertEqual(self.queue.count, 1)
|
||||||
job = self.queue.jobs[0]
|
job = self.queue.jobs[0]
|
||||||
self.assertEqual(job.kwargs['event_rule'], EventRule.objects.get(type_delete=True))
|
self.assertEqual(job.kwargs['event_rule'], EventRule.objects.get(name='Event Rule 3'))
|
||||||
self.assertEqual(job.kwargs['event_type'], OBJECT_DELETED)
|
self.assertEqual(job.kwargs['event_type'], OBJECT_DELETED)
|
||||||
self.assertEqual(job.kwargs['model_name'], 'site')
|
self.assertEqual(job.kwargs['model_name'], 'site')
|
||||||
self.assertEqual(job.kwargs['data']['id'], site.pk)
|
self.assertEqual(job.kwargs['data']['id'], site.pk)
|
||||||
@ -327,7 +326,7 @@ class EventRuleTest(APITestCase):
|
|||||||
# Verify that a background task was queued for each deleted object
|
# Verify that a background task was queued for each deleted object
|
||||||
self.assertEqual(self.queue.count, 3)
|
self.assertEqual(self.queue.count, 3)
|
||||||
for i, job in enumerate(self.queue.jobs):
|
for i, job in enumerate(self.queue.jobs):
|
||||||
self.assertEqual(job.kwargs['event_rule'], EventRule.objects.get(type_delete=True))
|
self.assertEqual(job.kwargs['event_rule'], EventRule.objects.get(name='Event Rule 3'))
|
||||||
self.assertEqual(job.kwargs['event_type'], OBJECT_DELETED)
|
self.assertEqual(job.kwargs['event_type'], OBJECT_DELETED)
|
||||||
self.assertEqual(job.kwargs['model_name'], 'site')
|
self.assertEqual(job.kwargs['model_name'], 'site')
|
||||||
self.assertEqual(job.kwargs['data']['id'], sites[i].pk)
|
self.assertEqual(job.kwargs['data']['id'], sites[i].pk)
|
||||||
@ -342,7 +341,7 @@ class EventRuleTest(APITestCase):
|
|||||||
A dummy implementation of Session.send() to be used for testing.
|
A dummy implementation of Session.send() to be used for testing.
|
||||||
Always returns a 200 HTTP response.
|
Always returns a 200 HTTP response.
|
||||||
"""
|
"""
|
||||||
event = EventRule.objects.get(type_create=True)
|
event = EventRule.objects.get(name='Event Rule 1')
|
||||||
webhook = event.action_object
|
webhook = event.action_object
|
||||||
signature = generate_signature(request.body, webhook.secret)
|
signature = generate_signature(request.body, webhook.secret)
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ from django.test import TestCase
|
|||||||
|
|
||||||
from circuits.models import Provider
|
from circuits.models import Provider
|
||||||
from core.choices import ManagedFileRootPathChoices, ObjectChangeActionChoices
|
from core.choices import ManagedFileRootPathChoices, ObjectChangeActionChoices
|
||||||
|
from core.events import *
|
||||||
from core.models import ObjectChange, ObjectType
|
from core.models import ObjectChange, ObjectType
|
||||||
from dcim.filtersets import SiteFilterSet
|
from dcim.filtersets import SiteFilterSet
|
||||||
from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup
|
from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup
|
||||||
@ -251,7 +252,7 @@ class WebhookTestCase(TestCase, BaseFilterSetTests):
|
|||||||
class EventRuleTestCase(TestCase, BaseFilterSetTests):
|
class EventRuleTestCase(TestCase, BaseFilterSetTests):
|
||||||
queryset = EventRule.objects.all()
|
queryset = EventRule.objects.all()
|
||||||
filterset = EventRuleFilterSet
|
filterset = EventRuleFilterSet
|
||||||
ignore_fields = ('action_data', 'conditions')
|
ignore_fields = ('action_data', 'conditions', 'event_types')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -292,11 +293,7 @@ class EventRuleTestCase(TestCase, BaseFilterSetTests):
|
|||||||
name='Event Rule 1',
|
name='Event Rule 1',
|
||||||
action_object=webhooks[0],
|
action_object=webhooks[0],
|
||||||
enabled=True,
|
enabled=True,
|
||||||
type_create=True,
|
event_types=[OBJECT_CREATED],
|
||||||
type_update=False,
|
|
||||||
type_delete=False,
|
|
||||||
type_job_start=False,
|
|
||||||
type_job_end=False,
|
|
||||||
action_type=EventRuleActionChoices.WEBHOOK,
|
action_type=EventRuleActionChoices.WEBHOOK,
|
||||||
description='foobar1'
|
description='foobar1'
|
||||||
),
|
),
|
||||||
@ -304,11 +301,7 @@ class EventRuleTestCase(TestCase, BaseFilterSetTests):
|
|||||||
name='Event Rule 2',
|
name='Event Rule 2',
|
||||||
action_object=webhooks[1],
|
action_object=webhooks[1],
|
||||||
enabled=True,
|
enabled=True,
|
||||||
type_create=False,
|
event_types=[OBJECT_UPDATED],
|
||||||
type_update=True,
|
|
||||||
type_delete=False,
|
|
||||||
type_job_start=False,
|
|
||||||
type_job_end=False,
|
|
||||||
action_type=EventRuleActionChoices.WEBHOOK,
|
action_type=EventRuleActionChoices.WEBHOOK,
|
||||||
description='foobar2'
|
description='foobar2'
|
||||||
),
|
),
|
||||||
@ -316,11 +309,7 @@ class EventRuleTestCase(TestCase, BaseFilterSetTests):
|
|||||||
name='Event Rule 3',
|
name='Event Rule 3',
|
||||||
action_object=webhooks[2],
|
action_object=webhooks[2],
|
||||||
enabled=False,
|
enabled=False,
|
||||||
type_create=False,
|
event_types=[OBJECT_DELETED],
|
||||||
type_update=False,
|
|
||||||
type_delete=True,
|
|
||||||
type_job_start=False,
|
|
||||||
type_job_end=False,
|
|
||||||
action_type=EventRuleActionChoices.WEBHOOK,
|
action_type=EventRuleActionChoices.WEBHOOK,
|
||||||
description='foobar3'
|
description='foobar3'
|
||||||
),
|
),
|
||||||
@ -328,22 +317,14 @@ class EventRuleTestCase(TestCase, BaseFilterSetTests):
|
|||||||
name='Event Rule 4',
|
name='Event Rule 4',
|
||||||
action_object=scripts[0],
|
action_object=scripts[0],
|
||||||
enabled=False,
|
enabled=False,
|
||||||
type_create=False,
|
event_types=[JOB_STARTED],
|
||||||
type_update=False,
|
|
||||||
type_delete=False,
|
|
||||||
type_job_start=True,
|
|
||||||
type_job_end=False,
|
|
||||||
action_type=EventRuleActionChoices.SCRIPT,
|
action_type=EventRuleActionChoices.SCRIPT,
|
||||||
),
|
),
|
||||||
EventRule(
|
EventRule(
|
||||||
name='Event Rule 5',
|
name='Event Rule 5',
|
||||||
action_object=scripts[1],
|
action_object=scripts[1],
|
||||||
enabled=False,
|
enabled=False,
|
||||||
type_create=False,
|
event_types=[JOB_COMPLETED],
|
||||||
type_update=False,
|
|
||||||
type_delete=False,
|
|
||||||
type_job_start=False,
|
|
||||||
type_job_end=True,
|
|
||||||
action_type=EventRuleActionChoices.SCRIPT,
|
action_type=EventRuleActionChoices.SCRIPT,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -384,25 +365,9 @@ class EventRuleTestCase(TestCase, BaseFilterSetTests):
|
|||||||
params = {'enabled': False}
|
params = {'enabled': False}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|
||||||
def test_type_create(self):
|
def test_event_type(self):
|
||||||
params = {'type_create': True}
|
params = {'event_type': [OBJECT_CREATED, OBJECT_UPDATED]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_type_update(self):
|
|
||||||
params = {'type_update': True}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
||||||
|
|
||||||
def test_type_delete(self):
|
|
||||||
params = {'type_delete': True}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
||||||
|
|
||||||
def test_type_job_start(self):
|
|
||||||
params = {'type_job_start': True}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
||||||
|
|
||||||
def test_type_job_end(self):
|
|
||||||
params = {'type_job_end': True}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
||||||
|
|
||||||
|
|
||||||
class CustomLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class CustomLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from core.events import *
|
||||||
from core.models import ObjectType
|
from core.models import ObjectType
|
||||||
from dcim.models import DeviceType, Manufacturer, Site
|
from dcim.models import DeviceType, Manufacturer, Site
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
@ -394,9 +395,9 @@ class EventRulesTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
|
|
||||||
site_type = ObjectType.objects.get_for_model(Site)
|
site_type = ObjectType.objects.get_for_model(Site)
|
||||||
event_rules = (
|
event_rules = (
|
||||||
EventRule(name='EventRule 1', type_create=True, action_object=webhooks[0]),
|
EventRule(name='EventRule 1', event_types=[OBJECT_CREATED], action_object=webhooks[0]),
|
||||||
EventRule(name='EventRule 2', type_create=True, action_object=webhooks[1]),
|
EventRule(name='EventRule 2', event_types=[OBJECT_CREATED], action_object=webhooks[1]),
|
||||||
EventRule(name='EventRule 3', type_create=True, action_object=webhooks[2]),
|
EventRule(name='EventRule 3', event_types=[OBJECT_CREATED], action_object=webhooks[2]),
|
||||||
)
|
)
|
||||||
for event in event_rules:
|
for event in event_rules:
|
||||||
event.save()
|
event.save()
|
||||||
@ -406,9 +407,7 @@ class EventRulesTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'name': 'Event X',
|
'name': 'Event X',
|
||||||
'object_types': [site_type.pk],
|
'object_types': [site_type.pk],
|
||||||
'type_create': False,
|
'event_types': ','.join([OBJECT_UPDATED, OBJECT_DELETED]),
|
||||||
'type_update': True,
|
|
||||||
'type_delete': True,
|
|
||||||
'conditions': None,
|
'conditions': None,
|
||||||
'action_type': 'webhook',
|
'action_type': 'webhook',
|
||||||
'action_object_type': webhook_ct.pk,
|
'action_object_type': webhook_ct.pk,
|
||||||
@ -418,8 +417,8 @@ class EventRulesTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"name,object_types,type_create,action_type,action_object",
|
f'name,object_types,event_types,action_type,action_object',
|
||||||
"Webhook 4,dcim.site,True,webhook,Webhook 1",
|
f'Webhook 4,dcim.site,"{OBJECT_CREATED}",webhook,Webhook 1',
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
@ -430,7 +429,7 @@ class EventRulesTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'type_update': True,
|
'description': 'New description',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ from extras.choices import LogLevelChoices
|
|||||||
from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm
|
from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm
|
||||||
from extras.dashboard.utils import get_widget_class
|
from extras.dashboard.utils import get_widget_class
|
||||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
||||||
|
from netbox.registry import registry
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from netbox.views.generic.mixins import TableMixin
|
from netbox.views.generic.mixins import TableMixin
|
||||||
from utilities.forms import ConfirmationForm, get_field_value
|
from utilities.forms import ConfirmationForm, get_field_value
|
||||||
@ -550,6 +551,14 @@ class EventRuleListView(generic.ObjectListView):
|
|||||||
class EventRuleView(generic.ObjectView):
|
class EventRuleView(generic.ObjectView):
|
||||||
queryset = EventRule.objects.all()
|
queryset = EventRule.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
return {
|
||||||
|
'event_types': [
|
||||||
|
event for name, event in registry['events'].items()
|
||||||
|
if name in instance.event_types
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(EventRule, 'edit')
|
@register_model_view(EventRule, 'edit')
|
||||||
class EventRuleEditView(generic.ObjectEditView):
|
class EventRuleEditView(generic.ObjectEditView):
|
||||||
|
@ -13,9 +13,28 @@ __all__ = (
|
|||||||
'EVENT_TYPE_SUCCESS',
|
'EVENT_TYPE_SUCCESS',
|
||||||
'EVENT_TYPE_WARNING',
|
'EVENT_TYPE_WARNING',
|
||||||
'Event',
|
'Event',
|
||||||
|
'get_event',
|
||||||
|
'get_event_type_choices',
|
||||||
|
'get_event_text',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_event(name):
|
||||||
|
return registry['events'].get(name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_event_text(name):
|
||||||
|
if event := registry['events'].get(name):
|
||||||
|
return event.text
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def get_event_type_choices():
|
||||||
|
return [
|
||||||
|
(event.name, event.text) for event in registry['events'].values()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Event:
|
class Event:
|
||||||
name: str
|
name: str
|
||||||
@ -26,6 +45,8 @@ class Event:
|
|||||||
return self.text
|
return self.text
|
||||||
|
|
||||||
def register(self):
|
def register(self):
|
||||||
|
if self.name in registry['events']:
|
||||||
|
raise Exception(f"An event named {self.name} has already been registered!")
|
||||||
registry['events'][self.name] = self
|
registry['events'][self.name] = self
|
||||||
|
|
||||||
def color(self):
|
def color(self):
|
||||||
|
@ -34,29 +34,25 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">{% trans "Events" %}</h5>
|
<h5 class="card-header">{% trans "Event Types" %}</h5>
|
||||||
<table class="table table-hover attr-table">
|
<ul class="list-group list-group-flush">
|
||||||
<tr>
|
{% for name, event in registry.events.items %}
|
||||||
<th scope="row">{% trans "Create" %}</th>
|
<li class="list-group-item">
|
||||||
<td>{% checkmark object.type_create %}</td>
|
<div class="row align-items-center">
|
||||||
</tr>
|
<div class="col-auto">
|
||||||
<tr>
|
{% if name in object.event_types %}
|
||||||
<th scope="row">{% trans "Update" %}</th>
|
{% checkmark True %}
|
||||||
<td>{% checkmark object.type_update %}</td>
|
{% else %}
|
||||||
</tr>
|
{{ ''|placeholder }}
|
||||||
<tr>
|
{% endif %}
|
||||||
<th scope="row">{% trans "Delete" %}</th>
|
</div>
|
||||||
<td>{% checkmark object.type_delete %}</td>
|
<div class="col">
|
||||||
</tr>
|
{{ event }}
|
||||||
<tr>
|
</div>
|
||||||
<th scope="row">{% trans "Job start" %}</th>
|
</div>
|
||||||
<td>{% checkmark object.type_job_start %}</td>
|
</li>
|
||||||
</tr>
|
{% endfor %}
|
||||||
<tr>
|
</ul>
|
||||||
<th scope="row">{% trans "Job end" %}</th>
|
|
||||||
<td>{% checkmark object.type_job_end %}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user