diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index 7c533a5b4..b38ac98fd 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -8,6 +8,7 @@ from dcim.api.nested_serializers import ( NestedRegionSerializer, NestedSiteSerializer, ) from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site +from extras.choices import * from extras.constants import * from extras.models import ( ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, Tag, @@ -255,7 +256,7 @@ class ObjectChangeSerializer(serializers.ModelSerializer): read_only=True ) action = ChoiceField( - choices=OBJECTCHANGE_ACTION_CHOICES, + choices=ObjectChangeActionChoices, read_only=True ) changed_object_type = ContentTypeField( diff --git a/netbox/extras/choices.py b/netbox/extras/choices.py index 962a5395b..856798ea0 100644 --- a/netbox/extras/choices.py +++ b/netbox/extras/choices.py @@ -75,3 +75,26 @@ class CustomLinkButtonClassChoices(ChoiceSet): (CLASS_DANGER, 'Danger (red)'), (CLASS_LINK, 'None (link)'), ) + + +# +# ObjectChanges +# + +class ObjectChangeActionChoices(ChoiceSet): + + ACTION_CREATE = 'create' + ACTION_UPDATE = 'update' + ACTION_DELETE = 'delete' + + CHOICES = ( + (ACTION_CREATE, 'Created'), + (ACTION_UPDATE, 'Updated'), + (ACTION_DELETE, 'Deleted'), + ) + + LEGACY_MAP = { + ACTION_CREATE: 1, + ACTION_UPDATE: 2, + ACTION_DELETE: 3, + } diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py index f22fb1dd9..d033395af 100644 --- a/netbox/extras/constants.py +++ b/netbox/extras/constants.py @@ -93,16 +93,6 @@ TEMPLATE_LANGUAGE_CHOICES = ( (TEMPLATE_LANGUAGE_JINJA2, 'Jinja2'), ) -# Change log actions -OBJECTCHANGE_ACTION_CREATE = 1 -OBJECTCHANGE_ACTION_UPDATE = 2 -OBJECTCHANGE_ACTION_DELETE = 3 -OBJECTCHANGE_ACTION_CHOICES = ( - (OBJECTCHANGE_ACTION_CREATE, 'Created'), - (OBJECTCHANGE_ACTION_UPDATE, 'Updated'), - (OBJECTCHANGE_ACTION_DELETE, 'Deleted'), -) - # User action types ACTION_CREATE = 1 ACTION_IMPORT = 2 diff --git a/netbox/extras/forms.py b/netbox/extras/forms.py index 34583eb0d..b9f2d1538 100644 --- a/netbox/extras/forms.py +++ b/netbox/extras/forms.py @@ -401,7 +401,7 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form): ) ) action = forms.ChoiceField( - choices=add_blank_choice(OBJECTCHANGE_ACTION_CHOICES), + choices=add_blank_choice(ObjectChangeActionChoices), required=False ) user = forms.ModelChoiceField( diff --git a/netbox/extras/middleware.py b/netbox/extras/middleware.py index 57f8f37d1..f305c1d56 100644 --- a/netbox/extras/middleware.py +++ b/netbox/extras/middleware.py @@ -10,7 +10,7 @@ from django.utils import timezone from django_prometheus.models import model_deletes, model_inserts, model_updates from utilities.querysets import DummyQuerySet -from .constants import * +from .choices import ObjectChangeActionChoices from .models import ObjectChange from .signals import purge_changelog from .webhooks import enqueue_webhooks @@ -23,7 +23,7 @@ def handle_changed_object(sender, instance, **kwargs): Fires when an object is created or updated. """ # Queue the object for processing once the request completes - action = OBJECTCHANGE_ACTION_CREATE if kwargs['created'] else OBJECTCHANGE_ACTION_UPDATE + action = ObjectChangeActionChoices.ACTION_CREATE if kwargs['created'] else ObjectChangeActionChoices.ACTION_UPDATE _thread_locals.changed_objects.append( (instance, action) ) @@ -46,7 +46,7 @@ def handle_deleted_object(sender, instance, **kwargs): # Queue the copy of the object for processing once the request completes _thread_locals.changed_objects.append( - (copy, OBJECTCHANGE_ACTION_DELETE) + (copy, ObjectChangeActionChoices.ACTION_DELETE) ) @@ -101,7 +101,7 @@ class ObjectChangeMiddleware(object): for instance, action in _thread_locals.changed_objects: # Refresh cached custom field values - if action in [OBJECTCHANGE_ACTION_CREATE, OBJECTCHANGE_ACTION_UPDATE]: + if action in [ObjectChangeActionChoices.ACTION_CREATE, ObjectChangeActionChoices.ACTION_UPDATE]: if hasattr(instance, 'cache_custom_fields'): instance.cache_custom_fields() @@ -116,11 +116,11 @@ class ObjectChangeMiddleware(object): enqueue_webhooks(instance, request.user, request.id, action) # Increment metric counters - if action == OBJECTCHANGE_ACTION_CREATE: + if action == ObjectChangeActionChoices.ACTION_CREATE: model_inserts.labels(instance._meta.model_name).inc() - elif action == OBJECTCHANGE_ACTION_UPDATE: + elif action == ObjectChangeActionChoices.ACTION_UPDATE: model_updates.labels(instance._meta.model_name).inc() - elif action == OBJECTCHANGE_ACTION_DELETE: + elif action == ObjectChangeActionChoices.ACTION_DELETE: model_deletes.labels(instance._meta.model_name).inc() # Housekeeping: 1% chance of clearing out expired ObjectChanges. This applies only to requests which result in diff --git a/netbox/extras/migrations/0030_3569_objectchange_fields.py b/netbox/extras/migrations/0030_3569_objectchange_fields.py new file mode 100644 index 000000000..de6c616f7 --- /dev/null +++ b/netbox/extras/migrations/0030_3569_objectchange_fields.py @@ -0,0 +1,37 @@ +from django.db import migrations, models +import django.db.models.deletion + + +OBJECTCHANGE_ACTION_CHOICES = ( + (1, 'create'), + (2, 'update'), + (3, 'delete'), +) + + +def objectchange_action_to_slug(apps, schema_editor): + ObjectChange = apps.get_model('extras', 'ObjectChange') + for id, slug in OBJECTCHANGE_ACTION_CHOICES: + ObjectChange.objects.filter(action=str(id)).update(action=slug) + + +class Migration(migrations.Migration): + atomic = False + + dependencies = [ + ('extras', '0029_3569_customfield_fields'), + ] + + operations = [ + + # ObjectChange.action + migrations.AlterField( + model_name='objectchange', + name='action', + field=models.CharField(max_length=50), + ), + migrations.RunPython( + code=objectchange_action_to_slug + ), + + ] diff --git a/netbox/extras/models.py b/netbox/extras/models.py index 07fdb86eb..dc0f6210e 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -802,8 +802,9 @@ class ObjectChange(models.Model): request_id = models.UUIDField( editable=False ) - action = models.PositiveSmallIntegerField( - choices=OBJECTCHANGE_ACTION_CHOICES + action = models.CharField( + max_length=50, + choices=ObjectChangeActionChoices ) changed_object_type = models.ForeignKey( to=ContentType, diff --git a/netbox/extras/tests/test_changelog.py b/netbox/extras/tests/test_changelog.py index 961adfd40..8f01cc3bf 100644 --- a/netbox/extras/tests/test_changelog.py +++ b/netbox/extras/tests/test_changelog.py @@ -50,7 +50,7 @@ class ChangeLogTest(APITestCase): changed_object_id=site.pk ) self.assertEqual(oc.changed_object, site) - self.assertEqual(oc.action, OBJECTCHANGE_ACTION_CREATE) + self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_CREATE) self.assertEqual(oc.object_data['custom_fields'], data['custom_fields']) self.assertListEqual(sorted(oc.object_data['tags']), data['tags']) @@ -82,7 +82,7 @@ class ChangeLogTest(APITestCase): changed_object_id=site.pk ) self.assertEqual(oc.changed_object, site) - self.assertEqual(oc.action, OBJECTCHANGE_ACTION_UPDATE) + self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_UPDATE) self.assertEqual(oc.object_data['custom_fields'], data['custom_fields']) self.assertListEqual(sorted(oc.object_data['tags']), data['tags']) @@ -111,6 +111,6 @@ class ChangeLogTest(APITestCase): oc = ObjectChange.objects.first() self.assertEqual(oc.changed_object, None) self.assertEqual(oc.object_repr, site.name) - self.assertEqual(oc.action, OBJECTCHANGE_ACTION_DELETE) + self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_DELETE) self.assertEqual(oc.object_data['custom_fields'], {'my_field': 'ABC'}) self.assertListEqual(sorted(oc.object_data['tags']), ['bar', 'foo']) diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py index 0f3625191..792390121 100644 --- a/netbox/extras/tests/test_views.py +++ b/netbox/extras/tests/test_views.py @@ -6,7 +6,7 @@ from django.test import Client, TestCase from django.urls import reverse from dcim.models import Site -from extras.constants import OBJECTCHANGE_ACTION_UPDATE +from extras.choices import ObjectChangeActionChoices from extras.models import ConfigContext, ObjectChange, Tag from utilities.testing import create_test_user @@ -83,7 +83,7 @@ class ObjectChangeTestCase(TestCase): # Create three ObjectChanges for i in range(1, 4): - oc = site.to_objectchange(action=OBJECTCHANGE_ACTION_UPDATE) + oc = site.to_objectchange(action=ObjectChangeActionChoices.ACTION_UPDATE) oc.user = user oc.request_id = uuid.uuid4() oc.save() diff --git a/netbox/extras/webhooks.py b/netbox/extras/webhooks.py index ac82bbb31..c95cb9f31 100644 --- a/netbox/extras/webhooks.py +++ b/netbox/extras/webhooks.py @@ -5,6 +5,7 @@ from django.contrib.contenttypes.models import ContentType from extras.models import Webhook from utilities.api import get_serializer_for_model +from .choices import * from .constants import * @@ -18,9 +19,9 @@ def enqueue_webhooks(instance, user, request_id, action): # Retrieve any applicable Webhooks action_flag = { - OBJECTCHANGE_ACTION_CREATE: 'type_create', - OBJECTCHANGE_ACTION_UPDATE: 'type_update', - OBJECTCHANGE_ACTION_DELETE: 'type_delete', + ObjectChangeActionChoices.ACTION_CREATE: 'type_create', + ObjectChangeActionChoices.ACTION_UPDATE: 'type_update', + ObjectChangeActionChoices.ACTION_DELETE: 'type_delete', }[action] obj_type = ContentType.objects.get_for_model(instance.__class__) webhooks = Webhook.objects.filter(obj_type=obj_type, enabled=True, **{action_flag: True}) diff --git a/netbox/extras/webhooks_worker.py b/netbox/extras/webhooks_worker.py index 9a637e852..bafb8e32c 100644 --- a/netbox/extras/webhooks_worker.py +++ b/netbox/extras/webhooks_worker.py @@ -15,7 +15,7 @@ def process_webhook(webhook, data, model_name, event, timestamp, username, reque Make a POST request to the defined Webhook """ payload = { - 'event': dict(OBJECTCHANGE_ACTION_CHOICES)[event].lower(), + 'event': event, 'timestamp': timestamp, 'model': model_name, 'username': username,