Skip to content

Commit 10fdff4

Browse files
committed
add dry_run option to volunteer import script
Update the script to run all changes inside a transaction and if the dry_run option is passed then rollback all changes. This lets you see any issues with running out of assignments without having made any changes to the database.
1 parent 99edc4d commit 10fdff4

File tree

2 files changed

+94
-47
lines changed

2 files changed

+94
-47
lines changed

crowdsourcer/management/commands/import_volunteers.py

+76-47
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from django.conf import settings
55
from django.contrib.auth.models import User
66
from django.core.management.base import BaseCommand
7+
from django.db import transaction
78
from django.db.models import Q
89

910
import pandas as pd
@@ -113,6 +114,12 @@ def add_arguments(self, parser):
113114
"--make_assignments", action="store_true", help="assign councils to users"
114115
)
115116

117+
parser.add_argument(
118+
"--dry_run",
119+
action="store_true",
120+
help="run everything and then undo changes. Helpful to check if assigment weighting is good",
121+
)
122+
116123
def get_df(self, filename):
117124
df = pd.read_csv(
118125
filename,
@@ -149,32 +156,7 @@ def get_assignment_count(self, user_type):
149156
num_councils = self.num_council_map.get(user_type)
150157
return num_councils
151158

152-
def handle(
153-
self,
154-
quiet: bool = False,
155-
file: str = None,
156-
session: str = None,
157-
col_names: str = None,
158-
response_type: str = None,
159-
assignment_map: str = None,
160-
authority_map: str = None,
161-
*args,
162-
**options,
163-
):
164-
if file is None:
165-
file = self.volunteer_file
166-
167-
self.set_cols(col_names)
168-
self.set_assignment_map(assignment_map)
169-
self.set_authority_map(authority_map)
170-
171-
df = self.get_df(file)
172-
173-
if response_type is None:
174-
response_type = self.response_type
175-
176-
session = MarkingSession.objects.get(label=session)
177-
rt = ResponseType.objects.get(type=response_type)
159+
def add_users_and_assignments(self, df, response_type, session, rt, options):
178160

179161
bad_councils = []
180162
for index, row in df.iterrows():
@@ -300,28 +282,70 @@ def handle(
300282
response_type=rt,
301283
)
302284

303-
council_count = PublicAuthority.objects.filter(
304-
marking_session=session, do_not_mark=False
305-
).count()
306-
for section in Section.objects.filter(marking_session=session).all():
307-
assigned = Assigned.objects.filter(section=section).count()
308-
if assigned != council_count:
309-
self.stdout.write(
310-
f"{RED}Not all councils assigned for {section.title} ({assigned}/{council_count}){NOBOLD}"
311-
)
312-
else:
313-
self.stdout.write(f"{GREEN}All councils and sections assigned{NOBOLD}")
314-
315-
volunteer_count = User.objects.filter(
316-
marker__marking_session=session, marker__response_type=rt
317-
).count()
318-
assigned_count = (
319-
Assigned.objects.filter(user__is_superuser=False)
320-
.distinct("user_id")
321-
.count()
322-
)
285+
return bad_councils
286+
287+
def handle(
288+
self,
289+
quiet: bool = False,
290+
file: str = None,
291+
session: str = None,
292+
col_names: str = None,
293+
response_type: str = None,
294+
assignment_map: str = None,
295+
authority_map: str = None,
296+
*args,
297+
**options,
298+
):
299+
if file is None:
300+
file = self.volunteer_file
301+
302+
self.set_cols(col_names)
303+
self.set_assignment_map(assignment_map)
304+
self.set_authority_map(authority_map)
305+
306+
df = self.get_df(file)
307+
308+
if response_type is None:
309+
response_type = self.response_type
310+
311+
session = MarkingSession.objects.get(label=session)
312+
rt = ResponseType.objects.get(type=response_type)
313+
314+
with transaction.atomic():
315+
bad_councils = self.add_users_and_assignments(
316+
df, response_type, session, rt, options
317+
)
318+
319+
council_count = PublicAuthority.objects.filter(
320+
marking_session=session, do_not_mark=False
321+
).count()
322+
for section in Section.objects.filter(marking_session=session).all():
323+
assigned = Assigned.objects.filter(section=section).count()
324+
if assigned != council_count:
325+
self.stdout.write(
326+
f"{RED}Not all councils assigned for {section.title} ({assigned}/{council_count}){NOBOLD}"
327+
)
328+
else:
329+
self.stdout.write(
330+
f"{GREEN}All councils and sections assigned{NOBOLD}"
331+
)
332+
333+
volunteer_count = User.objects.filter(
334+
marker__marking_session=session, marker__response_type=rt
335+
).count()
336+
assigned_count = (
337+
Assigned.objects.filter(user__is_superuser=False)
338+
.distinct("user_id")
339+
.count()
340+
)
341+
342+
self.stdout.write(
343+
f"{assigned_count}/{volunteer_count} users assigned marking"
344+
)
345+
346+
if options["dry_run"]:
347+
transaction.set_rollback(True)
323348

324-
self.stdout.write(f"{assigned_count}/{volunteer_count} users assigned marking")
325349
if not options["add_users"]:
326350
self.stdout.write(
327351
f"{YELLOW}Dry run, no users added, call with --add_users to add users{NOBOLD}"
@@ -331,6 +355,11 @@ def handle(
331355
f"{YELLOW}Dry run, no assignments made, call with --make_assignments to make them{NOBOLD}"
332356
)
333357

358+
if options["dry_run"]:
359+
self.stdout.write(
360+
f"{YELLOW}Dry run, no changes made, call without --dry_run to make changes{NOBOLD}"
361+
)
362+
334363
if len(bad_councils):
335364
self.stdout.write("Bad councils are:")
336365
for c in bad_councils:

crowdsourcer/tests/test_import_volunteers_script.py

+18
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,24 @@ def test_basic_run(self):
119119
self.assertTrue("Aberdeen City Council" in councils)
120120
self.assertTrue("Adur District Council" in councils)
121121

122+
def test_dry_run(self):
123+
data_file = pathlib.Path(__file__).parent.resolve() / "data" / "volunteers.csv"
124+
125+
self.assertEquals(User.objects.count(), 0)
126+
self.assertEquals(Marker.objects.count(), 0)
127+
self.assertEquals(Assigned.objects.count(), 0)
128+
self.call_command(
129+
"import_volunteers",
130+
session="Default",
131+
file=data_file,
132+
add_users=True,
133+
make_assignments=True,
134+
dry_run=True,
135+
)
136+
self.assertEquals(User.objects.count(), 0)
137+
self.assertEquals(Marker.objects.count(), 0)
138+
self.assertEquals(Assigned.objects.count(), 0)
139+
122140
def test_multi_councils(self):
123141
data_file = (
124142
pathlib.Path(__file__).parent.resolve()

0 commit comments

Comments
 (0)