Commit 13840363 authored by Martin Mareš's avatar Martin Mareš
Browse files

Improved copying of topics

First, the topic is copied together with all posts visible to all
students.

Second, it is now possible to copy to a different course (limited to
the current semester).
parent d10132a3
......@@ -1137,6 +1137,7 @@ class EditTopicForm(FlaskForm):
notify = wtforms.BooleanField("Notify now:")
submit = wtforms.SubmitField("Save")
delete = wtforms.SubmitField("☠ Delete topic ☠")
copy_to = wtforms.RadioField("Copy to:", choices=[], coerce=int)
def validate(self):
ret = FlaskForm.validate(self)
......@@ -1162,27 +1163,39 @@ def admin_edit_topic(sident, cident, tid=None, copy_tid=None):
if err:
return err
# XXX: We have not checked that the tid belongs to the specified course
# where the current user has edit rights. So we must check it in DB operations.
if request.method == 'POST':
form = EditTopicForm()
if copy_tid is None:
del form.copy_to
else:
form.copy_to.choices = course_choices(g.course.cid)
if not form.validate_on_submit():
return render_template('admin-edit-topic.html', form=form, tid=None, copy_tid=None, post_count=None)
if form.delete.data and tid is not None:
app.logger.info(f"Deleted topic: tid={tid} by_uid={g.uid}")
db_query("DELETE FROM owl_topics WHERE tid=%s", (tid,))
db_connection.commit()
flash('Topic deleted.', 'info')
return redirect(url_for('admin_topics', sident=sident, cident=cident))
if tid is None:
db_query("INSERT INTO owl_topics(cid, type) VALUES(%s, 'H') RETURNING tid", (g.course.cid,))
assert form.copy_to.data is not None
db_query("INSERT INTO owl_topics(cid, type) VALUES(%s, 'H') RETURNING *", (form.copy_to.data,))
row = db.fetchone()
assert row
cid = row.cid
tid = row.tid
app.logger.info(f"Created topic: tid={tid} by_uid={g.uid}")
app.logger.info(f"Created topic: cid={cid} tid={tid} by_uid={g.uid}")
else:
# XXX: We have not checked that the tid belongs to the specified course
# where the current user has edit rights. So we must check it here.
cid = g.course.cid
db_query('SELECT ident FROM owl_topics WHERE cid=%s AND tid=%s', (cid, tid))
if not db.fetchone():
return "This topic does not belong to the course", HTTPStatus.NOT_FOUND
if form.delete.data:
app.logger.info(f"Deleted topic: tid={tid} by_uid={g.uid}")
db_query("DELETE FROM owl_topics WHERE tid=%s", (tid,))
db_connection.commit()
flash('Topic deleted.', 'info')
return redirect(url_for('admin_topics', sident=sident, cident=cident))
deadline = form.deadline.data
if deadline is not None:
......@@ -1198,7 +1211,7 @@ def admin_edit_topic(sident, cident, tid=None, copy_tid=None):
deadline=%s,
type=%s,
max_points=%s
WHERE cid=%s AND tid=%s
WHERE tid=%s
""", (
form.ident.data if form.ident.data != "" else None,
form.rank.data,
......@@ -1207,11 +1220,21 @@ def admin_edit_topic(sident, cident, tid=None, copy_tid=None):
deadline,
form.type.data,
form.max_points.data,
g.course.cid,
tid,
)
)
if copy_tid is not None:
# Copy all posts
app.logger.info(f"Cloning posts: from_tid={copy_tid} to_tid={tid} by_uid={g.uid}")
db_query("""
INSERT INTO owl_posts(tid, target_uid, author_uid, comment, attachment)
(SELECT %s, target_uid, %s, comment, attachment
FROM owl_posts
WHERE tid=%s AND target_uid=-1
)
""", (tid, g.uid, copy_tid))
db_connection.commit()
if form.notify.data:
......@@ -1244,11 +1267,40 @@ def admin_edit_topic(sident, cident, tid=None, copy_tid=None):
post_count = count_row.cnt
form = EditTopicForm(obj=topic)
if copy_tid is None:
del form.copy_to
else:
form.copy_to.choices = course_choices(g.course.cid)
form.copy_to.data = g.course.cid
if topic.deadline:
form.deadline.data = topic.deadline.replace(tzinfo=dateutil.tz.UTC).astimezone()
return render_template('admin-edit-topic.html', form=form, tid=tid, copy_tid=copy_tid, post_count=post_count)
def course_choices(include_tid):
db_query("SELECT * FROM owl_semesters ORDER BY rank DESC LIMIT 1")
semester_row = db.fetchone()
if not semester_row:
return []
db_query("""
SELECT c.cid, s.ident AS sident, c.ident AS cident, c.name
FROM owl_courses c
JOIN owl_semesters s USING(semid)
JOIN owl_enroll e USING(cid)
WHERE (c.semid=%s OR c.cid=%s) AND e.uid=%s AND e.is_teacher=true
ORDER BY s.rank DESC, c.ident
""", (semester_row.semid, include_tid, g.uid))
choices = []
for c in db.fetchall():
choices.append((c.cid, f'{c.name} ({c.sident}/{c.cident})'))
return choices
@app.route('/admin/course/<sident>/<cident>/')
def admin_course(sident, cident):
err = course_init(sident, cident) or must_be_teacher()
......
......@@ -344,3 +344,9 @@ button:hover, input[type='submit']:hover {
.danger:hover, input[type='submit'].danger:hover {
background-color: #f2dede;
}
/* Choice of course to copy to */
#copy_to {
list-style-type: none;
}
......@@ -24,6 +24,10 @@
{{ field('public', 'topic is visible to students') }}
{{ field('notify', 'send notifications on all posts now') }}
</table>
{% if form.copy_to %}
<h3>Copy to course</h3>
{{ form.copy_to }}
{% endif %}
<p class=buttons>{{ form.submit(class='ok') }}
{% if tid != None %}
<a class=button href='{{ url_for('admin_edit_topic', sident=g.course.sident, cident=g.course.ident, copy_tid=tid) }}'>Copy</a>
......
......@@ -3,7 +3,7 @@
<head>
<title>The Postal Owl</title>
<link rel="icon" type="image/png" href='{{ url_for('static', filename='favicon.png') }}'>
<link rel=stylesheet href="{{ url_for('static', filename='owl.css') }}?v=15" type='text/css' media=all>
<link rel=stylesheet href="{{ url_for('static', filename='owl.css') }}?v=16" type='text/css' media=all>
{% if need_js %}
<link rel="stylesheet" href='{{ url_for('static', filename='github.css') }}'>
<link rel="stylesheet" href='{{ url_for('static', filename='katex.min.css') }}'>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment