Repository: nautobot/nautobot

Issue:
Unable to update device after deleting its device redundancy group if the device DRG priority is set
<!--
    NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.

    This form is only for reporting reproducible bugs. If you need assistance
    with Nautobot installation, or if you have a general question, please start a
    discussion instead: https://github.com/nautobot/nautobot/discussions

    Please describe the environment in which you are running Nautobot. Be sure
    that you are running an unmodified instance of the latest stable release
    before submitting a bug report, and that any plugins have been disabled.
-->
### Environment
* Nautobot version (Docker tag too if applicable): 1.6.4
* Python version: 3.11.6
* Database platform, version: mysql 8.1.0
* Middleware(s): pynautobot, GUI, Swagger UI

<!--
    Describe in detail the exact steps that someone else can take to reproduce
    this bug using the current stable release of Nautobot. Begin with the
    creation of any necessary database objects and call out every operation
    being performed explicitly. If reporting a bug in the REST API, be sure to
    reconstruct the raw HTTP request(s) being made: Don't rely on a client
    library such as pynautobot.
-->
### Steps to Reproduce
1. Create a device redundancy group (DRG)
2. Create a device
3. Assign device to redundancy group with a priority
4. Delete device redundancy group
5. Attempt to make an edit to the device

Note that with the DRG deleted, it's no longer listed in the device edit screen but the DRG priority remains set; since having a DRG priority set without having a corresponding DRG set fails validation, no edits can be made to the device until either the priority is removed or a new DRG is configured. Attempts to edit the device result in the error see in Observed Behavior, below. Bonus: If you attempt to add such a device to a Virtual Chassis, you get this error:

![Screenshot 2023-10-26 at 5 32 26 PM](https://github.com/nautobot/nautobot/assets/120611026/c911bc78-d9ba-42e6-b893-3dbceb9836ff)


<!-- What did you expect to happen? -->
### Expected Behavior
I can absolutely see that changing this behavior may be more trouble than it's worth, however in a perfect world it feels to me as though either any devices using the deleted redundancy group should have their priority set to `None` or DRG deletion should fail until/unless no devices are using it. That said, I don't know the code base well enough to know which (or if either) of those solutions is correct. 

<!-- What happened instead? -->
### Observed Behavior
After the DRG was deleted and I was unable to make any updates to the device without first nulling out the device DRG priority. returned message is:

```json
{
  "device_redundancy_group_priority": [
    "Must assign a redundancy group when defining a redundancy group priority."
  ]
}
```

Thoughts?





Discussion:

Resolving patch:
diff --git a/nautobot/dcim/signals.py b/nautobot/dcim/signals.py
--- a/nautobot/dcim/signals.py
+++ b/nautobot/dcim/signals.py
@@ -11,6 +11,7 @@
     Cable,
     CablePath,
     Device,
+    DeviceRedundancyGroup,
     PathEndpoint,
     PowerPanel,
     Rack,
@@ -171,6 +172,22 @@ def handle_rack_site_location_change(instance, created, raw=False, **kwargs):
                 device.save()
 
 
+#
+# Device redundancy group
+#
+
+
+@receiver(pre_delete, sender=DeviceRedundancyGroup)
+def clear_deviceredundancygroup_members(instance, **kwargs):
+    """
+    When a DeviceRedundancyGroup is deleted, nullify the device_redundancy_group_priority field of its prior members.
+    """
+    devices = Device.objects.filter(device_redundancy_group=instance.pk)
+    for device in devices:
+        device.device_redundancy_group_priority = None
+        device.save()
+
+
 #
 # Virtual chassis
 #


Test patch:
diff --git a/nautobot/dcim/tests/test_signals.py b/nautobot/dcim/tests/test_signals.py
--- a/nautobot/dcim/tests/test_signals.py
+++ b/nautobot/dcim/tests/test_signals.py
@@ -4,12 +4,14 @@
 
 from nautobot.dcim.models import (
     Device,
+    DeviceRedundancyGroup,
     DeviceRole,
     DeviceType,
     Manufacturer,
     Site,
     VirtualChassis,
 )
+from nautobot.extras.models import Status
 
 
 class VirtualChassisTest(TestCase):
@@ -65,3 +67,42 @@ def test_master_device_null_vc_assignment(self):
         self.device.refresh_from_db()
         self.assertEqual(self.device.vc_position, 1)
         self.assertEqual(self.device.virtual_chassis, virtualchassis)
+
+
+class DeviceRedundancyGroupTest(TestCase):
+    """Class to test signals for DeviceRedundancyGroup."""
+
+    def setUp(self):
+        """Setup Test Data for DeviceRedundancyGroup Signal tests."""
+        site = Site.objects.first()
+        active = Status.objects.get(name="Active")
+        manufacturer = Manufacturer.objects.create(name="Manufacturer 1", slug="manufacturer-1")
+        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model="Device Type", slug="device-type")
+        devicerole = DeviceRole.objects.create(name="Device Role", slug="device-role", color="ff0000")
+
+        self.device = Device.objects.create(
+            name="Device 1",
+            device_type=devicetype,
+            device_role=devicerole,
+            site=site,
+            status=active,
+        )
+
+    def test_device_redundancy_group_priority_is_null(self):
+        """Test device with not null device_redundancy_group_priority is null after its device redundancy group is deleted.
+
+        This test is for https://github.com/nautobot/nautobot/issues/4718
+        """
+        active = Status.objects.get(name="Active")
+        deviceredundancygroup = DeviceRedundancyGroup.objects.create(name="Device Redundancy Group 1", status=active)
+
+        self.device.device_redundancy_group = deviceredundancygroup
+        self.device.device_redundancy_group_priority = 1
+        self.device.validated_save()
+
+        deviceredundancygroup.delete()
+
+        self.device.refresh_from_db()
+
+        self.assertIsNone(self.device.device_redundancy_group)
+        self.assertIsNone(self.device.device_redundancy_group_priority)