Examtracker 1.2.1__tar.gz → 1.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {examtracker-1.2.1/src/Examtracker.egg-info → examtracker-1.2.3}/PKG-INFO +17 -3
  2. {examtracker-1.2.1 → examtracker-1.2.3}/README.md +12 -1
  3. {examtracker-1.2.1 → examtracker-1.2.3}/pyproject.toml +2 -2
  4. {examtracker-1.2.1 → examtracker-1.2.3}/requirements.txt +3 -0
  5. {examtracker-1.2.1 → examtracker-1.2.3/src/Examtracker.egg-info}/PKG-INFO +17 -3
  6. {examtracker-1.2.1 → examtracker-1.2.3}/src/Examtracker.egg-info/SOURCES.txt +2 -1
  7. {examtracker-1.2.1 → examtracker-1.2.3}/src/Examtracker.egg-info/requires.txt +3 -0
  8. {examtracker-1.2.1 → examtracker-1.2.3}/src/examtracker/app.py +4 -5
  9. examtracker-1.2.3/src/examtracker/data/style.css +44 -0
  10. {examtracker-1.2.1 → examtracker-1.2.3}/src/examtracker/database.py +32 -21
  11. {examtracker-1.2.1 → examtracker-1.2.3}/src/examtracker/database_scheme.py +8 -8
  12. {examtracker-1.2.1 → examtracker-1.2.3}/src/examtracker/main.py +1 -1
  13. {examtracker-1.2.1 → examtracker-1.2.3}/src/examtracker/screens/classscreen.py +45 -17
  14. {examtracker-1.2.1 → examtracker-1.2.3}/src/examtracker/screens/examscreen.py +68 -32
  15. examtracker-1.2.3/src/examtracker/screens/inputscreens.py +32 -0
  16. {examtracker-1.2.1 → examtracker-1.2.3}/src/examtracker/screens/semesterscreen.py +15 -12
  17. {examtracker-1.2.1 → examtracker-1.2.3}/src/examtracker/settings.py +7 -6
  18. {examtracker-1.2.1 → examtracker-1.2.3}/src/examtracker/textual_utils/vimtable.py +0 -1
  19. examtracker-1.2.3/test/test_database.py +281 -0
  20. examtracker-1.2.1/src/examtracker/data/style.css +0 -30
  21. examtracker-1.2.1/src/examtracker/screens/inputscreens.py +0 -56
  22. {examtracker-1.2.1 → examtracker-1.2.3}/LICENSE +0 -0
  23. {examtracker-1.2.1 → examtracker-1.2.3}/setup.cfg +0 -0
  24. {examtracker-1.2.1 → examtracker-1.2.3}/src/Examtracker.egg-info/dependency_links.txt +0 -0
  25. {examtracker-1.2.1 → examtracker-1.2.3}/src/Examtracker.egg-info/entry_points.txt +0 -0
  26. {examtracker-1.2.1 → examtracker-1.2.3}/src/Examtracker.egg-info/top_level.txt +0 -0
  27. {examtracker-1.2.1 → examtracker-1.2.3}/src/examtracker/__init__.py +0 -0
  28. {examtracker-1.2.1 → examtracker-1.2.3}/src/examtracker/__main__.py +0 -0
  29. {examtracker-1.2.1 → examtracker-1.2.3}/src/examtracker/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Examtracker
3
- Version: 1.2.1
3
+ Version: 1.2.3
4
4
  Summary: A Python exam tracker for the Lernphase .It allows you to keep track of all exams you have completed and the scores you achieved.
5
5
  Author-email: Samuel Huwiler <samuel.huwiler@gmx.ch>
6
6
  License: MIT License
@@ -25,7 +25,7 @@ License: MIT License
25
25
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
26
  SOFTWARE.
27
27
 
28
- Requires-Python: >=3.13
28
+ Requires-Python: >=3.10
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  Requires-Dist: textual
@@ -35,8 +35,12 @@ Requires-Dist: pydantic
35
35
  Requires-Dist: pydantic-settings
