We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
(deposit) support apostrophe in filenames
## Bug Report

**Problematic Behavior**
Loading a file with a typographic apostrophe (or curly quote) in the filename generates an error during the download process

``` xml
<Error>
<Code>InvalidArgument</Code>
<Message>Header value cannot be represented using ISO-8859-1.</Message>
<ArgumentName>response-content-disposition</ArgumentName>
<ArgumentValue>attachment; filename=Capture d’écran du 2023-07-05 17-55-04.png</ArgumentValue>
<RequestId>7576DY31G7YEKEHW</RequestId>
<HostId>20bDgURy8zglt3nW4UJbIbH0G5+c+DOmSeRKq/K2YNwP6rZMMHzS4h0Af91vioEWtbJsRurDzP0=</HostId>
</Error>
```

**Steps to Reproduce**
1. Add a Marsha Deposit XBlock on Studio
2. On student view, upload a file with a typographic apostrophe on its name 
3. Try to download the file

**Environment**
- Marsha version: production
- Platform: Open edX

**Possible Solution**
Use a more extensive character set like UTF-8?
Otherwise, escape apostrophes in filenames or replace it by right single quotation marks?

**Additional context/Screenshots**

- OK : https://www.compart.com/fr/unicode/U+0027
- KO : https://www.compart.com/fr/unicode/U+2019

