-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sync grades back to the LMS for auto grading #6672
Conversation
6ed9f3b
to
c783b22
Compare
486e72b
to
1f65d03
Compare
91aa414
to
2f1ebc0
Compare
@@ -284,6 +284,10 @@ def includeme(config): # noqa: PLR0915 | |||
config.add_route( | |||
"api.dashboard.assignment", r"/api/dashboard/assignments/{assignment_id}" | |||
) | |||
config.add_route( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New API endpoint
@@ -1,8 +1,8 @@ | |||
from pyramid.httpexceptions import HTTPNotFound, HTTPUnauthorized | |||
from sqlalchemy import select | |||
|
|||
from lms.models import Assignment, Organization |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding some type annos around here to get autocompletion.
@@ -83,15 +83,32 @@ def read_result(self, grading_id) -> GradingResult: | |||
def get_score_maximum(self, resource_link_id) -> float | None: | |||
return self._read_grading_configuration(resource_link_id).get("scoreMaximum") | |||
|
|||
def sync_grade( # noqa: PLR0913 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New method to sync an individual grade.
We don't use the existing record_result
because:
-
We'd need to backport now any changes here for the LTI1.1 as the share a common interface.
-
That method is more complex as it deals with comments (that might need to be formatted before), "pre_record_hook", to change the shape of the payload...
I reckon the simple method is the best solution right now.
permission=Permissions.GRADE_ASSIGNMENT, | ||
schema=AutoGradeSyncSchema, | ||
) | ||
def auto_grading_sync(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an endpoint that would need many changes before is ready for the MVP:
- Don't start a sync if there's already one running.
- Run the actual sync of grades asynchronously on a celery task.
I also image we'll need a GET endpoint to fetch progress (state of the last one?) of any running sync.
lms/views/dashboard/api/grading.py
Outdated
) | ||
def auto_grading_sync(self): | ||
assignment = self.dashboard_service.get_request_assignment(self.request) | ||
assert assignment.lis_outcome_service_url, "Assignment wihtout grading URL" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
asserts for the type checker.
ltia_service=self.request.find_service(LTIAHTTPService), | ||
line_item_url=None, | ||
line_item_container_url=None, | ||
product_family=None, # type: ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't be None in the grading service but to refactor that we have to refactor the v11 version as well as they share an interface. I'll have to do that once we have the MVP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps it makes sense to optionally inject a grading service in the constructor, and fall back to this if not provided. That way you can still use this at runtime, but pass a mock in tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see, but views are "built" by the framework itself.
This sticks out like a shore thumb so I reckon the right fix is to refactor the service to take this as parameters in v11 and v13.
Crated a note on the main issue to keep track of that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but views are "built" by the framework itself.
Oh, I didn't think about that. Let's keep it as is for now and apply your suggested improvements afterwards.
|
||
|
||
class AutoGradeSyncSchema(JSONPyramidRequestSchema): | ||
grades = fields.List(fields.Nested(_GradeSchema), required=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The endpoint takes the grades from the frontend. I reckon this makes sense because:
- It saves the BE to doing double work of calculating the grades again.
- It guarantees that what you see in the dashboard is what we are going to send to the LMS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about this as well, and I also think it makes sense.
It would even allow to eventually let instructors adjust the pre-calculated grade before syncing, right from the dashboard, if at some point we want to allow that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should add the new endpoint to the DashboardRoutes
definition. Other than that, this looks good.
|
||
|
||
class AutoGradeSyncSchema(JSONPyramidRequestSchema): | ||
grades = fields.List(fields.Nested(_GradeSchema), required=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about this as well, and I also think it makes sense.
It would even allow to eventually let instructors adjust the pre-calculated grade before syncing, right from the dashboard, if at some point we want to allow that.
ltia_service=self.request.find_service(LTIAHTTPService), | ||
line_item_url=None, | ||
line_item_container_url=None, | ||
product_family=None, # type: ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps it makes sense to optionally inject a grading service in the constructor, and fall back to this if not provided. That way you can still use this at runtime, but pass a mock in tests.
fe680b6
to
5b10d03
Compare
This will allow us to expose the configuring and calculating side of auto grading while we work on the syncing part.
9f04f16
to
d9d3665
Compare
For:
Basic API endpoint to support the first version of the "Sync grades" button.
Testing
use a request like:
with a token from an instructor (it won't work for staff members).