Fix certificate generation without persistent grades#29792
Conversation
|
Thanks for the pull request, @dyudyunov! I've created OSPR-6408 to keep track of it in JIRA, where we prioritize reviews. Please note that it may take us up to several weeks or months to complete a review and merge your PR. Feel free to add as much of the following information to the ticket as you can:
All technical communication about the code itself will be done via the GitHub pull request interface. As a reminder, our process documentation is here. Please let us know once your PR is ready for our review and all tests are green. |
8c1cd1b to
f385591
Compare
|
@dyudyunov Thank you for your contribution. Is this ready for our review? |
|
Hi there @dyudyunov 😄 I hope you're great!! |
|
Hi @mariajgrimaldi!
As a result, you'll see the same logs in the loop ending with the max recursion depth error leading to 500 (certificate is not generated) My fix relates to the certificate generation flow only, so any functionality that depends on the course grade factory was not affected. |
|
Thank you @dyudyunov . I tried to add inline reviews but there is something stuck with me in this particular PR. I just have one suggestion: I believe we can remove the try-.catch thing in the new test method |
Hi @shadinaif |
|
Thanks @dyudyunov, this changes fixed the recursion issue I was having when my grades where enough to pass a course and I rendered the When I turned Persistent Grades on, everything kept working correctly. |
Fix for an error related to endless recursion Dev Notes: The student gets the passing grade -> CourseGradeFactory sends the `COURSE_GRADE_NOW_PASSED` signal after the grade calculation -> `listen_for_passing_grade` receives the signal and calls `generate_certificate_task` -> `generate_certificate_task` calls `_generate_regular_certificate_task` -> `_generate_regular_certificate_task` trying to get grade by calling the `_get_course_grade` -> `_get_course_grade` calls `CourseGradeFactory().read()` but I have persistent grades off, so actually that ends with `CourseGradeFactory().update()` -> `CourseGradeFactory().update()` sends the `COURSE_GRADE_NOW_PASSED` And so on
f385591 to
9f8a1cd
Compare
|
Rebased on the latest master, please, review |
9f8a1cd to
956c985
Compare
|
Hi there @dyudyunov and @natabene. I'll be reviewing this :) |
| If True - sends: | ||
| COURSE_GRADE_CHANGED signal to listeners and | ||
| COURSE_GRADE_NOW_PASSED if learner has passed course or | ||
| COURSE_GRADE_NOW_FAILED if learner is now failing course |
There was a problem hiding this comment.
Can you help me list the cases where we want to send the signals? At least in this PR
There was a problem hiding this comment.
This is probably any place where the CourseGradeFactory().read() used (because it calls the _update() if there is no persistent grade present).
Some examples of the signal receivers:
- evaluation of any milestone relationships which are attached
to the course grade; - Notifies the Credentials IDA about certain grades it needs for its records, when a grade changes;
- update minimum grade requirement status;
- generate a certificate task;
- etc
There was a problem hiding this comment.
evaluation of any milestone relationships which are attached
to the course grade;
Notifies the Credentials IDA about certain grades it needs for its records, when a grade changes;
update minimum grade requirement status;
generate a certificate task;
etc
But the whole is to not send the course grade related signals when generating the certificate task right?
There was a problem hiding this comment.
Yes, it's the main goal to break the recursive grade recalculation when the certificate is generated
| COURSE_GRADE_NOW_PASSED.send( | ||
| sender=CourseGradeFactory, | ||
| user=user, | ||
| course_id=course_data.course_key, |
There was a problem hiding this comment.
why is using send_grade_signals=False better than sending the course_grade in the signal? and removing course_grade = _get_course_grade(user, course_key, send_grade_signals=False)? 🤔
NOTE: I haven't tested this, I'm just curious if you have
There was a problem hiding this comment.
Nice question!
I didn’t try it, but now I want to 🙂
I will give it a try ASAP
There was a problem hiding this comment.
I've tried this and can say that:
- this works perfectly for certificates generated automatically
- this will not work for certificates generated manually (this case requires to call
_get_course_grade'cause there is no other way to get grades)
There was a problem hiding this comment.
And calling it will lead to sending the signal and checking the certificate status again
There was a problem hiding this comment.
Thanks for testing that out!
|
I believe this was the change that introduced this bug 🤔 |
Yes. |
mariajgrimaldi
left a comment
There was a problem hiding this comment.
This is what I've tested until now with/without persistent grades, and with/without certificates.auto_certificate_generation.
- Certificate generation through the progress page
- Using cert_generation command
- Generation through allowlist
And this it's working correctly.
I'm OK with this change since it avoids sending the grade signals when getting them in the certificate generation process, as it was before the changes from last year.
|
I'm interested to know what do you think about this approach? @felipemontoya We explored another solution besides using the flag to send the grades signals, but it didn't work in all cases. Besides, PR #28196 explicitly added getting the course grade while generating the certificate, where the crucial thing is to get the grade not to send the signals. |
felipemontoya
left a comment
There was a problem hiding this comment.
I think that the approach is solid and it looks like it's working correctly. However I think it can be simplified a little as I left in the inline comment.
If that is not the case I would be fine with merging as is.
| else: | ||
| COURSE_GRADE_NOW_FAILED.send( | ||
| sender=CourseGradeFactory, | ||
| if send_course_grade_signals: |
There was a problem hiding this comment.
It is the case, that every time that send_course_grade_signals is false, should_persist_grades is also false?
I'm thinking that if signals should not be sent when persistent grades is disabled, then we could make the if statement dependant on that variable and avoid creating a new control variable that needs to be accounted for and passed by the generation_handler.
There was a problem hiding this comment.
We should send signals 1 time regardless of the persistent grade status.
The flow is:
- user answers the question in the course;
- recalculation of course grade is starting;
- course grade now passing -> send the
COURSE_GRADE_NOW_PASSEDsignal to start certificate generation;
here we should not break the signal sending
- certificate generation process would like to receive the course grades so it calls the
course_grade_factoryagain
here we should avoid sending the signals
There was a problem hiding this comment.
This makes sense. we should not send the signal within the processing of the same signal.
Since this was the last pending thing, I'll leave my approve
|
@dyudyunov 🎉 Your pull request was merged! Please take a moment to answer a two question survey so we can improve your experience in the future. |
|
EdX Release Notice: This PR has been deployed to the staging environment in preparation for a release to production. |
|
EdX Release Notice: This PR has been deployed to the production environment. |
Description
Fix for an error related to endless recursion
Dev Notes:
The student gets the passing grade
-> CourseGradeFactory sends the
COURSE_GRADE_NOW_PASSEDsignal after the grade calculation->
listen_for_passing_gradereceives the signal and callsgenerate_certificate_task->
generate_certificate_taskcalls_generate_regular_certificate_task->
_generate_regular_certificate_tasktrying to get grade by calling the_get_course_grade->
_get_course_gradecallsCourseGradeFactory().read()but I have persistent grades off, so actually that ends withCourseGradeFactory().update()->
CourseGradeFactory().update()sends theCOURSE_GRADE_NOW_PASSEDAnd so on
Related PR to open-release/maple.master: