We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Exporting CSV file that contains Unicode text fails on Python 3 on Windows
### Observed behavior
User reports of CSV file export failing prompted me to add a test for this in a Windows environment in https://github.com/learningequality/kolibri/pull/7899.

When Unicode text should be inserted into the exported CSV, such as a Unicode username or full_name, then an error occurs on windows.

```
Error trying to write csv file: 'charmap' codec can't encode characters in position 29-35: character maps to <undefined>
```

### Expected behavior
CSV Export should work on all platforms.

This is fixed by this commit: https://github.com/learningequality/kolibri/pull/7899/commits/c088d08b3e6c4a0f4194a17062457cbd7ae34816

### User-facing consequences
Windows users are unable to export CSV files.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `kolibri/core/logger/csv_export.py`
Content:
```
1 from __future__ import unicode_literals
2 
3 import csv
4 import io
5 import json
6 import logging
7 import math
8 import os
9 import sys
10 from collections import OrderedDict
11 
12 from django.core.cache import cache
13 from django.http import Http404
14 from django.http import HttpResponse
15 from django.http.response import FileResponse
16 from django.template.defaultfilters import slugify
17 from django.utils import translation
18 from django.utils.translation import get_language_from_request
19 from django.utils.translation import pgettext
20 
21 from .models import ContentSessionLog
22 from .models import ContentSummaryLog
23 from kolibri.core.auth.models import Facility
24 from kolibri.core.content.models import ChannelMetadata
25 from kolibri.core.content.models import ContentNode
26 from kolibri.utils import conf
27 
28 
29 logger = logging.getLogger(__name__)
30 
31 CSV_EXPORT_FILENAMES = {
32     "session": "{}_{}_content_session_logs.csv",
33     "summary": "{}_{}_content_summary_logs.csv",
34 }
35 
36 
37 def cache_channel_name(obj):
38     channel_id = obj["channel_id"]
39     key = "{id}_ChannelMetadata_name".format(id=channel_id)
40     channel_name = cache.get(key)
41     if channel_name is None:
42         try:
43             channel_name = ChannelMetadata.objects.get(id=channel_id)
44         except ChannelMetadata.DoesNotExist:
45             channel_name = ""
46         cache.set(key, channel_name, 60 * 10)
47     return channel_name
48 
49 
50 def cache_content_title(obj):
51     content_id = obj["content_id"]
52     key = "{id}_ContentNode_title".format(id=content_id)
53     title = cache.get(key)
54     if title is None:
55         node = ContentNode.objects.filter(content_id=content_id).first()
56         if node:
57             title = node.title
58         else:
59             title = ""
60         cache.set(key, title, 60 * 10)
61     return title
62 
63 
64 mappings = {
65     "channel_name": cache_channel_name,
66     "content_title": cache_content_title,
67     "time_spent": lambda x: "{:.1f}".format(round(x["time_spent"], 1)),
68     "progress": lambda x: "{:.4f}".format(math.floor(x["progress"] * 10000.0) / 10000),
69 }
70 
71 labels = OrderedDict(
72     (
73         ("user__facility__name", "Facility name"),
74         ("user__username", "Username"),
75         ("channel_id", "Channel id"),
76         ("channel_name", "Channel name"),
77         ("content_id", "Content id"),
78         ("content_title", "Content title"),
79         ("start_timestamp", "Time of first interaction"),
80         ("end_timestamp", "Time of last interaction"),
81         ("completion_timestamp", "Time of completion"),
82         ("time_spent", "Time Spent (sec)"),
83         ("progress", "Progress (0-1)"),
84         ("kind", "Content kind"),
85     )
86 )
87 
88 
89 def map_object(obj):
90     mapped_obj = {}
91     for header, label in labels.items():
92         if header in mappings:
93             mapped_obj[label] = mappings[header](obj)
94         elif header in obj:
95             mapped_obj[label] = obj[header]
96     return mapped_obj
97 
98 
99 classes_info = {
100     "session": {
101         "queryset": ContentSessionLog.objects.all(),
102         "filename": CSV_EXPORT_FILENAMES["session"],
103         "db_columns": (
104             "user__username",
105             "user__facility__name",
106             "channel_id",
107             "content_id",
108             "start_timestamp",
109             "end_timestamp",
110             "time_spent",
111             "progress",
112             "kind",
113         ),
114     },
115     "summary": {
116         "queryset": ContentSummaryLog.objects.all(),
117         "filename": CSV_EXPORT_FILENAMES["summary"],
118         "db_columns": (
119             "user__username",
120             "user__facility__name",
121             "content_id",
122             "channel_id",
123             "start_timestamp",
124             "end_timestamp",
125             "completion_timestamp",
126             "time_spent",
127             "progress",
128             "kind",
129         ),
130     },
131 }
132 
133 
134 def csv_file_generator(facility, log_type, filepath, overwrite=False):
135 
136     if log_type not in ("summary", "session"):
137         raise ValueError(
138             "Impossible to create a csv export file for {}".format(log_type)
139         )
140 
141     log_info = classes_info[log_type]
142 
143     if not overwrite and os.path.exists(filepath):
144         raise ValueError("{} already exists".format(filepath))
145     queryset = log_info["queryset"].filter(dataset_id=facility.dataset_id)
146 
147     # Exclude completion timestamp for the sessionlog CSV
148     header_labels = tuple(
149         label
150         for label in labels.values()
151         if log_type == "summary" or label != "completion_timestamp"
152     )
153 
154     if sys.version_info[0] < 3:
155         csv_file = io.open(filepath, "wb")
156     else:
157         csv_file = io.open(filepath, "w", newline="")
158 
159     with csv_file as f:
160         writer = csv.DictWriter(f, header_labels)
161         logger.info("Creating csv file {filename}".format(filename=filepath))
162         writer.writeheader()
163         for item in queryset.select_related("user", "user__facility").values(
164             *log_info["db_columns"]
165         ):
166             writer.writerow(map_object(item))
167             yield
168 
169 
170 def exported_logs_info(request, facility_id, facility):
171     """
172     Get the last modification timestamp of the summary logs exported
173 
174     :returns: An object with the files informatin
175     """
176     logs_dir = os.path.join(conf.KOLIBRI_HOME, "log_export")
177     csv_statuses = {}
178 
179     for log_type in CSV_EXPORT_FILENAMES.keys():
180         log_path = os.path.join(
181             logs_dir, CSV_EXPORT_FILENAMES[log_type].format(facility, facility_id[:4])
182         )
183         if os.path.exists(log_path):
184             csv_statuses[log_type] = os.path.getmtime(log_path)
185         else:
186             csv_statuses[log_type] = None
187 
188     return HttpResponse(json.dumps(csv_statuses), content_type="application/json")
189 
190 
191 def download_csv_file(request, log_type, facility_id):
192     if facility_id:
193         facility_name = Facility.objects.get(pk=facility_id).name
194     else:
195         facility_name = request.user.facility.name
196         facility_id = request.user.facility.id
197 
198     locale = get_language_from_request(request)
199     translation.activate(locale)
200 
201     csv_translated_filenames = {
202         "session": (
203             "{}_{}_"
204             + slugify(
205                 pgettext(
206                     "Default name for the exported CSV file with content session logs. Please keep the underscores between words in the translation",
207                     "content_session_logs",
208                 )
209             )
210             + ".csv"
211         ).replace("-", "_"),
212         "summary": (
213             "{}_{}_"
214             + slugify(
215                 pgettext(
216                     "Default name for the exported CSV file with content summary logs. Please keep the underscores between words in the translation",
217                     "content_summary_logs",
218                 )
219             )
220             + ".csv"
221         ).replace("-", "_"),
222     }
223 
224     if log_type in CSV_EXPORT_FILENAMES.keys():
225         filepath = os.path.join(
226             conf.KOLIBRI_HOME,
227             "log_export",
228             CSV_EXPORT_FILENAMES[log_type].format(facility_name, facility_id[:4]),
229         )
230     else:
231         filepath = None
232 
233     # if the file does not exist on disk, return a 404
234     if filepath is None or not os.path.exists(filepath):
235         raise Http404("There is no csv export file for {} available".format(log_type))
236 
237     # generate a file response
238     response = FileResponse(io.open(filepath, "rb"))
239     # set the content-type by guessing from the filename
240     response["Content-Type"] = "text/csv"
241 
242     # set the content-disposition as attachment to force download
243     response["Content-Disposition"] = "attachment; filename={}".format(
244         str(csv_translated_filenames[log_type]).format(facility_name, facility_id[:4])
245     )
246     translation.deactivate()
247 
248     # set the content-length to the file size
249     response["Content-Length"] = os.path.getsize(filepath)
250 
251     return response
252
```
--- 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/kolibri/core/logger/csv_export.py b/kolibri/core/logger/csv_export.py
--- a/kolibri/core/logger/csv_export.py
+++ b/kolibri/core/logger/csv_export.py
@@ -154,7 +154,7 @@
     if sys.version_info[0] < 3:
         csv_file = io.open(filepath, "wb")
     else:
