Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add to_field_name=slug on LocationForm.parent, update prepare_cloned_fields to handle it #2773

Merged
merged 1 commit into from
Nov 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/2470.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed incorrect automatic generation of Location slugs in the UI.
1 change: 1 addition & 0 deletions nautobot/dcim/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion nautobot/utilities/forms/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
22 changes: 20 additions & 2 deletions nautobot/utilities/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = ""
Expand Down Expand Up @@ -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}`.
Expand All @@ -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):
Expand Down