We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
CSV for teams+members+fields
We should have this CSV format to export but for some reason we don't. Should be an easy implementation. 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `CTFd/utils/csv/__init__.py`
Content:
```
1 import csv
2 import json
3 from io import BytesIO, StringIO
4 
5 from CTFd.models import (
6     Flags,
7     Hints,
8     Tags,
9     TeamFields,
10     Teams,
11     UserFields,
12     Users,
13     db,
14     get_class_by_tablename,
15 )
16 from CTFd.plugins.challenges import get_chal_class
17 from CTFd.schemas.challenges import ChallengeSchema
18 from CTFd.schemas.teams import TeamSchema
19 from CTFd.schemas.users import UserSchema
20 from CTFd.utils.config import is_teams_mode, is_users_mode
21 from CTFd.utils.scores import get_standings
22 
23 
24 def get_dumpable_tables():
25     csv_keys = list(CSV_KEYS.keys())
26     db_keys = list(db.metadata.tables.keys())
27     tables = csv_keys + db_keys
28     table_keys = list(zip(tables, tables))
29     return table_keys
30 
31 
32 def dump_csv(name):
33     dump_func = CSV_KEYS.get(name)
34     if dump_func:
35         return dump_func()
36     elif get_class_by_tablename(name):
37         return dump_database_table(tablename=name)
38     else:
39         raise KeyError
40 
41 
42 def dump_scoreboard_csv():
43     # TODO: Add fields to scoreboard data
44     temp = StringIO()
45     writer = csv.writer(temp)
46 
47     standings = get_standings()
48 
49     # Get all user fields in a specific order
50     user_fields = UserFields.query.all()
51     user_field_ids = [f.id for f in user_fields]
52     user_field_names = [f.name for f in user_fields]
53 
54     if is_teams_mode():
55         team_fields = TeamFields.query.all()
56         team_field_ids = [f.id for f in team_fields]
57         team_field_names = [f.name for f in team_fields]
58 
59         header = (
60             [
61                 "place",
62                 "team",
63                 "team id",
64                 "score",
65                 "member name",
66                 "member id",
67                 "member email",
68                 "member score",
69             ]
70             + user_field_names
71             + team_field_names
72         )
73         writer.writerow(header)
74 
75         for i, standing in enumerate(standings):
76             team = Teams.query.filter_by(id=standing.account_id).first()
77 
78             # Build field entries using the order of the field values
79             team_field_entries = {f.field_id: f.value for f in team.field_entries}
80             team_field_values = [
81                 team_field_entries.get(f_id, "") for f_id in team_field_ids
82             ]
83             user_field_values = len(user_field_names) * [""]
84             team_row = (
85                 [i + 1, team.name, team.id, standing.score, "", "", "", ""]
86                 + user_field_values
87                 + team_field_values
88             )
89 
90             writer.writerow(team_row)
91 
92             for member in team.members:
93                 user_field_entries = {f.field_id: f.value for f in member.field_entries}
94                 user_field_values = [
95                     user_field_entries.get(f_id, "") for f_id in user_field_ids
96                 ]
97                 team_field_values = len(team_field_names) * [""]
98                 user_row = (
99                     [
100                         "",
101                         "",
102                         "",
103                         "",
104                         member.name,
105                         member.id,
106                         member.email,
107                         member.score,
108                     ]
109                     + user_field_values
110                     + team_field_values
111                 )
112                 writer.writerow(user_row)
113     elif is_users_mode():
114         header = [
115             "place",
116             "user name",
117             "user id",
118             "user email",
119             "score",
120         ] + user_field_names
121         writer.writerow(header)
122 
123         for i, standing in enumerate(standings):
124             user = Users.query.filter_by(id=standing.account_id).first()
125 
126             # Build field entries using the order of the field values
127             user_field_entries = {f.field_id: f.value for f in user.field_entries}
128             user_field_values = [
129                 user_field_entries.get(f_id, "") for f_id in user_field_ids
130             ]
131             user_row = [
132                 i + 1,
133                 user.name,
134                 user.id,
135                 user.email,
136                 standing.score,
137             ] + user_field_values
138             writer.writerow(user_row)
139 
140     # In Python 3 send_file requires bytes
141     output = BytesIO()
142     output.write(temp.getvalue().encode("utf-8"))
143     output.seek(0)
144     temp.close()
145 
146     return output
147 
148 
149 def dump_users_with_fields_csv():
150     temp = StringIO()
151     writer = csv.writer(temp)
152 
153     user_fields = UserFields.query.all()
154     user_field_ids = [f.id for f in user_fields]
155     user_field_names = [f.name for f in user_fields]
156 
157     header = [column.name for column in Users.__mapper__.columns] + user_field_names
158     writer.writerow(header)
159 
160     responses = Users.query.all()
161 
162     for curr in responses:
163         user_field_entries = {f.field_id: f.value for f in curr.field_entries}
164         user_field_values = [
165             user_field_entries.get(f_id, "") for f_id in user_field_ids
166         ]
167         user_row = [
168             getattr(curr, column.name) for column in Users.__mapper__.columns
169         ] + user_field_values
170         writer.writerow(user_row)
171 
172     temp.seek(0)
173 
174     # In Python 3 send_file requires bytes
175     output = BytesIO()
176     output.write(temp.getvalue().encode("utf-8"))
177     output.seek(0)
178     temp.close()
179 
180     return output
181 
182 
183 def dump_teams_with_fields_csv():
184     temp = StringIO()
185     writer = csv.writer(temp)
186 
187     team_fields = TeamFields.query.all()
188     team_field_ids = [f.id for f in team_fields]
189     team_field_names = [f.name for f in team_fields]
190 
191     header = [column.name for column in Teams.__mapper__.columns] + team_field_names
192     writer.writerow(header)
193 
194     responses = Teams.query.all()
195 
196     for curr in responses:
197         team_field_entries = {f.field_id: f.value for f in curr.field_entries}
198         team_field_values = [
199             team_field_entries.get(f_id, "") for f_id in team_field_ids
200         ]
201 
202         team_row = [
203             getattr(curr, column.name) for column in Teams.__mapper__.columns
204         ] + team_field_values
205 
206         writer.writerow(team_row)
207 
208     temp.seek(0)
209 
210     # In Python 3 send_file requires bytes
211     output = BytesIO()
212     output.write(temp.getvalue().encode("utf-8"))
213     output.seek(0)
214     temp.close()
215 
216     return output
217 
218 
219 def dump_database_table(tablename):
220     # TODO: It might make sense to limit dumpable tables. Config could potentially leak sensitive information.
221     model = get_class_by_tablename(tablename)
222 
223     if model is None:
224         raise KeyError("Unknown database table")
225 
226     temp = StringIO()
227     writer = csv.writer(temp)
228 
229     header = [column.name for column in model.__mapper__.columns]
230     writer.writerow(header)
231 
232     responses = model.query.all()
233 
234     for curr in responses:
235         writer.writerow(
236             [getattr(curr, column.name) for column in model.__mapper__.columns]
237         )
238 
239     temp.seek(0)
240 
241     # In Python 3 send_file requires bytes
242     output = BytesIO()
243     output.write(temp.getvalue().encode("utf-8"))
244     output.seek(0)
245     temp.close()
246 
247     return output
248 
249 
250 def load_users_csv(dict_reader):
251     schema = UserSchema()
252     errors = []
253     for i, line in enumerate(dict_reader):
254         response = schema.load(line)
255         if response.errors:
256             errors.append((i, response.errors))
257         else:
258             db.session.add(response.data)
259             db.session.commit()
260     if errors:
261         return errors
262     return True
263 
264 
265 def load_teams_csv(dict_reader):
266     schema = TeamSchema()
267     errors = []
268     for i, line in enumerate(dict_reader):
269         response = schema.load(line)
270         if response.errors:
271             errors.append((i, response.errors))
272         else:
273             db.session.add(response.data)
274             db.session.commit()
275     if errors:
276         return errors
277     return True
278 
279 
280 def load_challenges_csv(dict_reader):
281     schema = ChallengeSchema()
282     errors = []
283 
284     for i, line in enumerate(dict_reader):
285         # Throw away fields that we can't trust if provided
286         _ = line.pop("id", None)
287         _ = line.pop("requirements", None)
288 
289         flags = line.pop("flags", None)
290         tags = line.pop("tags", None)
291         hints = line.pop("hints", None)
292         challenge_type = line.pop("type", "standard")
293 
294         # Load in custom type_data
295         type_data = json.loads(line.pop("type_data", "{}") or "{}")
296         line.update(type_data)
297 
298         response = schema.load(line)
299         if response.errors:
300             errors.append((i + 1, response.errors))
301             continue
302 
303         ChallengeClass = get_chal_class(challenge_type)
304         challenge = ChallengeClass.challenge_model(**line)
305         db.session.add(challenge)
306         db.session.commit()
307 
308         if flags:
309             flags = [flag.strip() for flag in flags.split(",")]
310             for flag in flags:
311                 f = Flags(type="static", challenge_id=challenge.id, content=flag,)
312                 db.session.add(f)
313                 db.session.commit()
314 
315         if tags:
316             tags = [tag.strip() for tag in tags.split(",")]
317             for tag in tags:
318                 t = Tags(challenge_id=challenge.id, value=tag,)
319                 db.session.add(t)
320                 db.session.commit()
321 
322         if hints:
323             hints = [hint.strip() for hint in hints.split(",")]
324             for hint in hints:
325                 h = Hints(challenge_id=challenge.id, content=hint,)
326                 db.session.add(h)
327                 db.session.commit()
328     if errors:
329         return errors
330     return True
331 
332 
333 CSV_KEYS = {
334     "scoreboard": dump_scoreboard_csv,
335     "users+fields": dump_users_with_fields_csv,
336     "teams+fields": dump_teams_with_fields_csv,
337 }
338
```
--- 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/CTFd/utils/csv/__init__.py b/CTFd/utils/csv/__init__.py
--- a/CTFd/utils/csv/__init__.py
+++ b/CTFd/utils/csv/__init__.py
@@ -216,6 +216,66 @@
     return output
 
 
