From 72ffb8b3a4ecd497a08fc677a574c991d02c42cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Kon=C4=8Dick=C3=BD?= <koncicky@kam.mff.cuni.cz> Date: Fri, 26 Feb 2021 17:30:19 +0100 Subject: [PATCH 1/7] Student grading: Database support --- db.ddl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/db.ddl b/db.ddl index 54ce5cf..8538518 100644 --- a/db.ddl +++ b/db.ddl @@ -22,7 +22,8 @@ CREATE TABLE owl_courses ( cid serial PRIMARY KEY, ident varchar(255) UNIQUE NOT NULL, name varchar(255) NOT NULL, - enroll_token varchar(255) UNIQUE NOT NULL + enroll_token varchar(255) UNIQUE NOT NULL, + student_grading boolean NOT NULL DEFAULT FALSE ); CREATE TABLE owl_enroll ( @@ -50,6 +51,12 @@ CREATE TABLE owl_topics ( UNIQUE(cid, ident) ); +CREATE TABLE owl_student_graders ( + tid int NOT NULL REFERENCES owl_topics(tid) ON DELETE CASCADE, + uid int NOT NULL REFERENCES owl_users(uid) ON DELETE CASCADE, + UNIQUE(tid, uid) +); + CREATE TABLE owl_posts ( pid serial PRIMARY KEY, tid int NOT NULL REFERENCES owl_topics(tid) ON DELETE CASCADE, -- GitLab From 1f711edd5f5dad03c565a8ffad7f17ded759bc2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Kon=C4=8Dick=C3=BD?= <koncicky@kam.mff.cuni.cz> Date: Fri, 26 Feb 2021 17:31:48 +0100 Subject: [PATCH 2/7] Student grading support in topics administration --- owl.py | 5 +++++ templates/admin-course.html | 1 + templates/admin-courses.html | 2 ++ templates/admin-topics.html | 4 ++++ 4 files changed, 12 insertions(+) diff --git a/owl.py b/owl.py index afee873..ea66da4 100644 --- a/owl.py +++ b/owl.py @@ -811,6 +811,11 @@ def admin_topics(cident): return render_template('admin-topics.html', topics=topics) +@app.route('/admin/topics/<cident>/graders/<tid>', methods=('GET', 'POST')) +def admin_topic_graders(cident, tid): + return "TODO" + + class EditTopicForm(FlaskForm): rank = wtforms_html5.IntegerField("Rank:", validators=[validators.required()]) ident = wtforms.StringField("Name:") diff --git a/templates/admin-course.html b/templates/admin-course.html index af06d53..3f43893 100644 --- a/templates/admin-course.html +++ b/templates/admin-course.html @@ -7,6 +7,7 @@ <tr><th>Name<td>{{ g.course.name }} <tr><th>Enroll token<td><code>{{ g.course.enroll_token }}</code> <tr><th>Teachers<td>{{ teachers|sort|join(', ') }} + <tr><th>Student grading<td>{{ 'enabled' if g.course.student_grading else 'disabled' }} </table> <div class=buttons> diff --git a/templates/admin-courses.html b/templates/admin-courses.html index 0f42fc9..682e25e 100644 --- a/templates/admin-courses.html +++ b/templates/admin-courses.html @@ -7,6 +7,7 @@ <th>Ident <th>Name <th>Teachers + <th>Student grading </thead> {% for c in courses %} <tr> @@ -17,6 +18,7 @@ {% else %} <td>– {% endif %} + <td>{{ 'enabled' if c.student_grading else 'disabled' }} {% endfor %} </table> diff --git a/templates/admin-topics.html b/templates/admin-topics.html index 700891f..01ff0ff 100644 --- a/templates/admin-topics.html +++ b/templates/admin-topics.html @@ -5,6 +5,7 @@ <table class=topics> <tr><th>Rank<th>Name<th>Title<th>Type<th>Deadline<th>Points + {% if g.course.student_grading %}<th>Student graders{% endif %} {% for t in topics %} {% if t.public %} {% set cls = "told" %} @@ -22,6 +23,9 @@ <td>{{ {'H': 'heading', 'T': 'task', 'D': 'discuss' }.get(t.type, t.type) }} <td>{{ t.deadline|reltimeformat }} <td class=pts>{{ t.max_points if t.max_points != None else "" }} + {% if g.course.student_grading and t.type == 'T' %} + <td><a href='{{ url_for('admin_topic_graders', cident=g.course.ident, tid=t.tid) }}'>Assign</a> + {% endif %} {% endfor %} </table> -- GitLab From 403235bfbf95260092253e032a008f8fa4367913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Kon=C4=8Dick=C3=BD?= <koncicky@kam.mff.cuni.cz> Date: Fri, 26 Feb 2021 18:11:43 +0100 Subject: [PATCH 3/7] Student grader now acts as a new grade role and sees grade action --- owl.py | 57 +++++++++++++++++++++++++++++++++------- templates/course.html | 6 +++++ templates/edit-post.html | 2 +- templates/topic.html | 4 +-- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/owl.py b/owl.py index ea66da4..8a9450e 100644 --- a/owl.py +++ b/owl.py @@ -236,6 +236,12 @@ def topic_init(cident, tident): if not g.topic.public and not g.is_teacher and not g.is_admin: return "This is a private topic", HTTPStatus.FORBIDDEN + if g.course.student_grading: + db_query("SELECT * FROM owl_student_graders WHERE tid=%s AND uid=%s", (g.topic.tid, g.uid)); + g.is_grader = g.is_teacher or bool(db.fetchone()) + else: + g.is_grader = g.is_teacher + return None @@ -264,7 +270,14 @@ def course_index(cident): else: target_uid = g.uid - db_query(""" + if g.course.student_grading: + sql_is_grader = "EXISTS (SELECT * FROM owl_student_graders x WHERE x.tid=t.tid AND x.uid=%s)" + sql_is_grader_params = [g.uid] + else: + sql_is_grader = "FALSE" + sql_is_grader_params = [] + + db_query(f""" SELECT t.cid, t.tid, t.ident, t.title, t.type, t.deadline, t.max_points, s.seen AS last_seen, (SELECT MAX(x.created) @@ -279,7 +292,8 @@ def course_index(cident): AND x.points IS NOT NULL ORDER BY x.created DESC LIMIT 1 - ) AS points + ) AS points, + {sql_is_grader} AS is_grader FROM owl_topics t LEFT JOIN owl_seen s ON s.tid = t.tid AND s.observer_uid = %s @@ -287,22 +301,26 @@ def course_index(cident): WHERE t.cid = %s AND t.public = true ORDER by rank, created - """, (g.uid, g.uid, g.uid, target_uid, g.course.cid)) + """, [g.uid, g.uid] + sql_is_grader_params + [g.uid, target_uid, g.course.cid]) topics = db.fetchall() course_total = Decimal('0.00') course_max = Decimal('0.00') + is_grader = False for t in topics: if t.points is not None: course_total += t.points if t.max_points is not None: course_max += t.max_points + if t.is_grader: + is_grader = True return render_template( 'course.html', topics=topics, course_total=course_total, - course_max=course_max) + course_max=course_max, + is_grader=is_grader) ### Posts ### @@ -359,8 +377,8 @@ def topic_index(cident, tident, student_uid=None): return err if student_uid is not None: - if not g.is_teacher: - return "Only teachers are allowed to do that", HTTPStatus.FORBIDDEN + if not g.is_grader: + return "Only graders are allowed to do that", HTTPStatus.FORBIDDEN else: if g.topic.type == 'D': # In discussion threads, all posts are global @@ -426,7 +444,7 @@ def topic_post(form, student_uid): app.logger.info("Uploaded file: id=%s by_uid=%d", id, g.uid) points = None - if g.is_teacher and form.points.data is not None: + if g.is_grader and form.points.data is not None: points = form.points.data if comment or attach or points is not None: @@ -472,6 +490,8 @@ def topic_post(form, student_uid): ) + "#p" + str(new_post.pid)) elif g.is_teacher: return redirect(url_for('teacher')) + elif g.is_grader: + return redirect(url_for('topic_student_grade', cident=g.course.ident, tident=g.topic.ident)) else: return redirect(url_for('course_index', cident=g.course.ident)) @@ -493,6 +513,15 @@ def edit_allowed_p(post): post.author_uid == g.uid and g.topic.public) +@app.route('/c/<cident>/<tident>/grade') +def topic_student_grade(cident=None, tident=None): + err = topic_init(cident, tident) + if err: + return err + + return "TODO" + + @app.route('/post/<cident>/<int:pid>/', methods=('GET', 'POST')) def edit_post(cident, pid): err = course_init(cident) @@ -508,6 +537,12 @@ def edit_post(cident, pid): g.topic = db.fetchone() assert g.topic + if g.course.student_grading: + db_query("SELECT * FROM owl_student_graders WHERE tid=%s AND uid=%s", (g.topic.tid, g.uid)); + g.is_grader = g.is_teacher or bool(db.fetchone()) + else: + g.is_grader = g.is_teacher + if not edit_allowed_p(post): return "Not allowed to edit this post", HTTPStatus.FORBIDDEN @@ -516,7 +551,7 @@ def edit_post(cident, pid): if not form.validate_on_submit(): return render_template('edit-post.html', form=form) - if g.is_teacher and post.target_uid >= 0: + if g.is_grader and post.target_uid >= 0: student_uid = post.target_uid else: student_uid = None @@ -529,15 +564,17 @@ def edit_post(cident, pid): db_connection.commit() return redirect(return_to) cmt = "*Post deleted by its author.*" + points = None else: cmt = strip_comment(form.comment.data) + points = form.points.data changed = False if cmt != post.comment: db_query("UPDATE owl_posts SET comment=%s WHERE pid=%s", (cmt, pid)) changed = True - if g.is_teacher and form.points.data != post.points: - db_query("UPDATE owl_posts SET points=%s WHERE pid=%s", (form.points.data, pid)) + if g.is_grader and points != post.points: + db_query("UPDATE owl_posts SET points=%s WHERE pid=%s", (points, pid)) changed = True if changed: diff --git a/templates/course.html b/templates/course.html index 661cc22..7e27899 100644 --- a/templates/course.html +++ b/templates/course.html @@ -23,6 +23,7 @@ {% set ns.have_table = True %} <table class=topics> <tr><th>Topic<th>Deadline<th>Points<th>Max + {% if is_grader %}<th>Action{% endif %} {% endif %} {% if t.last_posted != None and (t.last_seen == None or t.last_posted > t.last_seen) %} {% set cls = 'tnew' %} @@ -33,6 +34,11 @@ <td>{{ t.deadline|reltimeformat }} <td class=pts>{{ t.points if t.points != None else "" }} <td class=pts>{{ t.max_points if t.max_points != None else "" }} + {% if is_grader %} + <td>{% if t.is_grader %} + <a href="{{ url_for('topic_student_grade', cident=g.course.ident, tident=t.ident) }}">Grade</a> + {% endif %} + {% endif %} {% else %} {% if ns.have_table == True %} {% set ns.have_table = False %} diff --git a/templates/edit-post.html b/templates/edit-post.html index d9bdcde..0d83781 100644 --- a/templates/edit-post.html +++ b/templates/edit-post.html @@ -9,7 +9,7 @@ <p class=error>{{ form.errors['comment'][0] }} {% endif %} <p>{{ form.comment(rows=16, cols=80) }} - {% if g.is_teacher %} + {% if g.is_grader %} <p>{{ form.points.label }} {{ form.points(size=6) }} / {{ g.topic.max_points or "?" }} {% endif %} <div id=previewbox> diff --git a/templates/topic.html b/templates/topic.html index 3bd47e1..1d13109 100644 --- a/templates/topic.html +++ b/templates/topic.html @@ -43,7 +43,7 @@ {% set when=p.created %} {% endif %} {{ when|reltimeformat }} - {% if g.is_teacher and p.author_uid == student_uid and g.topic.deadline != None and when > g.topic.deadline %} + {% if g.is_grader and p.author_uid == student_uid and g.topic.deadline != None and when > g.topic.deadline %} <span class=phpast>(after deadline)</span> {% endif %} {% if g.is_teacher or p.author_uid == g.uid %} @@ -85,7 +85,7 @@ <p class=error>{{ form.errors['attachment'][0] }} {% endif %} <p>{{ form.attachment.label }} {{ form.attachment() }} - {% if g.is_teacher %} + {% if g.is_grader %} <p>{{ form.points.label }} {{ form.points(size=6) }} / <button id=maxbutton type=button onclick="javascript:document.getElementById('points').value = document.getElementById('maxbutton').innerText">{{ g.topic.max_points or "?" }}</button> {% endif %} <div id=previewbox> -- GitLab From 2913991cba20e9f0ba63e4fb1f4740b51b13ba08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Kon=C4=8Dick=C3=BD?= <koncicky@kam.mff.cuni.cz> Date: Fri, 5 Mar 2021 19:57:58 +0100 Subject: [PATCH 4/7] Page to grade a topic --- owl.py | 54 +++++++++++++++++++++++++++++++++++++- templates/topic-grade.html | 39 +++++++++++++++++++++++++++ templates/topic.html | 2 ++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 templates/topic-grade.html diff --git a/owl.py b/owl.py index 8a9450e..8d8c577 100644 --- a/owl.py +++ b/owl.py @@ -519,7 +519,59 @@ def topic_student_grade(cident=None, tident=None): if err: return err - return "TODO" + if not g.is_grader: + return "Only graders can grade", HTTPStatus.FORBIDDEN + + db_query(""" + SELECT s.uid, s.full_name + FROM owl_enroll e + JOIN owl_users s ON s.uid = e.uid + WHERE e.cid = %s + AND e.is_teacher = false + ORDER BY s.full_name + """, (g.course.cid,)) + + students = {} + for s in db.fetchall(): + students[s.uid] = s + + solutions = {} + for uid, s in students.items(): + solutions[uid] = TSolution( + points=None, + last_activity=None, + last_seen_by_me=None, + last_seen_by_teacher=None, + ) + + db_query(""" + SELECT p.target_uid AS target_uid, p.created AS post_created, p.points + FROM owl_posts p + JOIN owl_users s ON s.uid = p.target_uid + WHERE p.tid = %s + AND p.target_uid >= 0 + ORDER BY p.target_uid, p.created, p.pid + """, (g.topic.tid,)) + + for p in db.fetchall(): + s = solutions[p.target_uid] + if p.points is not None: + s.points = p.points + s.last_activity = p.post_created + + db_query(""" + SELECT s.target_uid, s.seen + FROM owl_seen s + WHERE s.tid = %s + AND s.observer_uid = %s + """, (g.topic.tid, g.uid)) + + for t in db.fetchall(): + if t.target_uid in solutions: + s = solutions[t.target_uid] + s.last_seen_by_me = t.seen + + return render_template('topic-grade.html', students=students, solutions=solutions) @app.route('/post/<cident>/<int:pid>/', methods=('GET', 'POST')) diff --git a/templates/topic-grade.html b/templates/topic-grade.html new file mode 100644 index 0000000..6f66481 --- /dev/null +++ b/templates/topic-grade.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} +{% block body %} + {% set c=g.course %} + {% set t=g.topic %} + <h2>{{c.name}}</h2> + + <p><a href='{{ url_for('course_index', cident=c.ident) }}'>Return to course</a> + + <h3>Grade {{t.title}}</h3> + <table class=results> + <tr><th class='tbeforehdr'>Full name + <th>Points + <tr><th class='tbeforehdr'>max. + <td class='pts'>{{ t.max_points if t.max_points != None else "" }} + {% for s in students.values() %} + <tr><td class='tbeforehdr'>{{ s.full_name }} + {% set sol=solutions[s.uid] %} + {% set cls=[] %} + {% if sol.last_activity == None %} + {% do cls.append("snull") %} + <td class='{{ cls|join(" ") }}'> + {% else %} + {% do cls.append("pts") %} + {% if sol.last_seen_by_me == None or sol.last_seen_by_me < sol.last_activity %} + {% do cls.append("snew") %} + {% else %} + {% do cls.append("sold") %} + {% endif %} + <td class="{{ cls|join(" ") }}"><a href='{{ url_for('topic_index', cident=c.ident, tident=t.ident, student_uid=s.uid) }}'> + {% if sol.points != None %} + {{ sol.points }} + {% else %} + ??? + {% endif %} + </a> + {% endif %} + {% endfor %} + </table> +{% endblock %} diff --git a/templates/topic.html b/templates/topic.html index 1d13109..711b5fd 100644 --- a/templates/topic.html +++ b/templates/topic.html @@ -10,6 +10,8 @@ <p><a href='{{ url_for('course_index', cident=g.course.ident) }}'>Back to the course…</a> {% if g.is_teacher %} <a href='{{ url_for('teacher') }}'>Teacher's summary</a> + {% elif g.is_grader %} + <a href='{{ url_for('topic_student_grade', cident=g.course.ident, tident=g.topic.ident) }}'>Grade…</a> {% endif %} <h3>{{ g.topic.title }}{% if not g.topic.public %} [private]{% endif %}</h3> -- GitLab From 8740999239c94b3a73911f25ea4f71000d5098ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Kon=C4=8Dick=C3=BD?= <koncicky@kam.mff.cuni.cz> Date: Fri, 5 Mar 2021 19:58:46 +0100 Subject: [PATCH 5/7] Student grading assignment page for topic --- owl.py | 63 +++++++++++++++++++++++++++++- templates/admin-topic-graders.html | 32 +++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 templates/admin-topic-graders.html diff --git a/owl.py b/owl.py index 8d8c577..5792072 100644 --- a/owl.py +++ b/owl.py @@ -899,10 +899,71 @@ def admin_topics(cident): return render_template('admin-topics.html', topics=topics) +class AssignGraderForm(FlaskForm): + uid = wtforms.HiddenField() + assign = wtforms.SubmitField() + unassign = wtforms.SubmitField() + + def validate(self): + if self.assign.data and self.unassign.data: + return False + return True + @app.route('/admin/topics/<cident>/graders/<tid>', methods=('GET', 'POST')) def admin_topic_graders(cident, tid): - return "TODO" + err = course_init(cident) or must_be_teacher() + if err: + return err + + if not g.course.student_grading: + return "Course does not allow student grading", HTTPStatus.FORBIDDEN + + if tid is None: + return "Topic needed", HTTPStatus.NOT_FOUND + + db_query('SELECT * FROM owl_topics WHERE cid=%s AND tid=%s', (g.course.cid, tid)) + topic = db.fetchone() + if not topic: + return "No such topic", HTTPStatus.NOT_FOUND + + if request.method == 'POST': + form = AssignGraderForm() + + db_query('SELECT * FROM owl_enroll WHERE uid=%s AND cid=%s AND is_teacher=FALSE', (form.uid.data, g.course.cid)) + if not db.fetchone(): + return "Given uid is not a student of this course", HTTPStatus.BAD_REQUEST + + db_query('SELECT * FROM owl_student_graders WHERE tid=%s AND uid=%s', (topic.tid, form.uid.data)) + is_grader = bool(db.fetchone()) + + if form.assign.data: + if is_grader: + return "This student is already assigned", HTTPStatus.BAD_REQUEST + db_query('INSERT INTO owl_student_graders (tid, uid) VALUES (%s, %s)', (topic.tid, form.uid.data)) + else: + if not is_grader: + return "This student is already not assigned", HTTPStatus.BAD_REQUEST + db_query('DELETE FROM owl_student_graders WHERE tid=%s AND uid=%s', (topic.tid, form.uid.data)) + + db_connection.commit() + return redirect(url_for('admin_topic_graders', cident=cident, tid=tid)) + else: + db_query(""" + SELECT s.uid, s.full_name, + EXISTS (SELECT * FROM owl_student_graders x WHERE x.tid=%s AND x.uid=s.uid) AS is_grader + FROM owl_enroll e + JOIN owl_users s ON s.uid = e.uid + WHERE e.cid = %s + AND e.is_teacher = false + ORDER BY s.full_name + """, (topic.tid, g.course.cid)) + students = [(s, AssignGraderForm(uid=s.uid)) for s in db.fetchall()] + + s_unassigned = [s for s in students if not s[0].is_grader] + s_assigned = [s for s in students if s[0].is_grader] + + return render_template('admin-topic-graders.html', s_unassigned=s_unassigned, s_assigned=s_assigned, topic=topic) class EditTopicForm(FlaskForm): diff --git a/templates/admin-topic-graders.html b/templates/admin-topic-graders.html new file mode 100644 index 0000000..11c717e --- /dev/null +++ b/templates/admin-topic-graders.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} +{% block body %} + <h2>Student graders of {{ topic.title }}</h2> + + <div class="buttons"> + <span class="button"><a href="{{url_for('admin_topics', cident=g.course.ident)}}">Back</a></span> + </div> + + <h3>Assigned graders</h3> + <ul> + {% for s, form in s_assigned %} + <li><form method="POST" action=""> + {{ form.csrf_token }} + {{ form.uid }} + <b>{{ s.full_name }}</b>: + {{ form.unassign }} + </form></li> + {% endfor %} + </ul> + + <h3>Unassigned students</h3> + <ul> + {% for s, form in s_unassigned %} + <li><form method="POST" action=""> + {{ form.csrf_token }} + {{ form.uid }} + <b>{{ s.full_name }}</b>: + {{ form.assign }} + </form></li> + {% endfor %} + </ul> +{% endblock %} -- GitLab From edbb69e42928127fe74fa484a1ba49d9fc095189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Kon=C4=8Dick=C3=BD?= <koncicky@kam.mff.cuni.cz> Date: Tue, 2 Mar 2021 23:24:23 +0100 Subject: [PATCH 6/7] Links converted to buttons --- templates/topic-grade.html | 4 +++- templates/topic.html | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/templates/topic-grade.html b/templates/topic-grade.html index 6f66481..83ec78f 100644 --- a/templates/topic-grade.html +++ b/templates/topic-grade.html @@ -4,7 +4,9 @@ {% set t=g.topic %} <h2>{{c.name}}</h2> - <p><a href='{{ url_for('course_index', cident=c.ident) }}'>Return to course</a> + <div class="buttons"> + <span class="button"><a href='{{ url_for('course_index', cident=c.ident) }}'>Return to course</a></span> + </div> <h3>Grade {{t.title}}</h3> <table class=results> diff --git a/templates/topic.html b/templates/topic.html index 711b5fd..d32965d 100644 --- a/templates/topic.html +++ b/templates/topic.html @@ -7,12 +7,14 @@ {% endif %} <h2>{{ g.course.name }}</h2> - <p><a href='{{ url_for('course_index', cident=g.course.ident) }}'>Back to the course…</a> + <div class="buttons"> + <span class="button"><a href='{{ url_for('course_index', cident=g.course.ident) }}'>Back to the course</a></span> {% if g.is_teacher %} - <a href='{{ url_for('teacher') }}'>Teacher's summary</a> + <span class="button"><a href='{{ url_for('teacher') }}'>Teacher's summary</a></span> {% elif g.is_grader %} - <a href='{{ url_for('topic_student_grade', cident=g.course.ident, tident=g.topic.ident) }}'>Grade…</a> + <span class="button"><a href='{{ url_for('topic_student_grade', cident=g.course.ident, tident=g.topic.ident) }}'>Grade</a></span> {% endif %} + </div> <h3>{{ g.topic.title }}{% if not g.topic.public %} [private]{% endif %}</h3> -- GitLab From b6c93aae1c9ee1312eecb5a89cabf5c870b7e16d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Kon=C4=8Dick=C3=BD?= <koncicky@kam.mff.cuni.cz> Date: Fri, 5 Mar 2021 20:27:22 +0100 Subject: [PATCH 7/7] Student graders also receive notifications --- owl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/owl.py b/owl.py index 5792072..6a3d277 100644 --- a/owl.py +++ b/owl.py @@ -1472,10 +1472,10 @@ def send_notify_post(post): FROM owl_enroll e JOIN owl_users u ON u.uid = e.uid WHERE e.cid = %s - AND (%s < 0 OR e.uid = %s OR e.is_teacher = true) + AND (%s < 0 OR e.uid = %s OR e.is_teacher = true OR e.uid IN (SELECT uid FROM owl_student_graders WHERE tid=%s)) AND u.notify = true AND u.email IS NOT NULL - """, (topic.cid, post.target_uid, post.target_uid)) + """, (topic.cid, post.target_uid, post.target_uid, topic.tid)) dests = db.fetchall() for dest in dests: -- GitLab