Skip to content

Commit 69fbdef

Browse files
committed
add ability to cache all assignment progress for a session
Moves the calculating of progress out to a method in a marking file, uses that in a management command to store all progress for all types in a pickle file and if that exists loads that on the front page. This should significantly speed up the display of the front page for admin users. For non admin users progress is loaded from the database as before.
1 parent 17e7b72 commit 69fbdef

File tree

3 files changed

+168
-83
lines changed

3 files changed

+168
-83
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from django.core.management.base import BaseCommand
2+
3+
from crowdsourcer.marking import (
4+
get_assignment_progress,
5+
save_cached_assignment_progress,
6+
)
7+
from crowdsourcer.models import Assigned, MarkingSession, ResponseType
8+
9+
YELLOW = "\033[33m"
10+
NOBOLD = "\033[0m"
11+
12+
13+
class Command(BaseCommand):
14+
help = "caches current progress for a stage and session to json"
15+
16+
def add_arguments(self, parser):
17+
parser.add_argument("--session", action="store", help="name of the session")
18+
19+
def handle(self, session, *args, **kwargs):
20+
ms = MarkingSession.objects.get(label=session)
21+
22+
for t in ResponseType.objects.all():
23+
qs = Assigned.objects.filter(
24+
marking_session=ms,
25+
section__isnull=False,
26+
active=True,
27+
response_type=t,
28+
user__is_active=True,
29+
)
30+
31+
progress = get_assignment_progress(qs, session, t.type)
32+
save_cached_assignment_progress(f"{session} {t.type}", progress)

crowdsourcer/marking.py

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import pickle
2+
3+
from django.conf import settings
4+
from django.utils.text import slugify
5+
6+
from crowdsourcer.models import (
7+
Assigned,
8+
MarkingSession,
9+
PublicAuthority,
10+
Question,
11+
ResponseType,
12+
)
13+
14+
15+
def get_assignment_progress_cache_name(name):
16+
name = slugify(name)
17+
file = settings.BASE_DIR / "data" / f"assignment_progress_{name}.pkl"
18+
19+
return file
20+
21+
22+
def get_cached_assignment_progress(name):
23+
file = get_assignment_progress_cache_name(name)
24+
25+
progress = None
26+
if file.exists():
27+
with open(file, "rb") as fp:
28+
progress = pickle.load(fp)
29+
30+
return progress
31+
32+
33+
def save_cached_assignment_progress(name, progress):
34+
file = get_assignment_progress_cache_name(name)
35+
with open(file, "wb") as fp:
36+
pickle.dump(progress, fp)
37+
38+
39+
def get_assignment_progress(assignments, marking_session, stage):
40+
current_session = MarkingSession.objects.get(label=marking_session)
41+
42+
assignments = assignments.distinct(
43+
"user_id", "section_id", "response_type_id"
44+
).select_related("section", "response_type")
45+
46+
types = Question.VOLUNTEER_TYPES
47+
if stage == "Audit":
48+
types = ["volunteer", "national_volunteer", "foi"]
49+
50+
first_mark = ResponseType.objects.get(type="First Mark")
51+
52+
progress = []
53+
question_cache = {}
54+
for assignment in assignments:
55+
assignment_user = assignment.user
56+
if hasattr(assignment_user, "marker"):
57+
stage = assignment_user.marker.response_type
58+
else:
59+
stage = first_mark
60+
61+
if question_cache.get(assignment.section_id, None) is not None:
62+
question_list = question_cache[assignment.section_id]
63+
else:
64+
questions = Question.objects.filter(
65+
section=assignment.section, how_marked__in=types
66+
)
67+
question_list = list(questions.values_list("id", flat=True))
68+
question_cache[assignment.section_id] = question_list
69+
70+
total = 0
71+
complete = 0
72+
started = 0
73+
74+
if assignment.section is not None:
75+
args = [
76+
question_list,
77+
assignment.section.title,
78+
assignment.user,
79+
current_session,
80+
]
81+
if assignment.authority_id is not None:
82+
authorities = Assigned.objects.filter(
83+
active=True,
84+
user=assignment.user_id,
85+
section=assignment.section_id,
86+
response_type=stage,
87+
).values_list("authority_id", flat=True)
88+
args.append(authorities)
89+
90+
# we pass the question list but we want to ignore it because there could be different types of council
91+
# included in assignments which throws the count off
92+
response_counts = PublicAuthority.response_counts(
93+
*args,
94+
question_types=types,
95+
response_type=assignment.response_type,
96+
ignore_question_list=True,
97+
).distinct()
98+
99+
for count in response_counts:
100+
total += 1
101+
if count.num_responses is not None and count.num_responses > 0:
102+
started += 1
103+
if count.num_responses == count.num_questions:
104+
complete += 1
105+
106+
if assignment.response_type is None:
107+
section_link = "home"
108+
elif assignment.response_type.type == "First Mark":
109+
section_link = "section_authorities"
110+
elif assignment.response_type.type == "Right of Reply":
111+
section_link = "authority_ror_authorities"
112+
elif assignment.response_type.type == "Audit":
113+
section_link = "audit_section_authorities"
114+
115+
progress.append(
116+
{
117+
"assignment": assignment,
118+
"complete": complete,
119+
"started": started,
120+
"total": total,
121+
"section_link": section_link,
122+
}
123+
)
124+
125+
return progress

crowdsourcer/views/marking.py

+11-83
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
from django.views.generic import FormView, ListView, TemplateView
88

99
from crowdsourcer.forms import SessionPropertyForm
10+
from crowdsourcer.marking import get_assignment_progress, get_cached_assignment_progress
1011
from crowdsourcer.models import (
1112
Assigned,
1213
MarkingSession,
1314
PublicAuthority,
14-
Question,
1515
ResponseType,
1616
SessionProperties,
1717
SessionPropertyValues,
@@ -157,89 +157,17 @@ def get_context_data(self, **kwargs):
157157

158158
return context
159159

160-
assignments = (
161-
context["assignments"]
162-
.distinct("user_id", "section_id", "response_type_id")
163-
.select_related("section", "response_type")
164-
)
165-
166-
types = Question.VOLUNTEER_TYPES
167-
if self.request.current_stage.type == "Audit":
168-
types = ["volunteer", "national_volunteer", "foi"]
169-
170-
first_mark = ResponseType.objects.get(type="First Mark")
171-
172-
progress = []
173-
question_cache = {}
174-
for assignment in assignments:
175-
assignment_user = assignment.user
176-
if hasattr(assignment_user, "marker"):
177-
stage = assignment_user.marker.response_type
178-
else:
179-
stage = first_mark
160+
progress = None
161+
if user.has_perm("crowdsourcer.can_view_all_responses"):
162+
progress = get_cached_assignment_progress(
163+
f"{self.request.current_session.label} {self.request.current_stage.type}"
164+
)
180165

181-
if question_cache.get(assignment.section_id, None) is not None:
182-
question_list = question_cache[assignment.section_id]
183-
else:
184-
questions = Question.objects.filter(
185-
section=assignment.section, how_marked__in=types
186-
)
187-
question_list = list(questions.values_list("id", flat=True))
188-
question_cache[assignment.section_id] = question_list
189-
190-
total = 0
191-
complete = 0
192-
started = 0
193-
194-
if assignment.section is not None:
195-
args = [
196-
question_list,
197-
assignment.section.title,
198-
assignment.user,
199-
self.request.current_session,
200-
]
201-
if assignment.authority_id is not None:
202-
authorities = Assigned.objects.filter(
203-
active=True,
204-
user=assignment.user_id,
205-
section=assignment.section_id,
206-
response_type=stage,
207-
).values_list("authority_id", flat=True)
208-
args.append(authorities)
209-
210-
# we pass the question list but we want to ignore it because there could be different types of council
211-
# included in assignments which throws the count off
212-
response_counts = PublicAuthority.response_counts(
213-
*args,
214-
question_types=types,
215-
response_type=assignment.response_type,
216-
ignore_question_list=True,
217-
).distinct()
218-
219-
for count in response_counts:
220-
total += 1
221-
if count.num_responses is not None and count.num_responses > 0:
222-
started += 1
223-
if count.num_responses == count.num_questions:
224-
complete += 1
225-
226-
if assignment.response_type is None:
227-
section_link = "home"
228-
elif assignment.response_type.type == "First Mark":
229-
section_link = "section_authorities"
230-
elif assignment.response_type.type == "Right of Reply":
231-
section_link = "authority_ror_authorities"
232-
elif assignment.response_type.type == "Audit":
233-
section_link = "audit_section_authorities"
234-
235-
progress.append(
236-
{
237-
"assignment": assignment,
238-
"complete": complete,
239-
"started": started,
240-
"total": total,
241-
"section_link": section_link,
242-
}
166+
if progress is None:
167+
progress = get_assignment_progress(
168+
context["assignments"],
169+
self.request.current_session.label,
170+
self.request.current_stage.type,
243171
)
244172

245173
context["sessions"] = MarkingSession.objects.filter(active=True)

0 commit comments

Comments
 (0)