prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Error on cable edit after B port was deleted / wrong status
  ### Deployment Type
  
  Self-hosted (Docker)
  
  ### NetBox Version
  
  v3.7.5
  
  ### Python Version
  
  3.11
  
  ### Steps to Reproduce
  
  1. Have a device with front- and rear-Ports (Patch Panel)
  2. Have a device with interfaces (switches)
  3. Connect switch interface (here gi43) with front-port (here 22)
  4. Delete rear ports on patch panel device
  5. Go To Connections > Cables
  6. Click on edit of cable --> error message
  
  ### Expected Behavior
  
  - Edit Button works to connect cable again
  
  ### Observed Behavior
  
  - Error Message 
  ![2024-04-19 19_20_00-Server Error and 5 more pages - Work - Microsoft​ Edge](https://github.com/netbox-community/netbox/assets/13959569/78d69dbd-7b02-4b1a-be0a-3523e51152a6)
  
  Cables/UI: Unable to change from front to rear while editing cable
  ### NetBox version
  
  v3.5.6
  
  ### Feature type
  
  Change to existing functionality
  
  ### Proposed functionality
  
  Currently, if someone accidentally connects a cable to the rear port instead of the front port, the entire cable must be deleted and created again. It would be nice to be able to change not only the port number but also the location (front/rear) when editing the cable. This might just be an ui change as the api seems to allow to change it.
  
  ### Use case
  
  It is not necessary to delete the cable and enter all information (label, length etc) again. You can just reconnect it.
  
  ### Database changes
  
  _No response_
  
  ### External dependencies
  
  _No response_
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `netbox/dcim/forms/connections.py`
  Content:
  ```
  1 from django import forms
  2 from django.contrib.contenttypes.models import ContentType
  3 from django.utils.translation import gettext_lazy as _
  4 
  5 from circuits.models import Circuit, CircuitTermination
  6 from dcim.models import *
  7 from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField
  8 from .model_forms import CableForm
  9 
  10 
  11 def get_cable_form(a_type, b_type):
  12 
  13     class FormMetaclass(forms.models.ModelFormMetaclass):
  14 
  15         def __new__(mcs, name, bases, attrs):
  16 
  17             for cable_end, term_cls in (('a', a_type), ('b', b_type)):
  18 
  19                 # Device component
  20                 if hasattr(term_cls, 'device'):
  21 
  22                     attrs[f'termination_{cable_end}_device'] = DynamicModelChoiceField(
  23                         queryset=Device.objects.all(),
  24                         label=_('Device'),
  25                         required=False,
  26                         selector=True,
  27                         initial_params={
  28                             f'{term_cls._meta.model_name}s__in': f'${cable_end}_terminations'
  29                         }
  30                     )
  31                     attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
  32                         queryset=term_cls.objects.all(),
  33                         label=term_cls._meta.verbose_name.title(),
  34                         context={
  35                             'disabled': '_occupied',
  36                         },
  37                         query_params={
  38                             'device_id': f'$termination_{cable_end}_device',
  39                             'kind': 'physical',  # Exclude virtual interfaces
  40                         }
  41                     )
  42 
  43                 # PowerFeed
  44                 elif term_cls == PowerFeed:
  45 
  46                     attrs[f'termination_{cable_end}_powerpanel'] = DynamicModelChoiceField(
  47                         queryset=PowerPanel.objects.all(),
  48                         label=_('Power Panel'),
  49                         required=False,
  50                         selector=True,
  51                         initial_params={
  52                             'powerfeeds__in': f'${cable_end}_terminations'
  53                         }
  54                     )
  55                     attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
  56                         queryset=term_cls.objects.all(),
  57                         label=_('Power Feed'),
  58                         context={
  59                             'disabled': '_occupied',
  60                         },
  61                         query_params={
  62                             'power_panel_id': f'$termination_{cable_end}_powerpanel',
  63                         }
  64                     )
  65 
  66                 # CircuitTermination
  67                 elif term_cls == CircuitTermination:
  68 
  69                     attrs[f'termination_{cable_end}_circuit'] = DynamicModelChoiceField(
  70                         queryset=Circuit.objects.all(),
  71                         label=_('Circuit'),
  72                         selector=True,
  73                         initial_params={
  74                             'terminations__in': f'${cable_end}_terminations'
  75                         }
  76                     )
  77                     attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
  78                         queryset=term_cls.objects.all(),
  79                         label=_('Side'),
  80                         context={
  81                             'disabled': '_occupied',
  82                         },
  83                         query_params={
  84                             'circuit_id': f'$termination_{cable_end}_circuit',
  85                         }
  86                     )
  87 
  88             return super().__new__(mcs, name, bases, attrs)
  89 
  90     class _CableForm(CableForm, metaclass=FormMetaclass):
  91 
  92         def __init__(self, *args, initial=None, **kwargs):
  93 
  94             initial = initial or {}
  95             if a_type:
  96                 ct = ContentType.objects.get_for_model(a_type)
  97                 initial['a_terminations_type'] = f'{ct.app_label}.{ct.model}'
  98             if b_type:
  99                 ct = ContentType.objects.get_for_model(b_type)
  100                 initial['b_terminations_type'] = f'{ct.app_label}.{ct.model}'
  101 
  102             # TODO: Temporary hack to work around list handling limitations with utils.normalize_querydict()
  103             for field_name in ('a_terminations', 'b_terminations'):
  104                 if field_name in initial and type(initial[field_name]) is not list:
  105                     initial[field_name] = [initial[field_name]]
  106 
  107             super().__init__(*args, initial=initial, **kwargs)
  108 
  109             if self.instance and self.instance.pk:
  110                 # Initialize A/B terminations when modifying an existing Cable instance
  111                 self.initial['a_terminations'] = self.instance.a_terminations
  112                 self.initial['b_terminations'] = self.instance.b_terminations
  113 
  114         def clean(self):
  115             super().clean()
  116 
  117             # Set the A/B terminations on the Cable instance
  118             self.instance.a_terminations = self.cleaned_data.get('a_terminations', [])
  119             self.instance.b_terminations = self.cleaned_data.get('b_terminations', [])
  120 
  121     return _CableForm
  122
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/netbox/dcim/forms/connections.py b/netbox/dcim/forms/connections.py
  --- a/netbox/dcim/forms/connections.py
  +++ b/netbox/dcim/forms/connections.py
  @@ -90,14 +90,14 @@
       class _CableForm(CableForm, metaclass=FormMetaclass):
   
           def __init__(self, *args, initial=None, **kwargs):
  -
               initial = initial or {}
  +
               if a_type:
  -                ct = ContentType.objects.get_for_model(a_type)
  -                initial['a_terminations_type'] = f'{ct.app_label}.{ct.model}'
  +                a_ct = ContentType.objects.get_for_model(a_type)
  +                initial['a_terminations_type'] = f'{a_ct.app_label}.{a_ct.model}'
               if b_type:
  -                ct = ContentType.objects.get_for_model(b_type)
  -                initial['b_terminations_type'] = f'{ct.app_label}.{ct.model}'
  +                b_ct = ContentType.objects.get_for_model(b_type)
  +                initial['b_terminations_type'] = f'{b_ct.app_label}.{b_ct.model}'
   
               # TODO: Temporary hack to work around list handling limitations with utils.normalize_querydict()
               for field_name in ('a_terminations', 'b_terminations'):
  @@ -108,8 +108,17 @@
   
               if self.instance and self.instance.pk:
                   # Initialize A/B terminations when modifying an existing Cable instance
  -                self.initial['a_terminations'] = self.instance.a_terminations
  -                self.initial['b_terminations'] = self.instance.b_terminations
  +                if a_type and self.instance.a_terminations and a_ct == ContentType.objects.get_for_model(self.instance.a_terminations[0]):
  +                    self.initial['a_terminations'] = self.instance.a_terminations
  +                if b_type and self.instance.b_terminations and b_ct == ContentType.objects.get_for_model(self.instance.b_terminations[0]):
  +                    self.initial['b_terminations'] = self.instance.b_terminations
  +            else:
  +                # Need to clear terminations if swapped type - but need to do it only
  +                # if not from instance
  +                if a_type:
  +                    initial.pop('a_terminations', None)
  +                if b_type:
  +                    initial.pop('b_terminations', None)
   
           def clean(self):
               super().clean()
  
verification_info: {"golden_diff": "diff --git a/netbox/dcim/forms/connections.py b/netbox/dcim/forms/connections.py\n--- a/netbox/dcim/forms/connections.py\n+++ b/netbox/dcim/forms/connections.py\n@@ -90,14 +90,14 @@\n     class _CableForm(CableForm, metaclass=FormMetaclass):\n \n         def __init__(self, *args, initial=None, **kwargs):\n-\n             initial = initial or {}\n+\n             if a_type:\n-                ct = ContentType.objects.get_for_model(a_type)\n-                initial['a_terminations_type'] = f'{ct.app_label}.{ct.model}'\n+                a_ct = ContentType.objects.get_for_model(a_type)\n+                initial['a_terminations_type'] = f'{a_ct.app_label}.{a_ct.model}'\n             if b_type:\n-                ct = ContentType.objects.get_for_model(b_type)\n-                initial['b_terminations_type'] = f'{ct.app_label}.{ct.model}'\n+                b_ct = ContentType.objects.get_for_model(b_type)\n+                initial['b_terminations_type'] = f'{b_ct.app_label}.{b_ct.model}'\n \n             # TODO: Temporary hack to work around list handling limitations with utils.normalize_querydict()\n             for field_name in ('a_terminations', 'b_terminations'):\n@@ -108,8 +108,17 @@\n \n             if self.instance and self.instance.pk:\n                 # Initialize A/B terminations when modifying an existing Cable instance\n-                self.initial['a_terminations'] = self.instance.a_terminations\n-                self.initial['b_terminations'] = self.instance.b_terminations\n+                if a_type and self.instance.a_terminations and a_ct == ContentType.objects.get_for_model(self.instance.a_terminations[0]):\n+                    self.initial['a_terminations'] = self.instance.a_terminations\n+                if b_type and self.instance.b_terminations and b_ct == ContentType.objects.get_for_model(self.instance.b_terminations[0]):\n+                    self.initial['b_terminations'] = self.instance.b_terminations\n+            else:\n+                # Need to clear terminations if swapped type - but need to do it only\n+                # if not from instance\n+                if a_type:\n+                    initial.pop('a_terminations', None)\n+                if b_type:\n+                    initial.pop('b_terminations', None)\n \n         def clean(self):\n             super().clean()\n", "issue": "Error on cable edit after B port was deleted / wrong status\n### Deployment Type\r\n\r\nSelf-hosted (Docker)\r\n\r\n### NetBox Version\r\n\r\nv3.7.5\r\n\r\n### Python Version\r\n\r\n3.11\r\n\r\n### Steps to Reproduce\r\n\r\n1. Have a device with front- and rear-Ports (Patch Panel)\r\n2. Have a device with interfaces (switches)\r\n3. Connect switch interface (here gi43) with front-port (here 22)\r\n4. Delete rear ports on patch panel device\r\n5. Go To Connections > Cables\r\n6. Click on edit of cable --> error message\r\n\r\n### Expected Behavior\r\n\r\n- Edit Button works to connect cable again\r\n\r\n### Observed Behavior\r\n\r\n- Error Message \r\n![2024-04-19 19_20_00-Server Error and 5 more pages - Work - Microsoft\u200b Edge](https://github.com/netbox-community/netbox/assets/13959569/78d69dbd-7b02-4b1a-be0a-3523e51152a6)\r\n\nCables/UI: Unable to change from front to rear while editing cable\n### NetBox version\n\nv3.5.6\n\n### Feature type\n\nChange to existing functionality\n\n### Proposed functionality\n\nCurrently, if someone accidentally connects a cable to the rear port instead of the front port, the entire cable must be deleted and created again. It would be nice to be able to change not only the port number but also the location (front/rear) when editing the cable. This might just be an ui change as the api seems to allow to change it.\n\n### Use case\n\nIt is not necessary to delete the cable and enter all information (label, length etc) again. You can just reconnect it.\n\n### Database changes\n\n_No response_\n\n### External dependencies\n\n_No response_\n", "before_files": [{"content": "from django import forms\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.utils.translation import gettext_lazy as _\n\nfrom circuits.models import Circuit, CircuitTermination\nfrom dcim.models import *\nfrom utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField\nfrom .model_forms import CableForm\n\n\ndef get_cable_form(a_type, b_type):\n\n    class FormMetaclass(forms.models.ModelFormMetaclass):\n\n        def __new__(mcs, name, bases, attrs):\n\n            for cable_end, term_cls in (('a', a_type), ('b', b_type)):\n\n                # Device component\n                if hasattr(term_cls, 'device'):\n\n                    attrs[f'termination_{cable_end}_device'] = DynamicModelChoiceField(\n                        queryset=Device.objects.all(),\n                        label=_('Device'),\n                        required=False,\n                        selector=True,\n                        initial_params={\n                            f'{term_cls._meta.model_name}s__in': f'${cable_end}_terminations'\n                        }\n                    )\n                    attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(\n                        queryset=term_cls.objects.all(),\n                        label=term_cls._meta.verbose_name.title(),\n                        context={\n                            'disabled': '_occupied',\n                        },\n                        query_params={\n                            'device_id': f'$termination_{cable_end}_device',\n                            'kind': 'physical',  # Exclude virtual interfaces\n                        }\n                    )\n\n                # PowerFeed\n                elif term_cls == PowerFeed:\n\n                    attrs[f'termination_{cable_end}_powerpanel'] = DynamicModelChoiceField(\n                        queryset=PowerPanel.objects.all(),\n                        label=_('Power Panel'),\n                        required=False,\n                        selector=True,\n                        initial_params={\n                            'powerfeeds__in': f'${cable_end}_terminations'\n                        }\n                    )\n                    attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(\n                        queryset=term_cls.objects.all(),\n                        label=_('Power Feed'),\n                        context={\n                            'disabled': '_occupied',\n                        },\n                        query_params={\n                            'power_panel_id': f'$termination_{cable_end}_powerpanel',\n                        }\n                    )\n\n                # CircuitTermination\n                elif term_cls == CircuitTermination:\n\n                    attrs[f'termination_{cable_end}_circuit'] = DynamicModelChoiceField(\n                        queryset=Circuit.objects.all(),\n                        label=_('Circuit'),\n                        selector=True,\n                        initial_params={\n                            'terminations__in': f'${cable_end}_terminations'\n                        }\n                    )\n                    attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(\n                        queryset=term_cls.objects.all(),\n                        label=_('Side'),\n                        context={\n                            'disabled': '_occupied',\n                        },\n                        query_params={\n                            'circuit_id': f'$termination_{cable_end}_circuit',\n                        }\n                    )\n\n            return super().__new__(mcs, name, bases, attrs)\n\n    class _CableForm(CableForm, metaclass=FormMetaclass):\n\n        def __init__(self, *args, initial=None, **kwargs):\n\n            initial = initial or {}\n            if a_type:\n                ct = ContentType.objects.get_for_model(a_type)\n                initial['a_terminations_type'] = f'{ct.app_label}.{ct.model}'\n            if b_type:\n                ct = ContentType.objects.get_for_model(b_type)\n                initial['b_terminations_type'] = f'{ct.app_label}.{ct.model}'\n\n            # TODO: Temporary hack to work around list handling limitations with utils.normalize_querydict()\n            for field_name in ('a_terminations', 'b_terminations'):\n                if field_name in initial and type(initial[field_name]) is not list:\n                    initial[field_name] = [initial[field_name]]\n\n            super().__init__(*args, initial=initial, **kwargs)\n\n            if self.instance and self.instance.pk:\n                # Initialize A/B terminations when modifying an existing Cable instance\n                self.initial['a_terminations'] = self.instance.a_terminations\n                self.initial['b_terminations'] = self.instance.b_terminations\n\n        def clean(self):\n            super().clean()\n\n            # Set the A/B terminations on the Cable instance\n            self.instance.a_terminations = self.cleaned_data.get('a_terminations', [])\n            self.instance.b_terminations = self.cleaned_data.get('b_terminations', [])\n\n    return _CableForm\n", "path": "netbox/dcim/forms/connections.py"}], "after_files": [{"content": "from django import forms\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.utils.translation import gettext_lazy as _\n\nfrom circuits.models import Circuit, CircuitTermination\nfrom dcim.models import *\nfrom utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField\nfrom .model_forms import CableForm\n\n\ndef get_cable_form(a_type, b_type):\n\n    class FormMetaclass(forms.models.ModelFormMetaclass):\n\n        def __new__(mcs, name, bases, attrs):\n\n            for cable_end, term_cls in (('a', a_type), ('b', b_type)):\n\n                # Device component\n                if hasattr(term_cls, 'device'):\n\n                    attrs[f'termination_{cable_end}_device'] = DynamicModelChoiceField(\n                        queryset=Device.objects.all(),\n                        label=_('Device'),\n                        required=False,\n                        selector=True,\n                        initial_params={\n                            f'{term_cls._meta.model_name}s__in': f'${cable_end}_terminations'\n                        }\n                    )\n                    attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(\n                        queryset=term_cls.objects.all(),\n                        label=term_cls._meta.verbose_name.title(),\n                        context={\n                            'disabled': '_occupied',\n                        },\n                        query_params={\n                            'device_id': f'$termination_{cable_end}_device',\n                            'kind': 'physical',  # Exclude virtual interfaces\n                        }\n                    )\n\n                # PowerFeed\n                elif term_cls == PowerFeed:\n\n                    attrs[f'termination_{cable_end}_powerpanel'] = DynamicModelChoiceField(\n                        queryset=PowerPanel.objects.all(),\n                        label=_('Power Panel'),\n                        required=False,\n                        selector=True,\n                        initial_params={\n                            'powerfeeds__in': f'${cable_end}_terminations'\n                        }\n                    )\n                    attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(\n                        queryset=term_cls.objects.all(),\n                        label=_('Power Feed'),\n                        context={\n                            'disabled': '_occupied',\n                        },\n                        query_params={\n                            'power_panel_id': f'$termination_{cable_end}_powerpanel',\n                        }\n                    )\n\n                # CircuitTermination\n                elif term_cls == CircuitTermination:\n\n                    attrs[f'termination_{cable_end}_circuit'] = DynamicModelChoiceField(\n                        queryset=Circuit.objects.all(),\n                        label=_('Circuit'),\n                        selector=True,\n                        initial_params={\n                            'terminations__in': f'${cable_end}_terminations'\n                        }\n                    )\n                    attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(\n                        queryset=term_cls.objects.all(),\n                        label=_('Side'),\n                        context={\n                            'disabled': '_occupied',\n                        },\n                        query_params={\n                            'circuit_id': f'$termination_{cable_end}_circuit',\n                        }\n                    )\n\n            return super().__new__(mcs, name, bases, attrs)\n\n    class _CableForm(CableForm, metaclass=FormMetaclass):\n\n        def __init__(self, *args, initial=None, **kwargs):\n            initial = initial or {}\n\n            if a_type:\n                a_ct = ContentType.objects.get_for_model(a_type)\n                initial['a_terminations_type'] = f'{a_ct.app_label}.{a_ct.model}'\n            if b_type:\n                b_ct = ContentType.objects.get_for_model(b_type)\n                initial['b_terminations_type'] = f'{b_ct.app_label}.{b_ct.model}'\n\n            # TODO: Temporary hack to work around list handling limitations with utils.normalize_querydict()\n            for field_name in ('a_terminations', 'b_terminations'):\n                if field_name in initial and type(initial[field_name]) is not list:\n                    initial[field_name] = [initial[field_name]]\n\n            super().__init__(*args, initial=initial, **kwargs)\n\n            if self.instance and self.instance.pk:\n                # Initialize A/B terminations when modifying an existing Cable instance\n                if a_type and self.instance.a_terminations and a_ct == ContentType.objects.get_for_model(self.instance.a_terminations[0]):\n                    self.initial['a_terminations'] = self.instance.a_terminations\n                if b_type and self.instance.b_terminations and b_ct == ContentType.objects.get_for_model(self.instance.b_terminations[0]):\n                    self.initial['b_terminations'] = self.instance.b_terminations\n            else:\n                # Need to clear terminations if swapped type - but need to do it only\n                # if not from instance\n                if a_type:\n                    initial.pop('a_terminations', None)\n                if b_type:\n                    initial.pop('b_terminations', None)\n\n        def clean(self):\n            super().clean()\n\n            # Set the A/B terminations on the Cable instance\n            self.instance.a_terminations = self.cleaned_data.get('a_terminations', [])\n            self.instance.b_terminations = self.cleaned_data.get('b_terminations', [])\n\n    return _CableForm\n", "path": "netbox/dcim/forms/connections.py"}]}