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

Replace NestedFooSerializer and ?brief=True parameters using DRF's built-in self.Meta.depth #3500

Merged
merged 58 commits into from
Apr 21, 2023

Conversation

HanlinMiao
Copy link
Contributor

@HanlinMiao HanlinMiao commented Mar 28, 2023

Closes: #3042

What's Changed

Remaining Items

  • Tags serializer representation
  • Polymorphic Serializer Functionality
  • Do we need extra_fields in class Meta of the serializers?
  • depth in OpenAPI Schema
  • What should be the default depth 0 or 1, depth should be 0
  • Testing & Documentation
    • Generic test bed to test depth to 2 but does not need to be exhaustive
    • Remove all of the brief testing functionality
  • The Dynamic Group Child Group Edit form is broken?
  • TokenSerializer whether user field is a required field or not.
  • DeviceSerializer and VirtualMachineSerializer
    • primary_ip and location fields are not inherent fields (how to solve this)
  • cluster_count is missing from TenantSerializer in next and develop
  • NestedSerializer representation of a non-direct model field e.g obj.cluster.location
  • ObjectPermission reinforcement on NestedSerializers

Location Browsable API
?depth=0 / default depth
Screen Shot 2023-04-11 at 4 20 02 PM
?depth = 1
Screen Shot 2023-04-11 at 4 20 33 PM
?depth = 2 too much info to fit on one screen but notice that parent's parent is expanded as well
Screen Shot 2023-04-11 at 4 20 47 PM

TODO

  • Explanation of Change(s)
  • Added change log fragment(s) (for more information see the documentation)
  • Attached Screenshots, Payload Example
  • Unit, Integration Tests
  • Documentation Updates (when adding/changing features)
  • Example Plugin Updates (when adding/changing features)
  • Outline Remaining Work, Constraints from Design

nautobot/dcim/api/serializers.py Show resolved Hide resolved
nautobot/core/api/serializers.py Outdated Show resolved Hide resolved
nautobot/core/api/views.py Outdated Show resolved Hide resolved
nautobot/circuits/api/serializers.py Outdated Show resolved Hide resolved
nautobot/circuits/api/serializers.py Show resolved Hide resolved
nautobot/circuits/api/serializers.py Show resolved Hide resolved
nautobot/core/api/serializers.py Outdated Show resolved Hide resolved
nautobot/core/api/serializers.py Outdated Show resolved Hide resolved
def build_nested_field(self, field_name, relation_info, nested_depth):
field = get_serializer_for_model(relation_info.related_model)

class NautobotNestedSerializer(field):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class logic looks really similar to NautobotPrimaryKeyRelatedField above, I guess finding a way to utitlize that class here might be a better approach

@jvanderaa
Copy link
Contributor

What is going to be the overall change on this. What is the goal, what should be tested in SDKs?

@jvanderaa
Copy link
Contributor

Some discussion points that will be coming up.

From my viewpoint, whether there is a default depth of 0 or 1 isn't really a big deal. I like the brevity that comes with 0, just need to make sure that the novel pynautobot and Nautobot API user know how to work with it.

From my standpoint, if the default is 0, pynautobot would remain having the default be passed in, and provide a mechanism to set the depth on the calls within the instance create time. The Nautobot Ansible modules would create the instance with a depth of 1 to get at the same nested serializer data that is coming along.

So if there is a vote, I'm a vote for a depth of 0 as default, and that the SDKs take care of providing sane default (Ansible) and access to the system.

Copy link
Contributor

@glennmatthews glennmatthews left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚤 🐎 ⏩

changes/3042.changed Outdated Show resolved Hide resolved
changes/3042.changed Outdated Show resolved Hide resolved
nautobot/core/api/mixins.py Show resolved Hide resolved
nautobot/core/api/mixins.py Outdated Show resolved Hide resolved
nautobot/core/api/mixins.py Outdated Show resolved Hide resolved
nautobot/ipam/api/serializers.py Outdated Show resolved Hide resolved
nautobot/ipam/api/serializers.py Show resolved Hide resolved
nautobot/virtualization/api/serializers.py Outdated Show resolved Hide resolved
nautobot/virtualization/api/serializers.py Outdated Show resolved Hide resolved
@@ -453,7 +453,7 @@ class VMInterfaceForm(NautobotModelForm, InterfaceCommonForm):
queryset=VLAN.objects.all(),
required=False,
label="Untagged VLAN",
brief_mode=False,
depth=1,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comments, I don't think this is in fact correct/necessary in the vast majority of cases.

