Repository: netbox-community/netbox

Issue:
Avoid unnecessary queries in Cable.from_db
### Proposed Changes
Change the code in https://github.com/netbox-community/netbox/blob/develop/netbox/dcim/models/__init__.py#L2118-L2121 where a copy is made of the termination points. I suggest to change it to:

```
instance._orig_termination_a_type_id = instance.termination_a_type_id
instance._orig_termination_a_id = instance.termination_a_id
instance._orig_termination_b_type_id = instance.termination_b_type_id
instance._orig_termination_b_id = instance.termination_b_id
```

And change the corresponding usage to:

```
if (
    self.termination_a_type_id != self._orig_termination_a_type_id or
    self.termination_a_id != self._orig_termination_a_id
):
    raise ValidationError({
        'termination_a': err_msg
    })
if (
    self.termination_b_type_id != self._orig_termination_b_type_id or
    self.termination_b_id != self._orig_termination_b_id
):
    raise ValidationError({
        'termination_b': err_msg
    })
```

### Justification
Reading the `…_type` properties triggers database queries to retrieve a `ContentType` object, which is later only used in a comparison. Storing the `…_type_id` is sufficient for that and avoids the queries.


Discussion:
I noticed this behaviour when looking at the debug toolbar:

<img width="1191" alt="Schermafbeelding 2020-06-04 om 16 21 47" src="https://user-images.githubusercontent.com/509689/83769357-064dad00-a680-11ea-9746-4bb29cb5c373.png">

I tested with `prefetch_related`, but that doesn't affect the behaviour. It fetches the cables in one query, which is then followed by all the queries for the content types, one by one.

Resolving patch:
diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py
--- a/netbox/dcim/models/__init__.py
+++ b/netbox/dcim/models/__init__.py
@@ -2115,9 +2115,9 @@ def from_db(cls, db, field_names, values):
         """
         instance = super().from_db(db, field_names, values)
 
-        instance._orig_termination_a_type = instance.termination_a_type
+        instance._orig_termination_a_type_id = instance.termination_a_type_id
         instance._orig_termination_a_id = instance.termination_a_id
-        instance._orig_termination_b_type = instance.termination_b_type
+        instance._orig_termination_b_type_id = instance.termination_b_type_id
         instance._orig_termination_b_id = instance.termination_b_id
 
         return instance
@@ -2154,14 +2154,14 @@ def clean(self):
         if self.pk:
             err_msg = 'Cable termination points may not be modified. Delete and recreate the cable instead.'
             if (
-                self.termination_a_type != self._orig_termination_a_type or
+                self.termination_a_type_id != self._orig_termination_a_type_id or
                 self.termination_a_id != self._orig_termination_a_id
             ):
                 raise ValidationError({
                     'termination_a': err_msg
                 })
             if (
-                self.termination_b_type != self._orig_termination_b_type or
+                self.termination_b_type_id != self._orig_termination_b_type_id or
                 self.termination_b_id != self._orig_termination_b_id
             ):
                 raise ValidationError({


Test patch: