Repository: pulp/pulpcore

Issue:
[noissue]: Update djangorestframework requirement from <=3.15.0,>=3.14.0 to >=3.14.0,<=3.15.1
Updates the requirements on [djangorestframework](https://github.com/encode/django-rest-framework) to permit the latest version.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/encode/django-rest-framework/commit/328591693d7a3e734ca5a740dddf85e11ccd208f"><code>3285916</code></a> Version 3.15.1 (<a href="https://redirect.github.com/encode/django-rest-framework/issues/9339">#9339</a>)</li>
<li><a href="https://github.com/encode/django-rest-framework/commit/eb361d289deb4bc99ad2ebab9c5f50a92de40339"><code>eb361d2</code></a> SearchFilter.get_search_terms returns list. (<a href="https://redirect.github.com/encode/django-rest-framework/issues/9338">#9338</a>)</li>
<li><a href="https://github.com/encode/django-rest-framework/commit/400b4c54419c5c88542ddd0b97219ad4fa8ee29a"><code>400b4c5</code></a> Revert &quot;Fix NamespaceVersioning ignoring DEFAULT_VERSION on non-None namespac...</li>
<li><a href="https://github.com/encode/django-rest-framework/commit/4ef3aaf0ad0e31efe13d3c503a7099915a7c8875"><code>4ef3aaf</code></a> Revert <a href="https://redirect.github.com/encode/django-rest-framework/issues/9030">#9030</a> (<a href="https://redirect.github.com/encode/django-rest-framework/issues/9333">#9333</a>)</li>
<li><a href="https://github.com/encode/django-rest-framework/commit/4f10c4e43ee57f4a2e387e0c8d44d28d21a3621c"><code>4f10c4e</code></a> Revert &quot;Fix Respect <code>can_read_model</code> permission in DjangoModelPermissions (<a href="https://redirect.github.com/encode/django-rest-framework/issues/8">#8</a>...</li>
<li><a href="https://github.com/encode/django-rest-framework/commit/a4d58077a0aca89b82f63ab33ebb36b16bf26d4a"><code>a4d5807</code></a> Revert &quot;feat: Add some changes to ValidationError to support django style vad...</li>
<li><a href="https://github.com/encode/django-rest-framework/commit/da78a147f2f3c5820396f9fcd9372b2f5b9bbd18"><code>da78a14</code></a> Revert &quot;Re-prefetch related objects after updating (<a href="https://redirect.github.com/encode/django-rest-framework/issues/8043">#8043</a>)&quot; (<a href="https://redirect.github.com/encode/django-rest-framework/issues/9327">#9327</a>)</li>
<li><a href="https://github.com/encode/django-rest-framework/commit/0e4ed816279a8b9332544192ad90f7324f49cd62"><code>0e4ed81</code></a> Revert &quot;feat: Add some changes to ValidationError to support django style vad...</li>
<li><a href="https://github.com/encode/django-rest-framework/commit/56a5b354d0f82ea7e0df5cc4aa5187fe8450cbcb"><code>56a5b35</code></a> Add drf-sendables to third-party-packages.md (<a href="https://redirect.github.com/encode/django-rest-framework/issues/9261">#9261</a>)</li>
<li><a href="https://github.com/encode/django-rest-framework/commit/d7c8dcfc7ee33edff0383688f89e7c1f6b474f6d"><code>d7c8dcf</code></a> Revert PR that adds autocomplete_fields to TokenAdmin, as this break some use...</li>
<li>Additional commits viewable in <a href="https://github.com/encode/django-rest-framework/compare/3.14.0...3.15.1">compare view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Discussion:

Resolving patch:
diff --git a/pulpcore/app/serializers/__init__.py b/pulpcore/app/serializers/__init__.py
--- a/pulpcore/app/serializers/__init__.py
+++ b/pulpcore/app/serializers/__init__.py
@@ -51,4 +51,9 @@
     TaskSerializer,
     WorkerSerializer,
 )
-from .upload import UploadChunkSerializer, UploadCommitSerializer, UploadSerializer  # noqa
+from .upload import (  # noqa
+    UploadChunkSerializer,
+    UploadCommitSerializer,
+    UploadSerializer,
+    UploadDetailSerializer
+)
diff --git a/pulpcore/app/serializers/upload.py b/pulpcore/app/serializers/upload.py
--- a/pulpcore/app/serializers/upload.py
+++ b/pulpcore/app/serializers/upload.py
@@ -5,6 +5,24 @@
 from pulpcore.app.serializers import base
 
 
+class UploadChunkSerializer(serializers.Serializer):
+    file = serializers.FileField(
+        help_text=_("A chunk of the uploaded file."),
+    )
+
+    sha256 = serializers.CharField(
+        help_text=_("The SHA-256 checksum of the chunk if available."),
+        required=False,
+        allow_null=True,
+    )
+
+
+class UploadChunkDetailSerializer(base.ModelSerializer):
+    class Meta:
+        model = models.UploadChunk
+        fields = ('offset', 'size')
+
+
 class UploadSerializer(base.ModelSerializer):
     """Serializer for chunked uploads."""
     _href = base.IdentityField(
@@ -25,16 +43,15 @@ class Meta:
         fields = base.ModelSerializer.Meta.fields + ('size', 'completed')
 
 
-class UploadChunkSerializer(serializers.Serializer):
-    file = serializers.FileField(
-        help_text=_("A chunk of the uploaded file."),
+class UploadDetailSerializer(UploadSerializer):
+    chunks = UploadChunkDetailSerializer(
+        many=True,
+        read_only=True,
     )
 
-    sha256 = serializers.CharField(
-        help_text=_("The SHA-256 checksum of the chunk if available."),
-        required=False,
-        allow_null=True,
-    )
+    class Meta:
+        model = models.Upload
+        fields = base.ModelSerializer.Meta.fields + ('size', 'completed', 'chunks')
 
 
 class UploadCommitSerializer(serializers.Serializer):
diff --git a/pulpcore/app/viewsets/upload.py b/pulpcore/app/viewsets/upload.py
--- a/pulpcore/app/viewsets/upload.py
+++ b/pulpcore/app/viewsets/upload.py
@@ -9,7 +9,12 @@
 from rest_framework.response import Response
 
 from pulpcore.app.models import Upload
-from pulpcore.app.serializers import UploadChunkSerializer, UploadCommitSerializer, UploadSerializer
+from pulpcore.app.serializers import (
+    UploadChunkSerializer,
+    UploadCommitSerializer,
+    UploadSerializer,
+    UploadDetailSerializer
+)
 from pulpcore.app.viewsets import BaseFilterSet
 from pulpcore.app.viewsets.base import DATETIME_FILTER_OPTIONS, NamedModelViewSet
 from pulpcore.app.viewsets.custom_filters import IsoDateTimeFilter
@@ -34,7 +39,6 @@ class UploadViewSet(NamedModelViewSet,
     """View for chunked uploads."""
     endpoint_name = 'uploads'
     queryset = Upload.objects.all()
-    serializer_class = UploadSerializer
     filterset_class = UploadFilter
     http_method_names = ['get', 'post', 'head', 'put', 'delete']  # remove PATCH
 
@@ -45,6 +49,11 @@ class UploadViewSet(NamedModelViewSet,
                   description='The Content-Range header specifies the location of the file chunk '
                               'within the file.')
 
+    def get_serializer_class(self):
+        if self.action == 'retrieve':
+            return UploadDetailSerializer
+        return UploadSerializer
+
     @swagger_auto_schema(operation_summary="Upload a file chunk",
                          request_body=UploadChunkSerializer,
                          manual_parameters=[content_range_parameter],


Test patch:
diff --git a/pulpcore/tests/functional/api/test_upload.py b/pulpcore/tests/functional/api/test_upload.py
--- a/pulpcore/tests/functional/api/test_upload.py
+++ b/pulpcore/tests/functional/api/test_upload.py
@@ -29,6 +29,7 @@ class ChunkedUploadTestCase(unittest.TestCase):
     * `Pulp #4197 <https://pulp.plan.io/issues/4197>`_
     * `Pulp #5092 <https://pulp.plan.io/issues/5092>`_
     * `Pulp #4982 <https://pulp.plan.io/issues/4982>`_
+    * `Pulp #5150 <https://pulp.plan.io/issues/5150>`_
     """
 
     @classmethod
@@ -123,6 +124,41 @@ def test_upload_chunk_wrong_checksum(self):
 
             assert response.status_code == 400
 
+    def test_upload_response(self):
+        """Test upload responses when creating an upload and uploading chunks."""
+        self.client.response_handler = api.echo_handler
+
+        upload_request = self.client.post(
+            UPLOAD_PATH, {'size': self.size_file}
+        )
+
+        expected_keys = ['_href', '_created', 'size', 'completed']
+
+        self.assertEquals([*upload_request.json()], expected_keys)
+
+        for data in self.chunked_data:
+            response = self.client.put(
+                upload_request.json()['_href'],
+                files={'file': data[0]},
+                headers=data[1],
+            )
+
+            self.assertEquals([*response.json()], expected_keys)
+
+        response = self.client.get(upload_request.json()['_href'])
+
+        expected_keys = ['_href', '_created', 'size', 'completed', 'chunks']
+
+        self.assertEquals([*response.json()], expected_keys)
+
+        expected_chunks = [
+            {'offset': 0, 'size': 6291456},
+            {'offset': 6291456, 'size': 4194304}
+        ]
+
+        sorted_chunks_response = sorted(response.json()['chunks'], key=lambda i: i['offset'])
+        self.assertEquals(sorted_chunks_response, expected_chunks)
+
     def test_delete_upload(self):
         """Test a deletion of an upload using upload of files in chunks."""