diff --git a/pyproject.toml b/pyproject.toml index 6763702..8d3cfad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ dependencies = [ 'PyQt6==6.7.1', 'dishka==1.4.0', 'tomli-w==1.1.0', + 'faker==33.1.0', ] [project.optional-dependencies] diff --git a/resources/forms/register.ui b/resources/forms/register.ui index 2083dce..af0dd27 100644 --- a/resources/forms/register.ui +++ b/resources/forms/register.ui @@ -92,6 +92,13 @@ + + + + Заполнить тестовыми данными + + + diff --git a/src/student_journal/adapters/load_test_data.py b/src/student_journal/adapters/load_test_data.py new file mode 100644 index 0000000..d8cb924 --- /dev/null +++ b/src/student_journal/adapters/load_test_data.py @@ -0,0 +1,117 @@ +import datetime +import random +from dataclasses import dataclass +from datetime import date, timedelta + +from faker import Faker + +from student_journal.application.hometask.create_home_task import ( + CreateHomeTask, + NewHomeTask, +) +from student_journal.application.lesson.create_lesson import CreateLesson, NewLesson +from student_journal.application.student.create_student import CreateStudent, NewStudent +from student_journal.application.student.read_student import ReadStudent +from student_journal.application.subject.create_subject import CreateSubject, NewSubject +from student_journal.application.teacher import CreateTeacher, NewTeacher +from student_journal.domain.value_object.student_id import StudentId + +fake = Faker(locale="ru_RU") + +SUBJECTS = [ + "Математика", + "Физика", + "Информатика", + "Химия", + "Физкультура", + "История", + "Обществознание", + "Биология", + "Английский язык", + "Русский язык", +] + + +@dataclass(slots=True, frozen=True) +class TestDataLoader: + create_student: CreateStudent + create_teacher: CreateTeacher + create_subject: CreateSubject + read_student: ReadStudent + create_lesson: CreateLesson + create_home_task: CreateHomeTask + + def insert_student(self) -> StudentId: + student_name = fake.name() + student_address = fake.address() + student_age = fake.random_int(min=14, max=18) + student_id = self.create_student.execute( + NewStudent( + age=student_age, + name=student_name, + home_address=student_address, + avatar=None, + ), + ) + student = self.read_student.execute(student_id) + return student.student_id + + def insert_data(self, student_id: StudentId) -> None: + teacher_names = [fake.name() for _ in range(10)] + teachers = [] + student = self.read_student.execute(student_id) + + for name in teacher_names: + teacher = self.create_teacher.execute( + NewTeacher( + full_name=name, + avatar=None, + ), + ) + teachers.append(teacher) + + subjects = [] + for teacher_id, subject in zip(teachers, SUBJECTS, strict=True): + new_subject = self.create_subject.execute( + NewSubject( + teacher_id=teacher_id, + title=subject, + ), + ) + subjects.append(new_subject) + + da_te = date.today() # noqa: DTZ011 + week_start = da_te - timedelta(days=da_te.weekday()) + week_end = week_start + timedelta(days=6) + week = [ + week_start + datetime.timedelta(days=x) + for x in range((week_end - week_start).days) + ] + + for da_te in week: + for hour in range(8, 14): + time = datetime.time(hour=hour) + lesson_subject = random.choice(subjects) # noqa: S311 + at = datetime.datetime.combine( + da_te, + time, + tzinfo=student.time_zone, + ) + lesson_id = self.create_lesson.execute( + NewLesson( + subject_id=lesson_subject, + at=at, + mark=random.randint(2, 5), # noqa: S311 + note=fake.text(), + room=random.randint(1000, 4000), # noqa: S311 + ), + ) + + if random.random() > 0.5: # noqa: PLR2004, S311 + self.create_home_task.execute( + NewHomeTask( + lesson_id=lesson_id, + description="Домашнее задание!", + is_done=random.choice([True, False]), # noqa: S311 + ), + ) diff --git a/src/student_journal/application/student/read_current_student.py b/src/student_journal/application/student/read_current_student.py new file mode 100644 index 0000000..c8ba6c0 --- /dev/null +++ b/src/student_journal/application/student/read_current_student.py @@ -0,0 +1,21 @@ +from dataclasses import dataclass + +from student_journal.application.common.id_provider import IdProvider +from student_journal.application.common.student_gateway import StudentGateway +from student_journal.application.converters.student import convert_student_to_read_model +from student_journal.application.models.student import StudentReadModel + + +@dataclass(slots=True) +class ReadCurrentStudent: + gateway: StudentGateway + idp: IdProvider + + def execute(self) -> StudentReadModel: + current_student_id = self.idp.get_id() + student = self.gateway.read_student( + current_student_id, + ) + + avg = self.gateway.get_overall_avg_mark() + return convert_student_to_read_model(student, avg, student.get_timezone()) diff --git a/src/student_journal/application/student/read_student.py b/src/student_journal/application/student/read_student.py index 25109df..2a381a2 100644 --- a/src/student_journal/application/student/read_student.py +++ b/src/student_journal/application/student/read_student.py @@ -1,21 +1,18 @@ from dataclasses import dataclass -from student_journal.application.common.id_provider import IdProvider from student_journal.application.common.student_gateway import StudentGateway from student_journal.application.converters.student import convert_student_to_read_model from student_journal.application.models.student import StudentReadModel +from student_journal.domain.value_object.student_id import StudentId @dataclass(slots=True) class ReadStudent: gateway: StudentGateway - idp: IdProvider - def execute(self) -> StudentReadModel: - current_student_id = self.idp.get_id() + def execute(self, student_id: StudentId) -> StudentReadModel: student = self.gateway.read_student( - current_student_id, + student_id, ) - avg = self.gateway.get_overall_avg_mark() return convert_student_to_read_model(student, avg, student.get_timezone()) diff --git a/src/student_journal/bootstrap/di/adapter_provider.py b/src/student_journal/bootstrap/di/adapter_provider.py index 33a6c34..965242f 100644 --- a/src/student_journal/bootstrap/di/adapter_provider.py +++ b/src/student_journal/bootstrap/di/adapter_provider.py @@ -2,6 +2,7 @@ from student_journal.adapters.error_locator import ErrorLocator, SimpleErrorLocator from student_journal.adapters.id_provider import FileIdProvider +from student_journal.adapters.load_test_data import TestDataLoader from student_journal.application.common.id_provider import IdProvider @@ -15,3 +16,4 @@ class AdapterProvider(Provider): provides=ErrorLocator, scope=Scope.APP, ) + data_loader = provide(TestDataLoader) diff --git a/src/student_journal/bootstrap/di/command_provider.py b/src/student_journal/bootstrap/di/command_provider.py index cde09f6..bf6b69a 100644 --- a/src/student_journal/bootstrap/di/command_provider.py +++ b/src/student_journal/bootstrap/di/command_provider.py @@ -18,6 +18,7 @@ from student_journal.application.lesson.read_lessons_for_week import ReadLessonsForWeek from student_journal.application.lesson.update_lesson import UpdateLesson from student_journal.application.student.create_student import CreateStudent +from student_journal.application.student.read_current_student import ReadCurrentStudent from student_journal.application.student.read_student import ReadStudent from student_journal.application.student.update_student import UpdateStudent from student_journal.application.subject.create_subject import CreateSubject @@ -39,6 +40,7 @@ class CommandProvider(Provider): commands = provide_all( CreateStudent, + ReadCurrentStudent, ReadStudent, UpdateStudent, CreateTeacher, diff --git a/src/student_journal/presentation/ui/edit_lesson.py b/src/student_journal/presentation/ui/edit_lesson.py index e9fcac4..3f08a5d 100644 --- a/src/student_journal/presentation/ui/edit_lesson.py +++ b/src/student_journal/presentation/ui/edit_lesson.py @@ -14,6 +14,7 @@ def setupUi(self, *args, **kwargs): def set_invariants(self): self.room_spinbox.setMinimum(MIN_ROOM) + self.room_spinbox.setMaximum(1_000_000) self.mark_spinbox.setMinimum(0) self.mark_spinbox.setValue(0) diff --git a/src/student_journal/presentation/ui/raw/register_ui.py b/src/student_journal/presentation/ui/raw/register_ui.py index 2982353..aad16f0 100644 --- a/src/student_journal/presentation/ui/raw/register_ui.py +++ b/src/student_journal/presentation/ui/raw/register_ui.py @@ -1,6 +1,6 @@ # Form implementation generated from reading ui file 'register.ui' # -# Created by: PyQt6 UI code generator 6.4.2 +# Created by: PyQt6 UI code generator 6.7.1 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. @@ -58,6 +58,9 @@ def setupUi(self, Register): self.submit_btn.setSizePolicy(sizePolicy) self.submit_btn.setObjectName("submit_btn") self.formLayout.setWidget(6, QtWidgets.QFormLayout.ItemRole.FieldRole, self.submit_btn) + self.insert_test_data = QtWidgets.QPushButton(parent=Register) + self.insert_test_data.setObjectName("insert_test_data") + self.formLayout.setWidget(7, QtWidgets.QFormLayout.ItemRole.FieldRole, self.insert_test_data) self.gridLayout.addLayout(self.formLayout, 5, 0, 1, 1) self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setContentsMargins(-1, -1, -1, 15) @@ -112,5 +115,6 @@ def retranslateUi(self, Register): self.label_5.setText(_translate("Register", "Предпросмотр")) self.avatar_preview.setText(_translate("Register", "TextLabel")) self.submit_btn.setText(_translate("Register", "Создать аккаунт")) + self.insert_test_data.setText(_translate("Register", "Заполнить тестовыми данными")) self.label_7.setText(_translate("Register", "Добро пожаловать")) self.label_8.setText(_translate("Register", "Расскажите нам о себе")) diff --git a/src/student_journal/presentation/widget/lesson/edit_lesson.py b/src/student_journal/presentation/widget/lesson/edit_lesson.py index 8752362..8c79a01 100644 --- a/src/student_journal/presentation/widget/lesson/edit_lesson.py +++ b/src/student_journal/presentation/widget/lesson/edit_lesson.py @@ -16,7 +16,7 @@ UpdatedLesson, UpdateLesson, ) -from student_journal.application.student.read_student import ReadStudent +from student_journal.application.student.read_current_student import ReadCurrentStudent from student_journal.application.subject.read_subject import ReadSubject from student_journal.application.subject.read_subjects import ReadSubjects from student_journal.domain.value_object.lesson_id import LessonId @@ -39,7 +39,7 @@ def __init__( self.setup_ui() with self.container() as r_container: - command = r_container.get(ReadStudent) + command = r_container.get(ReadCurrentStudent) student = command.execute() self.tzinfo = student.time_zone diff --git a/src/student_journal/presentation/widget/lesson/schedule.py b/src/student_journal/presentation/widget/lesson/schedule.py index ad94363..4b23a18 100644 --- a/src/student_journal/presentation/widget/lesson/schedule.py +++ b/src/student_journal/presentation/widget/lesson/schedule.py @@ -195,6 +195,7 @@ def copy_lesson_to_new_date(self, lesson_id: LessonId, column: int) -> None: ), mark=None, note=None, + room=lesson.room, ) create_command = r_container.get(CreateLesson) @@ -334,8 +335,8 @@ def copy_to_next_week(self) -> None: new_lesson = NewLesson( subject_id=lesson.subject_id, at=lesson.at + timedelta(weeks=1), - mark=lesson.mark, - note=lesson.note, + mark=None, + note=None, room=lesson.room, ) command.execute(new_lesson) diff --git a/src/student_journal/presentation/widget/main_window.py b/src/student_journal/presentation/widget/main_window.py index 15fd93b..d5fdbb5 100644 --- a/src/student_journal/presentation/widget/main_window.py +++ b/src/student_journal/presentation/widget/main_window.py @@ -1,13 +1,10 @@ -from uuid import UUID - from dishka import Container from PyQt6.QtWidgets import QMainWindow, QStackedWidget -from student_journal.adapters.id_provider import FileIdProvider +from student_journal.application.common.id_provider import IdProvider from student_journal.application.exceptions.student import ( StudentIsNotAuthenticatedError, ) -from student_journal.domain.value_object.student_id import StudentId from student_journal.presentation.widget.dashboard import Dashboard from student_journal.presentation.widget.student.register import Register @@ -19,7 +16,7 @@ def __init__(self, container: Container) -> None: self.container = container with self.container() as r_container: - self.idp = r_container.get(FileIdProvider) + self.idp: IdProvider = r_container.get(IdProvider) self.dashboard: None | Dashboard = None self.register_form = Register(container) @@ -46,7 +43,5 @@ def display_dashboard(self) -> None: self.stacked_widget.addWidget(self.dashboard) self.stacked_widget.setCurrentWidget(self.dashboard) - def finish_register(self, student_id: str) -> None: - student_id_uuid = StudentId(UUID(student_id)) - self.idp.save(student_id_uuid) + def finish_register(self) -> None: self.display_dashboard() diff --git a/src/student_journal/presentation/widget/student/edit_student.py b/src/student_journal/presentation/widget/student/edit_student.py index 1151a7c..aa1298d 100644 --- a/src/student_journal/presentation/widget/student/edit_student.py +++ b/src/student_journal/presentation/widget/student/edit_student.py @@ -3,7 +3,7 @@ from PyQt6.QtWidgets import QFileDialog, QWidget from student_journal.adapters.error_locator import ErrorLocator -from student_journal.application.student.read_student import ReadStudent +from student_journal.application.student.read_current_student import ReadCurrentStudent from student_journal.application.student.update_student import ( UpdatedStudent, UpdateStudent, @@ -38,7 +38,7 @@ def __init__(self, container: Container) -> None: def load_student(self) -> None: with self.container() as r_container: - command = r_container.get(ReadStudent) + command = r_container.get(ReadCurrentStudent) student = command.execute() self.ui.name_input.setText(student.name) @@ -58,7 +58,7 @@ def load_student(self) -> None: def refresh_avg_mark(self) -> None: with self.container() as r_container: - command = r_container.get(ReadStudent) + command = r_container.get(ReadCurrentStudent) student = command.execute() self.ui.avg_mark.setValue(student.student_overall_avg_mark) diff --git a/src/student_journal/presentation/widget/student/register.py b/src/student_journal/presentation/widget/student/register.py index cab7941..dbb834c 100644 --- a/src/student_journal/presentation/widget/student/register.py +++ b/src/student_journal/presentation/widget/student/register.py @@ -1,9 +1,12 @@ from dishka import Container from PyQt6.QtCore import pyqtSignal from PyQt6.QtGui import QPixmap -from PyQt6.QtWidgets import QFileDialog, QWidget +from PyQt6.QtWidgets import QFileDialog, QMessageBox, QWidget from student_journal.adapters.exceptions.ui.student import NameNotSpecifiedError +from student_journal.adapters.id_provider import FileIdProvider +from student_journal.adapters.load_test_data import TestDataLoader +from student_journal.application.common.id_provider import IdProvider from student_journal.application.student.create_student import CreateStudent, NewStudent from student_journal.presentation.ui.register import RegisterUI @@ -31,6 +34,7 @@ def __init__( self.ui.age_input.valueChanged.connect(self.on_age_input) self.ui.address_input.textChanged.connect(self.on_address_input) self.ui.avatar_upload_btn.clicked.connect(self.on_avatar_upload_btn) + self.ui.insert_test_data.clicked.connect(self.on_insert_test_data) self.update_avatar_preview() @@ -46,6 +50,7 @@ def on_submit_btn(self) -> None: raise NameNotSpecifiedError with self.container() as r_container: + idp = r_container.get(FileIdProvider) data = NewStudent( age=self.age, name=self.name, @@ -54,6 +59,41 @@ def on_submit_btn(self) -> None: ) command = r_container.get(CreateStudent) student_id = command.execute(data) + idp.save(student_id) + self.finish.emit(student_id.hex) + + def on_insert_test_data(self) -> None: + reply = QMessageBox.question( + self, + "Вы уверены?", + "Эта операция заполнит приложение тестовыми данными " + "(операция может занять какое-то время). " + "Атомарность операции не гарантируется", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + + if reply == QMessageBox.StandardButton.No: + return + + with self.container() as r_container: + loader = r_container.get(TestDataLoader) + idp = r_container.get(FileIdProvider) + student_id = loader.insert_student() + idp.save(student_id) + + with self.container() as r_container: + loader = r_container.get(TestDataLoader) + idp = r_container.get(IdProvider) + loader.insert_data(idp.get_id()) + + QMessageBox.information( + self, + "Операция завершена", + "Данные загружены", + QMessageBox.StandardButton.Ok, + ) + self.finish.emit(student_id.hex) def on_name_input(self) -> None: diff --git a/tests/unit/student/conftest.py b/tests/unit/student/conftest.py index 1d21a0c..e70961a 100644 --- a/tests/unit/student/conftest.py +++ b/tests/unit/student/conftest.py @@ -4,7 +4,7 @@ from student_journal.application.common.student_gateway import StudentGateway from student_journal.application.common.transaction_manager import TransactionManager from student_journal.application.student.create_student import CreateStudent -from student_journal.application.student.read_student import ReadStudent +from student_journal.application.student.read_current_student import ReadCurrentStudent from student_journal.application.student.update_student import UpdateStudent from unit.student.mock.student_gateway import MockedStudentGateway @@ -29,8 +29,8 @@ def create_student( def read_student( student_gateway: StudentGateway, idp: IdProvider, -) -> ReadStudent: - return ReadStudent( +) -> ReadCurrentStudent: + return ReadCurrentStudent( gateway=student_gateway, idp=idp, ) diff --git a/tests/unit/student/test_student.py b/tests/unit/student/test_student.py index f6dbbd5..96a5614 100644 --- a/tests/unit/student/test_student.py +++ b/tests/unit/student/test_student.py @@ -18,7 +18,7 @@ NAME_MIN_LENGTH, ) from student_journal.application.student.create_student import CreateStudent, NewStudent -from student_journal.application.student.read_student import ReadStudent +from student_journal.application.student.read_current_student import ReadCurrentStudent from student_journal.application.student.update_student import ( UpdatedStudent, UpdateStudent, @@ -87,7 +87,7 @@ def test_create_student_bad_invariants( def test_read_student( - read_student: ReadStudent, + read_student: ReadCurrentStudent, student_gateway: MockedStudentGateway, ) -> None: student_gateway.write_student(STUDENT)