@bryanculver bryanculver mentioned this pull request Apr 21, 2023
6 tasks
Comment on lines 204 to 205
field_name == "tags"
and isinstance(model_class.tags, (_NautobotTaggableManager, _TaggableManager))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably just the isinstance check is sufficient here - I think even if we had a _TaggableManager field named something other than tags we'd still need this case?

@@ -315,3 +425,194 @@ class BulkOperationIntegerIDSerializer(serializers.Serializer):
class GraphQLAPISerializer(serializers.Serializer):
query = serializers.CharField(required=True, help_text="GraphQL query")
variables = serializers.JSONField(required=False, help_text="Variables in JSON Format")


class CustomFieldModelSerializerMixin(ValidatedModelSerializer):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should this be in nautobot.core.api.mixins instead?

pass


class RelationshipModelSerializerMixin(ValidatedModelSerializer):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: move to nautobot.core.api.mixins?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did try that before, however ValidatedModelSerializer and BaseModelSerializer base classes for these mixins seem to create another circular import problem🫠

return fields


class NotesSerializerMixin(BaseModelSerializer):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: move to nautobot.core.api.mixins?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment above

in which relation_info and nested_depth are already given.
"""
nested_serializer_name = (
"Nested" + str(nested_depth) + f"{relation_info.related_model._meta.model_name.capitalize()}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly?

Suggested change
"Nested" + str(nested_depth) + f"{relation_info.related_model._meta.model_name.capitalize()}"
f"Nested{nested_depth}{relation_info.related_model.__name__}"

Comment on lines -746 to +475
@extend_schema_field(NestedDeviceSerializer)
def get_parent_device(self, obj):
@extend_schema_field(DeviceBaySerializer)
def get_parent_bay(self, obj):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to document this as an API change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added as an entry in v2-api-renamed-fields.yaml

context = {"request": self.context["request"]}
data = NestedDeviceSerializer(instance=device_bay.device, context=context).data
data["device_bay"] = NestedDeviceBaySerializer(instance=device_bay, context=context).data
depth = int(self.context.get("depth", 0))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll be honest, I still don't fully understand this. :-) I see where Meta.depth is decremented when we create a nested serializer, but I don't see where context["depth"] does this. So it seems like with this code as-is, a query with depth=1 would result in the Device serializer rendering with depth 1, the nested device-bay serializer rendering with depth 1, its nested device serializer rendering with depth 1, its own nested device-bay serializer rendering with depth 1, etc. to infinite depth. But obviously that isn't happening... so what am I missing? :-)

Copy link
Contributor

@glennmatthews glennmatthews left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚢 🇮🇹, I say :shipit:!


def get_queryset_filter_params(self, data, queryset):
"""
Data could be a dictionary and an int (for the User model) or a str that represents the primary key.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dictionary and an int? It's not clear to me what that means.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be {"location": location.pk} or a nested dictionary {"location": {"name": "Campus-01"}}

params = dict_to_filter_params(data)
return self.remove_non_filter_fields(params)
try:
pk = int(data) if isinstance(queryset.model._meta.pk, AutoField) else uuid.UUID(str(data))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why casting data to a UUID in the latter case? UUIDs can be strings. Are you just trying to force the error to assert that it must be either an int or UUID? If so can you please add a brief comment as to the intent here for posterity?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the int case is for the User model mentioned in the docstring above. But yeah I can add a comment here as well.

@@ -128,6 +128,10 @@ These endpoints `/ipam/roles/`, `/dcim/rack-roles/` and `/dcim/device-roles/` ar
| `/dcim/rack-roles/` | `/extras/roles/` |
| `/ipam/roles/` | `/extras/roles/` |

### API Query Parameters Changes

