Repository: nautobot/nautobot

Issue:
[v2.2] Inconsistent required fields for Contact & Team for UI vs API
<!--
    NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.

    This form is only for reporting reproducible bugs. If you need assistance
    with Nautobot installation, or if you have a general question, please start a
    discussion instead: https://github.com/nautobot/nautobot/discussions

    Please describe the environment in which you are running Nautobot. Be sure
    that you are running an unmodified instance of the latest stable release
    before submitting a bug report, and that any plugins have been disabled.
-->
### Environment
* Nautobot version (Docker tag too if applicable): 2.2.0b1
* Python version: 3.11.8
* Database platform, version:
* Middleware(s):

<!--
    Describe in detail the exact steps that someone else can take to reproduce
    this bug using the current stable release of Nautobot. Begin with the
    creation of any necessary database objects and call out every operation
    being performed explicitly. If reporting a bug in the REST API, be sure to
    reconstruct the raw HTTP request(s) being made: Don't rely on a client
    library such as pynautobot.
-->
### Steps to Reproduce
1. Try and create a Contact or Team via both UI and API

<!-- What did you expect to happen? -->
### Expected Behavior
Either Phone and Email are required or not required for both methods

<!-- What happened instead? -->
### Observed Behavior
Phone and Email are **not required for UI**:
![Screenshot 2024-03-26 at 3 45 29 PM](https://github.com/nautobot/nautobot/assets/10467633/97974371-ad1b-45be-aad2-23cf381fb908)
Phone and Email are **required for API**:
![Screenshot 2024-03-26 at 3 45 21 PM](https://github.com/nautobot/nautobot/assets/10467633/b50f789d-d901-425d-8350-7f8744464d9b)


Discussion:
Looks like the recurrent DRF issue where any field included in a `unique_together` gets incorrectly auto-marked as required in the API. Solution is to override the automatically generated validators on the serializer and do the validation manually instead. (Example: https://github.com/nautobot/nautobot/blob/develop/nautobot/dcim/api/serializers.py#L208-L209 and https://github.com/nautobot/nautobot/blob/develop/nautobot/dcim/api/serializers.py#L239-L247)
FYI, it looks like Teams are also affected.

Resolving patch:
diff --git a/nautobot/extras/api/serializers.py b/nautobot/extras/api/serializers.py
--- a/nautobot/extras/api/serializers.py
+++ b/nautobot/extras/api/serializers.py
@@ -193,10 +193,18 @@ class ContactSerializer(NautobotModelSerializer):
     class Meta:
         model = Contact
         fields = "__all__"
+        # https://www.django-rest-framework.org/api-guide/validators/#optional-fields
+        validators = []
+        extra_kwargs = {
+            "email": {"default": ""},
+            "phone": {"default": ""},
+        }
 
     def validate(self, data):
         attrs = data.copy()
         attrs.pop("teams", None)
+        validator = UniqueTogetherValidator(queryset=Contact.objects.all(), fields=("name", "phone", "email"))
+        validator(attrs, self)
         super().validate(attrs)
         return data
 
@@ -946,7 +954,18 @@ class TeamSerializer(NautobotModelSerializer):
     class Meta:
         model = Team
         fields = "__all__"
-        extra_kwargs = {"contacts": {"required": False}}
+        extra_kwargs = {
+            "contacts": {"required": False},
+            "email": {"default": ""},
+            "phone": {"default": ""},
+        }
+        # https://www.django-rest-framework.org/api-guide/validators/#optional-fields
+        validators = []
+
+    def validate(self, data):
+        validator = UniqueTogetherValidator(queryset=Team.objects.all(), fields=("name", "phone", "email"))
+        validator(data, self)
+        return super().validate(data)
 
 
 #


Test patch:
diff --git a/nautobot/extras/tests/test_api.py b/nautobot/extras/tests/test_api.py
--- a/nautobot/extras/tests/test_api.py
+++ b/nautobot/extras/tests/test_api.py
@@ -409,11 +409,9 @@ def setUpTestData(cls):
             {
                 "name": "Contact 3",
                 "phone": "555-0123",
-                "email": "",
             },
             {
                 "name": "Contact 4",
-                "phone": "",
                 "email": "contact4@example.com",
             },
         ]
@@ -3613,11 +3611,9 @@ def setUpTestData(cls):
             {
                 "name": "Team 3",
                 "phone": "555-0123",
-                "email": "",
             },
             {
                 "name": "Team 4",
-                "phone": "",
                 "email": "team4@example.com",
                 "address": "Rainbow Bridge, Central NJ",
             },