Skip to content

Commit

Permalink
feature: Blur Table for Anonymous Users, Unify Students and Teachers (#…
Browse files Browse the repository at this point in the history
…51)

* feature: Hide groups if configured
* feature: Remove teacher entity
* fix: Urls
* fix: Redirects
* fix: Reformat HTML
* fix: Undefined issue
* fix: Margins and params
* fix: PEP8
* feature: Quick access to submission history
* feature: Non email-based logins are allowed
* fix: Buttons visibility, logout button
  • Loading branch information
worldbeater authored May 14, 2024
1 parent 969cd09 commit 5a538b4
Show file tree
Hide file tree
Showing 32 changed files with 1,195 additions and 1,341 deletions.
1 change: 1 addition & 0 deletions tests/config.defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"TASK_BASE_URL": "http://sovietov.com/kispython",
"FINAL_TASKS": null,
"ENABLE_REGISTRATION": false,
"HIDE_GROUPS": false,
"IMAP_LOGIN": "[email protected]",
"IMAP_PASSWORD": "CHANGE_ME",
"ENABLE_LKS_OAUTH": false,
Expand Down
14 changes: 0 additions & 14 deletions tests/ui/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,20 +116,6 @@ def test_register_off_get(db: AppDatabase, client: FlaskClient):
assert response.status_code == 302


def test_login_off_post(client: FlaskClient, db: AppDatabase):
create_student(db)

response = client.post("/login")
assert response.status_code == 302


def test_login_off_get(client: FlaskClient, db: AppDatabase):
create_student(db)

response = client.get("/login")
assert response.status_code == 302


