From cc054d5954af093a9a05f06ab2dd54c59547f89e Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Sat, 22 Jun 2019 20:24:10 +0300 Subject: [PATCH 01/33] show threads (but not send messages to them yet) --- ennead/app.py | 3 ++ ennead/models/task.py | 6 ++++ ennead/models/thread.py | 12 +++++-- ennead/models/user.py | 2 +- ennead/static/style.css | 21 ++++++++++++ ennead/templates/dialogue.html | 31 ++++++++++++++++++ ennead/views/dialogue.py | 20 ++++++++++++ populate_db.py | 58 ++++++++++++++++++++++++++++++++++ 8 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 ennead/templates/dialogue.html create mode 100644 ennead/views/dialogue.py create mode 100644 populate_db.py diff --git a/ennead/app.py b/ennead/app.py index 2c17a27..d39047d 100644 --- a/ennead/app.py +++ b/ennead/app.py @@ -11,6 +11,7 @@ from ennead.views.admin import adm_task_list_page, task_edit_page, task_edit, task_delete from ennead.views.system import render_markdown_endpoint from ennead.views.file import upload_file, uploaded_file, files_page +from ennead.views.dialogue import student_thread_page from ennead.models.base import database from ennead.models.file import File @@ -62,6 +63,8 @@ def create_app(config_path: Optional[str] = None) -> Flask: app.add_url_rule('/login', 'login_page', login_page) app.add_url_rule('/login', 'login', login, methods=['POST']) app.add_url_rule('/logout', 'logout', logout) + + app.add_url_rule('/student_thread_page/', 'student_thread_page', student_thread_page) app.add_url_rule('/adm/tasks', 'adm_task_list_page', adm_task_list_page) app.add_url_rule('/adm/tasks/', 'task_edit_page', task_edit_page) diff --git a/ennead/models/task.py b/ennead/models/task.py index 61e198a..be16440 100644 --- a/ennead/models/task.py +++ b/ennead/models/task.py @@ -27,6 +27,10 @@ class TaskSet(BaseModel): tasks: List['Task'] threads: List['Thread'] + @property + def ordered_tasks(self) -> List["Task"]: + return sorted(self.tasks, key=lambda task: task.order_num) + class Task(BaseModel): """One task for student @@ -36,12 +40,14 @@ class Task(BaseModel): description: `Task` description in Markdown base_score: basic maximal score for `Task` task_set: set this `Task` belongs to + order_num: order of this `Task` in a `TaskSet` threads: list of `Thread`s about this `Task` """ name: str = CharField() description: str = TextField() base_score: int = IntegerField() + order_num: int = IntegerField() task_set: TaskSet = ForeignKeyField(TaskSet, backref='tasks') threads: List['Thread'] diff --git a/ennead/models/thread.py b/ennead/models/thread.py index b811b96..11daae2 100644 --- a/ennead/models/thread.py +++ b/ennead/models/thread.py @@ -3,7 +3,7 @@ import datetime from typing import List -from peewee import DateTimeField, IntegerField, TextField, ForeignKeyField +from peewee import DateTimeField, IntegerField, DecimalField, TextField, ForeignKeyField from ennead.models.user import User from ennead.models.task import Task @@ -21,11 +21,19 @@ class Thread(BaseModel): """ task: Task = ForeignKeyField(Task, backref='threads') - score: int = IntegerField() + score: float = DecimalField(default=0) student: User = ForeignKeyField(User, backref='threads') posts: List['Post'] + def ordered_posts(self, show_hidden=False): + posts = self.posts + if show_hidden: + posts = filter(lambda post: not post.hidden, posts) + posts = sorted(posts, key=lambda post: post.date) + return posts + + class Post(BaseModel): """One post in `Thread` with `User` about `Task` diff --git a/ennead/models/user.py b/ennead/models/user.py index d0d69b5..7feccf8 100644 --- a/ennead/models/user.py +++ b/ennead/models/user.py @@ -82,7 +82,7 @@ def is_teacher(self) -> bool: return self.group == UserGroup.teacher @property - def score(self) -> int: + def score(self) -> float: """Get `User`s score in current task set""" return sum( diff --git a/ennead/static/style.css b/ennead/static/style.css index aeaf912..fda632b 100644 --- a/ennead/static/style.css +++ b/ennead/static/style.css @@ -43,3 +43,24 @@ margin-right: auto; max-width: 70%; } + +.post { + border: 1px solid black; + margin: 10px 0; + padding: 10px; +} +.post-teacher { + text-align: right; + background-color: lightblue; +} +.post-student { + text-align: left; + background-color: white; +} + +.message-form { + +} +.message-form textarea { + width: 100%; +} diff --git a/ennead/templates/dialogue.html b/ennead/templates/dialogue.html new file mode 100644 index 0000000..ef7c51f --- /dev/null +++ b/ennead/templates/dialogue.html @@ -0,0 +1,31 @@ +{% extends 'base.html' %} + +{% block title %}{{task.order}}. {{ task.name }}{% endblock %} + +{% block body %} +
+
+

{{ task.order }}{{ task.name }}

+

Максимальный балл: {{ task.base_score }}

+

{{ task.description }}

