Repository: nautobot/nautobot

Issue:
Child location's slug is autogenerated using parent's UUID rather than slug
### Environment
* Nautobot version (Docker tag too if applicable): v1.4.2
* Python version: 3.8
* Database platform, version: PostgreSQL v13

### Steps to Reproduce
1. Create root location type
2. Create child location type of the root location type
3. Create root location
4. Create child location of the root location

### Expected Behavior
Child Location's slug auto generated using the parent location's slug

![Screen Shot 2022-09-22 at 10 25 28 AM](https://user-images.githubusercontent.com/98360253/191788181-eb8eb14e-ceaa-468b-9cf7-672811287a57.png)

### Observed Behavior
Child Location's slug auto generated using the parent location's UUID

![Screen Shot 2022-09-22 at 10 28 18 AM](https://user-images.githubusercontent.com/98360253/191788941-9923a1e2-be37-42ba-8ff1-e32e84d4ac1d.png)



Discussion:
Appears to have been introduced by #2322. Any fix needs to be careful to not re-introduce #2311.

Resolving patch:
diff --git a/nautobot/dcim/forms.py b/nautobot/dcim/forms.py
--- a/nautobot/dcim/forms.py
+++ b/nautobot/dcim/forms.py
@@ -468,6 +468,7 @@ class LocationForm(NautobotModelForm, TenancyForm):
     parent = DynamicModelChoiceField(
         queryset=Location.objects.all(),
         query_params={"child_location_type": "$location_type"},
+        to_field_name="slug",
         required=False,
     )
     site = DynamicModelChoiceField(queryset=Site.objects.all(), required=False)
diff --git a/nautobot/utilities/forms/fields.py b/nautobot/utilities/forms/fields.py
--- a/nautobot/utilities/forms/fields.py
+++ b/nautobot/utilities/forms/fields.py
@@ -541,7 +541,7 @@ def get_bound_field(self, form, field_name):
             filter_ = self.filter(field_name=field_name)
             try:
                 self.queryset = filter_.filter(self.queryset, data)
-            except TypeError:
+            except (TypeError, ValidationError):
                 # Catch any error caused by invalid initial data passed from the user
                 self.queryset = self.queryset.none()
         else:
diff --git a/nautobot/utilities/utils.py b/nautobot/utilities/utils.py
--- a/nautobot/utilities/utils.py
+++ b/nautobot/utilities/utils.py
@@ -14,7 +14,7 @@
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError
 from django.core.serializers import serialize
-from django.db.models import Count, Model, OuterRef, Subquery
+from django.db.models import Count, ForeignKey, Model, OuterRef, Subquery
 from django.db.models.functions import Coalesce
 from django.http import QueryDict
 from django.utils.tree import Node
@@ -347,11 +347,27 @@ def prepare_cloned_fields(instance):
     Compile an object's `clone_fields` list into a string of URL query parameters. Tags are automatically cloned where
     applicable.
     """
+    form_class = get_form_for_model(instance)
+    form = form_class() if form_class is not None else None
     params = []
     for field_name in getattr(instance, "clone_fields", []):
         field = instance._meta.get_field(field_name)
         field_value = field.value_from_object(instance)
 
+        # For foreign-key fields, if the ModelForm's field has a defined `to_field_name`,
+        # use that field from the related object instead of its PK.
+        # Example: Location.parent, LocationForm().fields["parent"].to_field_name = "slug", so use slug rather than PK.
+        if isinstance(field, ForeignKey):
+            related_object = getattr(instance, field_name)
+            if (
+                related_object is not None
+                and form is not None
+                and field_name in form.fields
+                and hasattr(form.fields[field_name], "to_field_name")
+                and form.fields[field_name].to_field_name is not None
+            ):
+                field_value = getattr(related_object, form.fields[field_name].to_field_name)
+
         # Swap out False with URL-friendly value
         if field_value is False:
             field_value = ""
@@ -542,7 +558,7 @@ def get_related_class_for_model(model, module_name, object_suffix):
     """Return the appropriate class associated with a given model matching the `module_name` and
     `object_suffix`.
 
-    The given `model` can either be a model class or a dotted representation (ex: `dcim.device`).
+    The given `model` can either be a model class, a model instance, or a dotted representation (ex: `dcim.device`).
 
     The object class is expected to be in the module within the application
     associated with the model and its name is expected to be `{ModelName}{object_suffix}`.
@@ -554,6 +570,8 @@ def get_related_class_for_model(model, module_name, object_suffix):
     """
     if isinstance(model, str):
         model = get_model_from_name(model)
+    if isinstance(model, Model):
+        model = type(model)
     if not inspect.isclass(model):
         raise TypeError(f"{model!r} is not a Django Model class")
     if not issubclass(model, Model):


Test patch: