Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Martin Mareš
Postal Owl
Commits
23475abb
Commit
23475abb
authored
Feb 27, 2022
by
Martin Mareš
Browse files
Merge branch 'master' of gitlab.kam.mff.cuni.cz:mj/owl
parents
ca5c1147
714d89a8
Changes
17
Expand all
Hide whitespace changes
Inline
Side-by-side
db.ddl
View file @
23475abb
...
...
@@ -3,10 +3,10 @@ SET ROLE owl;
CREATE TABLE owl_users (
uid serial PRIMARY KEY,
ukco int UNIQUE DEFAULT NULL,
auth_token varchar(64) UNIQUE DEFAULT NULL,
full_name varchar(255) NOT NULL,
auth_token varchar(64) UNIQUE DEFAULT NULL, -- this is called "access key" in UI
first_name varchar(255) NOT NULL COLLATE "cs_CZ",
last_name varchar(255) NOT NULL COLLATE "cs_CZ",
email varchar(255) DEFAULT NULL,
-- we allow CAS's format "{email1,email2,...}"
created timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
is_admin boolean NOT NULL DEFAULT false,
notify boolean NOT NULL DEFAULT false,
...
...
@@ -15,7 +15,7 @@ CREATE TABLE owl_users (
);
-- uid=-1 means "everybody"
INSERT INTO owl_users(uid, full_name) VALUES (-1, 'Everybody');
INSERT INTO owl_users(uid, full_name
, first_name, last_name
) VALUES (-1, 'Everybody'
, 'Every', 'Body'
);
CREATE TABLE owl_semesters (
semid serial PRIMARY KEY,
...
...
@@ -27,11 +27,12 @@ CREATE TABLE owl_semesters (
CREATE TABLE owl_courses (
cid serial PRIMARY KEY,
semid int NOT NULL REFERENCES owl_semesters(semid) ON DELETE CASCADE,
ident varchar(255)
UNIQUE
NOT NULL,
ident varchar(255) NOT NULL,
name varchar(255) NOT NULL,
enroll_token varchar(255) UNIQUE NOT NULL,
student_grading boolean NOT NULL DEFAULT FALSE,
anon_grading boolean NOT NULL DEFAULT FALSE
anon_grading boolean NOT NULL DEFAULT FALSE,
UNIQUE(semid, cid)
);
CREATE TABLE owl_enroll (
...
...
owl.py
View file @
23475abb
This diff is collapsed.
Click to expand it.
requirements.txt
View file @
23475abb
Flask
Flask
==1.1.2
Flask-WTF
WTForms
WTForms
==2.3.3
blinker
click
dateutils
...
...
static/owl.css
View file @
23475abb
...
...
@@ -68,6 +68,14 @@ h1 {
margin-bottom
:
3ex
;
}
/* Footer */
footer
{
margin-top
:
4ex
;
border-top
:
1px
solid
black
;
padding-top
:
0.5ex
;
}
/* Flashing messages */
.flash
{
...
...
templates/admin-course.html
View file @
23475abb
...
...
@@ -11,6 +11,6 @@
</table>
<div
class=
buttons
>
<a
class=
button
href=
'{{ url_for('
course_index
',
cident=
g.course.ident)
}}'
>
Back to the course
…
</a>
<a
class=
button
href=
'{{ url_for('
course_index
',
sident=
g.course.sident,
cident=
g.course.ident)
}}'
>
Back to the course
…
</a>
</div>
{% endblock %}
templates/admin-courses.html
View file @
23475abb
...
...
@@ -4,6 +4,7 @@
<table
class=
data
>
<thead><tr>
<th>
Sem.
<th>
Ident
<th>
Name
<th>
Teachers
...
...
@@ -11,8 +12,9 @@
</thead>
{% for c in courses %}
<tr>
<td>
{{ c.sident }}
<td
style=
'white-space: nowrap'
>
{{ c.ident }}
<td><a
href=
'{{ url_for('
course_index
',
cident=
c.ident)
}}'
>
{{ c.name }}
</a>
<td><a
href=
'{{ url_for('
course_index
',
sident=
c.sident,
cident=
c.ident)
}}'
>
{{ c.name }}
</a>
{% if c.cid in teachers %}
<td>
{{ teachers[c.cid]|sort|join(", ") }}
{% else %}
...
...
templates/admin-edit-topic.html
View file @
23475abb
...
...
@@ -26,7 +26,7 @@
</table>
<p
class=
buttons
>
{{ form.submit(class='ok') }}
{% if tid != None %}
<a
class=
button
href=
'{{ url_for('
admin_edit_topic
',
cident=
g.course.ident,
copy_tid=
tid)
}}'
>
Copy
</a>
<a
class=
button
href=
'{{ url_for('
admin_edit_topic
',
sident=
g.course.sident,
cident=
g.course.ident,
copy_tid=
tid)
}}'
>
Copy
</a>
<p>
{{ form.delete(class='danger') }}
(including {{ post_count }} posts)
{% endif %}
</form>
...
...
templates/admin-topic-graders.html
View file @
23475abb
...
...
@@ -3,7 +3,7 @@
<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>
<span
class=
"button"
><a
href=
"{{url_for('admin_topics',
sident=g.course.sident,
cident=g.course.ident)}}"
>
Back
</a></span>
</div>
<h3>
Assigned graders
</h3>
...
...
@@ -12,7 +12,7 @@
<li><form
method=
"POST"
action=
""
>
{{ form.csrf_token }}
{{ form.uid }}
<b>
{{ s.f
ull
_name }}
</b>
:
<b>
{{ s.f
irst_name }} {{ s.last
_name }}
</b>
:
{{ form.unassign(class='ok') }}
</form></li>
{% endfor %}
...
...
@@ -24,7 +24,7 @@
<li><form
method=
"POST"
action=
""
>
{{ form.csrf_token }}
{{ form.uid }}
<b>
{{ s.f
ull
_name }}
</b>
:
<b>
{{ s.f
irst_name }} {{ s.last
_name }}
</b>
:
{{ form.assign(class='ok') }}
</form></li>
{% endfor %}
...
...
templates/admin-topics.html
View file @
23475abb
...
...
@@ -13,10 +13,10 @@
{% set cls = "thidden" %}
{% endif %}
<tr
class=
{{
cls
}}
>
<td><a
href=
'{{ url_for('
admin_edit_topic
',
cident=
g.course.ident,
tid=
t.tid)
}}'
>
{{ t.rank }}
</a>
<td><a
href=
'{{ url_for('
admin_edit_topic
',
sident=
g.course.sident,
cident=
g.course.ident,
tid=
t.tid)
}}'
>
{{ t.rank }}
</a>
<td>
{{ t.ident or "" }}
{% if t.ident %}
<td><a
href=
'{{ url_for('
topic_index
',
cident=
g.course.ident,
tident=
t.ident)
}}'
>
{{ t.title }}
</a>
<td><a
href=
'{{ url_for('
topic_index
',
sident=
g.course.sident,
cident=
g.course.ident,
tident=
t.ident)
}}'
>
{{ t.title }}
</a>
{% else %}
<td>
{{ t.title }}
{% endif %}
...
...
@@ -24,13 +24,13 @@
<td>
{{ t.deadline|reltimeformat }}
<td
class=
pts
>
{{ t.max_points if t.max_points != None else "" }}
{% if g.course.student_grading %}
<td>
{% if t.type == 'T' %}
<a
href=
'{{ url_for('
admin_topic_graders
',
cident=
g.course.ident,
tid=
t.tid)
}}'
>
Assign
</a>
{% endif %}
<td>
{% if t.type == 'T' %}
<a
href=
'{{ url_for('
admin_topic_graders
',
sident=
g.course.sident,
cident=
g.course.ident,
tid=
t.tid)
}}'
>
Assign
</a>
{% endif %}
{% endif %}
{% endfor %}
</table>
<div
class=
buttons
>
<a
class=
button
href=
'{{ url_for('
admin_edit_topic
',
cident=
g.course.ident)
}}'
>
New topic
</a>
<a
class=
button
href=
'{{ url_for('
course_index
',
cident=
g.course.ident)
}}'
>
Back to the course
</a>
<a
class=
button
href=
'{{ url_for('
admin_edit_topic
',
sident=
g.course.sident,
cident=
g.course.ident)
}}'
>
New topic
</a>
<a
class=
button
href=
'{{ url_for('
course_index
',
sident=
g.course.sident,
cident=
g.course.ident)
}}'
>
Back to the course
</a>
</div>
{% endblock %}
templates/base.html
View file @
23475abb
...
...
@@ -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=1
4
"
type=
'text/css'
media=
all
>
<link
rel=
stylesheet
href=
"{{ url_for('static', filename='owl.css') }}?v=1
5
"
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'
)
}}'
>
...
...
@@ -38,5 +38,9 @@
{% endif %}
{% endwith %}
{% block body %}{% endblock %}
<footer>
<p>
The Owl is maintained by
<a
href=
'https://mj.ucw.cz/'
>
Martin Mareš
</a>
.
Send all suggestions, bug reports, and requests for new courses to
<a
href=
'mailto:mj@ucw.cz'
>
mj@ucw.cz
</a>
.
</footer>
</body>
</html>
templates/course.html
View file @
23475abb
...
...
@@ -7,9 +7,9 @@
{% if g.is_teacher %}
<div
class=
buttons
>
<a
class=
button
href=
'{{ url_for('
admin_topics
',
cident=
g.course.ident)
}}'
>
Edit topics
</a>
<a
class=
button
href=
'{{ url_for('
teacher
',
cident=
g.course.ident)
}}'
>
Teacher's summary
</a>
<a
class=
button
href=
'{{ url_for('
admin_course
',
cident=
g.course.ident)
}}'
>
Details
</a>
<a
class=
button
href=
'{{ url_for('
admin_topics
',
sident=
g.course.sident,
cident=
g.course.ident)
}}'
>
Edit topics
</a>
<a
class=
button
href=
'{{ url_for('
teacher
',
sident=
g.course.sident,
cident=
g.course.ident)
}}'
>
Teacher's summary
</a>
<a
class=
button
href=
'{{ url_for('
admin_course
',
sident=
g.course.sident,
cident=
g.course.ident)
}}'
>
Details
</a>
</div>
{% endif %}
...
...
@@ -30,13 +30,13 @@
{% else %}
{% set cls = 'told' %}
{% endif %}
<tr
class=
{{
cls
}}
><td><a
href=
"{{ url_for('topic_index', cident=g.course.ident, tident=t.ident) }}"
>
{{ t.title }}
</a>
<tr
class=
{{
cls
}}
><td><a
href=
"{{ url_for('topic_index',
sident=g.course.sident,
cident=g.course.ident, tident=t.ident) }}"
>
{{ t.title }}
</a>
<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>
<a
href=
"{{ url_for('topic_student_grade',
sident=g.course.sident,
cident=g.course.ident, tident=t.ident) }}"
>
Grade
</a>
{% endif %}
{% endif %}
{% else %}
...
...
templates/login.html
View file @
23475abb
...
...
@@ -14,10 +14,7 @@
<form
method=
"POST"
action=
"?"
>
{{ form.csrf_token }}
{{ form.next() }}
Login by
to
ke
n
: {{ form.
to
ke
n
(size=64) }}
Login by
access
ke
y
: {{ form.ke
y
(size=64) }}
<input
type=
submit
value=
'Go!'
>
</form>
<p>
If you have problems with logging in, please clear your cookies and tell Martin Mareš
if it helped.
{% endblock %}
templates/main.html
View file @
23475abb
...
...
@@ -5,13 +5,13 @@
<h2>
{{ s.name }}
</h2>
<ul>
{% for c in courses[s.semid] %}
<li><a
href=
"{{ url_for('course_index', cident=c.ident) }}"
>
{{ c.name }}
</a>
{% if c.is_teacher %} (teacher){% endif %}
<li><a
href=
"{{ url_for('course_index',
sident=s.ident,
cident=c.ident) }}"
>
{{ c.name }}
</a>
{% if c.is_teacher %} (teacher){% endif %}
{% endfor %}
{% if loop.first %}
<li><a
href=
'{{ url_for('
enroll
')
}}'
>
Join a new course
</a>
{% endif %}
{% if sem_teacher[s.semid] %}
<li><a
href=
'{{ url_for('
teacher
',
s
em
ident=
s.ident)
}}'
>
Teacher's summary
</a>
<li><a
href=
'{{ url_for('
teacher
',
sident=
s.ident)
}}'
>
Teacher's summary
</a>
{% endif %}
</ul>
{% endif %}
...
...
@@ -28,6 +28,9 @@
<h2>
News
</h2>
<table
class=
news
>
<tr><td>
2021-09-28
<td>
We changed structure of URLs to include semester code,
so that we can recycle course IDs.
<tr><td>
2021-05-21
<td>
Teachers can now download results in CSV or JSON.
<tr><td>
2021-04-20
<td>
Added a Reply button for quoting of posts.
<tr><td>
2021-03-13
<td>
Added syntax highlighting for C, C++, Python, Haskell, and Prolog.
Use
<code>
```python
</code>
in Markdown to start a code block. Ask for more languages
...
...
@@ -38,7 +41,4 @@
<tr><td>
2021-03-08
<td>
We now support grading of solutions by other students.
<tr><td>
2021-02-19
<td>
The Owl has a
<a
href=
'{{ url_for('
manual
')
}}'
>
manual
</a>
.
</table>
<p>
The Owl was written by
<a
href=
'https://mj.ucw.cz/'
>
Martin Mareš
</a>
.
Send all suggestions and bug reports to
<a
href=
'mailto:mj@ucw.cz'
>
mj@ucw.cz
</a>
.
{% endblock %}
templates/settings.html
View file @
23475abb
...
...
@@ -5,8 +5,9 @@
<form
method=
"POST"
action=
""
>
{{ form.csrf_token }}
<table
class=
settings
>
<tr><td>
Name:
<td>
{{ user.full_name }}
<tr><td>
E-mail:
<td>
{{ "
<br>
".join(emails) }}
<tr><td>
First name:
<td>
{{ user.first_name }}
<tr><td>
Last name:
<td>
{{ user.last_name }}
<tr><td>
E-mail:
<td>
{{ user.email }}
<tr><td>
Notify by e-mail:
<td>
{{ form.notify() }}
<tr><td>
Notify on own posts:
<td>
{{ form.notify_self() }}
<tr><td>
Show PDF attachments inline:
<td>
{{ form.inline_att() }}
...
...
templates/teacher.html
View file @
23475abb
...
...
@@ -4,7 +4,7 @@
<h2>
Teacher's Summary
</h2>
{% for c in courses.values() %}
<h3>
{{ c.name }} (
<a
href=
'{{ url_for('
course_index
',
cident=
c.ident)
}}'
>
{{ c.ident }}
</a>
)
</h3>
<h3>
{{ c.name }} (
<a
href=
'{{ url_for('
course_index
',
sident=
c.sident,
cident=
c.ident)
}}'
>
{{ c.ident }}
</a>
)
</h3>
<table
class=
results
>
<tr><th
class=
'tbeforehdr'
>
...
...
@@ -21,7 +21,7 @@
{% do cls.append("tbeforehdr") %}
{% endif %}
<th
class=
'{{ cls|join(" ") }}'
title=
'{{ t.title }}{% if t.deadline != None %} [{{ t.deadline|reltimeformat }}]{% endif %}'
>
<a
href=
'{{ url_for('
topic_index
',
cident=
c.ident,
tident=
t.ident)
}}'
>
{{ t.ident }}
</a>
<a
href=
'{{ url_for('
topic_index
',
sident=
c.sident,
cident=
c.ident,
tident=
t.ident)
}}'
>
{{ t.ident }}
</a>
{% endfor %}
<th>
Σ
<tr><th
class=
'tbeforehdr'
>
max.
...
...
@@ -34,7 +34,7 @@
{% endfor %}
<td
class=
pts
>
{{ course_totals[c.cid] }}
{% for s in students[c.cid].values() %}
<tr><td
class=
'tbeforehdr'
>
{{ s.f
ull
_name }}
<tr><td
class=
'tbeforehdr'
>
{{ s.f
irst_name }} {{ s.last
_name }}
{{ s.email|mailto('✉') }}
{% for t in topics[c.cid].values() %}
{% set sol=solutions[c.cid][s.uid][t.tid] %}
...
...
@@ -46,7 +46,7 @@
{% do cls.append("snull") %}
{% if t.type == 'A' %}
{% do cls.append("pts") %}
<td
class=
'{{ cls|join(" ") }}'
><a
href=
'{{ url_for('
topic_index
',
cident=
c.ident,
tident=
t.ident,
student_uid=
s.uid)
}}'
>
+
</a>
<td
class=
'{{ cls|join(" ") }}'
><a
href=
'{{ url_for('
topic_index
',
sident=
c.sident,
cident=
c.ident,
tident=
t.ident,
student_uid=
s.uid)
}}'
>
+
</a>
{% else %}
<td
class=
'{{ cls|join(" ") }}'
>
{% endif %}
...
...
@@ -61,7 +61,7 @@
{% 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)
}}'
>
<td
class=
"{{ cls|join("
")
}}"
><a
href=
'{{ url_for('
topic_index
',
sident=
c.sident,
cident=
c.ident,
tident=
t.ident,
student_uid=
s.uid)
}}'
>
{% if sol.points != None %}
{{ sol.points }}
{% else %}
...
...
@@ -73,6 +73,10 @@
<td
class=
pts
>
{{ totals[c.cid][s.uid] }}
{% endfor %}
</table>
<p>
Download as
<a
href=
'{{ url_for('
results_json
',
sident=
c.sident,
cident=
c.ident)
}}'
>
JSON
</a>
,
<a
href=
'{{ url_for('
results_csv
',
sident=
c.sident,
cident=
c.ident)
}}'
>
CSV
</a>
.
{% endfor %}
{% endblock %}
templates/topic-grade.html
View file @
23475abb
...
...
@@ -5,7 +5,7 @@
<h2>
{{c.name}}
</h2>
<div
class=
"buttons"
>
<span
class=
"button"
><a
href=
'{{ url_for('
course_index
',
cident=
c.ident)
}}'
>
Return to course
</a></span>
<span
class=
"button"
><a
href=
'{{ url_for('
course_index
',
sident=
c.sident,
cident=
c.ident)
}}'
>
Return to course
</a></span>
</div>
<h3>
Grade {{t.title}}
</h3>
...
...
@@ -19,7 +19,7 @@
{% if c.anon_grading and s.uid != g.uid %}
Student {{ s.uid }}
{% else %}
{{ s.f
ull
_name }}
{{ s.f
irst_name }} {{ s.last
_name }}
{% endif %}
{% set sol=solutions[s.uid] %}
{% set cls=[] %}
...
...
@@ -33,7 +33,7 @@
{%
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)
}}'
>
<
td
class=
"{{ cls|join("
")
}}"
><a
href=
'{{ url_for('
topic_index
',
sident=
c.sident,
cident=
c.ident,
tident=
t.ident,
student_uid=
s.uid)
}}'
>
{% if sol.points != None %}
{{ sol.points }}
{% else %}
...
...
templates/topic.html
View file @
23475abb
...
...
@@ -8,12 +8,12 @@
<h2>
{{ g.course.name }}
</h2>
<div
class=
"buttons"
>
<a
class=
"button"
href=
'{{ url_for('
course_index
',
cident=
g.course.ident)
}}'
>
Back to the course
</a>
<a
class=
"button"
href=
'{{ url_for('
course_index
',
sident=
g.course.sident,
cident=
g.course.ident)
}}'
>
Back to the course
</a>
{% if g.is_teacher %}
<a
class=
"button"
href=
'{{ url_for('
teacher
',
cident=
g.course.ident)
}}'
>
Teacher's summary
</a>
<a
class=
"button"
href=
'{{ url_for('
admin_edit_topic
',
cident=
g.course.ident,
tid=
g.topic.tid)
}}'
>
Edit topic
</a>
<a
class=
"button"
href=
'{{ url_for('
teacher
',
sident=
g.course.sident,
cident=
g.course.ident)
}}'
>
Teacher's summary
</a>
<a
class=
"button"
href=
'{{ url_for('
admin_edit_topic
',
sident=
g.course.sident,
cident=
g.course.ident,
tid=
g.topic.tid)
}}'
>
Edit topic
</a>
{% elif g.is_grader %}
<a
class=
"button"
href=
'{{ url_for('
topic_student_grade
',
cident=
g.course.ident,
tident=
g.topic.ident)
}}'
>
Grade
</a>
<a
class=
"button"
href=
'{{ url_for('
topic_student_grade
',
sident=
g.course.sident,
cident=
g.course.ident,
tident=
g.topic.ident)
}}'
>
Grade
</a>
{% endif %}
</div>
...
...
@@ -57,7 +57,7 @@
<span
class=
phpast
>
(after deadline)
</span>
{% endif %}
{% if g.is_teacher or p.author_uid == g.uid %}
—
<a
href=
'{{ url_for('
edit_post
',
cident=
g.course.ident,
pid=
p.pid)
}}'
>
edit
</a>
—
<a
href=
'{{ url_for('
edit_post
',
sident=
g.course.sident,
cident=
g.course.ident,
pid=
p.pid)
}}'
>
edit
</a>
{% endif %}
—
<a
href=
"#comment"
class=
"preply"
>
reply
</a>
{% if p.comment != None %}
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment