Skip to content

Commit 0f6d636

Browse files
committed
chore: make function get_latest and get_best_submissions reusable for both student and instructor version
#250
1 parent 8c16002 commit 0f6d636

File tree

2 files changed

+72
-117
lines changed

2 files changed

+72
-117
lines changed

grader_service/handlers/base_handler.py

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151

5252
auth_header_pat = re.compile(r'^(token|bearer|basic)\s+([^\s]+)$', flags=re.IGNORECASE)
5353

54+
5455
def check_authorization(self: "GraderBaseHandler", scopes: list[Scope], lecture_id: Union[int, None]) -> bool:
5556
if (("/permissions" in self.request.path)
5657
or ("/config" in self.request.path)):
@@ -112,6 +113,7 @@ async def request_handler_wrapper(self: "GraderBaseHandler", *args,
112113

113114
return wrapper
114115

116+
115117
class BaseHandler(web.RequestHandler):
116118
"""Base class of all handler classes
117119
@@ -136,13 +138,13 @@ def __init__(
136138
self.log = self.application.log
137139

138140
async def prepare(self) -> Optional[Awaitable[None]]:
139-
#strip trailing slash
141+
# strip trailing slash
140142
self.request.path = self.request.path.rstrip("/")
141-
142-
#start session
143+
144+
# start session
143145
self.session: Session = self.application.session_maker()
144-
145-
#authenticate
146+
147+
# authenticate
146148
try:
147149
await self.get_current_user()
148150

@@ -154,25 +156,27 @@ async def prepare(self) -> Optional[Awaitable[None]]:
154156
url_path_join(self.application.base_url, "/api/oauth2/token"),
155157
url_path_join(self.application.base_url, "/oauth_callback"),
156158
url_path_join(self.application.base_url, "/lti13/oauth_callback")
157-
]:
159+
]:
158160
# require git to authenticate with token -> otherwise return 401 code
159161
if self.request.path.startswith(url_path_join(self.application.base_url, "/git")):
160162
raise HTTPError(401, reason="Git: authenticate request")
161-
163+
162164
# send to login page if ui page request
163-
if self.request.path in [url_path_join(self.application.base_url, "/api/oauth2/authorize")] or self.request.path.startswith(url_path_join(self.application.base_url, "/ui")):
165+
if self.request.path in [
166+
url_path_join(self.application.base_url, "/api/oauth2/authorize")] or self.request.path.startswith(
167+
url_path_join(self.application.base_url, "/ui")):
164168
url = url_concat(self.settings["login_url"], dict(next=self.request.uri))
165169
self.redirect(url)
166170
return
167-
171+
168172
if self.request.headers.get("Authorization") is None:
169173
raise HTTPError(401, reason="No API token in auth header")
170-
174+
171175
# do not redirect to login page if we hit api endpoints
172176
raise HTTPError(401, reason="API Token is invalid or expired.")
173-
174-
175-
177+
178+
179+
176180
except Exception as e:
177181
# ensure get_current_user is never called again for this handler,
178182
# since it failed
@@ -775,7 +779,7 @@ def append_query_parameters(self, url, exclude=None):
775779

776780

777781
class GraderBaseHandler(BaseHandler):
778-
782+
779783
def validate_parameters(self, *args):
780784
if len(self.request.arguments) == 0:
781785
return
@@ -816,56 +820,65 @@ def get_submission(self, lecture_id: int, assignment_id: int,
816820
raise HTTPError(HTTPStatus.NOT_FOUND,
817821
reason=msg)
818822
return submission
819-
820-
def get_latest_submissions(self, assignment_id, must_have_feedback = False):
823+
824+
def get_latest_submissions(self, assignment_id, must_have_feedback=False, username=None):
821825
subquery = (
822826
self.session.query(Submission.username,
823-
func.max(Submission.date).label(
824-
"max_date"))
827+
func.max(Submission.date).label("max_date"))
825828
.filter(Submission.assignid == assignment_id)
826829
.filter(Submission.deleted == DeleteState.active)
827830
.group_by(Submission.username)
828831
.subquery())
832+
829833
if must_have_feedback:
830834
subquery = subquery.filter(Submission.feedback_status != "not_generated")
831835

832-
# build the main query
836+
if username:
837+
subquery = subquery.filter(Submission.username == username)
838+
839+
# Build the main query
833840
submissions = (
834841
self.session.query(Submission)
835842
.join(subquery,
836-
(Submission.username == subquery.c.username) & (
837-
Submission.date == subquery.c.max_date) & (
838-
Submission.assignid == assignment_id) & (
839-
Submission.deleted == DeleteState.active
840-
))
843+
(Submission.username == subquery.c.username) & (
844+
Submission.date == subquery.c.max_date) & (
845+
Submission.assignid == assignment_id) & (
846+
Submission.deleted == DeleteState.active
847+
))
841848
.order_by(Submission.id)
842-
.all())
849+
.all()
850+
)
851+
843852
return submissions
844-
845-
def get_best_submissions(self, assignment_id, must_have_feedback = False):
846-
# build the subquery
847-
subquery = (self.session.query(Submission.username, func.max(
848-
Submission.score).label("max_score"))
849-
.filter(Submission.assignid == assignment_id)
850-
.filter(Submission.deleted == DeleteState.active)
851-
.group_by(Submission.username)
852-
.subquery())
853-
853+
854+
def get_best_submissions(self, assignment_id, must_have_feedback=False, username=None):
855+
subquery = (
856+
self.session.query(Submission.username,
857+
func.max(Submission.score).label("max_score"))
858+
.filter(Submission.assignid == assignment_id)
859+
.filter(Submission.deleted == DeleteState.active)
860+
.group_by(Submission.username)
861+
.subquery())
862+
854863
if must_have_feedback:
855864
subquery = subquery.filter(Submission.feedback_status != "not_generated")
856865

857-
# build the main query
866+
if username:
867+
subquery = subquery.filter(Submission.username == username)
868+
869+
# Build the main query
858870
submissions = (
859871
self.session.query(Submission)
860872
.join(subquery,
861-
(Submission.username == subquery.c.username) & (
862-
Submission.score == subquery.c.max_score) & (
863-
Submission.assignid == assignment_id) & (
864-
Submission.deleted == DeleteState.active
865-
))
873+
(Submission.username == subquery.c.username) & (
874+
Submission.score == subquery.c.max_score) & (
875+
Submission.assignid == assignment_id) & (
876+
Submission.deleted == DeleteState.active
877+
))
866878
.group_by(Submission.username)
867879
.order_by(Submission.id)
868-
.all())
880+
.all()
881+
)
869882
return submissions
870883

871884
@property

grader_service/handlers/submissions.py

Lines changed: 17 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from sqlalchemy import label
1111

12+
from grader_service.api.models import Lecture
1213
from grader_service.orm.base import DeleteState
1314
import isodate
1415
import os.path
@@ -173,83 +174,24 @@ async def get(self, lecture_id: int, assignment_id: int):
173174
role: Role = self.get_role(lecture_id)
174175
if instr_version and role.role < Scope.tutor:
175176
raise HTTPError(HTTPStatus.FORBIDDEN, reason="Forbidden")
176-
assignment = self.get_assignment(lecture_id, assignment_id)
177177

178-
if instr_version:
179-
if submission_filter == 'latest':
180-
submissions = self.get_latest_submissions(assignment_id)
181-
elif submission_filter == 'best':
182-
submissions = self.get_best_submissions(assignment_id)
183-
else:
184-
submissions = [
185-
s for s in assignment.submissions if (s.deleted
186-
== DeleteState.active)
187-
]
178+
username = None if instr_version else role.username
179+
if submission_filter == "latest":
180+
submissions = self.get_latest_submissions(
181+
assignment_id, username=username)
182+
elif submission_filter == "best":
183+
submissions = self.get_best_submissions(
184+
assignment_id, username=username)
188185
else:
189-
if submission_filter == 'latest':
190-
# build the subquery
191-
subquery = (self.session.query(Submission.username,
192-
func.max(Submission.date).label(
193-
"max_date"))
194-
.filter(Submission.assignid == assignment_id)
195-
.filter(Submission.deleted == DeleteState.active)
196-
.group_by(Submission.username)
197-
.subquery())
198-
199-
# build the main query
200-
submissions = (
201-
self.session.query(Submission)
202-
.join(subquery,
203-
(Submission.username == subquery.c.username) & (
204-
Submission.date == subquery.c.max_date) & (
205-
Submission.assignid == assignment_id) & (
206-
Submission.deleted == DeleteState.active
207-
))
208-
.filter(
209-
Submission.assignid == assignment_id,
210-
Submission.username == role.username, )
211-
.order_by(Submission.id)
212-
.all())
213-
214-
elif submission_filter == 'best':
215-
216-
# build the subquery
217-
subquery = (self.session.query(Submission.username, func.max(
218-
Submission.score).label("max_score"))
219-
.filter(Submission.assignid == assignment_id)
220-
.filter(Submission.deleted == DeleteState.active)
221-
.group_by(Submission.username)
222-
.subquery())
223-
224-
# build the main query
225-
submissions = (
226-
self.session.query(Submission)
227-
.join(subquery,
228-
(Submission.username == subquery.c.username) & (
229-
Submission.score == subquery.c.max_score) & (
230-
Submission.assignid == assignment_id) & (
231-
Submission.deleted == DeleteState.active
232-
))
233-
.filter(
234-
Submission.assignid == assignment_id,
235-
Submission.username == role.username, )
236-
.order_by(Submission.id)
237-
.all())
238-
else:
239-
submissions = (
240-
self.session.query(
241-
Submission
242-
)
243-
.filter(
244-
Submission.assignid == assignment_id,
245-
Submission.username == role.username,
246-
Submission.deleted == DeleteState.active
247-
)
248-
.order_by(Submission.id)
249-
.all()
250-
)
186+
query = self.session.query(Submission).filter(
187+
Submission.assignid == assignment_id,
188+
Submission.deleted == DeleteState.active
189+
)
190+
if username:
191+
query = query.filter(Submission.username == username)
192+
submissions = query.order_by(Submission.id).all()
193+
251194
if response_format == "csv":
252-
# csv format does not include logs
253195
self.set_header("Content-Type", "text/csv")
254196
for i, s in enumerate(submissions):
255197
d = s.model.to_dict()
@@ -263,7 +205,7 @@ async def get(self, lecture_id: int, assignment_id: int):
263205
submissions = remove_points_from_submission(submissions)
264206

265207
self.write_json(submissions)
266-
self.session.close() # manually close here because on_finish overwrite
208+
self.session.close()
267209

268210
@authorize([Scope.student, Scope.tutor, Scope.instructor])
269211
async def post(self, lecture_id: int, assignment_id: int):

0 commit comments

Comments
 (0)