+ +
+
+
+
+ {% for post in thread.ordered_posts(show_hidden=False): %} +
+
{{ post.text }}
+
+ {% endfor %} +
+
+
+
+
+ + +
+
+
+{% endblock %} diff --git a/ennead/views/dialogue.py b/ennead/views/dialogue.py new file mode 100644 index 0000000..4aa87b4 --- /dev/null +++ b/ennead/views/dialogue.py @@ -0,0 +1,20 @@ +"""Views for dialogues""" + +import datetime +from flask import g + +from flask import session, current_app, render_template, request, redirect, url_for +from werkzeug.wrappers import Response + +from ennead.utils import require_logged_in +from ennead.models.user import User, UserGroup +from ennead.models.task import Task +from ennead.models.thread import Thread, Post + + +@require_logged_in +def student_thread_page(task_id: int) -> Response: + """GET /thread/{task}: show student's thread for a specified task""" + task = Task.get_by_id(task_id) + thread, _ = Thread.get_or_create(task=task_id, student=g.user) + return render_template('dialogue.html', task=task, thread=thread) diff --git a/populate_db.py b/populate_db.py new file mode 100644 index 0000000..c4cfb89 --- /dev/null +++ b/populate_db.py @@ -0,0 +1,58 @@ +import datetime +from ennead.config import Config + +from ennead.models.base import database +from ennead.models.user import User, UserGroup +from ennead.models.task import TaskSet, Task +from ennead.models.thread import Thread, Post + +config_path = 'ennead.json' +if config_path: + config = Config.from_filename(config_path) +else: + config = Config() + +database.initialize(config.DB_CLASS(config.DB_NAME, **config.DB_PARAMS)) +database.create_tables([User, Task, TaskSet, Thread, Post]) + +student = User( + username = 'test', + email = 'test@test.com', + registered_at = datetime.datetime.now(), + first_name = 'Иван', + surname = 'Петров', + patronym = 'Иванович', + group = UserGroup.student) +student.set_password('password') +student.save() + +teacher = User( + username = 'prep', + email = 'prep@test.com', + registered_at = datetime.datetime.now(), + first_name = 'Препод', + surname = 'Злой', + patronym = '', + group = UserGroup.teacher) +teacher.set_password('password') +teacher.save() + + +prev_task_set = TaskSet.create(name='Старая заочка', active=True) +task_set = TaskSet.create(name='Текущая заочка', active=True) + +task_1_1 = Task.create(order_num=1, name='Очень старая задача #1', description='Когда трава была зеленее', base_score=1, task_set=prev_task_set) +task_1_2 = Task.create(order_num=2, name='Очень старая задача #2', description='И задачи были забористей', base_score=1, task_set=prev_task_set) + +task_2_1 = Task.create(order_num=1, name='Задача первая', description='Самая первая', base_score=1, task_set=task_set) +task_2_2 = Task.create(order_num=3, name='Задача последняя', description='Хардкор', base_score=42, task_set=task_set) +task_2_3 = Task.create(order_num=2, name='Задача два', description='Посложнее', base_score=5, task_set=task_set) + +thread_1 = Thread.create(student=student, task=task_2_1) +thread_2 = Thread.create(student=student, task=task_2_2) + +post_1_1 = Post.create(thread=thread_1, text='Первый нах', date = datetime.datetime.now(), author=student) +post_1_2 = Post.create(thread=thread_1, text='Так себе решение. Пока 0 баллов', date = datetime.datetime.now(), author=teacher) +post_1_3 = Post.create(thread=thread_1, text='Ну ладно, 2+2=3', date = datetime.datetime.now(), author=student) +post_1_3 = Post.create(thread=thread_1, text='Ой, 2+2=4', date = datetime.datetime.now(), author=student) +post_1_4 = Post.create(thread=thread_1, text='Ок, угадал. 1 балл', date = datetime.datetime.now(), author=teacher) From ea04a513b62a1efd94d345dea78ed90540a7e718 Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Sat, 22 Jun 2019 22:59:11 +0300 Subject: [PATCH 02/33] finalize message exchange --- ennead/app.py | 4 +++- ennead/models/thread.py | 8 +++++--- ennead/models/user.py | 6 ++++++ ennead/static/style.css | 15 +++++++++++---- ennead/templates/dialogue.html | 27 +++++++++++++++++++++++---- ennead/utils/__init__.py | 22 ++++++++++++++++++++++ ennead/views/dialogue.py | 31 ++++++++++++++++++++++++++++--- populate_db.py | 3 ++- 8 files changed, 100 insertions(+), 16 deletions(-) diff --git a/ennead/app.py b/ennead/app.py index d39047d..b0447dd 100644 --- a/ennead/app.py +++ b/ennead/app.py @@ -11,7 +11,7 @@ from ennead.views.admin import adm_task_list_page, task_edit_page, task_edit, task_delete from ennead.views.system import render_markdown_endpoint from ennead.views.file import upload_file, uploaded_file, files_page -from ennead.views.dialogue import student_thread_page +from ennead.views.dialogue import student_thread_page, teacher_thread_page, send_post_to_thread from ennead.models.base import database from ennead.models.file import File @@ -65,6 +65,8 @@ def create_app(config_path: Optional[str] = None) -> Flask: app.add_url_rule('/logout', 'logout', logout) app.add_url_rule('/student_thread_page/', 'student_thread_page', student_thread_page) + app.add_url_rule('/teacher_thread_page//', 'teacher_thread_page', teacher_thread_page) + app.add_url_rule('/send_post_to_thread/', 'send_post_to_thread', send_post_to_thread, methods=['POST']) app.add_url_rule('/adm/tasks', 'adm_task_list_page', adm_task_list_page) app.add_url_rule('/adm/tasks/', 'task_edit_page', task_edit_page) diff --git a/ennead/models/thread.py b/ennead/models/thread.py index 11daae2..d3fa507 100644 --- a/ennead/models/thread.py +++ b/ennead/models/thread.py @@ -3,7 +3,7 @@ import datetime from typing import List -from peewee import DateTimeField, IntegerField, DecimalField, TextField, ForeignKeyField +from peewee import DateTimeField, IntegerField, DecimalField, TextField, BooleanField, ForeignKeyField from ennead.models.user import User from ennead.models.task import Task @@ -28,8 +28,8 @@ class Thread(BaseModel): def ordered_posts(self, show_hidden=False): posts = self.posts - if show_hidden: - posts = filter(lambda post: not post.hidden, posts) + if not show_hidden: + posts = filter(lambda post: not post.hide_from_student, posts) posts = sorted(posts, key=lambda post: post.date) return posts @@ -43,9 +43,11 @@ class Post(BaseModel): date: date this `Post` was posted author: `User` who wrote this post thread: `Thread` this task belongs to + hide_from_student: marks `Post` as auxiliary (for teacher's use only) """ text: str = TextField() date: datetime.datetime = DateTimeField() author: User = ForeignKeyField(User, backref='+') # '+' means 'no backref' + hide_from_student: bool = BooleanField(default=False) thread: Thread = ForeignKeyField(Thread, backref='posts') diff --git a/ennead/models/user.py b/ennead/models/user.py index 7feccf8..1637f97 100644 --- a/ennead/models/user.py +++ b/ennead/models/user.py @@ -75,6 +75,12 @@ def check_password(self, password: str) -> bool: self.password_sha512 ) + @property + def is_student(self) -> bool: + """Check is user a student""" + + return self.group == UserGroup.student + @property def is_teacher(self) -> bool: """Check is user a teacher""" diff --git a/ennead/static/style.css b/ennead/static/style.css index fda632b..b8c9016 100644 --- a/ennead/static/style.css +++ b/ennead/static/style.css @@ -51,16 +51,23 @@ } .post-teacher { text-align: right; - background-color: lightblue; + background-color: #ccf; +} +.post-teacher-hidden { + text-align: right; + background-color: lightgray; } .post-student { text-align: left; background-color: white; } -.message-form { - +.post-date { + font-size: 0.75em; + color: #555; +} +.post-form { } -.message-form textarea { +.post-form textarea { width: 100%; } diff --git a/ennead/templates/dialogue.html b/ennead/templates/dialogue.html index ef7c51f..0ccb757 100644 --- a/ennead/templates/dialogue.html +++ b/ennead/templates/dialogue.html @@ -13,17 +13,36 @@

{{ task.order }}{{ task.name }}

- {% for post in thread.ordered_posts(show_hidden=False): %} -
+ {% for post in thread.ordered_posts(show_hidden=g.user.is_teacher): %} + {% if post.author.is_teacher %} + {% if post.hide_from_student %} + {% set post_klass = 'post-teacher-hidden' %} + {% else %} + {% set post_klass = 'post-teacher' %} + {% endif %} + {%else%} + {% set post_klass = 'post-student' %} + {%endif%} +
+
{{ post.text }}
{% endfor %}
-
+
-
+
+ + {% if g.user.is_teacher: %} +
+ + +
+ {% endif %}
diff --git a/ennead/utils/__init__.py b/ennead/utils/__init__.py index b1528fb..cf3fc21 100644 --- a/ennead/utils/__init__.py +++ b/ennead/utils/__init__.py @@ -23,6 +23,28 @@ def wrapped(*args: Any, **kwargs: Any) -> Response: return wrapped +def require_logged_in_as_student(func: Callable) -> Callable: + """Make endpoint require logged in user as a student""" + + @wraps(func) + def wrapped(*args: Any, **kwargs: Any) -> Response: + if not (g.user and g.user.is_student): + return redirect(url_for('login_page')) + return func(*args, **kwargs) + + return wrapped + +def require_logged_in_as_teacher(func: Callable) -> Callable: + """Make endpoint require logged in user as a teacher""" + + @wraps(func) + def wrapped(*args: Any, **kwargs: Any) -> Response: + if not (g.user and g.user.is_teacher): + return redirect(url_for('login_page')) + return func(*args, **kwargs) + + return wrapped + def require_not_logged_in(func: Callable) -> Callable: """Make endpoint require NOT logged in user""" diff --git a/ennead/views/dialogue.py b/ennead/views/dialogue.py index 4aa87b4..36a21c2 100644 --- a/ennead/views/dialogue.py +++ b/ennead/views/dialogue.py @@ -6,15 +6,40 @@ from flask import session, current_app, render_template, request, redirect, url_for from werkzeug.wrappers import Response -from ennead.utils import require_logged_in +from ennead.utils import require_logged_in, require_logged_in_as_teacher, require_logged_in_as_student from ennead.models.user import User, UserGroup from ennead.models.task import Task from ennead.models.thread import Thread, Post -@require_logged_in +@require_logged_in_as_student def student_thread_page(task_id: int) -> Response: - """GET /thread/{task}: show student's thread for a specified task""" + """GET /student_thread_page/{task}: show student's thread for a specified task""" task = Task.get_by_id(task_id) thread, _ = Thread.get_or_create(task=task_id, student=g.user) return render_template('dialogue.html', task=task, thread=thread) + +@require_logged_in_as_teacher +def teacher_thread_page(task_id: int, student_id: int) -> Response: + """GET /teacher_thread_page/{task}: show student's thread for a specified task""" + task = Task.get_by_id(task_id) + student = User.get_by_id(student_id) + thread, _ = Thread.get_or_create(task=task_id, student=student) + return render_template('dialogue.html', task=task, thread=thread) + + +@require_logged_in +def send_post_to_thread(thread_id: int) -> Response: + thread = Thread.get_by_id(thread_id) + if g.user.is_teacher or (g.user.is_student and thread.student == g.user): + text = request.form['text'] + hide_from_student = False + if g.user.is_teacher: + hide_from_student = request.form.get('hide_from_student', False) + post = Post.create(text=text, date=datetime.datetime.now(), author=g.user, thread=thread, hide_from_student=hide_from_student) + if g.user.is_student: + return redirect(url_for('student_thread_page', task_id=thread.task.id)) + if g.user.is_teacher: + return redirect(url_for('teacher_thread_page', task_id=thread.task.id, student_id=thread.student.id)) + else: + pass diff --git a/populate_db.py b/populate_db.py index c4cfb89..2a69897 100644 --- a/populate_db.py +++ b/populate_db.py @@ -55,4 +55,5 @@ post_1_2 = Post.create(thread=thread_1, text='Так себе решение. Пока 0 баллов', date = datetime.datetime.now(), author=teacher) post_1_3 = Post.create(thread=thread_1, text='Ну ладно, 2+2=3', date = datetime.datetime.now(), author=student) post_1_3 = Post.create(thread=thread_1, text='Ой, 2+2=4', date = datetime.datetime.now(), author=student) -post_1_4 = Post.create(thread=thread_1, text='Ок, угадал. 1 балл', date = datetime.datetime.now(), author=teacher) +post_1_4 = Post.create(thread=thread_1, text='Это вот серьёзно сейчас было?', hide_from_student=True, date = datetime.datetime.now(), author=teacher) +post_1_5 = Post.create(thread=thread_1, text='Ок, угадал. 1 балл', date = datetime.datetime.now(), author=teacher) From 990f41f2e24d1d3e3a6a3a3955069e141fd37fec Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Sun, 23 Jun 2019 01:21:23 +0300 Subject: [PATCH 03/33] update score; ignore empty messages; submit on ctrl+enter --- ennead/app.py | 3 ++- ennead/static/app.js | 9 +++++++++ ennead/static/style.css | 4 +--- ennead/templates/base.html | 1 + ennead/templates/dialogue.html | 30 +++++++++++++++++++++++------- ennead/views/dialogue.py | 21 +++++++++++++-------- 6 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 ennead/static/app.js diff --git a/ennead/app.py b/ennead/app.py index b0447dd..020206f 100644 --- a/ennead/app.py +++ b/ennead/app.py @@ -11,7 +11,7 @@ from ennead.views.admin import adm_task_list_page, task_edit_page, task_edit, task_delete from ennead.views.system import render_markdown_endpoint from ennead.views.file import upload_file, uploaded_file, files_page -from ennead.views.dialogue import student_thread_page, teacher_thread_page, send_post_to_thread +from ennead.views.dialogue import student_thread_page, teacher_thread_page, send_post_to_thread, update_score from ennead.models.base import database from ennead.models.file import File @@ -67,6 +67,7 @@ def create_app(config_path: Optional[str] = None) -> Flask: app.add_url_rule('/student_thread_page/', 'student_thread_page', student_thread_page) app.add_url_rule('/teacher_thread_page//', 'teacher_thread_page', teacher_thread_page) app.add_url_rule('/send_post_to_thread/', 'send_post_to_thread', send_post_to_thread, methods=['POST']) + app.add_url_rule('/update_score/', 'update_score', update_score, methods=['POST']) app.add_url_rule('/adm/tasks', 'adm_task_list_page', adm_task_list_page) app.add_url_rule('/adm/tasks/', 'task_edit_page', task_edit_page) diff --git a/ennead/static/app.js b/ennead/static/app.js new file mode 100644 index 0000000..96814ba --- /dev/null +++ b/ennead/static/app.js @@ -0,0 +1,9 @@ +window.onload = function(){ + let textarea = document.getElementById('post-textarea'); + textarea.addEventListener('keydown', function(event) { + if (event.ctrlKey && event.keyCode == 13) { // Ctrl-Enter pressed + let form = document.getElementById('post-form'); + form.submit(); + } + }); +}; diff --git a/ennead/static/style.css b/ennead/static/style.css index b8c9016..75ebcfb 100644 --- a/ennead/static/style.css +++ b/ennead/static/style.css @@ -66,8 +66,6 @@ font-size: 0.75em; color: #555; } -.post-form { -} -.post-form textarea { +#post-textarea { width: 100%; } diff --git a/ennead/templates/base.html b/ennead/templates/base.html index 0fe678d..09adac8 100644 --- a/ennead/templates/base.html +++ b/ennead/templates/base.html @@ -83,5 +83,6 @@ + diff --git a/ennead/templates/dialogue.html b/ennead/templates/dialogue.html index 0ccb757..259cd90 100644 --- a/ennead/templates/dialogue.html +++ b/ennead/templates/dialogue.html @@ -6,9 +6,7 @@

{{ task.order }}{{ task.name }}

-

Максимальный балл: {{ task.base_score }}

-

{{ task.description }}

- +

Балл: {{ thread.score }} из {{ task.base_score }}

@@ -30,14 +28,14 @@

{{ task.order }}{{ task.name }}

{% endfor %}
-
+

-
- + + {% if g.user.is_teacher: %}
- + @@ -47,4 +45,22 @@

{{ task.order }}{{ task.name }}

+ {% if g.user.is_teacher %} +
+
+
+
+
+ +
+ +
+
+ +
+
+
+
+
+ {% endif %} {% endblock %} diff --git a/ennead/views/dialogue.py b/ennead/views/dialogue.py index 36a21c2..38186e1 100644 --- a/ennead/views/dialogue.py +++ b/ennead/views/dialogue.py @@ -31,15 +31,20 @@ def teacher_thread_page(task_id: int, student_id: int) -> Response: @require_logged_in def send_post_to_thread(thread_id: int) -> Response: thread = Thread.get_by_id(thread_id) - if g.user.is_teacher or (g.user.is_student and thread.student == g.user): - text = request.form['text'] + text = request.form['text'].strip() + if len(text) != 0 and (g.user.is_teacher or (g.user.is_student and thread.student == g.user)): hide_from_student = False if g.user.is_teacher: hide_from_student = request.form.get('hide_from_student', False) post = Post.create(text=text, date=datetime.datetime.now(), author=g.user, thread=thread, hide_from_student=hide_from_student) - if g.user.is_student: - return redirect(url_for('student_thread_page', task_id=thread.task.id)) - if g.user.is_teacher: - return redirect(url_for('teacher_thread_page', task_id=thread.task.id, student_id=thread.student.id)) - else: - pass + if g.user.is_student: + return redirect(url_for('student_thread_page', task_id=thread.task.id)) + if g.user.is_teacher: + return redirect(url_for('teacher_thread_page', task_id=thread.task.id, student_id=thread.student.id)) + +@require_logged_in_as_teacher +def update_score(thread_id: int) -> Response: + thread = Thread.get_by_id(thread_id) + thread.score = request.form.get('score', thread.score) + thread.save() + return redirect(url_for('teacher_thread_page', task_id=thread.task.id, student_id=thread.student.id)) From 13baf4557eea2f7a3e2a3e165d5d56ab3e7de296 Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Sun, 23 Jun 2019 01:45:59 +0300 Subject: [PATCH 04/33] reuse require_teacher --- ennead/utils/__init__.py | 35 ++++++++++++----------------------- ennead/views/dialogue.py | 8 ++++---- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/ennead/utils/__init__.py b/ennead/utils/__init__.py index cf3fc21..09b9c98 100644 --- a/ennead/utils/__init__.py +++ b/ennead/utils/__init__.py @@ -23,29 +23,6 @@ def wrapped(*args: Any, **kwargs: Any) -> Response: return wrapped -def require_logged_in_as_student(func: Callable) -> Callable: - """Make endpoint require logged in user as a student""" - - @wraps(func) - def wrapped(*args: Any, **kwargs: Any) -> Response: - if not (g.user and g.user.is_student): - return redirect(url_for('login_page')) - return func(*args, **kwargs) - - return wrapped - -def require_logged_in_as_teacher(func: Callable) -> Callable: - """Make endpoint require logged in user as a teacher""" - - @wraps(func) - def wrapped(*args: Any, **kwargs: Any) -> Response: - if not (g.user and g.user.is_teacher): - return redirect(url_for('login_page')) - return func(*args, **kwargs) - - return wrapped - - def require_not_logged_in(func: Callable) -> Callable: """Make endpoint require NOT logged in user""" @@ -69,3 +46,15 @@ def wrapped(*args: Any, **kwargs: Any) -> Response: abort(403) return wrapped + +def require_student(func: Callable) -> Callable: + """Make endpoint require logged in teacher""" + + # pylint: disable=inconsistent-return-statements + @wraps(func) + def wrapped(*args: Any, **kwargs: Any) -> Response: + if g.user and g.user.is_student: + return func(*args, **kwargs) + abort(403) + + return wrapped diff --git a/ennead/views/dialogue.py b/ennead/views/dialogue.py index 38186e1..53b2dcd 100644 --- a/ennead/views/dialogue.py +++ b/ennead/views/dialogue.py @@ -6,20 +6,20 @@ from flask import session, current_app, render_template, request, redirect, url_for from werkzeug.wrappers import Response -from ennead.utils import require_logged_in, require_logged_in_as_teacher, require_logged_in_as_student +from ennead.utils import require_logged_in, require_teacher, require_student from ennead.models.user import User, UserGroup from ennead.models.task import Task from ennead.models.thread import Thread, Post -@require_logged_in_as_student +@require_student def student_thread_page(task_id: int) -> Response: """GET /student_thread_page/{task}: show student's thread for a specified task""" task = Task.get_by_id(task_id) thread, _ = Thread.get_or_create(task=task_id, student=g.user) return render_template('dialogue.html', task=task, thread=thread) -@require_logged_in_as_teacher +@require_teacher def teacher_thread_page(task_id: int, student_id: int) -> Response: """GET /teacher_thread_page/{task}: show student's thread for a specified task""" task = Task.get_by_id(task_id) @@ -42,7 +42,7 @@ def send_post_to_thread(thread_id: int) -> Response: if g.user.is_teacher: return redirect(url_for('teacher_thread_page', task_id=thread.task.id, student_id=thread.student.id)) -@require_logged_in_as_teacher +@require_teacher def update_score(thread_id: int) -> Response: thread = Thread.get_by_id(thread_id) thread.score = request.form.get('score', thread.score) From 35e74c27fbfa944c00f950ff45e293c1936aebc0 Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Sun, 23 Jun 2019 15:24:00 +0300 Subject: [PATCH 05/33] prepods can see who gave an answer in a thread --- ennead/static/style.css | 12 +++++++++++- ennead/templates/dialogue.html | 7 ++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/ennead/static/style.css b/ennead/static/style.css index 75ebcfb..3b11f92 100644 --- a/ennead/static/style.css +++ b/ennead/static/style.css @@ -62,10 +62,20 @@ background-color: white; } -.post-date { +.post-header { font-size: 0.75em; color: #555; } + +.post-date { + display: inline-block; +} + +.post-author { + display: inline-block; + margin-right: 10px; +} + #post-textarea { width: 100%; } diff --git a/ennead/templates/dialogue.html b/ennead/templates/dialogue.html index 259cd90..86243f9 100644 --- a/ennead/templates/dialogue.html +++ b/ennead/templates/dialogue.html @@ -22,7 +22,12 @@

{{ task.order }}{{ task.name }}

{% set post_klass = 'post-student' %} {%endif%}
- +
+ {% if g.user.is_teacher %} + + {% endif %} + +
{{ post.text }}
{% endfor %} From 20faf86a396a96f0bd2db0a6f410425ad9665328 Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Sun, 23 Jun 2019 15:28:44 +0300 Subject: [PATCH 06/33] fix bug with order field name in a template --- ennead/templates/dialogue.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ennead/templates/dialogue.html b/ennead/templates/dialogue.html index 86243f9..208e8a6 100644 --- a/ennead/templates/dialogue.html +++ b/ennead/templates/dialogue.html @@ -1,11 +1,11 @@ {% extends 'base.html' %} -{% block title %}{{task.order}}. {{ task.name }}{% endblock %} +{% block title %}{{task.order_num}}. {{ task.name }}{% endblock %} {% block body %}
-

{{ task.order }}{{ task.name }}

+

{{ task.order_num }}. {{ task.name }}

Балл: {{ thread.score }} из {{ task.base_score }}

From de4b477f460d5069630685fc67389b2191f9c2f3 Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Sun, 23 Jun 2019 16:01:26 +0300 Subject: [PATCH 07/33] Task description is now rendered in a dialogue. Both task and mesages should be written in markdown --- ennead/models/thread.py | 7 +++++++ ennead/templates/dialogue.html | 3 ++- ennead/views/dialogue.py | 10 +++++++++- populate_db.py | 6 +++--- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/ennead/models/thread.py b/ennead/models/thread.py index d3fa507..86a2790 100644 --- a/ennead/models/thread.py +++ b/ennead/models/thread.py @@ -9,6 +9,7 @@ from ennead.models.task import Task from ennead.models.base import BaseModel +from ennead.utils import render_markdown class Thread(BaseModel): """Student-with-teachers chat about `Task` @@ -51,3 +52,9 @@ class Post(BaseModel): author: User = ForeignKeyField(User, backref='+') # '+' means 'no backref' hide_from_student: bool = BooleanField(default=False) thread: Thread = ForeignKeyField(Thread, backref='posts') + + @property + def html_description(self): + """`Post` description in HTML""" + + return render_markdown(self.text) diff --git a/ennead/templates/dialogue.html b/ennead/templates/dialogue.html index 208e8a6..4bfcf6c 100644 --- a/ennead/templates/dialogue.html +++ b/ennead/templates/dialogue.html @@ -7,6 +7,7 @@

{{ task.order_num }}. {{ task.name }}

Балл: {{ thread.score }} из {{ task.base_score }}

+
{{ task.html_description|safe }}
@@ -28,7 +29,7 @@

{{ task.order_num }}. {{ task.name }}

{% endif %}
-
{{ post.text }}
+
{{ post.html_description|safe }}
{% endfor %}
diff --git a/ennead/views/dialogue.py b/ennead/views/dialogue.py index 53b2dcd..443e39e 100644 --- a/ennead/views/dialogue.py +++ b/ennead/views/dialogue.py @@ -7,6 +7,7 @@ from werkzeug.wrappers import Response from ennead.utils import require_logged_in, require_teacher, require_student +from ennead.utils import render_markdown from ennead.models.user import User, UserGroup from ennead.models.task import Task from ennead.models.thread import Thread, Post @@ -32,7 +33,14 @@ def teacher_thread_page(task_id: int, student_id: int) -> Response: def send_post_to_thread(thread_id: int) -> Response: thread = Thread.get_by_id(thread_id) text = request.form['text'].strip() - if len(text) != 0 and (g.user.is_teacher or (g.user.is_student and thread.student == g.user)): + correct_message = (len(text) != 0) + try: + render_markdown(text) + except: + correct_message = False + # Not sure is it possible to create incorrect markdown, but if yes, we shouldn't store it + # ToDo: redirect back to a dialogue, restoring a message draft not to lose it + if correct_message and (g.user.is_teacher or (g.user.is_student and thread.student == g.user)): hide_from_student = False if g.user.is_teacher: hide_from_student = request.form.get('hide_from_student', False) diff --git a/populate_db.py b/populate_db.py index 2a69897..20a4816 100644 --- a/populate_db.py +++ b/populate_db.py @@ -44,7 +44,7 @@ task_1_1 = Task.create(order_num=1, name='Очень старая задача #1', description='Когда трава была зеленее', base_score=1, task_set=prev_task_set) task_1_2 = Task.create(order_num=2, name='Очень старая задача #2', description='И задачи были забористей', base_score=1, task_set=prev_task_set) -task_2_1 = Task.create(order_num=1, name='Задача первая', description='Самая первая', base_score=1, task_set=task_set) +task_2_1 = Task.create(order_num=1, name='Задача первая', description='Начнём с простого: $$2+2=?$$', base_score=1, task_set=task_set) task_2_2 = Task.create(order_num=3, name='Задача последняя', description='Хардкор', base_score=42, task_set=task_set) task_2_3 = Task.create(order_num=2, name='Задача два', description='Посложнее', base_score=5, task_set=task_set) @@ -53,7 +53,7 @@ post_1_1 = Post.create(thread=thread_1, text='Первый нах', date = datetime.datetime.now(), author=student) post_1_2 = Post.create(thread=thread_1, text='Так себе решение. Пока 0 баллов', date = datetime.datetime.now(), author=teacher) -post_1_3 = Post.create(thread=thread_1, text='Ну ладно, 2+2=3', date = datetime.datetime.now(), author=student) -post_1_3 = Post.create(thread=thread_1, text='Ой, 2+2=4', date = datetime.datetime.now(), author=student) +post_1_3 = Post.create(thread=thread_1, text='Ну ладно, \\(2+2=3\\)', date = datetime.datetime.now(), author=student) +post_1_3 = Post.create(thread=thread_1, text='Ой, $$2+2=4$$', date = datetime.datetime.now(), author=student) post_1_4 = Post.create(thread=thread_1, text='Это вот серьёзно сейчас было?', hide_from_student=True, date = datetime.datetime.now(), author=teacher) post_1_5 = Post.create(thread=thread_1, text='Ок, угадал. 1 балл', date = datetime.datetime.now(), author=teacher) From ca28b1ef6b93adedbeaa1ffd8d694bf5863a8caf Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Sun, 23 Jun 2019 19:09:09 +0300 Subject: [PATCH 08/33] Update ennead/utils.py Co-Authored-By: Goldstein --- ennead/utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ennead/utils/__init__.py b/ennead/utils/__init__.py index 09b9c98..5359c9d 100644 --- a/ennead/utils/__init__.py +++ b/ennead/utils/__init__.py @@ -48,7 +48,7 @@ def wrapped(*args: Any, **kwargs: Any) -> Response: return wrapped def require_student(func: Callable) -> Callable: - """Make endpoint require logged in teacher""" + """Make endpoint require logged in student""" # pylint: disable=inconsistent-return-statements @wraps(func) From d66ac3f45ee669c3d1c5e85c29e8a3bce6cd51e3 Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Sun, 23 Jun 2019 19:16:04 +0300 Subject: [PATCH 09/33] shorten thread urls --- ennead/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ennead/app.py b/ennead/app.py index 020206f..0f708e3 100644 --- a/ennead/app.py +++ b/ennead/app.py @@ -64,9 +64,9 @@ def create_app(config_path: Optional[str] = None) -> Flask: app.add_url_rule('/login', 'login', login, methods=['POST']) app.add_url_rule('/logout', 'logout', logout) - app.add_url_rule('/student_thread_page/', 'student_thread_page', student_thread_page) - app.add_url_rule('/teacher_thread_page//', 'teacher_thread_page', teacher_thread_page) - app.add_url_rule('/send_post_to_thread/', 'send_post_to_thread', send_post_to_thread, methods=['POST']) + app.add_url_rule('/thread/', 'student_thread_page', student_thread_page) + app.add_url_rule('/thread//', 'teacher_thread_page', teacher_thread_page) + app.add_url_rule('/thread/', 'send_post_to_thread', send_post_to_thread, methods=['POST']) app.add_url_rule('/update_score/', 'update_score', update_score, methods=['POST']) app.add_url_rule('/adm/tasks', 'adm_task_list_page', adm_task_list_page) From 767bd57f0aa3fc61d1f470838ee3e3fc906373e7 Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Tue, 25 Jun 2019 16:34:47 +0300 Subject: [PATCH 10/33] refactor and merge endpoints for thread --- .gitignore | 4 ++ ennead/app.py | 8 ++-- ennead/static/style.css | 1 + ennead/templates/dialogue.html | 24 +++-------- ennead/views/dialogue.py | 74 ++++++++++++++++++---------------- 5 files changed, 53 insertions(+), 58 deletions(-) diff --git a/.gitignore b/.gitignore index 53254ec..d81c451 100644 --- a/.gitignore +++ b/.gitignore @@ -97,6 +97,10 @@ venv.bak/ # Rope project settings .ropeproject +# Sublime project settings +*.sublime-project +*.sublime-workspace + # mkdocs documentation /site diff --git a/ennead/app.py b/ennead/app.py index 0f708e3..cf8f7c8 100644 --- a/ennead/app.py +++ b/ennead/app.py @@ -11,7 +11,7 @@ from ennead.views.admin import adm_task_list_page, task_edit_page, task_edit, task_delete from ennead.views.system import render_markdown_endpoint from ennead.views.file import upload_file, uploaded_file, files_page -from ennead.views.dialogue import student_thread_page, teacher_thread_page, send_post_to_thread, update_score +from ennead.views.dialogue import thread_page, post_to_thread from ennead.models.base import database from ennead.models.file import File @@ -64,10 +64,8 @@ def create_app(config_path: Optional[str] = None) -> Flask: app.add_url_rule('/login', 'login', login, methods=['POST']) app.add_url_rule('/logout', 'logout', logout) - app.add_url_rule('/thread/', 'student_thread_page', student_thread_page) - app.add_url_rule('/thread//', 'teacher_thread_page', teacher_thread_page) - app.add_url_rule('/thread/', 'send_post_to_thread', send_post_to_thread, methods=['POST']) - app.add_url_rule('/update_score/', 'update_score', update_score, methods=['POST']) + app.add_url_rule('/thread//', 'thread', thread_page) + app.add_url_rule('/thread//', 'post_to_thread', post_to_thread, methods=['POST']) app.add_url_rule('/adm/tasks', 'adm_task_list_page', adm_task_list_page) app.add_url_rule('/adm/tasks/', 'task_edit_page', task_edit_page) diff --git a/ennead/static/style.css b/ennead/static/style.css index 3b11f92..b42d58a 100644 --- a/ennead/static/style.css +++ b/ennead/static/style.css @@ -54,6 +54,7 @@ background-color: #ccf; } .post-teacher-hidden { + /* ToDo: not sure, that it's good to use right align for code */ text-align: right; background-color: lightgray; } diff --git a/ennead/templates/dialogue.html b/ennead/templates/dialogue.html index 4bfcf6c..812058d 100644 --- a/ennead/templates/dialogue.html +++ b/ennead/templates/dialogue.html @@ -12,7 +12,7 @@

{{ task.order_num }}. {{ task.name }}

- {% for post in thread.ordered_posts(show_hidden=g.user.is_teacher): %} + {% for post in posts: %} {% if post.author.is_teacher %} {% if post.hide_from_student %} {% set post_klass = 'post-teacher-hidden' %} @@ -37,7 +37,7 @@

{{ task.order_num }}. {{ task.name }}


-
+ {% if g.user.is_teacher: %}
@@ -46,27 +46,15 @@

{{ task.order_num }}. {{ task.name }}

Скрыть комментарий от школьника
- {% endif %} - -
-
-
- {% if g.user.is_teacher %} -
-
-
-
-
- -
-
-
+ {% endif %} + +
- {% endif %} +
{% endblock %} diff --git a/ennead/views/dialogue.py b/ennead/views/dialogue.py index 443e39e..68f8659 100644 --- a/ennead/views/dialogue.py +++ b/ennead/views/dialogue.py @@ -12,47 +12,51 @@ from ennead.models.task import Task from ennead.models.thread import Thread, Post +def has_access_to_thread(student_id): + return g.user.is_teacher or (g.user.is_student and (g.user.id == student_id)) -@require_student -def student_thread_page(task_id: int) -> Response: - """GET /student_thread_page/{task}: show student's thread for a specified task""" - task = Task.get_by_id(task_id) - thread, _ = Thread.get_or_create(task=task_id, student=g.user) - return render_template('dialogue.html', task=task, thread=thread) +def markdown_valid(text): + try: + render_markdown(text) + return True + except: + # Not sure is it possible to create incorrect markdown + return False -@require_teacher -def teacher_thread_page(task_id: int, student_id: int) -> Response: - """GET /teacher_thread_page/{task}: show student's thread for a specified task""" +def get_thread(task_id, student_id): task = Task.get_by_id(task_id) student = User.get_by_id(student_id) - thread, _ = Thread.get_or_create(task=task_id, student=student) - return render_template('dialogue.html', task=task, thread=thread) + thread, _ = Thread.get_or_create(task=task, student=student) + return thread + +def correct_message(text): + return (len(text) != 0) and markdown_valid(text) +@require_logged_in +def thread_page(task_id: int, student_id: int) -> Response: + """GET /thread/{task}/{student}: show student's thread for a specified task""" + if not has_access_to_thread(student_id): + return redirect(url_for('index')) + thread = get_thread(task_id, student_id) + posts = thread.ordered_posts(show_hidden=g.user.is_teacher) + return render_template('dialogue.html', thread=thread, task=thread.task, student=thread.student, posts=posts) @require_logged_in -def send_post_to_thread(thread_id: int) -> Response: - thread = Thread.get_by_id(thread_id) +def post_to_thread(task_id: int, student_id: int) -> Response: + if not has_access_to_thread(student_id): + return redirect(url_for('index')) + thread = get_thread(task_id, student_id) + text = request.form['text'].strip() - correct_message = (len(text) != 0) - try: - render_markdown(text) - except: - correct_message = False - # Not sure is it possible to create incorrect markdown, but if yes, we shouldn't store it + hide_from_student = g.user.is_teacher and request.form.get('hide_from_student', False) + score = request.form.get('score', thread.score) + + if correct_message(text): + thread.update(score=score).execute() + post = Post.create(text=text, date=datetime.datetime.now(), + author=g.user, thread=thread, + hide_from_student=hide_from_student) + else: # ToDo: redirect back to a dialogue, restoring a message draft not to lose it - if correct_message and (g.user.is_teacher or (g.user.is_student and thread.student == g.user)): - hide_from_student = False - if g.user.is_teacher: - hide_from_student = request.form.get('hide_from_student', False) - post = Post.create(text=text, date=datetime.datetime.now(), author=g.user, thread=thread, hide_from_student=hide_from_student) - if g.user.is_student: - return redirect(url_for('student_thread_page', task_id=thread.task.id)) - if g.user.is_teacher: - return redirect(url_for('teacher_thread_page', task_id=thread.task.id, student_id=thread.student.id)) - -@require_teacher -def update_score(thread_id: int) -> Response: - thread = Thread.get_by_id(thread_id) - thread.score = request.form.get('score', thread.score) - thread.save() - return redirect(url_for('teacher_thread_page', task_id=thread.task.id, student_id=thread.student.id)) + pass + return redirect(url_for('thread', task_id=task_id, student_id=student_id)) From 7ba0ef6e998977ba7ffbeb49f5f6327a94f9c2c7 Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Tue, 25 Jun 2019 22:39:22 +0300 Subject: [PATCH 11/33] added solution to a task --- ennead/models/task.py | 8 ++++++++ ennead/templates/dialogue.html | 3 +++ populate_db.py | 14 +++++++------- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/ennead/models/task.py b/ennead/models/task.py index be16440..91c241f 100644 --- a/ennead/models/task.py +++ b/ennead/models/task.py @@ -38,6 +38,7 @@ class Task(BaseModel): Attributes: name: human-readable name of `Task` description: `Task` description in Markdown + solution: correct `Task` solution and comments about inspection of works base_score: basic maximal score for `Task` task_set: set this `Task` belongs to order_num: order of this `Task` in a `TaskSet` @@ -46,6 +47,7 @@ class Task(BaseModel): name: str = CharField() description: str = TextField() + solution: str = TextField() base_score: int = IntegerField() order_num: int = IntegerField() task_set: TaskSet = ForeignKeyField(TaskSet, backref='tasks') @@ -57,3 +59,9 @@ def html_description(self): """`Task` description in HTML""" return render_markdown(self.description) + + @property + def solution_html_description(self): + """`Task` solution in HTML""" + + return render_markdown(self.solution) \ No newline at end of file diff --git a/ennead/templates/dialogue.html b/ennead/templates/dialogue.html index 812058d..c8dfe50 100644 --- a/ennead/templates/dialogue.html +++ b/ennead/templates/dialogue.html @@ -8,6 +8,9 @@

{{ task.order_num }}. {{ task.name }}

Балл: {{ thread.score }} из {{ task.base_score }}

{{ task.html_description|safe }}
+ {% if g.user.is_teacher %} +
{{ task.solution_html_description|safe }}
+ {% endif %}
diff --git a/populate_db.py b/populate_db.py index 20a4816..a581e13 100644 --- a/populate_db.py +++ b/populate_db.py @@ -41,18 +41,18 @@ prev_task_set = TaskSet.create(name='Старая заочка', active=True) task_set = TaskSet.create(name='Текущая заочка', active=True) -task_1_1 = Task.create(order_num=1, name='Очень старая задача #1', description='Когда трава была зеленее', base_score=1, task_set=prev_task_set) -task_1_2 = Task.create(order_num=2, name='Очень старая задача #2', description='И задачи были забористей', base_score=1, task_set=prev_task_set) +task_1_1 = Task.create(order_num=1, name='Очень старая задача #1', description='Когда трава была зеленее', solution="Как-нибудь проверим", base_score=1, task_set=prev_task_set) +task_1_2 = Task.create(order_num=2, name='Очень старая задача #2', description='И задачи были забористей', solution="Как-нибудь проверим", base_score=1, task_set=prev_task_set) -task_2_1 = Task.create(order_num=1, name='Задача первая', description='Начнём с простого: $$2+2=?$$', base_score=1, task_set=task_set) -task_2_2 = Task.create(order_num=3, name='Задача последняя', description='Хардкор', base_score=42, task_set=task_set) -task_2_3 = Task.create(order_num=2, name='Задача два', description='Посложнее', base_score=5, task_set=task_set) +task_2_1 = Task.create(order_num=1, name='Задача первая', description='Начнём с простого: $$2+2=?$$', solution="Ноль баллов обычно.\n\nПолный балл за $$2+2=4$$", base_score=1, task_set=task_set) +task_2_2 = Task.create(order_num=3, name='Задача последняя', description='Хардкор', solution="Как-нибудь проверим", base_score=42, task_set=task_set) +task_2_3 = Task.create(order_num=2, name='Задача два', description='Посложнее', solution="Как-нибудь проверим", base_score=5, task_set=task_set) thread_1 = Thread.create(student=student, task=task_2_1) thread_2 = Thread.create(student=student, task=task_2_2) -post_1_1 = Post.create(thread=thread_1, text='Первый нах', date = datetime.datetime.now(), author=student) -post_1_2 = Post.create(thread=thread_1, text='Так себе решение. Пока 0 баллов', date = datetime.datetime.now(), author=teacher) +post_1_1 = Post.create(thread=thread_1, text='Первый коммент', date = datetime.datetime.now(), author=student) +post_1_2 = Post.create(thread=thread_1, text='Так себе "решение". Пока 0 баллов', date = datetime.datetime.now(), author=teacher) post_1_3 = Post.create(thread=thread_1, text='Ну ладно, \\(2+2=3\\)', date = datetime.datetime.now(), author=student) post_1_3 = Post.create(thread=thread_1, text='Ой, $$2+2=4$$', date = datetime.datetime.now(), author=student) post_1_4 = Post.create(thread=thread_1, text='Это вот серьёзно сейчас было?', hide_from_student=True, date = datetime.datetime.now(), author=teacher) From c630d840d2fb4466e20ea57595c96422d9363735 Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Tue, 25 Jun 2019 22:59:08 +0300 Subject: [PATCH 12/33] more explicity about conditions to change score --- ennead/views/dialogue.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ennead/views/dialogue.py b/ennead/views/dialogue.py index 68f8659..61d5859 100644 --- a/ennead/views/dialogue.py +++ b/ennead/views/dialogue.py @@ -49,10 +49,13 @@ def post_to_thread(task_id: int, student_id: int) -> Response: text = request.form['text'].strip() hide_from_student = g.user.is_teacher and request.form.get('hide_from_student', False) - score = request.form.get('score', thread.score) if correct_message(text): - thread.update(score=score).execute() + if g.user.is_teacher: + score = request.form.get('score') + if thread.score != score: + thread.update(score=score).execute() + post = Post.create(text=text, date=datetime.datetime.now(), author=g.user, thread=thread, hide_from_student=hide_from_student) From 3cf4e69960e7a9aa02fa3e64e2ca2ceaa45d96b3 Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Tue, 25 Jun 2019 22:52:25 +0300 Subject: [PATCH 13/33] refine onload callback registration --- ennead/static/app.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ennead/static/app.js b/ennead/static/app.js index 96814ba..b07c56a 100644 --- a/ennead/static/app.js +++ b/ennead/static/app.js @@ -1,9 +1,13 @@ -window.onload = function(){ - let textarea = document.getElementById('post-textarea'); - textarea.addEventListener('keydown', function(event) { - if (event.ctrlKey && event.keyCode == 13) { // Ctrl-Enter pressed - let form = document.getElementById('post-form'); - form.submit(); +window.addEventListener("load", function() { + let forms = document.getElementsByTagName('form'); + for (let form of forms) { + let textareas = form.getElementsByTagName('textarea'); + for (let textarea of textareas) { + textarea.addEventListener('keydown', function(event) { + if (event.ctrlKey && event.keyCode == 13) { // Ctrl-Enter pressed + form.submit(); + } + }); } - }); -}; + } +}); From 133fe6412e56c4f862e54597b6d3e5070837a39a Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Wed, 26 Jun 2019 13:33:12 +0300 Subject: [PATCH 14/33] teacher messages are now left-aligned, but message containers have different offsets --- ennead/static/style.css | 3 --- ennead/templates/dialogue.html | 30 ++++++++++++++++-------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/ennead/static/style.css b/ennead/static/style.css index b42d58a..ee74877 100644 --- a/ennead/static/style.css +++ b/ennead/static/style.css @@ -50,12 +50,9 @@ padding: 10px; } .post-teacher { - text-align: right; background-color: #ccf; } .post-teacher-hidden { - /* ToDo: not sure, that it's good to use right align for code */ - text-align: right; background-color: lightgray; } .post-student { diff --git a/ennead/templates/dialogue.html b/ennead/templates/dialogue.html index c8dfe50..09da313 100644 --- a/ennead/templates/dialogue.html +++ b/ennead/templates/dialogue.html @@ -13,18 +13,20 @@

{{ task.order_num }}. {{ task.name }}

{% endif %}
-
-
- {% for post in posts: %} - {% if post.author.is_teacher %} - {% if post.hide_from_student %} - {% set post_klass = 'post-teacher-hidden' %} - {% else %} - {% set post_klass = 'post-teacher' %} - {% endif %} - {%else%} - {% set post_klass = 'post-student' %} - {%endif%} + {% for post in posts: %} + {% if post.author.is_teacher %} + {% set offset_class = 'offset-1' %} + {% if post.hide_from_student %} + {% set post_klass = 'post-teacher-hidden' %} + {% else %} + {% set post_klass = 'post-teacher' %} + {% endif %} + {%else%} + {% set offset_class = '' %} + {% set post_klass = 'post-student' %} + {%endif%} +
+
{% if g.user.is_teacher %} @@ -34,9 +36,9 @@

{{ task.order_num }}. {{ task.name }}

{{ post.html_description|safe }}
- {% endfor %} +
-
+ {% endfor %}

From beac9209048352bb4c6cc498ccbcf3af60f6660b Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Thu, 27 Jun 2019 09:59:13 +0300 Subject: [PATCH 15/33] rename Task.solution_html_description --> html_solution, Post.html_description --> html_text --- ennead/models/task.py | 2 +- ennead/models/thread.py | 4 ++-- ennead/templates/dialogue.html | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ennead/models/task.py b/ennead/models/task.py index 91c241f..d4ca85d 100644 --- a/ennead/models/task.py +++ b/ennead/models/task.py @@ -61,7 +61,7 @@ def html_description(self): return render_markdown(self.description) @property - def solution_html_description(self): + def html_solution(self): """`Task` solution in HTML""" return render_markdown(self.solution) \ No newline at end of file diff --git a/ennead/models/thread.py b/ennead/models/thread.py index 86a2790..8027c05 100644 --- a/ennead/models/thread.py +++ b/ennead/models/thread.py @@ -54,7 +54,7 @@ class Post(BaseModel): thread: Thread = ForeignKeyField(Thread, backref='posts') @property - def html_description(self): - """`Post` description in HTML""" + def html_text(self): + """`Post` text in HTML""" return render_markdown(self.text) diff --git a/ennead/templates/dialogue.html b/ennead/templates/dialogue.html index 09da313..c87e051 100644 --- a/ennead/templates/dialogue.html +++ b/ennead/templates/dialogue.html @@ -9,7 +9,7 @@

{{ task.order_num }}. {{ task.name }}

Балл: {{ thread.score }} из {{ task.base_score }}

{{ task.html_description|safe }}
{% if g.user.is_teacher %} -
{{ task.solution_html_description|safe }}
+
{{ task.html_solution|safe }}
{% endif %}
@@ -34,7 +34,7 @@

{{ task.order_num }}. {{ task.name }}

{% endif %}
-
{{ post.html_description|safe }}
+
{{ post.html_text|safe }}
From 8716cc7e02fd7dec67333931206a3dbf33c28a14 Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Thu, 27 Jun 2019 10:00:19 +0300 Subject: [PATCH 16/33] post_klass --> post_class --- ennead/templates/dialogue.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ennead/templates/dialogue.html b/ennead/templates/dialogue.html index c87e051..0ef365d 100644 --- a/ennead/templates/dialogue.html +++ b/ennead/templates/dialogue.html @@ -17,17 +17,17 @@

{{ task.order_num }}. {{ task.name }}

{% if post.author.is_teacher %} {% set offset_class = 'offset-1' %} {% if post.hide_from_student %} - {% set post_klass = 'post-teacher-hidden' %} + {% set post_class = 'post-teacher-hidden' %} {% else %} - {% set post_klass = 'post-teacher' %} + {% set post_class = 'post-teacher' %} {% endif %} {%else%} {% set offset_class = '' %} - {% set post_klass = 'post-student' %} + {% set post_class = 'post-student' %} {%endif%}
-
+
{% if g.user.is_teacher %} From dfab954d58aeef5120ac9361484232ba893cee5c Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Thu, 27 Jun 2019 10:05:25 +0300 Subject: [PATCH 17/33] canonical jinja doesnt want colon in statements --- ennead/templates/dialogue.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ennead/templates/dialogue.html b/ennead/templates/dialogue.html index 0ef365d..d2237b0 100644 --- a/ennead/templates/dialogue.html +++ b/ennead/templates/dialogue.html @@ -13,7 +13,7 @@

{{ task.order_num }}. {{ task.name }}

{% endif %}
- {% for post in posts: %} + {% for post in posts %} {% if post.author.is_teacher %} {% set offset_class = 'offset-1' %} {% if post.hide_from_student %} @@ -44,7 +44,7 @@

{{ task.order_num }}. {{ task.name }}


- {% if g.user.is_teacher: %} + {% if g.user.is_teacher %}