-        csv_file = io.open(filepath, "w", newline="")
+        csv_file = io.open(filepath, "w", newline="", encoding="utf-8")
 
     with csv_file as f:
         writer = csv.DictWriter(f, header_labels)

{"golden_diff": "diff --git a/kolibri/core/logger/csv_export.py b/kolibri/core/logger/csv_export.py\n--- a/kolibri/core/logger/csv_export.py\n+++ b/kolibri/core/logger/csv_export.py\n@@ -154,7 +154,7 @@\n     if sys.version_info[0] < 3:\n         csv_file = io.open(filepath, \"wb\")\n     else:\n-        csv_file = io.open(filepath, \"w\", newline=\"\")\n+        csv_file = io.open(filepath, \"w\", newline=\"\", encoding=\"utf-8\")\n \n     with csv_file as f:\n         writer = csv.DictWriter(f, header_labels)\n", "issue": "Exporting CSV file that contains Unicode text fails on Python 3 on Windows\n### Observed behavior\r\nUser reports of CSV file export failing prompted me to add a test for this in a Windows environment in https://github.com/learningequality/kolibri/pull/7899.\r\n\r\nWhen Unicode text should be inserted into the exported CSV, such as a Unicode username or full_name, then an error occurs on windows.\r\n\r\n```\r\nError trying to write csv file: 'charmap' codec can't encode characters in position 29-35: character maps to <undefined>\r\n```\r\n\r\n### Expected behavior\r\nCSV Export should work on all platforms.\r\n\r\nThis is fixed by this commit: https://github.com/learningequality/kolibri/pull/7899/commits/c088d08b3e6c4a0f4194a17062457cbd7ae34816\r\n\r\n### User-facing consequences\r\nWindows users are unable to export CSV files.\r\n\n", "before_files": [{"content": "from __future__ import unicode_literals\n\nimport csv\nimport io\nimport json\nimport logging\nimport math\nimport os\nimport sys\nfrom collections import OrderedDict\n\nfrom django.core.cache import cache\nfrom django.http import Http404\nfrom django.http import HttpResponse\nfrom django.http.response import FileResponse\nfrom django.template.defaultfilters import slugify\nfrom django.utils import translation\nfrom django.utils.translation import get_language_from_request\nfrom django.utils.translation import pgettext\n\nfrom .models import ContentSessionLog\nfrom .models import ContentSummaryLog\nfrom kolibri.core.auth.models import Facility\nfrom kolibri.core.content.models import ChannelMetadata\nfrom kolibri.core.content.models import ContentNode\nfrom kolibri.utils import conf\n\n\nlogger = logging.getLogger(__name__)\n\nCSV_EXPORT_FILENAMES = {\n    \"session\": \"{}_{}_content_session_logs.csv\",\n    \"summary\": \"{}_{}_content_summary_logs.csv\",\n}\n\n\ndef cache_channel_name(obj):\n    channel_id = obj[\"channel_id\"]\n    key = \"{id}_ChannelMetadata_name\".format(id=channel_id)\n    channel_name = cache.get(key)\n    if channel_name is None:\n        try:\n            channel_name = ChannelMetadata.objects.get(id=channel_id)\n        except ChannelMetadata.DoesNotExist:\n            channel_name = \"\"\n        cache.set(key, channel_name, 60 * 10)\n    return channel_name\n\n\ndef cache_content_title(obj):\n    content_id = obj[\"content_id\"]\n    key = \"{id}_ContentNode_title\".format(id=content_id)\n    title = cache.get(key)\n    if title is None:\n        node = ContentNode.objects.filter(content_id=content_id).first()\n        if node:\n            title = node.title\n        else:\n            title = \"\"\n        cache.set(key, title, 60 * 10)\n    return title\n\n\nmappings = {\n    \"channel_name\": cache_channel_name,\n    \"content_title\": cache_content_title,\n    \"time_spent\": lambda x: \"{:.1f}\".format(round(x[\"time_spent\"], 1)),\n    \"progress\": lambda x: \"{:.4f}\".format(math.floor(x[\"progress\"] * 10000.0) / 10000),\n}\n\nlabels = OrderedDict(\n    (\n        (\"user__facility__name\", \"Facility name\"),\n        (\"user__username\", \"Username\"),\n        (\"channel_id\", \"Channel id\"),\n        (\"channel_name\", \"Channel name\"),\n        (\"content_id\", \"Content id\"),\n        (\"content_title\", \"Content title\"),\n        (\"start_timestamp\", \"Time of first interaction\"),\n        (\"end_timestamp\", \"Time of last interaction\"),\n        (\"completion_timestamp\", \"Time of completion\"),\n        (\"time_spent\", \"Time Spent (sec)\"),\n        (\"progress\", \"Progress (0-1)\"),\n        (\"kind\", \"Content kind\"),\n    )\n)\n\n\ndef map_object(obj):\n    mapped_obj = {}\n    for header, label in labels.items():\n        if header in mappings:\n            mapped_obj[label] = mappings[header](obj)\n        elif header in obj:\n            mapped_obj[label] = obj[header]\n    return mapped_obj\n\n\nclasses_info = {\n    \"session\": {\n        \"queryset\": ContentSessionLog.objects.all(),\n        \"filename\": CSV_EXPORT_FILENAMES[\"session\"],\n        \"db_columns\": (\n            \"user__username\",\n            \"user__facility__name\",\n            \"channel_id\",\n            \"content_id\",\n            \"start_timestamp\",\n            \"end_timestamp\",\n            \"time_spent\",\n            \"progress\",\n            \"kind\",\n        ),\n    },\n    \"summary\": {\n        \"queryset\": ContentSummaryLog.objects.all(),\n        \"filename\": CSV_EXPORT_FILENAMES[\"summary\"],\n        \"db_columns\": (\n            \"user__username\",\n            \"user__facility__name\",\n            \"content_id\",\n            \"channel_id\",\n            \"start_timestamp\",\n            \"end_timestamp\",\n            \"completion_timestamp\",\n            \"time_spent\",\n            \"progress\",\n            \"kind\",\n        ),\n    },\n}\n\n\ndef csv_file_generator(facility, log_type, filepath, overwrite=False):\n\n    if log_type not in (\"summary\", \"session\"):\n        raise ValueError(\n            \"Impossible to create a csv export file for {}\".format(log_type)\n        )\n\n    log_info = classes_info[log_type]\n\n    if not overwrite and os.path.exists(filepath):\n        raise ValueError(\"{} already exists\".format(filepath))\n    queryset = log_info[\"queryset\"].filter(dataset_id=facility.dataset_id)\n\n    # Exclude completion timestamp for the sessionlog CSV\n    header_labels = tuple(\n        label\n        for label in labels.values()\n        if log_type == \"summary\" or label != \"completion_timestamp\"\n    )\n\n    if sys.version_info[0] < 3:\n        csv_file = io.open(filepath, \"wb\")\n    else:\n        csv_file = io.open(filepath, \"w\", newline=\"\")\n\n    with csv_file as f:\n        writer = csv.DictWriter(f, header_labels)\n        logger.info(\"Creating csv file {filename}\".format(filename=filepath))\n        writer.writeheader()\n        for item in queryset.select_related(\"user\", \"user__facility\").values(\n            *log_info[\"db_columns\"]\n        ):\n            writer.writerow(map_object(item))\n            yield\n\n\ndef exported_logs_info(request, facility_id, facility):\n    \"\"\"\n    Get the last modification timestamp of the summary logs exported\n\n    :returns: An object with the files informatin\n    \"\"\"\n    logs_dir = os.path.join(conf.KOLIBRI_HOME, \"log_export\")\n    csv_statuses = {}\n\n    for log_type in CSV_EXPORT_FILENAMES.keys():\n        log_path = os.path.join(\n            logs_dir, CSV_EXPORT_FILENAMES[log_type].format(facility, facility_id[:4])\n        )\n        if os.path.exists(log_path):\n            csv_statuses[log_type] = os.path.getmtime(log_path)\n        else:\n            csv_statuses[log_type] = None\n\n    return HttpResponse(json.dumps(csv_statuses), content_type=\"application/json\")\n\n\ndef download_csv_file(request, log_type, facility_id):\n    if facility_id:\n        facility_name = Facility.objects.get(pk=facility_id).name\n    else:\n        facility_name = request.user.facility.name\n        facility_id = request.user.facility.id\n\n    locale = get_language_from_request(request)\n    translation.activate(locale)\n\n    csv_translated_filenames = {\n        \"session\": (\n            \"{}_{}_\"\n            + slugify(\n                pgettext(\n                    \"Default name for the exported CSV file with content session logs. Please keep the underscores between words in the translation\",\n                    \"content_session_logs\",\n                )\n            )\n            + \".csv\"\n        ).replace(\"-\", \"_\"),\n        \"summary\": (\n            \"{}_{}_\"\n            + slugify(\n                pgettext(\n                    \"Default name for the exported CSV file with content summary logs. Please keep the underscores between words in the translation\",\n                    \"content_summary_logs\",\n                )\n            )\n            + \".csv\"\n        ).replace(\"-\", \"_\"),\n    }\n\n    if log_type in CSV_EXPORT_FILENAMES.keys():\n        filepath = os.path.join(\n            conf.KOLIBRI_HOME,\n            \"log_export\",\n            CSV_EXPORT_FILENAMES[log_type].format(facility_name, facility_id[:4]),\n        )\n    else:\n        filepath = None\n\n    # if the file does not exist on disk, return a 404\n    if filepath is None or not os.path.exists(filepath):\n        raise Http404(\"There is no csv export file for {} available\".format(log_type))\n\n    # generate a file response\n    response = FileResponse(io.open(filepath, \"rb\"))\n    # set the content-type by guessing from the filename\n    response[\"Content-Type\"] = \"text/csv\"\n\n    # set the content-disposition as attachment to force download\n    response[\"Content-Disposition\"] = \"attachment; filename={}\".format(\n        str(csv_translated_filenames[log_type]).format(facility_name, facility_id[:4])\n    )\n    translation.deactivate()\n\n    # set the content-length to the file size\n    response[\"Content-Length\"] = os.path.getsize(filepath)\n\n    return response\n", "path": "kolibri/core/logger/csv_export.py"}], "after_files": [{"content": "from __future__ import unicode_literals\n\nimport csv\nimport io\nimport json\nimport logging\nimport math\nimport os\nimport sys\nfrom collections import OrderedDict\n\nfrom django.core.cache import cache\nfrom django.http import Http404\nfrom django.http import HttpResponse\nfrom django.http.response import FileResponse\nfrom django.template.defaultfilters import slugify\nfrom django.utils import translation\nfrom django.utils.translation import get_language_from_request\nfrom django.utils.translation import pgettext\n\nfrom .models import ContentSessionLog\nfrom .models import ContentSummaryLog\nfrom kolibri.core.auth.models import Facility\nfrom kolibri.core.content.models import ChannelMetadata\nfrom kolibri.core.content.models import ContentNode\nfrom kolibri.utils import conf\n\n\nlogger = logging.getLogger(__name__)\n\nCSV_EXPORT_FILENAMES = {\n    \"session\": \"{}_{}_content_session_logs.csv\",\n    \"summary\": \"{}_{}_content_summary_logs.csv\",\n}\n\n\ndef cache_channel_name(obj):\n    channel_id = obj[\"channel_id\"]\n    key = \"{id}_ChannelMetadata_name\".format(id=channel_id)\n    channel_name = cache.get(key)\n    if channel_name is None:\n        try:\n            channel_name = ChannelMetadata.objects.get(id=channel_id)\n        except ChannelMetadata.DoesNotExist:\n            channel_name = \"\"\n        cache.set(key, channel_name, 60 * 10)\n    return channel_name\n\n\ndef cache_content_title(obj):\n    content_id = obj[\"content_id\"]\n    key = \"{id}_ContentNode_title\".format(id=content_id)\n    title = cache.get(key)\n    if title is None:\n        node = ContentNode.objects.filter(content_id=content_id).first()\n        if node:\n            title = node.title\n        else:\n            title = \"\"\n        cache.set(key, title, 60 * 10)\n    return title\n\n\nmappings = {\n    \"channel_name\": cache_channel_name,\n    \"content_title\": cache_content_title,\n    \"time_spent\": lambda x: \"{:.1f}\".format(round(x[\"time_spent\"], 1)),\n    \"progress\": lambda x: \"{:.4f}\".format(math.floor(x[\"progress\"] * 10000.0) / 10000),\n}\n\nlabels = OrderedDict(\n    (\n        (\"user__facility__name\", \"Facility name\"),\n        (\"user__username\", \"Username\"),\n        (\"channel_id\", \"Channel id\"),\n        (\"channel_name\", \"Channel name\"),\n        (\"content_id\", \"Content id\"),\n        (\"content_title\", \"Content title\"),\n        (\"start_timestamp\", \"Time of first interaction\"),\n        (\"end_timestamp\", \"Time of last interaction\"),\n        (\"completion_timestamp\", \"Time of completion\"),\n        (\"time_spent\", \"Time Spent (sec)\"),\n        (\"progress\", \"Progress (0-1)\"),\n        (\"kind\", \"Content kind\"),\n    )\n)\n\n\ndef map_object(obj):\n    mapped_obj = {}\n    for header, label in labels.items():\n        if header in mappings:\n            mapped_obj[label] = mappings[header](obj)\n        elif header in obj:\n            mapped_obj[label] = obj[header]\n    return mapped_obj\n\n\nclasses_info = {\n    \"session\": {\n        \"queryset\": ContentSessionLog.objects.all(),\n        \"filename\": CSV_EXPORT_FILENAMES[\"session\"],\n        \"db_columns\": (\n            \"user__username\",\n            \"user__facility__name\",\n            \"channel_id\",\n            \"content_id\",\n            \"start_timestamp\",\n            \"end_timestamp\",\n            \"time_spent\",\n            \"progress\",\n            \"kind\",\n        ),\n    },\n    \"summary\": {\n        \"queryset\": ContentSummaryLog.objects.all(),\n        \"filename\": CSV_EXPORT_FILENAMES[\"summary\"],\n        \"db_columns\": (\n            \"user__username\",\n            \"user__facility__name\",\n            \"content_id\",\n            \"channel_id\",\n            \"start_timestamp\",\n            \"end_timestamp\",\n            \"completion_timestamp\",\n            \"time_spent\",\n            \"progress\",\n            \"kind\",\n        ),\n    },\n}\n\n\ndef csv_file_generator(facility, log_type, filepath, overwrite=False):\n\n    if log_type not in (\"summary\", \"session\"):\n        raise ValueError(\n            \"Impossible to create a csv export file for {}\".format(log_type)\n        )\n\n    log_info = classes_info[log_type]\n\n    if not overwrite and os.path.exists(filepath):\n        raise ValueError(\"{} already exists\".format(filepath))\n    queryset = log_info[\"queryset\"].filter(dataset_id=facility.dataset_id)\n\n    # Exclude completion timestamp for the sessionlog CSV\n    header_labels = tuple(\n        label\n        for label in labels.values()\n        if log_type == \"summary\" or label != \"completion_timestamp\"\n    )\n\n    if sys.version_info[0] < 3:\n        csv_file = io.open(filepath, \"wb\")\n    else:\n        csv_file = io.open(filepath, \"w\", newline=\"\", encoding=\"utf-8\")\n\n    with csv_file as f:\n        writer = csv.DictWriter(f, header_labels)\n        logger.info(\"Creating csv file {filename}\".format(filename=filepath))\n        writer.writeheader()\n        for item in queryset.select_related(\"user\", \"user__facility\").values(\n            *log_info[\"db_columns\"]\n        ):\n            writer.writerow(map_object(item))\n            yield\n\n\ndef exported_logs_info(request, facility_id, facility):\n    \"\"\"\n    Get the last modification timestamp of the summary logs exported\n\n    :returns: An object with the files informatin\n    \"\"\"\n    logs_dir = os.path.join(conf.KOLIBRI_HOME, \"log_export\")\n    csv_statuses = {}\n\n    for log_type in CSV_EXPORT_FILENAMES.keys():\n        log_path = os.path.join(\n            logs_dir, CSV_EXPORT_FILENAMES[log_type].format(facility, facility_id[:4])\n        )\n        if os.path.exists(log_path):\n            csv_statuses[log_type] = os.path.getmtime(log_path)\n        else:\n            csv_statuses[log_type] = None\n\n    return HttpResponse(json.dumps(csv_statuses), content_type=\"application/json\")\n\n\ndef download_csv_file(request, log_type, facility_id):\n    if facility_id:\n        facility_name = Facility.objects.get(pk=facility_id).name\n    else:\n        facility_name = request.user.facility.name\n        facility_id = request.user.facility.id\n\n    locale = get_language_from_request(request)\n    translation.activate(locale)\n\n    csv_translated_filenames = {\n        \"session\": (\n            \"{}_{}_\"\n            + slugify(\n                pgettext(\n                    \"Default name for the exported CSV file with content session logs. Please keep the underscores between words in the translation\",\n                    \"content_session_logs\",\n                )\n            )\n            + \".csv\"\n        ).replace(\"-\", \"_\"),\n        \"summary\": (\n            \"{}_{}_\"\n            + slugify(\n                pgettext(\n                    \"Default name for the exported CSV file with content summary logs. Please keep the underscores between words in the translation\",\n                    \"content_summary_logs\",\n                )\n            )\n            + \".csv\"\n        ).replace(\"-\", \"_\"),\n    }\n\n    if log_type in CSV_EXPORT_FILENAMES.keys():\n        filepath = os.path.join(\n            conf.KOLIBRI_HOME,\n            \"log_export\",\n            CSV_EXPORT_FILENAMES[log_type].format(facility_name, facility_id[:4]),\n        )\n    else:\n        filepath = None\n\n    # if the file does not exist on disk, return a 404\n    if filepath is None or not os.path.exists(filepath):\n        raise Http404(\"There is no csv export file for {} available\".format(log_type))\n\n    # generate a file response\n    response = FileResponse(io.open(filepath, \"rb\"))\n    # set the content-type by guessing from the filename\n    response[\"Content-Type\"] = \"text/csv\"\n\n    # set the content-disposition as attachment to force download\n    response[\"Content-Disposition\"] = \"attachment; filename={}\".format(\n        str(csv_translated_filenames[log_type]).format(facility_name, facility_id[:4])\n    )\n    translation.deactivate()\n\n    # set the content-length to the file size\n    response[\"Content-Length\"] = os.path.getsize(filepath)\n\n    return response\n", "path": "kolibri/core/logger/csv_export.py"}]}