Refactor database handling: replace sqlite_handler with sqlitehandlernr and implement fetching grade data from database
This commit is contained in:
parent
4c355b9455
commit
2f6a8fb398
5 changed files with 198 additions and 193 deletions
|
@ -3,15 +3,15 @@ import sqlite3
|
|||
import datetime
|
||||
from datetime import timedelta
|
||||
from collections import defaultdict
|
||||
from sqlite_handler import get_current_week_grades
|
||||
from sqlitehandlernr import fetch_grades_this_week
|
||||
from i18n import _
|
||||
|
||||
def format_grade(grade):
|
||||
"""
|
||||
Format a single grade into a readable string.
|
||||
"""
|
||||
# For simple grade display
|
||||
return str(grade['value'])
|
||||
# Access the grade value as an attribute, not as a dictionary key
|
||||
return str(grade.value)
|
||||
|
||||
def RecentGradesColumn(db_path="grades.db"):
|
||||
"""
|
||||
|
@ -27,12 +27,12 @@ def RecentGradesColumn(db_path="grades.db"):
|
|||
def load_grades(e=None):
|
||||
try:
|
||||
# Get grades from the current week
|
||||
grades_list = get_current_week_grades(db_path)
|
||||
grades_list = fetch_grades_this_week()
|
||||
|
||||
# Group by subject
|
||||
grades_by_subject = defaultdict(list)
|
||||
for grade in grades_list:
|
||||
subject = grade['subject']
|
||||
subject = grade.subject
|
||||
grades_by_subject[subject].append(grade)
|
||||
|
||||
# Prepare the list of controls
|
||||
|
@ -49,7 +49,6 @@ def RecentGradesColumn(db_path="grades.db"):
|
|||
for subject, grades in grades_by_subject.items():
|
||||
# Format grades as a space-separated string
|
||||
formatted_grades = [format_grade(grade) for grade in grades]
|
||||
grades_str = " ".join(formatted_grades)
|
||||
|
||||
# Create a row for each subject
|
||||
subject_row = ft.Row([
|
||||
|
@ -103,7 +102,7 @@ def RecentGradesColumn(db_path="grades.db"):
|
|||
# Create the header section like in the image
|
||||
header = ft.Row([
|
||||
ft.Icon(ft.Icons.FILTER_6, size=32, color="#FFFFFF"),
|
||||
ft.Text((_("Recent Grades")), size=24, font_family="Roboto", weight="bold")
|
||||
ft.Text((_("Recent Grades")), size=24, font_family="Roboto", weight="bold", color="#FFFFFF")
|
||||
], spacing=12, alignment=ft.MainAxisAlignment.START)
|
||||
|
||||
# Create the column with header and content
|
||||
|
|
|
@ -13,7 +13,7 @@ from pages.exams import *
|
|||
from pages.grades import *
|
||||
from pages.homework import *
|
||||
from pages.settings import *
|
||||
from sqlite_handler import *
|
||||
from sqlitehandlernr import *
|
||||
from pages.timetable import *
|
||||
from pages.behaviour import *
|
||||
from pages.attendance import *
|
||||
|
|
|
@ -1,25 +1,99 @@
|
|||
import flet as ft
|
||||
from sqlitehandlernr import fetch_all_grades
|
||||
from i18n import _
|
||||
|
||||
def parse_grade_value(value):
|
||||
"""
|
||||
Parse grade value with optional + or - suffix.
|
||||
Args:
|
||||
value (str): Grade value as a string
|
||||
|
||||
Returns:
|
||||
float: Parsed grade value, or None if unrecognized
|
||||
"""
|
||||
try:
|
||||
base_value = float(value.rstrip('+-')) # Remove +/- suffix and convert to float
|
||||
if value.endswith('+'):
|
||||
return base_value + 0.25 # Add 0.25 for "+" suffix
|
||||
elif value.endswith('-'):
|
||||
return base_value - 0.25 # Subtract 0.25 for "-" suffix
|
||||
return base_value
|
||||
except ValueError:
|
||||
return None # Ignore unrecognized values
|
||||
|
||||
def GradesPage(page):
|
||||
"""
|
||||
Generate grades page with subject-based panels and expandable content
|
||||
|
||||
Args:
|
||||
page (flet.Page): Flet page instance
|
||||
"""
|
||||
|
||||
# Retrieve all grades from SQLite database
|
||||
grades = fetch_all_grades()
|
||||
|
||||
# Create modal dialog for displaying grade details
|
||||
modal = ft.AlertDialog(
|
||||
modal=True,
|
||||
title=ft.Text((_("Test modal"))),
|
||||
content=ft.Placeholder(),
|
||||
title=ft.Text(_("Test modal")), # Set modal title with translated text
|
||||
content=ft.Text(""), # Placeholder text that will be updated dynamically
|
||||
actions=[
|
||||
ft.TextButton("OK", on_click=lambda e: page.close(modal)),
|
||||
ft.TextButton("OK", on_click=lambda e: page.close(modal)), # Close modal when OK button clicked
|
||||
],
|
||||
actions_alignment=ft.MainAxisAlignment.END,
|
||||
)
|
||||
|
||||
# Initialize subject-based panels and data storage
|
||||
panels = []
|
||||
subjects = {}
|
||||
|
||||
# Group grades by subject
|
||||
for grade in grades:
|
||||
subject = grade.subject
|
||||
if subject not in subjects: # Create new subject entry if not already present
|
||||
subjects[subject] = []
|
||||
subjects[subject].append(grade) # Add grade to corresponding subject list
|
||||
|
||||
def format_date(dt):
|
||||
"""Format a datetime object to YYYY-MM-DD string"""
|
||||
return dt.strftime("%Y-%m-%d")
|
||||
|
||||
def open_grade_modal(grade):
|
||||
"""
|
||||
Open modal dialog with detailed grade information
|
||||
|
||||
Args:
|
||||
grade (dict): Grade dictionary containing details
|
||||
"""
|
||||
modal.title = ft.Text(_("Grade Details")) # Update modal title
|
||||
parsed_value = parse_grade_value(grade.value) # Parse grade value for display
|
||||
|
||||
# Fixed line - don't use replace with None
|
||||
grade_text = f"{_('Grade')}: {grade.value}"
|
||||
if parsed_value is None:
|
||||
grade_text = f"{_('Grade')}: {_('Grade not recognized')}"
|
||||
|
||||
modal.content = ft.Column([
|
||||
ft.Text(f"{_('Subject')}: {grade.subject}", size=14), # Display subject and translated text
|
||||
ft.Text(grade_text, size=14),
|
||||
ft.Text(f"{_('Date')}: {format_date(grade.created_at)}", size=14), # Format datetime object
|
||||
ft.Text(f"{_('Weight')}: {getattr(grade, 'weight', 1.0)}", size=14),
|
||||
ft.Text(f"{_('Description')}: {grade.name}", size=14),
|
||||
ft.Text(f"{_('Creator')}: {grade.creator}", size=14)
|
||||
], expand=False)
|
||||
page.open(modal) # Open modal dialog
|
||||
|
||||
# Generate subject-based panels with expandable content
|
||||
for subject, grades_list in subjects.items():
|
||||
header = ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Text("Example Subject", size=15),
|
||||
ft.Text(subject, size=15), # Display subject name
|
||||
ft.Row([
|
||||
ft.Text("2 "+(_("grades")), size=14),
|
||||
ft.Text((_("Average"))+": 5.00", size=14),
|
||||
ft.Text(f"{len(grades_list)} "+_("grades"), size=14), # Display number of grades and translated text
|
||||
ft.Text((_("Average"))+": {:.2f}".format(
|
||||
sum(parse_grade_value(g.value) for g in grades_list if parse_grade_value(g.value) is not None) / max(len([g for g in grades_list if parse_grade_value(g.value) is not None]), 1)
|
||||
), size=14), # Calculate and display average grade
|
||||
])
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
|
@ -30,57 +104,47 @@ def GradesPage(page):
|
|||
expand=True,
|
||||
)
|
||||
|
||||
grade_rows = []
|
||||
for grade in grades_list:
|
||||
grade_rows.append(
|
||||
ft.Container(
|
||||
content=ft.Row([
|
||||
ft.Container(
|
||||
content=ft.Text(grade.value, text_align=ft.TextAlign.CENTER), # Display grade value
|
||||
bgcolor=ft.Colors.SURFACE_CONTAINER_HIGHEST,
|
||||
padding=ft.padding.symmetric(horizontal=12, vertical=8),
|
||||
border_radius=5,
|
||||
margin=ft.margin.all(5),
|
||||
width=None, # Allow the container to size based on content
|
||||
on_click=lambda e, grade=grade: open_grade_modal(grade), # Open modal dialog when row clicked
|
||||
),
|
||||
ft.Column(
|
||||
[
|
||||
ft.Text(grade.name), # Display grade name
|
||||
ft.Row([
|
||||
ft.Text(format_date(grade.created_at)), # Format datetime object
|
||||
ft.Text((_("Weight"))+": {:.1f}".format(getattr(grade, 'weight', 1.0)))
|
||||
])
|
||||
],
|
||||
spacing=1
|
||||
),
|
||||
], expand=True), # Allow Row to expand and avoid tight squeezing
|
||||
on_click=lambda e, grade=grade: open_grade_modal(grade),
|
||||
)
|
||||
)
|
||||
|
||||
panel = ft.ExpansionPanel(
|
||||
header=header,
|
||||
content=ft.Column([
|
||||
ft.Row([
|
||||
ft.Container(
|
||||
content=ft.Text("5", text_align=ft.TextAlign.CENTER),
|
||||
bgcolor=ft.Colors.SURFACE_CONTAINER_HIGHEST,
|
||||
padding=10,
|
||||
border_radius=5,
|
||||
width=40,
|
||||
margin=ft.margin.all(5),
|
||||
on_click=lambda e: page.open(modal)
|
||||
),
|
||||
ft.Column(
|
||||
[
|
||||
ft.Text("Example grade"),
|
||||
ft.Row([
|
||||
ft.Text("21.02.2025"),
|
||||
ft.Text((_("Weight"))+": 1.0"),
|
||||
])
|
||||
],
|
||||
spacing=1
|
||||
),
|
||||
]),
|
||||
ft.Row([
|
||||
ft.Container(
|
||||
content=ft.Text("5", text_align=ft.TextAlign.CENTER),
|
||||
bgcolor=ft.Colors.SURFACE_CONTAINER_HIGHEST,
|
||||
padding=10,
|
||||
border_radius=5,
|
||||
width=40,
|
||||
margin=ft.margin.all(5),
|
||||
on_click=lambda e: page.open(modal)
|
||||
),
|
||||
ft.Column(
|
||||
[
|
||||
ft.Text("Example grade"),
|
||||
ft.Row([
|
||||
ft.Text("21.02.2025"),
|
||||
ft.Text((_("Weight"))+": 1.0"),
|
||||
])
|
||||
],
|
||||
spacing=1
|
||||
),
|
||||
]),
|
||||
]),
|
||||
content=ft.Column(grade_rows),
|
||||
expand=False,
|
||||
#bgcolor=ft.Colors.SURFACE_CONTAINER_HIGHEST,
|
||||
#can_tap_header=True,
|
||||
)
|
||||
panels.append(panel)
|
||||
|
||||
return ft.Column([
|
||||
ft.Text((_("Grades")), size=30, weight="bold"),
|
||||
ft.ExpansionPanelList([panel]),
|
||||
ft.ExpansionPanelList([panel])
|
||||
])
|
||||
ft.ExpansionPanelList(panels) # Display subject-based panels with expandable content
|
||||
],
|
||||
scroll=True
|
||||
)
|
|
@ -1,115 +0,0 @@
|
|||
import sqlite3
|
||||
import datetime
|
||||
import re
|
||||
from datetime import timedelta
|
||||
|
||||
def create_grades_database(grades_list, db_path="grades.db"):
|
||||
"""
|
||||
Create a SQLite database from a list of Grade objects.
|
||||
|
||||
Args:
|
||||
grades_list (list or str): List of Grade objects or string representation of the list
|
||||
db_path (str): Path to the SQLite database file
|
||||
|
||||
Returns:
|
||||
bool: True if database was created successfully
|
||||
"""
|
||||
# Connect to SQLite database (will create it if it doesn't exist)
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Create grades table
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS grades (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
value TEXT NOT NULL,
|
||||
is_point BOOLEAN NOT NULL,
|
||||
point_numerator INTEGER,
|
||||
point_denominator INTEGER,
|
||||
weight REAL NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
subject TEXT NOT NULL,
|
||||
creator TEXT NOT NULL
|
||||
)
|
||||
''')
|
||||
|
||||
# Clear existing data if table exists
|
||||
cursor.execute('DELETE FROM grades')
|
||||
|
||||
# Define regex pattern to extract grade information
|
||||
pattern = r"Grade\(value='(.*?)', is_point=(.*?), point_numerator=(.*?), point_denominator=(.*?), weight=(.*?), name='(.*?)', created_at=datetime\.datetime\((.*?)\), subject='(.*?)', creator='(.*?)'\)"
|
||||
|
||||
# Convert to string if it's not already
|
||||
grades_str = str(grades_list)
|
||||
|
||||
# Find all grades in the input string
|
||||
matches = re.finditer(pattern, grades_str)
|
||||
|
||||
for match in matches:
|
||||
value = match.group(1)
|
||||
is_point = match.group(2).lower() == 'true'
|
||||
point_numerator = None if match.group(3) == 'None' else int(match.group(3))
|
||||
point_denominator = None if match.group(4) == 'None' else int(match.group(4))
|
||||
weight = float(match.group(5))
|
||||
name = match.group(6)
|
||||
|
||||
# Parse datetime components
|
||||
datetime_parts = match.group(7).split(', ')
|
||||
year = int(datetime_parts[0])
|
||||
month = int(datetime_parts[1])
|
||||
day = int(datetime_parts[2])
|
||||
hour = int(datetime_parts[3])
|
||||
minute = int(datetime_parts[4])
|
||||
second = int(datetime_parts[5])
|
||||
microsecond = int(datetime_parts[6]) if len(datetime_parts) > 6 else 0
|
||||
|
||||
created_at = datetime.datetime(year, month, day, hour, minute, second, microsecond)
|
||||
subject = match.group(8)
|
||||
creator = match.group(9)
|
||||
|
||||
# Insert data into the database
|
||||
cursor.execute('''
|
||||
INSERT INTO grades (value, is_point, point_numerator, point_denominator, weight, name, created_at, subject, creator)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (value, is_point, point_numerator, point_denominator, weight, name, created_at, subject, creator))
|
||||
|
||||
# Commit changes and close connection
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print(f"Database created successfully with data from {len(list(re.finditer(pattern, grades_str)))} grades")
|
||||
return True
|
||||
|
||||
def get_current_week_grades(db_path="grades.db"):
|
||||
"""
|
||||
Get all grades from the current week (Monday to Sunday).
|
||||
"""
|
||||
# Connect to SQLite database
|
||||
conn = sqlite3.connect(db_path)
|
||||
|
||||
# Configure connection to return dictionary-like objects
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Calculate the start and end of the current week
|
||||
today = datetime.datetime.now()
|
||||
start_of_week = today - timedelta(days=today.weekday())
|
||||
start_of_week = start_of_week.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
end_of_week = start_of_week + timedelta(days=6, hours=23, minutes=59, seconds=59)
|
||||
|
||||
# Query to get grades from the current week
|
||||
cursor.execute('''
|
||||
SELECT id, subject, name, value, is_point, point_numerator, point_denominator, created_at
|
||||
FROM grades
|
||||
WHERE created_at BETWEEN ? AND ?
|
||||
ORDER BY subject, created_at
|
||||
''', (start_of_week, end_of_week))
|
||||
|
||||
# Convert cursor results to dictionaries
|
||||
grades = [dict(row) for row in cursor.fetchall()]
|
||||
|
||||
# Close the connection
|
||||
conn.close()
|
||||
|
||||
return grades
|
57
src/sqlitehandlernr.py
Normal file
57
src/sqlitehandlernr.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
from sqlmodel import *
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy import func
|
||||
from typing import Optional
|
||||
|
||||
class Grades(SQLModel, table=True):
|
||||
id: int = Field(default=None, primary_key=True) # Add an auto-incrementing id
|
||||
value: str # value is no longer a primary key
|
||||
is_point: bool
|
||||
point_numerator: Optional[float] = None
|
||||
point_denominator: Optional[float] = None
|
||||
weight: float
|
||||
name: str
|
||||
created_at: datetime
|
||||
subject: str
|
||||
creator: str
|
||||
|
||||
engine = create_engine("sqlite:///database.db")
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
def create_grades_database(grades_list):
|
||||
with Session(engine) as session:
|
||||
session.execute(delete(Grades))
|
||||
for grade in grades_list:
|
||||
# Format the created_at to the desired format
|
||||
formatted_time = grade.created_at.strftime('%d/%m/%Y %H:%M:%S')
|
||||
|
||||
grade_obj = Grades(
|
||||
value=grade.value,
|
||||
is_point=grade.is_point,
|
||||
point_numerator=grade.point_numerator,
|
||||
point_denominator=grade.point_denominator,
|
||||
weight=grade.weight,
|
||||
name=grade.name,
|
||||
created_at=grade.created_at,
|
||||
subject=grade.subject,
|
||||
creator=grade.creator,
|
||||
)
|
||||
|
||||
session.add(grade_obj)
|
||||
session.commit()
|
||||
|
||||
def fetch_grades_this_week():
|
||||
today = datetime.today()
|
||||
start_of_week = today - timedelta(days=today.weekday())
|
||||
end_of_week = start_of_week + timedelta(days=7)
|
||||
|
||||
with Session(engine) as session:
|
||||
grades = session.query(Grades).filter(Grades.created_at >= start_of_week, Grades.created_at < end_of_week).all()
|
||||
|
||||
return grades
|
||||
|
||||
def fetch_all_grades():
|
||||
with Session(engine) as session:
|
||||
grades = session.query(Grades).all()
|
||||
|
||||
return grades
|
Loading…
Add table
Reference in a new issue