+def dump_teams_with_members_fields_csv():
+    temp = StringIO()
+    writer = csv.writer(temp)
+
+    team_fields = TeamFields.query.all()
+    team_field_ids = [f.id for f in team_fields]
+    team_field_names = [f.name for f in team_fields]
+
+    user_fields = UserFields.query.all()
+    user_field_ids = [f.id for f in user_fields]
+    user_field_names = [f.name for f in user_fields]
+
+    user_header = [
+        f"member_{column.name}" for column in Users.__mapper__.columns
+    ] + user_field_names
+
+    header = (
+        [column.name for column in Teams.__mapper__.columns]
+        + team_field_names
+        + user_header
+    )
+    writer.writerow(header)
+
+    responses = Teams.query.all()
+
+    for curr in responses:
+        team_field_entries = {f.field_id: f.value for f in curr.field_entries}
+        team_field_values = [
+            team_field_entries.get(f_id, "") for f_id in team_field_ids
+        ]
+
+        team_row = [
+            getattr(curr, column.name) for column in Teams.__mapper__.columns
+        ] + team_field_values
+
+        writer.writerow(team_row)
+
+        for member in curr.members:
+            padding = [""] * len(team_row)
+
+            user_field_entries = {f.field_id: f.value for f in member.field_entries}
+            user_field_values = [
+                user_field_entries.get(f_id, "") for f_id in user_field_ids
+            ]
+            user_row = [
+                getattr(member, column.name) for column in Users.__mapper__.columns
+            ] + user_field_values
+            writer.writerow(padding + user_row)
+
+    temp.seek(0)
+
+    # In Python 3 send_file requires bytes
+    output = BytesIO()
+    output.write(temp.getvalue().encode("utf-8"))
+    output.seek(0)
+    temp.close()
+
+    return output
+
+
 def dump_database_table(tablename):
     # TODO: It might make sense to limit dumpable tables. Config could potentially leak sensitive information.
     model = get_class_by_tablename(tablename)