@mode('registration')
@pytest.mark.parametrize("password, message", [
("1234", "Пароль содержит как минимум 8 символов."),
Expand Down
8 changes: 4 additions & 4 deletions tests/ui/test_teacher.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ def test_login_logout(db: AppDatabase, client: FlaskClient):
assert "login" not in response.request.path
assert "Выгрузка всех присланных сообщений" in response.get_data(as_text=True)

response = client.get("/teacher/logout", follow_redirects=True)
response = client.get("/logout", follow_redirects=True)

assert "login" in response.request.path
assert response.request.path == "/"


def test_false_login(db: AppDatabase, client: FlaskClient):
response = client.post("/teacher/login", data={
response = client.post("/login", data={
"login": "badLogin",
"password": "evenWorsePassword"
}, follow_redirects=True)
Expand Down Expand Up @@ -93,7 +93,7 @@ def test_anonymous_submission(db: AppDatabase, client: FlaskClient):

assert db.groups.get_by_id(gid).title in response.get_data(as_text=True)
assert code in response.get_data(as_text=True)
assert "студент" not in response.get_data(as_text=True)
assert ", студент" not in response.get_data(as_text=True)


def test_invalid_page_redirect(db: AppDatabase, client: FlaskClient):
Expand Down
13 changes: 7 additions & 6 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from flask.testing import FlaskClient

from webapp.managers import TeacherManager
from webapp.managers import StudentManager
from webapp.repositories import AppDatabase


Expand Down Expand Up @@ -57,12 +57,13 @@ def get_tags(


def teacher_login(db: AppDatabase, client: FlaskClient):
login = unique_str()
email = f'{unique_str()}@{unique_str()}.ru'
password = unique_str()
tm = TeacherManager(db.teachers)
tm.create(login, password)
return client.post("/teacher/login", data={
"login": login,
students = StudentManager(None, db.students, db.mailers)
students.create(email, password, True)
db.students.confirm(email)
return client.post("/login", data={
"login": email,
"password": password
}, follow_redirects=True)

Expand Down
31 changes: 31 additions & 0 deletions webapp/alembic/versions/20240514.22-12.students_as_teachers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
Revision ID: 0ed9e0bfabcb
Revises: 30b579748cec
Create Date: 2024-05-14 22:12:06.014972
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '0ed9e0bfabcb'
down_revision = '30b579748cec'
branch_labels = None
depends_on = None


def upgrade():
op.add_column("students", sa.Column("teacher", sa.Boolean, nullable=True))
op.drop_table("teachers")


def downgrade():
op.drop_column("students", "teacher")
op.create_table(
'teachers',
sa.Column("id", sa.Integer, primary_key=True, nullable=False),
sa.Column("login", sa.String, nullable=False),
sa.Column("password_hash", sa.String, nullable=False),
)
1 change: 1 addition & 0 deletions webapp/config.defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"READONLY": false,
"TASK_BASE_URL": "http://kispython.ru/docs",
"ENABLE_REGISTRATION": false,
"HIDE_GROUPS": false,
"IMAP_LOGIN": null,
"IMAP_PASSWORD": null,
"FINAL_TASKS": {
Expand Down
1 change: 1 addition & 0 deletions webapp/dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def __init__(self, config: Config):
self.places_in_group: int = config["PLACES_IN_GROUP"]
self.groups: dict = config["GROUPS"]
self.allow_ip: list[str] = config["ALLOW_IP"]
self.hide_groups: bool = config["HIDE_GROUPS"]

@property
def exam(self) -> bool:
Expand Down
17 changes: 11 additions & 6 deletions webapp/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,21 @@ class StudentMessageForm(FlaskForm):
])


class TeacherLoginForm(FlaskForm):
login = StringField('login', [DataRequired(message="Данное поле не может быть пустым!")])
class StudentLoginForm(FlaskForm):
def __init__(self, lks_oauth_enabled: bool = False, *args, **kwargs):
super().__init__(*args, **kwargs)
self.lks_oauth_enabled = lks_oauth_enabled

login = StringField('email', [
DataRequired(message="Данное поле не может быть пустым!"),
])

password = PasswordField('password', [
DataRequired(message="Данное поле не может быть пустым!"),
Length(min=8, message="Пароль содержит как минимум 8 символов."),
])


class StudentLoginForm(FlaskForm):
class StudentRegisterForm(FlaskForm):
def __init__(self, lks_oauth_enabled: bool = False, *args, **kwargs):
super().__init__(*args, **kwargs)
self.lks_oauth_enabled = lks_oauth_enabled
Expand All @@ -35,14 +41,13 @@ def __init__(self, lks_oauth_enabled: bool = False, *args, **kwargs):
"Убедитесь, что строка не включает пробелы."
))
])

password = PasswordField('password', [
DataRequired(message="Данное поле не может быть пустым!"),
Length(min=8, message="Пароль содержит как минимум 8 символов."),
NoneOf(["12345678", "password"], message="Не используйте такие пароли, как 12345678 и password.")
])


class StudentRegisterForm(StudentLoginForm):
confirm = PasswordField('password', [
DataRequired(message="Данное поле не может быть пустым!"),
EqualTo('password', message="Пароли не совпадают!")
Expand Down
26 changes: 3 additions & 23 deletions webapp/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
TaskStatusDto,
VariantDto
)
from webapp.models import FinalSeed, Group, Message, MessageCheck, Student, Task, TaskStatus, Teacher, Variant
from webapp.models import FinalSeed, Group, Message, MessageCheck, Student, Task, TaskStatus, Variant
from webapp.repositories import (
FinalSeedRepository,
GroupRepository,
Expand All @@ -29,7 +29,6 @@
StudentRepository,
TaskRepository,
TaskStatusRepository,
TeacherRepository,
VariantRepository
)

Expand Down Expand Up @@ -482,10 +481,10 @@ def email_allowed(self, email: str) -> bool:
exists = self.mailers.exists(domain)
return exists

def create(self, email: str, password: str) -> int:
def create(self, email: str, password: str, teacher=False) -> int:
given = password.encode('utf8')
hashed = bcrypt.hashpw(given, bcrypt.gensalt())
student = self.students.create(email, hashed.decode('utf8'))
student = self.students.create(email, hashed.decode('utf8'), teacher)
return student.id


Expand Down Expand Up @@ -599,22 +598,3 @@ def __create_csv(self, table: list[list[str]], delimiter: str):
bom = u"\uFEFF"
value = bom + si.getvalue()
return value


class TeacherManager:
def __init__(self, teachers: TeacherRepository):
self.teachers = teachers

def check_password(self, login: str, password: str) -> Teacher | None:
teacher = self.teachers.find_by_login(login)
if teacher:
given = password.encode('utf8')
actual = teacher.password_hash.encode('utf8')
if bcrypt.checkpw(given, actual):
return teacher

def create(self, login: str, password: str):
given = password.encode('utf8')
hashed = bcrypt.hashpw(given, bcrypt.gensalt())
teacher = self.teachers.create(login, hashed.decode('utf8'))
return teacher.id
8 changes: 1 addition & 7 deletions webapp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,6 @@ class FinalSeed(Base):
group = sa.Column("group", sa.Integer, sa.ForeignKey('groups.id'), primary_key=True, nullable=False)


class Teacher(Base):
__tablename__ = "teachers"
id = sa.Column("id", sa.Integer, primary_key=True, nullable=False)
login = sa.Column("login", sa.String, nullable=False)
password_hash = sa.Column("password_hash", sa.String, nullable=False)


class Student(Base):
__tablename__ = "students"
id = sa.Column("id", sa.Integer, primary_key=True, nullable=False)
Expand All @@ -139,6 +132,7 @@ class Student(Base):
password_hash = sa.Column("password_hash", sa.String, nullable=True)
unconfirmed_hash = sa.Column("unconfirmed_hash", sa.String, nullable=True)
blocked = sa.Column("blocked", sa.Boolean, nullable=False)
teacher = sa.Column("teacher", sa.Boolean, nullable=True)


class Mailer(Base):
Expand Down
30 changes: 2 additions & 28 deletions webapp/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
Student,
Task,
TaskStatus,
Teacher,
Variant,
create_session_maker
)
Expand Down Expand Up @@ -457,30 +456,6 @@ def delete_final_seed(self, group: int):
.delete()


class TeacherRepository:
def __init__(self, db: DbContextManager):
self.db = db

def get_by_id(self, id: int) -> Teacher | None:
with self.db.create_session() as session:
teacher = session.query(Teacher).get(id)
return teacher

def find_by_login(self, login: str) -> Teacher | None:
with self.db.create_session() as session:
teacher = session.query(Teacher) \
.filter_by(login=login) \
.first()
return teacher

def create(self, login: str, password: str):
with self.db.create_session() as session:
teacher = Teacher(login=login,
password_hash=password)
session.add(teacher)
return teacher


class StudentRepository:
def __init__(self, db: DbContextManager):
self.db = db
Expand Down Expand Up @@ -524,10 +499,10 @@ def confirm(self, email: str):
unconfirmed_hash=None,
))

def create(self, email: str, password: str) -> Student:
def create(self, email: str, password: str, teacher=False) -> Student:
email = email.lower()
with self.db.create_session() as session:
student = Student(email=email, unconfirmed_hash=password, blocked=False)
student = Student(email=email, unconfirmed_hash=password, teacher=teacher, blocked=False)
session.add(student)
return student

Expand Down Expand Up @@ -580,6 +555,5 @@ def __init__(self, get_connection: Callable[[], str]):
self.messages = MessageRepository(db)
self.checks = MessageCheckRepository(db)
self.seeds = FinalSeedRepository(db)
self.teachers = TeacherRepository(db)
self.students = StudentRepository(db)
self.mailers = MailerRepository(db)
2 changes: 1 addition & 1 deletion webapp/templates/error.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
</div>
</div>
</div>
{% endblock %}
{% endblock %}
Loading

0 comments on commit 5a538b4

Please sign in to comment.