Nautobot 2.0 removes the `?brief` query parameter and adds support for the `?depth` query parameter. As a result, the ability to specify `brief_mode` in `DynamicModelChoiceField` , `DynamicModelMultipleChoiceField` or `MultiMatchModelMultipleChoiceField` is also removed. For every occurrence of the aforementioned fields where you have `brief_mode` set to `True/False` e.g. `brief_mode=True`, please remove the statement. And leave other occurrences of the fields where you do not have `brief_mode` specified as they are. See more details about the `?depth` query parameter [here](../rest-api/overview.md/#depth-query-parameter)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should never link anything in documentation using "here". Better to always link the contextual text.

Suggested change
Nautobot 2.0 removes the `?brief` query parameter and adds support for the `?depth` query parameter. As a result, the ability to specify `brief_mode` in `DynamicModelChoiceField` , `DynamicModelMultipleChoiceField` or `MultiMatchModelMultipleChoiceField` is also removed. For every occurrence of the aforementioned fields where you have `brief_mode` set to `True/False` e.g. `brief_mode=True`, please remove the statement. And leave other occurrences of the fields where you do not have `brief_mode` specified as they are. See more details about the `?depth` query parameter [here](../rest-api/overview.md/#depth-query-parameter)
Nautobot 2.0 removes the `?brief` query parameter and adds support for the `?depth` query parameter. As a result, the ability to specify `brief_mode` in `DynamicModelChoiceField`, `DynamicModelMultipleChoiceField`, and `MultiMatchModelMultipleChoiceField` has also been removed. For every occurrence of the aforementioned fields where you have `brief_mode` set to `True/False` (e.g. `brief_mode=True`), please remove the statement, leaving other occurrences of the fields where you do not have `brief_mode` specified as they are.
Please see the [documentation on the `?depth` query parameter](../rest-api/overview.md/#depth-query-parameter) for more information.

@@ -62,9 +62,9 @@ Any model form that is intended to have a `status` field must inherit from one o

- FIXME: CSV import forms

### `StatusModelSerializerMixin` serializer mixin
### `` serializer mixin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a typo? Also everything below appears to be in bold now?

@@ -98,7 +98,7 @@ To fully integrate a model to include a `status` field, assert the following:

### Serializers

- Serializers for your model must inherit from `nautobot.extras.api.serializers.StatusModelSerializerMixin`
- Serializers for your model must inherit from `nautobot.extras.api.serializers.`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad search/replace?

@@ -18,6 +18,10 @@ DeviceRole, RackRole, IPAM Role, and IPAddressRoleChoices have all been merged i

Added Site Model Fields to Location. Location Model now has `asn`, `comments`, `contact_email`, `contact_name`, `contact_phone`, `facility`, `latitude`, `longitude`, `physical_address`, `shipping_address` and `time_zone` fields.

#### Added Depth REST API Query Parameter

Added the `?depth` query parameter in Nautobot v2.X to replace the `?brief` parameter in the REST API. It enables [nested serialization](https://www.django-rest-framework.org/api-guide/serializers/#specifying-nested-serialization) functionality and offers a more dynamic and comprehensive browsable API. It allows users greater control of the API response data and it is available for both retrieving a single object and a list of objects. This parameter is an positive integer value that can range from 0 to 10. Check out more about the `?depth` query parameter in our [REST API Documentation](../rest-api/overview.md/#depth-query-parameter)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Added the `?depth` query parameter in Nautobot v2.X to replace the `?brief` parameter in the REST API. It enables [nested serialization](https://www.django-rest-framework.org/api-guide/serializers/#specifying-nested-serialization) functionality and offers a more dynamic and comprehensive browsable API. It allows users greater control of the API response data and it is available for both retrieving a single object and a list of objects. This parameter is an positive integer value that can range from 0 to 10. Check out more about the `?depth` query parameter in our [REST API Documentation](../rest-api/overview.md/#depth-query-parameter)
Added the `?depth` query parameter in Nautobot v2.X to replace the `?brief` parameter in the REST API. It enables [nested serialization](https://www.django-rest-framework.org/api-guide/serializers/#specifying-nested-serialization) functionality and offers a more dynamic and comprehensive browsable API. It allows users greater control of the API response data and it is available for both retrieving a single object and a list of objects. This parameter is a positive integer value that can range from 0 to 10. To learn more more, check out the [documentation on the `?depth` query parameter](../rest-api/overview.md/#depth-query-parameter).

@@ -236,6 +240,10 @@ The "Container" status will be removed and all prefixes will be migrated to the

### Removed

#### Removed Brief REST API Query Parameter

Support for `?brief` REST API query parameter and `Nested*Serializers` are being removed in Nautobot v2.X. They are replaced by the new `?depth` query parameter. Check out the specific changes in [REST API documentation](../rest-api/overview.md/#depth-query-parameter)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Support for `?brief` REST API query parameter and `Nested*Serializers` are being removed in Nautobot v2.X. They are replaced by the new `?depth` query parameter. Check out the specific changes in [REST API documentation](../rest-api/overview.md/#depth-query-parameter)
Support for `?brief` REST API query parameter and `Nested*Serializers` have been removed in Nautobot v2.X. They are replaced by the new [`?depth` query parameter](../rest-api/overview.md/#depth-query-parameter).

// DynamicGroupSerializer has a `children` field which fits an inappropriate if condition
// in select2.min.js, which will result in the incorrect rendering of DynamicGroup DynamicChoiceField.
// So we nullify the field here since we do not need this field.
if (record.url.includes("dynamic-groups")){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't wait to yeet this in v2 UI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Replace NestedFooSerializer and brief=True pattern with DRF built-in Meta.depth
5 participants