@@ -334,4 +394,5 @@
     "scoreboard": dump_scoreboard_csv,
     "users+fields": dump_users_with_fields_csv,
     "teams+fields": dump_teams_with_fields_csv,
+    "teams+members+fields": dump_teams_with_members_fields_csv,
 }

{"golden_diff": "diff --git a/CTFd/utils/csv/__init__.py b/CTFd/utils/csv/__init__.py\n--- a/CTFd/utils/csv/__init__.py\n+++ b/CTFd/utils/csv/__init__.py\n@@ -216,6 +216,66 @@\n     return output\n \n \n+def dump_teams_with_members_fields_csv():\n+    temp = StringIO()\n+    writer = csv.writer(temp)\n+\n+    team_fields = TeamFields.query.all()\n+    team_field_ids = [f.id for f in team_fields]\n+    team_field_names = [f.name for f in team_fields]\n+\n+    user_fields = UserFields.query.all()\n+    user_field_ids = [f.id for f in user_fields]\n+    user_field_names = [f.name for f in user_fields]\n+\n+    user_header = [\n+        f\"member_{column.name}\" for column in Users.__mapper__.columns\n+    ] + user_field_names\n+\n+    header = (\n+        [column.name for column in Teams.__mapper__.columns]\n+        + team_field_names\n+        + user_header\n+    )\n+    writer.writerow(header)\n+\n+    responses = Teams.query.all()\n+\n+    for curr in responses:\n+        team_field_entries = {f.field_id: f.value for f in curr.field_entries}\n+        team_field_values = [\n+            team_field_entries.get(f_id, \"\") for f_id in team_field_ids\n+        ]\n+\n+        team_row = [\n+            getattr(curr, column.name) for column in Teams.__mapper__.columns\n+        ] + team_field_values\n+\n+        writer.writerow(team_row)\n+\n+        for member in curr.members:\n+            padding = [\"\"] * len(team_row)\n+\n+            user_field_entries = {f.field_id: f.value for f in member.field_entries}\n+            user_field_values = [\n+                user_field_entries.get(f_id, \"\") for f_id in user_field_ids\n+            ]\n+            user_row = [\n+                getattr(member, column.name) for column in Users.__mapper__.columns\n+            ] + user_field_values\n+            writer.writerow(padding + user_row)\n+\n+    temp.seek(0)\n+\n+    # In Python 3 send_file requires bytes\n+    output = BytesIO()\n+    output.write(temp.getvalue().encode(\"utf-8\"))\n+    output.seek(0)\n+    temp.close()\n+\n+    return output\n+\n+\n def dump_database_table(tablename):\n     # TODO: It might make sense to limit dumpable tables. Config could potentially leak sensitive information.\n     model = get_class_by_tablename(tablename)\n@@ -334,4 +394,5 @@\n     \"scoreboard\": dump_scoreboard_csv,\n     \"users+fields\": dump_users_with_fields_csv,\n     \"teams+fields\": dump_teams_with_fields_csv,\n+    \"teams+members+fields\": dump_teams_with_members_fields_csv,\n }\n", "issue": "CSV for teams+members+fields\nWe should have this CSV format to export but for some reason we don't. Should be an easy implementation. \n", "before_files": [{"content": "import csv\nimport json\nfrom io import BytesIO, StringIO\n\nfrom CTFd.models import (\n    Flags,\n    Hints,\n    Tags,\n    TeamFields,\n    Teams,\n    UserFields,\n    Users,\n    db,\n    get_class_by_tablename,\n)\nfrom CTFd.plugins.challenges import get_chal_class\nfrom CTFd.schemas.challenges import ChallengeSchema\nfrom CTFd.schemas.teams import TeamSchema\nfrom CTFd.schemas.users import UserSchema\nfrom CTFd.utils.config import is_teams_mode, is_users_mode\nfrom CTFd.utils.scores import get_standings\n\n\ndef get_dumpable_tables():\n    csv_keys = list(CSV_KEYS.keys())\n    db_keys = list(db.metadata.tables.keys())\n    tables = csv_keys + db_keys\n    table_keys = list(zip(tables, tables))\n    return table_keys\n\n\ndef dump_csv(name):\n    dump_func = CSV_KEYS.get(name)\n    if dump_func:\n        return dump_func()\n    elif get_class_by_tablename(name):\n        return dump_database_table(tablename=name)\n    else:\n        raise KeyError\n\n\ndef dump_scoreboard_csv():\n    # TODO: Add fields to scoreboard data\n    temp = StringIO()\n    writer = csv.writer(temp)\n\n    standings = get_standings()\n\n    # Get all user fields in a specific order\n    user_fields = UserFields.query.all()\n    user_field_ids = [f.id for f in user_fields]\n    user_field_names = [f.name for f in user_fields]\n\n    if is_teams_mode():\n        team_fields = TeamFields.query.all()\n        team_field_ids = [f.id for f in team_fields]\n        team_field_names = [f.name for f in team_fields]\n\n        header = (\n            [\n                \"place\",\n                \"team\",\n                \"team id\",\n                \"score\",\n                \"member name\",\n                \"member id\",\n                \"member email\",\n                \"member score\",\n            ]\n            + user_field_names\n            + team_field_names\n        )\n        writer.writerow(header)\n\n        for i, standing in enumerate(standings):\n            team = Teams.query.filter_by(id=standing.account_id).first()\n\n            # Build field entries using the order of the field values\n            team_field_entries = {f.field_id: f.value for f in team.field_entries}\n            team_field_values = [\n                team_field_entries.get(f_id, \"\") for f_id in team_field_ids\n            ]\n            user_field_values = len(user_field_names) * [\"\"]\n            team_row = (\n                [i + 1, team.name, team.id, standing.score, \"\", \"\", \"\", \"\"]\n                + user_field_values\n                + team_field_values\n            )\n\n            writer.writerow(team_row)\n\n            for member in team.members:\n                user_field_entries = {f.field_id: f.value for f in member.field_entries}\n                user_field_values = [\n                    user_field_entries.get(f_id, \"\") for f_id in user_field_ids\n                ]\n                team_field_values = len(team_field_names) * [\"\"]\n                user_row = (\n                    [\n                        \"\",\n                        \"\",\n                        \"\",\n                        \"\",\n                        member.name,\n                        member.id,\n                        member.email,\n                        member.score,\n                    ]\n                    + user_field_values\n                    + team_field_values\n                )\n                writer.writerow(user_row)\n    elif is_users_mode():\n        header = [\n            \"place\",\n            \"user name\",\n            \"user id\",\n            \"user email\",\n            \"score\",\n        ] + user_field_names\n        writer.writerow(header)\n\n        for i, standing in enumerate(standings):\n            user = Users.query.filter_by(id=standing.account_id).first()\n\n            # Build field entries using the order of the field values\n            user_field_entries = {f.field_id: f.value for f in user.field_entries}\n            user_field_values = [\n                user_field_entries.get(f_id, \"\") for f_id in user_field_ids\n            ]\n            user_row = [\n                i + 1,\n                user.name,\n                user.id,\n                user.email,\n                standing.score,\n            ] + user_field_values\n            writer.writerow(user_row)\n\n    # In Python 3 send_file requires bytes\n    output = BytesIO()\n    output.write(temp.getvalue().encode(\"utf-8\"))\n    output.seek(0)\n    temp.close()\n\n    return output\n\n\ndef dump_users_with_fields_csv():\n    temp = StringIO()\n    writer = csv.writer(temp)\n\n    user_fields = UserFields.query.all()\n    user_field_ids = [f.id for f in user_fields]\n    user_field_names = [f.name for f in user_fields]\n\n    header = [column.name for column in Users.__mapper__.columns] + user_field_names\n    writer.writerow(header)\n\n    responses = Users.query.all()\n\n    for curr in responses:\n        user_field_entries = {f.field_id: f.value for f in curr.field_entries}\n        user_field_values = [\n            user_field_entries.get(f_id, \"\") for f_id in user_field_ids\n        ]\n        user_row = [\n            getattr(curr, column.name) for column in Users.__mapper__.columns\n        ] + user_field_values\n        writer.writerow(user_row)\n\n    temp.seek(0)\n\n    # In Python 3 send_file requires bytes\n    output = BytesIO()\n    output.write(temp.getvalue().encode(\"utf-8\"))\n    output.seek(0)\n    temp.close()\n\n    return output\n\n\ndef dump_teams_with_fields_csv():\n    temp = StringIO()\n    writer = csv.writer(temp)\n\n    team_fields = TeamFields.query.all()\n    team_field_ids = [f.id for f in team_fields]\n    team_field_names = [f.name for f in team_fields]\n\n    header = [column.name for column in Teams.__mapper__.columns] + team_field_names\n    writer.writerow(header)\n\n    responses = Teams.query.all()\n\n    for curr in responses:\n        team_field_entries = {f.field_id: f.value for f in curr.field_entries}\n        team_field_values = [\n            team_field_entries.get(f_id, \"\") for f_id in team_field_ids\n        ]\n\n        team_row = [\n            getattr(curr, column.name) for column in Teams.__mapper__.columns\n        ] + team_field_values\n\n        writer.writerow(team_row)\n\n    temp.seek(0)\n\n    # In Python 3 send_file requires bytes\n    output = BytesIO()\n    output.write(temp.getvalue().encode(\"utf-8\"))\n    output.seek(0)\n    temp.close()\n\n    return output\n\n\ndef dump_database_table(tablename):\n    # TODO: It might make sense to limit dumpable tables. Config could potentially leak sensitive information.\n    model = get_class_by_tablename(tablename)\n\n    if model is None:\n        raise KeyError(\"Unknown database table\")\n\n    temp = StringIO()\n    writer = csv.writer(temp)\n\n    header = [column.name for column in model.__mapper__.columns]\n    writer.writerow(header)\n\n    responses = model.query.all()\n\n    for curr in responses:\n        writer.writerow(\n            [getattr(curr, column.name) for column in model.__mapper__.columns]\n        )\n\n    temp.seek(0)\n\n    # In Python 3 send_file requires bytes\n    output = BytesIO()\n    output.write(temp.getvalue().encode(\"utf-8\"))\n    output.seek(0)\n    temp.close()\n\n    return output\n\n\ndef load_users_csv(dict_reader):\n    schema = UserSchema()\n    errors = []\n    for i, line in enumerate(dict_reader):\n        response = schema.load(line)\n        if response.errors:\n            errors.append((i, response.errors))\n        else:\n            db.session.add(response.data)\n            db.session.commit()\n    if errors:\n        return errors\n    return True\n\n\ndef load_teams_csv(dict_reader):\n    schema = TeamSchema()\n    errors = []\n    for i, line in enumerate(dict_reader):\n        response = schema.load(line)\n        if response.errors:\n            errors.append((i, response.errors))\n        else:\n            db.session.add(response.data)\n            db.session.commit()\n    if errors:\n        return errors\n    return True\n\n\ndef load_challenges_csv(dict_reader):\n    schema = ChallengeSchema()\n    errors = []\n\n    for i, line in enumerate(dict_reader):\n        # Throw away fields that we can't trust if provided\n        _ = line.pop(\"id\", None)\n        _ = line.pop(\"requirements\", None)\n\n        flags = line.pop(\"flags\", None)\n        tags = line.pop(\"tags\", None)\n        hints = line.pop(\"hints\", None)\n        challenge_type = line.pop(\"type\", \"standard\")\n\n        # Load in custom type_data\n        type_data = json.loads(line.pop(\"type_data\", \"{}\") or \"{}\")\n        line.update(type_data)\n\n        response = schema.load(line)\n        if response.errors:\n            errors.append((i + 1, response.errors))\n            continue\n\n        ChallengeClass = get_chal_class(challenge_type)\n        challenge = ChallengeClass.challenge_model(**line)\n        db.session.add(challenge)\n        db.session.commit()\n\n        if flags:\n            flags = [flag.strip() for flag in flags.split(\",\")]\n            for flag in flags:\n                f = Flags(type=\"static\", challenge_id=challenge.id, content=flag,)\n                db.session.add(f)\n                db.session.commit()\n\n        if tags:\n            tags = [tag.strip() for tag in tags.split(\",\")]\n            for tag in tags:\n                t = Tags(challenge_id=challenge.id, value=tag,)\n                db.session.add(t)\n                db.session.commit()\n\n        if hints:\n            hints = [hint.strip() for hint in hints.split(\",\")]\n            for hint in hints:\n                h = Hints(challenge_id=challenge.id, content=hint,)\n                db.session.add(h)\n                db.session.commit()\n    if errors:\n        return errors\n    return True\n\n\nCSV_KEYS = {\n    \"scoreboard\": dump_scoreboard_csv,\n    \"users+fields\": dump_users_with_fields_csv,\n    \"teams+fields\": dump_teams_with_fields_csv,\n}\n", "path": "CTFd/utils/csv/__init__.py"}], "after_files": [{"content": "import csv\nimport json\nfrom io import BytesIO, StringIO\n\nfrom CTFd.models import (\n    Flags,\n    Hints,\n    Tags,\n    TeamFields,\n    Teams,\n    UserFields,\n    Users,\n    db,\n    get_class_by_tablename,\n)\nfrom CTFd.plugins.challenges import get_chal_class\nfrom CTFd.schemas.challenges import ChallengeSchema\nfrom CTFd.schemas.teams import TeamSchema\nfrom CTFd.schemas.users import UserSchema\nfrom CTFd.utils.config import is_teams_mode, is_users_mode\nfrom CTFd.utils.scores import get_standings\n\n\ndef get_dumpable_tables():\n    csv_keys = list(CSV_KEYS.keys())\n    db_keys = list(db.metadata.tables.keys())\n    tables = csv_keys + db_keys\n    table_keys = list(zip(tables, tables))\n    return table_keys\n\n\ndef dump_csv(name):\n    dump_func = CSV_KEYS.get(name)\n    if dump_func:\n        return dump_func()\n    elif get_class_by_tablename(name):\n        return dump_database_table(tablename=name)\n    else:\n        raise KeyError\n\n\ndef dump_scoreboard_csv():\n    # TODO: Add fields to scoreboard data\n    temp = StringIO()\n    writer = csv.writer(temp)\n\n    standings = get_standings()\n\n    # Get all user fields in a specific order\n    user_fields = UserFields.query.all()\n    user_field_ids = [f.id for f in user_fields]\n    user_field_names = [f.name for f in user_fields]\n\n    if is_teams_mode():\n        team_fields = TeamFields.query.all()\n        team_field_ids = [f.id for f in team_fields]\n        team_field_names = [f.name for f in team_fields]\n\n        header = (\n            [\n                \"place\",\n                \"team\",\n                \"team id\",\n                \"score\",\n                \"member name\",\n                \"member id\",\n                \"member email\",\n                \"member score\",\n            ]\n            + user_field_names\n            + team_field_names\n        )\n        writer.writerow(header)\n\n        for i, standing in enumerate(standings):\n            team = Teams.query.filter_by(id=standing.account_id).first()\n\n            # Build field entries using the order of the field values\n            team_field_entries = {f.field_id: f.value for f in team.field_entries}\n            team_field_values = [\n                team_field_entries.get(f_id, \"\") for f_id in team_field_ids\n            ]\n            user_field_values = len(user_field_names) * [\"\"]\n            team_row = (\n                [i + 1, team.name, team.id, standing.score, \"\", \"\", \"\", \"\"]\n                + user_field_values\n                + team_field_values\n            )\n\n            writer.writerow(team_row)\n\n            for member in team.members:\n                user_field_entries = {f.field_id: f.value for f in member.field_entries}\n                user_field_values = [\n                    user_field_entries.get(f_id, \"\") for f_id in user_field_ids\n                ]\n                team_field_values = len(team_field_names) * [\"\"]\n                user_row = (\n                    [\n                        \"\",\n                        \"\",\n                        \"\",\n                        \"\",\n                        member.name,\n                        member.id,\n                        member.email,\n                        member.score,\n                    ]\n                    + user_field_values\n                    + team_field_values\n                )\n                writer.writerow(user_row)\n    elif is_users_mode():\n        header = [\n            \"place\",\n            \"user name\",\n            \"user id\",\n            \"user email\",\n            \"score\",\n        ] + user_field_names\n        writer.writerow(header)\n\n        for i, standing in enumerate(standings):\n            user = Users.query.filter_by(id=standing.account_id).first()\n\n            # Build field entries using the order of the field values\n            user_field_entries = {f.field_id: f.value for f in user.field_entries}\n            user_field_values = [\n                user_field_entries.get(f_id, \"\") for f_id in user_field_ids\n            ]\n            user_row = [\n                i + 1,\n                user.name,\n                user.id,\n                user.email,\n                standing.score,\n            ] + user_field_values\n            writer.writerow(user_row)\n\n    # In Python 3 send_file requires bytes\n    output = BytesIO()\n    output.write(temp.getvalue().encode(\"utf-8\"))\n    output.seek(0)\n    temp.close()\n\n    return output\n\n\ndef dump_users_with_fields_csv():\n    temp = StringIO()\n    writer = csv.writer(temp)\n\n    user_fields = UserFields.query.all()\n    user_field_ids = [f.id for f in user_fields]\n    user_field_names = [f.name for f in user_fields]\n\n    header = [column.name for column in Users.__mapper__.columns] + user_field_names\n    writer.writerow(header)\n\n    responses = Users.query.all()\n\n    for curr in responses:\n        user_field_entries = {f.field_id: f.value for f in curr.field_entries}\n        user_field_values = [\n            user_field_entries.get(f_id, \"\") for f_id in user_field_ids\n        ]\n        user_row = [\n            getattr(curr, column.name) for column in Users.__mapper__.columns\n        ] + user_field_values\n        writer.writerow(user_row)\n\n    temp.seek(0)\n\n    # In Python 3 send_file requires bytes\n    output = BytesIO()\n    output.write(temp.getvalue().encode(\"utf-8\"))\n    output.seek(0)\n    temp.close()\n\n    return output\n\n\ndef dump_teams_with_fields_csv():\n    temp = StringIO()\n    writer = csv.writer(temp)\n\n    team_fields = TeamFields.query.all()\n    team_field_ids = [f.id for f in team_fields]\n    team_field_names = [f.name for f in team_fields]\n\n    header = [column.name for column in Teams.__mapper__.columns] + team_field_names\n    writer.writerow(header)\n\n    responses = Teams.query.all()\n\n    for curr in responses:\n        team_field_entries = {f.field_id: f.value for f in curr.field_entries}\n        team_field_values = [\n            team_field_entries.get(f_id, \"\") for f_id in team_field_ids\n        ]\n\n        team_row = [\n            getattr(curr, column.name) for column in Teams.__mapper__.columns\n        ] + team_field_values\n\n        writer.writerow(team_row)\n\n    temp.seek(0)\n\n    # In Python 3 send_file requires bytes\n    output = BytesIO()\n    output.write(temp.getvalue().encode(\"utf-8\"))\n    output.seek(0)\n    temp.close()\n\n    return output\n\n\ndef dump_teams_with_members_fields_csv():\n    temp = StringIO()\n    writer = csv.writer(temp)\n\n    team_fields = TeamFields.query.all()\n    team_field_ids = [f.id for f in team_fields]\n    team_field_names = [f.name for f in team_fields]\n\n    user_fields = UserFields.query.all()\n    user_field_ids = [f.id for f in user_fields]\n    user_field_names = [f.name for f in user_fields]\n\n    user_header = [\n        f\"member_{column.name}\" for column in Users.__mapper__.columns\n    ] + user_field_names\n\n    header = (\n        [column.name for column in Teams.__mapper__.columns]\n        + team_field_names\n        + user_header\n    )\n    writer.writerow(header)\n\n    responses = Teams.query.all()\n\n    for curr in responses:\n        team_field_entries = {f.field_id: f.value for f in curr.field_entries}\n        team_field_values = [\n            team_field_entries.get(f_id, \"\") for f_id in team_field_ids\n        ]\n\n        team_row = [\n            getattr(curr, column.name) for column in Teams.__mapper__.columns\n        ] + team_field_values\n\n        writer.writerow(team_row)\n\n        for member in curr.members:\n            padding = [\"\"] * len(team_row)\n\n            user_field_entries = {f.field_id: f.value for f in member.field_entries}\n            user_field_values = [\n                user_field_entries.get(f_id, \"\") for f_id in user_field_ids\n            ]\n            user_row = [\n                getattr(member, column.name) for column in Users.__mapper__.columns\n            ] + user_field_values\n            writer.writerow(padding + user_row)\n\n    temp.seek(0)\n\n    # In Python 3 send_file requires bytes\n    output = BytesIO()\n    output.write(temp.getvalue().encode(\"utf-8\"))\n    output.seek(0)\n    temp.close()\n\n    return output\n\n\ndef dump_database_table(tablename):\n    # TODO: It might make sense to limit dumpable tables. Config could potentially leak sensitive information.\n    model = get_class_by_tablename(tablename)\n\n    if model is None:\n        raise KeyError(\"Unknown database table\")\n\n    temp = StringIO()\n    writer = csv.writer(temp)\n\n    header = [column.name for column in model.__mapper__.columns]\n    writer.writerow(header)\n\n    responses = model.query.all()\n\n    for curr in responses:\n        writer.writerow(\n            [getattr(curr, column.name) for column in model.__mapper__.columns]\n        )\n\n    temp.seek(0)\n\n    # In Python 3 send_file requires bytes\n    output = BytesIO()\n    output.write(temp.getvalue().encode(\"utf-8\"))\n    output.seek(0)\n    temp.close()\n\n    return output\n\n\ndef load_users_csv(dict_reader):\n    schema = UserSchema()\n    errors = []\n    for i, line in enumerate(dict_reader):\n        response = schema.load(line)\n        if response.errors:\n            errors.append((i, response.errors))\n        else:\n            db.session.add(response.data)\n            db.session.commit()\n    if errors:\n        return errors\n    return True\n\n\ndef load_teams_csv(dict_reader):\n    schema = TeamSchema()\n    errors = []\n    for i, line in enumerate(dict_reader):\n        response = schema.load(line)\n        if response.errors:\n            errors.append((i, response.errors))\n        else:\n            db.session.add(response.data)\n            db.session.commit()\n    if errors:\n        return errors\n    return True\n\n\ndef load_challenges_csv(dict_reader):\n    schema = ChallengeSchema()\n    errors = []\n\n    for i, line in enumerate(dict_reader):\n        # Throw away fields that we can't trust if provided\n        _ = line.pop(\"id\", None)\n        _ = line.pop(\"requirements\", None)\n\n        flags = line.pop(\"flags\", None)\n        tags = line.pop(\"tags\", None)\n        hints = line.pop(\"hints\", None)\n        challenge_type = line.pop(\"type\", \"standard\")\n\n        # Load in custom type_data\n        type_data = json.loads(line.pop(\"type_data\", \"{}\") or \"{}\")\n        line.update(type_data)\n\n        response = schema.load(line)\n        if response.errors:\n            errors.append((i + 1, response.errors))\n            continue\n\n        ChallengeClass = get_chal_class(challenge_type)\n        challenge = ChallengeClass.challenge_model(**line)\n        db.session.add(challenge)\n        db.session.commit()\n\n        if flags:\n            flags = [flag.strip() for flag in flags.split(\",\")]\n            for flag in flags:\n                f = Flags(type=\"static\", challenge_id=challenge.id, content=flag,)\n                db.session.add(f)\n                db.session.commit()\n\n        if tags:\n            tags = [tag.strip() for tag in tags.split(\",\")]\n            for tag in tags:\n                t = Tags(challenge_id=challenge.id, value=tag,)\n                db.session.add(t)\n                db.session.commit()\n\n        if hints:\n            hints = [hint.strip() for hint in hints.split(\",\")]\n            for hint in hints:\n                h = Hints(challenge_id=challenge.id, content=hint,)\n                db.session.add(h)\n                db.session.commit()\n    if errors:\n        return errors\n    return True\n\n\nCSV_KEYS = {\n    \"scoreboard\": dump_scoreboard_csv,\n    \"users+fields\": dump_users_with_fields_csv,\n    \"teams+fields\": dump_teams_with_fields_csv,\n    \"teams+members+fields\": dump_teams_with_members_fields_csv,\n}\n", "path": "CTFd/utils/csv/__init__.py"}]}