[deposit-typographic-apostrophe-bug.webm](https://github.com/openfun/marsha/assets/23249365/ee43dd63-9b1a-43a7-912c-6ed84f07404c)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/backend/marsha/deposit/serializers.py`
Content:
```
1 """Structure of deposit related models API responses with Django Rest Framework serializers."""
2 import mimetypes
3 from os.path import splitext
4 from urllib.parse import quote_plus
5 
6 from django.conf import settings
7 from django.urls import reverse
8 
9 from rest_framework import serializers
10 
11 from marsha.core.serializers import (
12     BaseInitiateUploadSerializer,
13     UploadableFileWithExtensionSerializerMixin,
14     get_resource_cloudfront_url_params,
15 )
16 from marsha.core.serializers.playlist import PlaylistLiteSerializer
17 from marsha.core.utils import cloudfront_utils, time_utils
18 
19 from ..core.models import User
20 from .models import DepositedFile, FileDepository
21 
22 
23 class DepositedFileSerializer(
24     UploadableFileWithExtensionSerializerMixin,
25     serializers.ModelSerializer,
26     BaseInitiateUploadSerializer,
27 ):
28     """A serializer to display a deposited file."""
29 
30     class Meta:  # noqa
31         model = DepositedFile
32         fields = (
33             "filename",
34             "author_name",
35             "id",
36             "file_depository_id",
37             "read",
38             "url",
39             "uploaded_on",
40             "upload_state",
41             "size",
42         )
43         read_only_fields = (
44             "id",
45             "file_depository_id",
46             "url",
47             "uploaded_on",
48             "upload_state",
49         )
50 
51     # file_depository = FileDepositorySerializer(read_only=True)
52     # filename = serializers.SerializerMethodField()
53     url = serializers.SerializerMethodField()
54     # Make sure file depository UUID is converted to a string during serialization
55     file_depository_id = serializers.PrimaryKeyRelatedField(
56         read_only=True, pk_field=serializers.CharField()
57     )
58 
59     @property
60     def max_upload_file_size(self):
61         """return the deposited max file size define in the settings.
62 
63         The @property decorator is used to ease the use of @override_settings
64         in tests. Otherwise the setting is not changed and we can't easily test
65         an upload with a size higher than the one defined in the settings
66         """
67         return settings.DEPOSITED_FILE_SOURCE_MAX_SIZE
68 
69     def create(self, validated_data):
70         """Force the file depository field to the file depository of the JWT Token if any,
71         and set the author name to the username of the JWT Token if any.
72 
73         Parameters
74         ----------
75         validated_data : dictionary
76             Dictionary of the deserialized values of each field after validation.
77 
78         Returns
79         -------
80         dictionary
81             The "validated_data" dictionary is returned after modification.
82 
83         """
84         resource = self.context["request"].resource
85         user = self.context["request"].user
86         file_depository_id = self.context["view"].get_related_filedepository_id()
87 
88         if not validated_data.get("file_depository_id"):
89             validated_data["file_depository_id"] = file_depository_id
90 
91         if resource:
92             validated_data["author_id"] = resource.user.get("id")
93 
94             # try to get the most useful username from the token
95             if resource.user:
96                 if author_name := (
97                     resource.user.get("user_fullname") or resource.user.get("username")
98                 ):
99                     validated_data["author_name"] = author_name
100         else:
101             validated_data["author_id"] = user.id
102             validated_data["author_name"] = User.objects.get(id=user.id).username
103 
104         return super().create(validated_data)
105 
106     def _get_extension_string(self, obj):
107         """Deposited file extension with the leading dot.
108 
109         Parameters
110         ----------
111         obj : Type[models.DepositedFile]
112             The deposited file that we want to serialize
113 
114         Returns
115         -------
116         String
117             The extension with the leading dot if the deposited file has an extension
118             An empty string otherwise
119 
120         """
121         return "." + obj.extension if obj.extension else ""
122 
123     # def get_filename(self, obj):
124     #     """Filename of the deposited file."""
125     #     return self._get_filename(obj.title) if obj.uploaded_on else None
126 
127     def get_url(self, obj):
128         """Url of the DepositedFile.
129 
130         Parameters
131         ----------
132         obj : Type[models.DepositedFile]
133             The deposited file that we want to serialize
134 
135         Returns
136         -------
137         String or None
138             the url to fetch the deposited file on CloudFront
139             None if the deposited file is still not uploaded to S3 with success
140 
141         """
142         if obj.uploaded_on is None:
143             return None
144 
145         base = (
146             f"{settings.AWS_S3_URL_PROTOCOL}://{settings.CLOUDFRONT_DOMAIN}/"
147             f"{obj.file_depository.pk}/depositedfile/{obj.pk}/"
148             f"{time_utils.to_timestamp(obj.uploaded_on)}"
149         )
150 
151         url = (
152             f"{base:s}{self._get_extension_string(obj)}?"
153             f"response-content-disposition={quote_plus('attachment; filename=' + obj.filename)}"
154         )
155 
156         if settings.CLOUDFRONT_SIGNED_URLS_ACTIVE:
157             params = get_resource_cloudfront_url_params(
158                 "depositedfile", obj.file_depository_id
159             )
160             url = cloudfront_utils.build_signed_url(url, params)
161         return url
162 
163 
164 class DepositedFileInitiateUploadSerializer(BaseInitiateUploadSerializer):
165     """An initiate-upload serializer dedicated to deposited file."""
166 
167     @property
168     def max_upload_file_size(self):
169         """return the deposited max file size define in the settings.
170 
171         The @property decorator is used to ease the use of @override_settings
172         in tests. Otherwise the setting is not changed and we can't easily test
173         an upload with a size higher than the one defined in the settings
174         """
175         return settings.DEPOSITED_FILE_SOURCE_MAX_SIZE
176 
177     def validate(self, attrs):
178         """Validate if the mimetype is allowed or not."""
179         # mimetype is provided, we directly check it
180         if attrs["mimetype"] != "":
181             attrs["extension"] = mimetypes.guess_extension(attrs["mimetype"])
182 
183         # mimetype is not provided, we have to guess it from the extension
184         else:
185             mimetypes.init()
186             extension = splitext(attrs["filename"])[1]
187             mimetype = mimetypes.types_map.get(extension)
188             # extension is added to the data in order to be used later
189             attrs["extension"] = extension
190             attrs["mimetype"] = mimetype
191 
192         return attrs
193 
194 
195 class FileDepositorySerializer(serializers.ModelSerializer):
196     """A serializer to display a FileDepository resource."""
197 
198     class Meta:  # noqa
199         model = FileDepository
200         fields = (
201             "id",
202             "lti_id",
203             "title",
204             "description",
205             "playlist",
206         )
207         read_only_fields = (
208             "id",
209             "lti_id",
210             "playlist",
211         )
212 
213     playlist = PlaylistLiteSerializer(read_only=True)
214 
215 
216 class FileDepositorySelectLTISerializer(FileDepositorySerializer):
217     """A serializer to display a FileDepository resource for LTI select content request."""
218 
219     class Meta:  # noqa
220         model = FileDepository
221         fields = (
222             "id",
223             "lti_id",
224             "lti_url",
225             "title",
226             "description",
227             "playlist",
228         )
229 
230     lti_url = serializers.SerializerMethodField()
231 
232     def get_lti_url(self, obj):
233         """LTI Url of the FileDepository.
234 
235         Parameters
236         ----------
237         obj : Type[models.Document]
238             The document that we want to serialize
239 
240         Returns
241         -------
242         String
243             the LTI url to be used by LTI consumers
244 
245         """
246         return self.context["request"].build_absolute_uri(
247             reverse("deposit:file_depository_lti_view", args=[obj.id]),
248         )
249
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/backend/marsha/deposit/serializers.py b/src/backend/marsha/deposit/serializers.py
--- a/src/backend/marsha/deposit/serializers.py
+++ b/src/backend/marsha/deposit/serializers.py
@@ -1,7 +1,7 @@
 """Structure of deposit related models API responses with Django Rest Framework serializers."""
 import mimetypes
 from os.path import splitext
-from urllib.parse import quote_plus
+from urllib.parse import quote, quote_plus
 
 from django.conf import settings
 from django.urls import reverse
@@ -148,9 +148,12 @@
             f"{time_utils.to_timestamp(obj.uploaded_on)}"
         )
 
+        response_content_disposition = quote_plus(
+            "attachment; filename=" + quote(obj.filename)
+        )
         url = (
             f"{base:s}{self._get_extension_string(obj)}?"
-            f"response-content-disposition={quote_plus('attachment; filename=' + obj.filename)}"
+            f"response-content-disposition={response_content_disposition}"
         )
 
         if settings.CLOUDFRONT_SIGNED_URLS_ACTIVE:

{"golden_diff": "diff --git a/src/backend/marsha/deposit/serializers.py b/src/backend/marsha/deposit/serializers.py\n--- a/src/backend/marsha/deposit/serializers.py\n+++ b/src/backend/marsha/deposit/serializers.py\n@@ -1,7 +1,7 @@\n \"\"\"Structure of deposit related models API responses with Django Rest Framework serializers.\"\"\"\n import mimetypes\n from os.path import splitext\n-from urllib.parse import quote_plus\n+from urllib.parse import quote, quote_plus\n \n from django.conf import settings\n from django.urls import reverse\n@@ -148,9 +148,12 @@\n             f\"{time_utils.to_timestamp(obj.uploaded_on)}\"\n         )\n \n+        response_content_disposition = quote_plus(\n+            \"attachment; filename=\" + quote(obj.filename)\n+        )\n         url = (\n             f\"{base:s}{self._get_extension_string(obj)}?\"\n-            f\"response-content-disposition={quote_plus('attachment; filename=' + obj.filename)}\"\n+            f\"response-content-disposition={response_content_disposition}\"\n         )\n \n         if settings.CLOUDFRONT_SIGNED_URLS_ACTIVE:\n", "issue": "(deposit) support apostrophe in filenames\n## Bug Report\r\n\r\n**Problematic Behavior**\r\nLoading a file with a typographic apostrophe (or curly quote) in the filename generates an error during the download process\r\n\r\n``` xml\r\n<Error>\r\n<Code>InvalidArgument</Code>\r\n<Message>Header value cannot be represented using ISO-8859-1.</Message>\r\n<ArgumentName>response-content-disposition</ArgumentName>\r\n<ArgumentValue>attachment; filename=Capture d\u2019\u00e9cran du 2023-07-05 17-55-04.png</ArgumentValue>\r\n<RequestId>7576DY31G7YEKEHW</RequestId>\r\n<HostId>20bDgURy8zglt3nW4UJbIbH0G5+c+DOmSeRKq/K2YNwP6rZMMHzS4h0Af91vioEWtbJsRurDzP0=</HostId>\r\n</Error>\r\n```\r\n\r\n**Steps to Reproduce**\r\n1. Add a Marsha Deposit XBlock on Studio\r\n2. On student view, upload a file with a typographic apostrophe on its name \r\n3. Try to download the file\r\n\r\n**Environment**\r\n- Marsha version: production\r\n- Platform: Open edX\r\n\r\n**Possible Solution**\r\nUse a more extensive character set like UTF-8?\r\nOtherwise, escape apostrophes in filenames or replace it by right single quotation marks?\r\n\r\n**Additional context/Screenshots**\r\n\r\n- OK : https://www.compart.com/fr/unicode/U+0027\r\n- KO : https://www.compart.com/fr/unicode/U+2019\r\n\r\n[deposit-typographic-apostrophe-bug.webm](https://github.com/openfun/marsha/assets/23249365/ee43dd63-9b1a-43a7-912c-6ed84f07404c)\n", "before_files": [{"content": "\"\"\"Structure of deposit related models API responses with Django Rest Framework serializers.\"\"\"\nimport mimetypes\nfrom os.path import splitext\nfrom urllib.parse import quote_plus\n\nfrom django.conf import settings\nfrom django.urls import reverse\n\nfrom rest_framework import serializers\n\nfrom marsha.core.serializers import (\n    BaseInitiateUploadSerializer,\n    UploadableFileWithExtensionSerializerMixin,\n    get_resource_cloudfront_url_params,\n)\nfrom marsha.core.serializers.playlist import PlaylistLiteSerializer\nfrom marsha.core.utils import cloudfront_utils, time_utils\n\nfrom ..core.models import User\nfrom .models import DepositedFile, FileDepository\n\n\nclass DepositedFileSerializer(\n    UploadableFileWithExtensionSerializerMixin,\n    serializers.ModelSerializer,\n    BaseInitiateUploadSerializer,\n):\n    \"\"\"A serializer to display a deposited file.\"\"\"\n\n    class Meta:  # noqa\n        model = DepositedFile\n        fields = (\n            \"filename\",\n            \"author_name\",\n            \"id\",\n            \"file_depository_id\",\n            \"read\",\n            \"url\",\n            \"uploaded_on\",\n            \"upload_state\",\n            \"size\",\n        )\n        read_only_fields = (\n            \"id\",\n            \"file_depository_id\",\n            \"url\",\n            \"uploaded_on\",\n            \"upload_state\",\n        )\n\n    # file_depository = FileDepositorySerializer(read_only=True)\n    # filename = serializers.SerializerMethodField()\n    url = serializers.SerializerMethodField()\n    # Make sure file depository UUID is converted to a string during serialization\n    file_depository_id = serializers.PrimaryKeyRelatedField(\n        read_only=True, pk_field=serializers.CharField()\n    )\n\n    @property\n    def max_upload_file_size(self):\n        \"\"\"return the deposited max file size define in the settings.\n\n        The @property decorator is used to ease the use of @override_settings\n        in tests. Otherwise the setting is not changed and we can't easily test\n        an upload with a size higher than the one defined in the settings\n        \"\"\"\n        return settings.DEPOSITED_FILE_SOURCE_MAX_SIZE\n\n    def create(self, validated_data):\n        \"\"\"Force the file depository field to the file depository of the JWT Token if any,\n        and set the author name to the username of the JWT Token if any.\n\n        Parameters\n        ----------\n        validated_data : dictionary\n            Dictionary of the deserialized values of each field after validation.\n\n        Returns\n        -------\n        dictionary\n            The \"validated_data\" dictionary is returned after modification.\n\n        \"\"\"\n        resource = self.context[\"request\"].resource\n        user = self.context[\"request\"].user\n        file_depository_id = self.context[\"view\"].get_related_filedepository_id()\n\n        if not validated_data.get(\"file_depository_id\"):\n            validated_data[\"file_depository_id\"] = file_depository_id\n\n        if resource:\n            validated_data[\"author_id\"] = resource.user.get(\"id\")\n\n            # try to get the most useful username from the token\n            if resource.user:\n                if author_name := (\n                    resource.user.get(\"user_fullname\") or resource.user.get(\"username\")\n                ):\n                    validated_data[\"author_name\"] = author_name\n        else:\n            validated_data[\"author_id\"] = user.id\n            validated_data[\"author_name\"] = User.objects.get(id=user.id).username\n\n        return super().create(validated_data)\n\n    def _get_extension_string(self, obj):\n        \"\"\"Deposited file extension with the leading dot.\n\n        Parameters\n        ----------\n        obj : Type[models.DepositedFile]\n            The deposited file that we want to serialize\n\n        Returns\n        -------\n        String\n            The extension with the leading dot if the deposited file has an extension\n            An empty string otherwise\n\n        \"\"\"\n        return \".\" + obj.extension if obj.extension else \"\"\n\n    # def get_filename(self, obj):\n    #     \"\"\"Filename of the deposited file.\"\"\"\n    #     return self._get_filename(obj.title) if obj.uploaded_on else None\n\n    def get_url(self, obj):\n        \"\"\"Url of the DepositedFile.\n\n        Parameters\n        ----------\n        obj : Type[models.DepositedFile]\n            The deposited file that we want to serialize\n\n        Returns\n        -------\n        String or None\n            the url to fetch the deposited file on CloudFront\n            None if the deposited file is still not uploaded to S3 with success\n\n        \"\"\"\n        if obj.uploaded_on is None:\n            return None\n\n        base = (\n            f\"{settings.AWS_S3_URL_PROTOCOL}://{settings.CLOUDFRONT_DOMAIN}/\"\n            f\"{obj.file_depository.pk}/depositedfile/{obj.pk}/\"\n            f\"{time_utils.to_timestamp(obj.uploaded_on)}\"\n        )\n\n        url = (\n            f\"{base:s}{self._get_extension_string(obj)}?\"\n            f\"response-content-disposition={quote_plus('attachment; filename=' + obj.filename)}\"\n        )\n\n        if settings.CLOUDFRONT_SIGNED_URLS_ACTIVE:\n            params = get_resource_cloudfront_url_params(\n                \"depositedfile\", obj.file_depository_id\n            )\n            url = cloudfront_utils.build_signed_url(url, params)\n        return url\n\n\nclass DepositedFileInitiateUploadSerializer(BaseInitiateUploadSerializer):\n    \"\"\"An initiate-upload serializer dedicated to deposited file.\"\"\"\n\n    @property\n    def max_upload_file_size(self):\n        \"\"\"return the deposited max file size define in the settings.\n\n        The @property decorator is used to ease the use of @override_settings\n        in tests. Otherwise the setting is not changed and we can't easily test\n        an upload with a size higher than the one defined in the settings\n        \"\"\"\n        return settings.DEPOSITED_FILE_SOURCE_MAX_SIZE\n\n    def validate(self, attrs):\n        \"\"\"Validate if the mimetype is allowed or not.\"\"\"\n        # mimetype is provided, we directly check it\n        if attrs[\"mimetype\"] != \"\":\n            attrs[\"extension\"] = mimetypes.guess_extension(attrs[\"mimetype\"])\n\n        # mimetype is not provided, we have to guess it from the extension\n        else:\n            mimetypes.init()\n            extension = splitext(attrs[\"filename\"])[1]\n            mimetype = mimetypes.types_map.get(extension)\n            # extension is added to the data in order to be used later\n            attrs[\"extension\"] = extension\n            attrs[\"mimetype\"] = mimetype\n\n        return attrs\n\n\nclass FileDepositorySerializer(serializers.ModelSerializer):\n    \"\"\"A serializer to display a FileDepository resource.\"\"\"\n\n    class Meta:  # noqa\n        model = FileDepository\n        fields = (\n            \"id\",\n            \"lti_id\",\n            \"title\",\n            \"description\",\n            \"playlist\",\n        )\n        read_only_fields = (\n            \"id\",\n            \"lti_id\",\n            \"playlist\",\n        )\n\n    playlist = PlaylistLiteSerializer(read_only=True)\n\n\nclass FileDepositorySelectLTISerializer(FileDepositorySerializer):\n    \"\"\"A serializer to display a FileDepository resource for LTI select content request.\"\"\"\n\n    class Meta:  # noqa\n        model = FileDepository\n        fields = (\n            \"id\",\n            \"lti_id\",\n            \"lti_url\",\n            \"title\",\n            \"description\",\n            \"playlist\",\n        )\n\n    lti_url = serializers.SerializerMethodField()\n\n    def get_lti_url(self, obj):\n        \"\"\"LTI Url of the FileDepository.\n\n        Parameters\n        ----------\n        obj : Type[models.Document]\n            The document that we want to serialize\n\n        Returns\n        -------\n        String\n            the LTI url to be used by LTI consumers\n\n        \"\"\"\n        return self.context[\"request\"].build_absolute_uri(\n            reverse(\"deposit:file_depository_lti_view\", args=[obj.id]),\n        )\n", "path": "src/backend/marsha/deposit/serializers.py"}], "after_files": [{"content": "\"\"\"Structure of deposit related models API responses with Django Rest Framework serializers.\"\"\"\nimport mimetypes\nfrom os.path import splitext\nfrom urllib.parse import quote, quote_plus\n\nfrom django.conf import settings\nfrom django.urls import reverse\n\nfrom rest_framework import serializers\n\nfrom marsha.core.serializers import (\n    BaseInitiateUploadSerializer,\n    UploadableFileWithExtensionSerializerMixin,\n    get_resource_cloudfront_url_params,\n)\nfrom marsha.core.serializers.playlist import PlaylistLiteSerializer\nfrom marsha.core.utils import cloudfront_utils, time_utils\n\nfrom ..core.models import User\nfrom .models import DepositedFile, FileDepository\n\n\nclass DepositedFileSerializer(\n    UploadableFileWithExtensionSerializerMixin,\n    serializers.ModelSerializer,\n    BaseInitiateUploadSerializer,\n):\n    \"\"\"A serializer to display a deposited file.\"\"\"\n\n    class Meta:  # noqa\n        model = DepositedFile\n        fields = (\n            \"filename\",\n            \"author_name\",\n            \"id\",\n            \"file_depository_id\",\n            \"read\",\n            \"url\",\n            \"uploaded_on\",\n            \"upload_state\",\n            \"size\",\n        )\n        read_only_fields = (\n            \"id\",\n            \"file_depository_id\",\n            \"url\",\n            \"uploaded_on\",\n            \"upload_state\",\n        )\n\n    # file_depository = FileDepositorySerializer(read_only=True)\n    # filename = serializers.SerializerMethodField()\n    url = serializers.SerializerMethodField()\n    # Make sure file depository UUID is converted to a string during serialization\n    file_depository_id = serializers.PrimaryKeyRelatedField(\n        read_only=True, pk_field=serializers.CharField()\n    )\n\n    @property\n    def max_upload_file_size(self):\n        \"\"\"return the deposited max file size define in the settings.\n\n        The @property decorator is used to ease the use of @override_settings\n        in tests. Otherwise the setting is not changed and we can't easily test\n        an upload with a size higher than the one defined in the settings\n        \"\"\"\n        return settings.DEPOSITED_FILE_SOURCE_MAX_SIZE\n\n    def create(self, validated_data):\n        \"\"\"Force the file depository field to the file depository of the JWT Token if any,\n        and set the author name to the username of the JWT Token if any.\n\n        Parameters\n        ----------\n        validated_data : dictionary\n            Dictionary of the deserialized values of each field after validation.\n\n        Returns\n        -------\n        dictionary\n            The \"validated_data\" dictionary is returned after modification.\n\n        \"\"\"\n        resource = self.context[\"request\"].resource\n        user = self.context[\"request\"].user\n        file_depository_id = self.context[\"view\"].get_related_filedepository_id()\n\n        if not validated_data.get(\"file_depository_id\"):\n            validated_data[\"file_depository_id\"] = file_depository_id\n\n        if resource:\n            validated_data[\"author_id\"] = resource.user.get(\"id\")\n\n            # try to get the most useful username from the token\n            if resource.user:\n                if author_name := (\n                    resource.user.get(\"user_fullname\") or resource.user.get(\"username\")\n                ):\n                    validated_data[\"author_name\"] = author_name\n        else:\n            validated_data[\"author_id\"] = user.id\n            validated_data[\"author_name\"] = User.objects.get(id=user.id).username\n\n        return super().create(validated_data)\n\n    def _get_extension_string(self, obj):\n        \"\"\"Deposited file extension with the leading dot.\n\n        Parameters\n        ----------\n        obj : Type[models.DepositedFile]\n            The deposited file that we want to serialize\n\n        Returns\n        -------\n        String\n            The extension with the leading dot if the deposited file has an extension\n            An empty string otherwise\n\n        \"\"\"\n        return \".\" + obj.extension if obj.extension else \"\"\n\n    # def get_filename(self, obj):\n    #     \"\"\"Filename of the deposited file.\"\"\"\n    #     return self._get_filename(obj.title) if obj.uploaded_on else None\n\n    def get_url(self, obj):\n        \"\"\"Url of the DepositedFile.\n\n        Parameters\n        ----------\n        obj : Type[models.DepositedFile]\n            The deposited file that we want to serialize\n\n        Returns\n        -------\n        String or None\n            the url to fetch the deposited file on CloudFront\n            None if the deposited file is still not uploaded to S3 with success\n\n        \"\"\"\n        if obj.uploaded_on is None:\n            return None\n\n        base = (\n            f\"{settings.AWS_S3_URL_PROTOCOL}://{settings.CLOUDFRONT_DOMAIN}/\"\n            f\"{obj.file_depository.pk}/depositedfile/{obj.pk}/\"\n            f\"{time_utils.to_timestamp(obj.uploaded_on)}\"\n        )\n\n        response_content_disposition = quote_plus(\n            \"attachment; filename=\" + quote(obj.filename)\n        )\n        url = (\n            f\"{base:s}{self._get_extension_string(obj)}?\"\n            f\"response-content-disposition={response_content_disposition}\"\n        )\n\n        if settings.CLOUDFRONT_SIGNED_URLS_ACTIVE:\n            params = get_resource_cloudfront_url_params(\n                \"depositedfile\", obj.file_depository_id\n            )\n            url = cloudfront_utils.build_signed_url(url, params)\n        return url\n\n\nclass DepositedFileInitiateUploadSerializer(BaseInitiateUploadSerializer):\n    \"\"\"An initiate-upload serializer dedicated to deposited file.\"\"\"\n\n    @property\n    def max_upload_file_size(self):\n        \"\"\"return the deposited max file size define in the settings.\n\n        The @property decorator is used to ease the use of @override_settings\n        in tests. Otherwise the setting is not changed and we can't easily test\n        an upload with a size higher than the one defined in the settings\n        \"\"\"\n        return settings.DEPOSITED_FILE_SOURCE_MAX_SIZE\n\n    def validate(self, attrs):\n        \"\"\"Validate if the mimetype is allowed or not.\"\"\"\n        # mimetype is provided, we directly check it\n        if attrs[\"mimetype\"] != \"\":\n            attrs[\"extension\"] = mimetypes.guess_extension(attrs[\"mimetype\"])\n\n        # mimetype is not provided, we have to guess it from the extension\n        else:\n            mimetypes.init()\n            extension = splitext(attrs[\"filename\"])[1]\n            mimetype = mimetypes.types_map.get(extension)\n            # extension is added to the data in order to be used later\n            attrs[\"extension\"] = extension\n            attrs[\"mimetype\"] = mimetype\n\n        return attrs\n\n\nclass FileDepositorySerializer(serializers.ModelSerializer):\n    \"\"\"A serializer to display a FileDepository resource.\"\"\"\n\n    class Meta:  # noqa\n        model = FileDepository\n        fields = (\n            \"id\",\n            \"lti_id\",\n            \"title\",\n            \"description\",\n            \"playlist\",\n        )\n        read_only_fields = (\n            \"id\",\n            \"lti_id\",\n            \"playlist\",\n        )\n\n    playlist = PlaylistLiteSerializer(read_only=True)\n\n\nclass FileDepositorySelectLTISerializer(FileDepositorySerializer):\n    \"\"\"A serializer to display a FileDepository resource for LTI select content request.\"\"\"\n\n    class Meta:  # noqa\n        model = FileDepository\n        fields = (\n            \"id\",\n            \"lti_id\",\n            \"lti_url\",\n            \"title\",\n            \"description\",\n            \"playlist\",\n        )\n\n    lti_url = serializers.SerializerMethodField()\n\n    def get_lti_url(self, obj):\n        \"\"\"LTI Url of the FileDepository.\n\n        Parameters\n        ----------\n        obj : Type[models.Document]\n            The document that we want to serialize\n\n        Returns\n        -------\n        String\n            the LTI url to be used by LTI consumers\n\n        \"\"\"\n        return self.context[\"request\"].build_absolute_uri(\n            reverse(\"deposit:file_depository_lti_view\", args=[obj.id]),\n        )\n", "path": "src/backend/marsha/deposit/serializers.py"}]}