36
36
  Requires-Dist: pathlib
37
37
  Requires-Dist: pyyaml
38
+ Requires-Dist: rich
39
+ Requires-Dist: pymysql
40
+ Requires-Dist: cryptography
38
41
  Dynamic: license-file
39
42
 
43
+ ![test](https://github.com/Samhuw8a/Examtracker/actions/workflows/test.yml/badge.svg)
40
44
  [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/)
41
45
  <a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
42
46
  ![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2FSamhuw8a%2FExamtracker%2Fmaster%2Fpyproject.toml&color=d8634c)
@@ -92,6 +96,7 @@ By default, the program searches for:
92
96
 
93
97
  ```yaml
94
98
  database_path: "~/.config/examtracker/examtracker.db"
99
+ database_uri: "sqllite:///~/.config/examtracker/examtracker.db"
95
100
  ```
96
101
 
97
102
  </details>
@@ -132,6 +137,7 @@ Environment variables override values defined in `config.yml`.
132
137
  - `id`
133
138
  - `name`
134
139
  - `semester_id`
140
+ - `exam_grade`
135
141
 
136
142
  ### `semester`
137
143
  - `id`
@@ -141,10 +147,18 @@ Environment variables override values defined in `config.yml`.
141
147
 
142
148
  # TODO
143
149
 
150
+ - [x] Allow for floating point values for the points scored
144
151
  - [x] Handle SQL errors
145
152
  - [x] Initialize database
146
153
  - [x] Configuration file support
147
154
  - [x] Abort edit and add screens
148
155
  - [x] Cross-platform config discovery
149
- - [ ] Improve CSS
156
+ - [x] Improve CSS
150
157
  - [x] enable Editing Exams and Semester
158
+ - [x] Feature where you can add the grade you got in the exams
159
+ - [ ] Enable externaly hosted database in config
160
+ - [x] Add a Notes section to the Exams for adding notes
161
+ - [ ] Fix sizing issue when removing a large note from an exam
162
+ - [x] Save the collumn when entering a submenu to set the cursor to the same place as before instead of to the top
163
+ - [x] write Tests
164
+ - [x] automagicly push new pypi versions and make release upon github tag push tag push
@@ -1,3 +1,4 @@
1
+ ![test](https://github.com/Samhuw8a/Examtracker/actions/workflows/test.yml/badge.svg)
1
2
  [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/)
2
3
  <a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
3
4
  ![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2FSamhuw8a%2FExamtracker%2Fmaster%2Fpyproject.toml&color=d8634c)
@@ -53,6 +54,7 @@ By default, the program searches for:
53
54
 
54
55
  ```yaml
55
56
  database_path: "~/.config/examtracker/examtracker.db"
57
+ database_uri: "sqllite:///~/.config/examtracker/examtracker.db"
56
58
  ```
57
59
 
58
60
  </details>
@@ -93,6 +95,7 @@ Environment variables override values defined in `config.yml`.
93
95
  - `id`
94
96
  - `name`
95
97
  - `semester_id`
98
+ - `exam_grade`
96
99
 
97
100
  ### `semester`
98
101
  - `id`
@@ -102,10 +105,18 @@ Environment variables override values defined in `config.yml`.
102
105
 
103
106
  # TODO
104
107
 
108
+ - [x] Allow for floating point values for the points scored
105
109
  - [x] Handle SQL errors
106
110
  - [x] Initialize database
107
111
  - [x] Configuration file support
108
112
  - [x] Abort edit and add screens
109
113
  - [x] Cross-platform config discovery
110
- - [ ] Improve CSS
114
+ - [x] Improve CSS
111
115
  - [x] enable Editing Exams and Semester
116
+ - [x] Feature where you can add the grade you got in the exams
117
+ - [ ] Enable externaly hosted database in config
118
+ - [x] Add a Notes section to the Exams for adding notes
119
+ - [ ] Fix sizing issue when removing a large note from an exam
120
+ - [x] Save the collumn when entering a submenu to set the cursor to the same place as before instead of to the top
121
+ - [x] write Tests
122
+ - [x] automagicly push new pypi versions and make release upon github tag push tag push
@@ -7,12 +7,12 @@ build-backend = "setuptools.build_meta" # If not defined, then legacy behavior c
7
7
 
8
8
  [project]
9
9
  name = "Examtracker" # REQUIRED, is the only field that cannot be marked as dynamic.
10
- version = "1.2.1" # REQUIRED, although can be dynamic
10
+ version = "1.2.3" # REQUIRED, although can be dynamic
11
11
  description = "A Python exam tracker for the Lernphase .It allows you to keep track of all exams you have completed and the scores you achieved."
12
12
 
13
13
  readme = "README.md"
14
14
 
15
- requires-python = ">=3.13"
15
+ requires-python = ">=3.10"
16
16
  license = { file = "LICENSE" }
17
17
 
18
18
  authors = [{ name = "Samuel Huwiler", email = "samuel.huwiler@gmx.ch" }]
@@ -5,3 +5,6 @@ pydantic
5
5
  pydantic-settings
6
6
  pathlib
7
7
  pyyaml
8
+ rich
9
+ pymysql
10
+ cryptography
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Examtracker
3
- Version: 1.2.1
3
+ Version: 1.2.3
4
4
  Summary: A Python exam tracker for the Lernphase .It allows you to keep track of all exams you have completed and the scores you achieved.
5
5
  Author-email: Samuel Huwiler <samuel.huwiler@gmx.ch>
6
6
  License: MIT License
@@ -25,7 +25,7 @@ License: MIT License
25
25
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
26
  SOFTWARE.
27
27
 
28
- Requires-Python: >=3.13
28
+ Requires-Python: >=3.10
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  Requires-Dist: textual
@@ -35,8 +35,12 @@ Requires-Dist: pydantic
35
35
  Requires-Dist: pydantic-settings
36
36
  Requires-Dist: pathlib
37
37
  Requires-Dist: pyyaml
38
+ Requires-Dist: rich
39
+ Requires-Dist: pymysql
40
+ Requires-Dist: cryptography
38
41
  Dynamic: license-file
39
42
 
43
+ ![test](https://github.com/Samhuw8a/Examtracker/actions/workflows/test.yml/badge.svg)
40
44
  [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/)
41
45
  <a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
42
46
  ![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2FSamhuw8a%2FExamtracker%2Fmaster%2Fpyproject.toml&color=d8634c)
@@ -92,6 +96,7 @@ By default, the program searches for:
92
96
 
93
97
  ```yaml
94
98
  database_path: "~/.config/examtracker/examtracker.db"
99
+ database_uri: "sqllite:///~/.config/examtracker/examtracker.db"
95
100
  ```
96
101
 
97
102
  </details>
@@ -132,6 +137,7 @@ Environment variables override values defined in `config.yml`.
132
137
  - `id`
133
138
  - `name`
134
139
  - `semester_id`
140
+ - `exam_grade`
135
141
 
136
142
  ### `semester`
137
143
  - `id`
@@ -141,10 +147,18 @@ Environment variables override values defined in `config.yml`.
141
147
 
142
148
  # TODO
143
149
 
150
+ - [x] Allow for floating point values for the points scored
144
151
  - [x] Handle SQL errors
145
152
  - [x] Initialize database
146
153
  - [x] Configuration file support
147
154
  - [x] Abort edit and add screens
148
155
  - [x] Cross-platform config discovery
149
- - [ ] Improve CSS
156
+ - [x] Improve CSS
150
157
  - [x] enable Editing Exams and Semester
158
+ - [x] Feature where you can add the grade you got in the exams
159
+ - [ ] Enable externaly hosted database in config
160
+ - [x] Add a Notes section to the Exams for adding notes
161
+ - [ ] Fix sizing issue when removing a large note from an exam
162
+ - [x] Save the collumn when entering a submenu to set the cursor to the same place as before instead of to the top
163
+ - [x] write Tests
164
+ - [x] automagicly push new pypi versions and make release upon github tag push tag push
@@ -21,4 +21,5 @@ src/examtracker/screens/classscreen.py
21
21
  src/examtracker/screens/examscreen.py
22
22
  src/examtracker/screens/inputscreens.py
23
23
  src/examtracker/screens/semesterscreen.py
24
- src/examtracker/textual_utils/vimtable.py
24
+ src/examtracker/textual_utils/vimtable.py
25
+ test/test_database.py
@@ -5,3 +5,6 @@ pydantic
5
5
  pydantic-settings
6
6
  pathlib
7
7
  pyyaml
8
+ rich
9
+ pymysql
10
+ cryptography
@@ -1,6 +1,6 @@
1
1
  from sqlalchemy import Engine # type:ignore
2
2
  from sqlalchemy.orm import Session
3
- from textual.app import App, ComposeResult
3
+ from textual.app import App
4
4
 
5
5
  from examtracker.screens.semesterscreen import SemesterScreen
6
6
  from examtracker.settings import Settings
@@ -11,12 +11,11 @@ class ExamTracker(App):
11
11
  ("q", "quit", "Quit"),
12
12
  ("ctrl+c", "quit"),
13
13
  ]
14
- # CSS_PATH = "/Users/samuel/Repositories/Examtracker/data/style.css"
15
-
16
- def compose(self) -> ComposeResult:
17
- yield SemesterScreen()
14
+ ENABLE_COMMAND_PALETTE = False
15
+ # CSS_PATH = "/Users/samuel/Repositories/Examtracker/src/examtracker/data/style.css"
18
16
 
19
17
  def on_mount(self) -> None:
18
+ self.theme = "gruvbox"
20
19
  self.push_screen(SemesterScreen())
21
20
  self.stylesheet.read(self.config.css_path)
22
21
 
@@ -0,0 +1,44 @@
1
+ DataTable {
2
+ border-title-align: center;
3
+ border-title-style: bold;
4
+ border-title-color: $foreground;
5
+ margin: 3;
6
+ padding: 2;
7
+ width: 75%;
8
+ border: white;
9
+ text-style: bold;
10
+ }
11
+
12
+
13
+ DataTable .datatable--header {
14
+ text-style: bold;
15
+ background: $primary;
16
+ color: black;
17
+ }
18
+
19
+ DataTable .datatable--cursor {
20
+ background: $accent 60%;
21
+ text-style: italic underline;
22
+ }
23
+
24
+ Screen {
25
+ position: relative;
26
+ align: center top;
27
+ margin: 3;
28
+ }
29
+
30
+ Vertical {
31
+ margin: 5;
32
+ align: center top;
33
+ width: 75%;
34
+ }
35
+
36
+ Input {
37
+ width: 75%;
38
+ }
39
+
40
+
41
+ Label {
42
+ width: 75%;
43
+ padding: 1;
44
+ }
@@ -2,20 +2,19 @@
2
2
  Database related functions for getting and adding entries
3
3
  """
4
4
 
5
- from typing import List
5
+ from typing import Optional
6
6
 
7
- from sqlalchemy import Engine, create_engine, inspect # type:ignore
7
+ from sqlalchemy import Engine # type:ignore
8
8
  from sqlalchemy.orm import Session
9
9
 
10
10
  from examtracker.database_scheme import Base, Class, Exam, Semester
11
11
 
12
12
 
13
- def create_database_engine(path: str) -> Engine:
14
- engine = create_engine("sqlite:///" + path, echo=False)
15
- inspector = inspect(engine)
13
+ def create_database_engine(uri: str) -> Engine:
14
+ from sqlalchemy import create_engine
16
15
 
17
- if not inspector.get_table_names():
18
- create_tables(engine)
16
+ engine = create_engine(uri, echo=False)
17
+ create_tables(engine)
19
18
 
20
19
  return engine
21
20
 
@@ -31,7 +30,7 @@ def get_semester_by_name(session: Session, name: str) -> Semester:
31
30
  def add_semester(session: Session, name: str) -> None:
32
31
  semester_obj = Semester(name=name)
33
32
  session.add(semester_obj)
34
- session.flush
33
+ session.flush()
35
34
 
36
35
 
37
36
  def get_class_by_id(session: Session, class_id: int) -> Class:
@@ -43,37 +42,48 @@ def get_exam_by_id(session: Session, exam_id: int) -> Exam:
43
42
 
44
43
 
45
44
  def add_class_to_semester(
46
- session: Session, class_name: str, semster_obj: Semester
45
+ session: Session,
46
+ class_name: str,
47
+ semster_obj: Semester,
48
+ exam_grade: Optional[float] = None,
47
49
  ) -> None:
48
- class_obj = Class(name=class_name, semester_id=semster_obj.semester_id)
50
+ class_obj = Class(
51
+ name=class_name, semester_id=semster_obj.semester_id, exam_grade=exam_grade
52
+ )
49
53
  session.add(class_obj)
50
54
  session.flush()
51
55
 
52
56
 
53
57
  def add_exam_to_class(
54
- session: Session, name: str, max_points: int, scored_points: int, class_obj: Class
58
+ session: Session,
59
+ name: str,
60
+ max_points: float,
61
+ scored_points: float,
62
+ notes: str,
63
+ class_obj: Class,
55
64
  ) -> None:
56
65
  exam_obj = Exam(
57
66
  name=name,
58
67
  max_points=max_points,
59
68
  scored_points=scored_points,
69
+ notes=notes,
60
70
  class_id=class_obj.class_id,
61
71
  )
62
72
  session.add(exam_obj)
63
73
  session.flush()
64
74
 
65
75
 
66
- def get_all_exams_for_class(session: Session, class_obj: Class) -> List[Exam]:
76
+ def get_all_exams_for_class(session: Session, class_obj: Class) -> list[Exam]:
67
77
  return session.query(Exam).filter_by(class_id=class_obj.class_id).all()
68
78
 
69
79
 
70
- def get_all_semester(session: Session) -> List[Semester]:
71
- return session.query(Semester).all()
80
+ def get_all_semester(session: Session) -> list[Semester]:
81
+ return session.query(Semester).all()[::-1]
72
82
 
73
83
 
74
84
  def get_all_classes_for_semester(
75
85
  session: Session, semster_obj: Semester
76
- ) -> List[Class]:
86
+ ) -> list[Class]:
77
87
  return session.query(Class).filter_by(semester_id=semster_obj.semester_id).all()
78
88
 
79
89
 
@@ -93,15 +103,16 @@ def remove_exam_by_id(session: Session, exam_id: int) -> None:
93
103
 
94
104
 
95
105
  def main() -> int:
96
- engine: Engine = create_database_engine("test.db")
97
- # create_tables(engine)
106
+ engine: Engine = create_database_engine("sqlite:///test.db")
107
+ create_tables(engine)
98
108
  session = Session(engine)
99
- # # add_semester(session, "FS25")
109
+ add_semester(session, "FS25")
100
110
  # # remove_semester_by_name(session, "FS25")
101
111
  # # print(get_all_semester(session))
102
- # sem = get_semester_by_name(session, "HS24")
103
- # add_class_to_semester(session, "Eprog", sem)
104
- # # cls = get_class_by_name(session,"Eprog")
112
+ sem = get_semester_by_name(session, "FS25")
113
+ add_class_to_semester(session, "wEprog", sem)
114
+ cls = get_all_classes_for_semester(session, sem)[0]
115
+ print(cls.exam_grade)
105
116
  # # print(get_all_exams_for_class(session, cls))
106
117
  session.commit()
107
118
  return 0
@@ -2,12 +2,10 @@
2
2
  Defining the database tables and entries
3
3
  """
4
4
 
5
- from typing import List
6
-
7
5
  from sqlalchemy import ForeignKey, String
8
- from sqlalchemy.orm import (
6
+ from sqlalchemy.orm import ( # type: ignore
9
7
  DeclarativeBase,
10
- Mapped, # type:ignore
8
+ Mapped,
11
9
  mapped_column,
12
10
  relationship,
13
11
  )
@@ -21,8 +19,9 @@ class Exam(Base):
21
19
  __tablename__ = "exams"
22
20
  exam_id: Mapped[int] = mapped_column(primary_key=True)
23
21
  name: Mapped[str] = mapped_column(String(30))
24
- max_points: Mapped[int]
25
- scored_points: Mapped[int]
22
+ max_points: Mapped[float]
23
+ scored_points: Mapped[float]
24
+ notes: Mapped[str] = mapped_column(String(100))
26
25
  class_id: Mapped[int] = mapped_column(ForeignKey("classes.class_id"))
27
26
  class_: Mapped["Class"] = relationship(back_populates="exams") # type:ignore
28
27
 
@@ -34,10 +33,11 @@ class Class(Base):
34
33
  __tablename__ = "classes"
35
34
  class_id: Mapped[int] = mapped_column(primary_key=True)
36
35
  name: Mapped[str] = mapped_column(String(30))
36
+ exam_grade: Mapped[float | None]
37
37
  semester_id: Mapped[int] = mapped_column(ForeignKey("semester.semester_id"))
38
38
  semester: Mapped["Semester"] = relationship(back_populates="classes") # type:ignore
39
39
 
40
- exams: Mapped[List["Exam"]] = relationship(
40
+ exams: Mapped[list["Exam"]] = relationship(
41
41
  back_populates="class_",
42
42
  cascade="all, delete-orphan",
43
43
  ) # type: ignore
@@ -50,7 +50,7 @@ class Semester(Base):
50
50
  __tablename__ = "semester"
51
51
  semester_id: Mapped[int] = mapped_column(primary_key=True)
52
52
  name: Mapped[str] = mapped_column(String(30), unique=True)
53
- classes: Mapped[List["Class"]] = relationship(
53
+ classes: Mapped[list["Class"]] = relationship(
54
54
  back_populates="semester",
55
55
  cascade="all, delete-orphan",
56
56
  ) # type: ignore
@@ -7,7 +7,7 @@ from examtracker.settings import Settings
7
7
 
8
8
  def main() -> int:
9
9
  config: Settings = Settings()
10
- db_engine = create_database_engine(config.database_path)
10
+ db_engine = create_database_engine(config.database_uri)
11
11
  app = ExamTracker(db_engine, config)
12
12
  app.run()
13
13
  return 0
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import math
4
+
3
5
  from sqlalchemy.orm import Session
4
6
  from textual import on
5
7
  from textual.app import ComposeResult, Screen
@@ -13,57 +15,75 @@ from examtracker.database import (
13
15
  remove_class_by_id,
14
16
  )
15
17
  from examtracker.screens.examscreen import ExamScreen
16
- from examtracker.screens.inputscreens import SingleInputScreen
18
+ from examtracker.screens.inputscreens import MultiInputScreen
17
19
  from examtracker.textual_utils.vimtable import VimTable
18
20
 
19
21
 
20
- class AddClassScreen(SingleInputScreen):
21
-
22
+ class AddClassScreen(MultiInputScreen):
22
23
  def __init__(self, semester_name: str, **kwargs) -> None:
23
- super().__init__(**kwargs)
24
+ super().__init__(2, **kwargs)
24
25
  self.semester_name = semester_name
25
26
  self.label_text = f"Add class to: {self.semester_name}"
26
27
 
27
28
  def on_mount(self) -> None:
28
- self.input_button.placeholder = "class name"
29
- self.input_button.value = ""
30
- self.input_button.focus()
29
+ self.buttons[0].placeholder = "class name"
30
+ self.buttons[0].value = ""
31
+ self.buttons[1].placeholder = "exam_score"
32
+ self.buttons[1].value = ""
33
+ self.buttons[0].focus()
31
34
 
32
35
  # Submit on Enter from any Input
33
36
  def submit(self) -> None:
34
- name = self.input_button.value.strip()
37
+ name = self.buttons[0].value.strip()
38
+ exam_score = self.buttons[1].value.strip()
39
+ try:
40
+ score = float(exam_score)
41
+ except ValueError:
42
+ score = None
35
43
  if not name:
36
44
  return # Require class name
37
45
  semester = get_semester_by_name(self.db_session, self.semester_name)
38
46
  # Add the class
39
- add_class_to_semester(self.db_session, name, semester) # type: ignore
47
+ add_class_to_semester(self.db_session, name, semester, score) # type: ignore
40
48
  self.db_session.commit()
41
49
 
42
50
  # Pop the screen and return
43
51
  self.app.pop_screen()
44
52
 
45
53
 
46
- class EditClassScreen(SingleInputScreen):
47
-
54
+ class EditClassScreen(MultiInputScreen):
48
55
  def __init__(self, class_id: int, **kwargs) -> None:
49
- super().__init__(**kwargs)
56
+ super().__init__(2, **kwargs)
50
57
  self.class_id = class_id
51
58
  self.label_text = "Edit class"
52
59
 
53
60
  def on_mount(self) -> None:
54
61
  class_obj = get_class_by_id(self.db_session, self.class_id)
55
- self.input_button.value = class_obj.name
56
- self.input_button.focus()
62
+ self.buttons[0].value = class_obj.name
63
+ if class_obj.exam_grade is not None:
64
+ self.buttons[1].value = str(class_obj.exam_grade)
65
+ else:
66
+ self.buttons[1].value = ""
67
+ self.buttons[0].focus()
57
68
 
58
69
  # Submit on Enter from any Input
59
70
  def submit(self) -> None:
60
- name = self.input_button.value.strip()
71
+ name = self.buttons[0].value.strip()
72
+ exam_score = self.buttons[1].value.strip()
73
+ try:
74
+ score = float(exam_score)
75
+ except ValueError:
76
+ score = None
77
+
78
+ if isinstance(score, float) and not math.isfinite(score):
79
+ score = None
61
80
  if not name:
62
81
  return # Require class name
63
82
 
64
83
  class_obj = get_class_by_id(self.db_session, self.class_id)
65
84
  # Add the class
66
85
  class_obj.name = name
86
+ class_obj.exam_grade = score
67
87
  self.db_session.commit()
68
88
 
69
89
  # Pop the screen and return
@@ -86,11 +106,12 @@ class ClassScreen(Screen):
86
106
  super().__init__()
87
107
  self.db_session: Session = self.app.db_session # type: ignore
88
108
  self.semester_name = semester_name
109
+ self.latest_row = 0
89
110
 
90
111
  def compose(self) -> ComposeResult:
91
112
  yield Header()
92
113
  self.class_table: VimTable = VimTable()
93
- self.class_table.add_columns("ID", "Name")
114
+ self.class_table.add_columns("ID", "Name", "Exam grade")
94
115
  self.class_table.cursor_type = "row"
95
116
  self.class_table.border_title = f"classes for: {self.semester_name}"
96
117
  yield self.class_table
@@ -129,7 +150,13 @@ class ClassScreen(Screen):
129
150
 
130
151
  self.class_table.clear()
131
152
  for cls in get_all_classes_for_semester(self.db_session, semester):
132
- self.class_table.add_row(cls.class_id, cls.name)
153
+ score = cls.exam_grade
154
+ str_score = ""
155
+ if score is not None:
156
+ str_score = str(cls.exam_grade)
157
+ self.class_table.add_row(cls.class_id, cls.name, str_score)
158
+
159
+ self.class_table.move_cursor(row=self.latest_row, column=0)
133
160
 
134
161
  def on_screen_resume(self) -> None:
135
162
  self.refresh_table()
@@ -139,6 +166,7 @@ class ClassScreen(Screen):
139
166
  row_index = self.class_table.cursor_row
140
167
  if row_index is None:
141
168
  return
169
+ self.latest_row = row_index
142
170
 
143
171
  class_id = self.class_table.get_row_at(row_index)[0]
144
172
  self.app.push_screen(ExamScreen(class_id))