1
0
Fork 0
forked from Fuji/Fuji

Refactor database handling: replace sqlite_handler with sqlitehandlernr and implement fetching grade data from database

This commit is contained in:
Maarceeli 2025-03-21 12:09:59 +01:00
parent 4c355b9455
commit 2f6a8fb398
5 changed files with 198 additions and 193 deletions

View file

@ -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([
@ -62,7 +61,7 @@ def RecentGradesColumn(db_path="grades.db"):
),
*[
ft.Container(
content=ft.Text(grade_val,color="#FFFFFF",size=16,text_align=ft.TextAlign.CENTER,width=30),
content=ft.Text(grade_val, color="#FFFFFF", size=16, text_align=ft.TextAlign.CENTER, width=30),
expand=False,
bgcolor=ft.Colors.with_opacity(0.3, ft.Colors.BLACK),
width=30,
@ -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

View file

@ -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 *

View file

@ -1,86 +1,150 @@
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,
)
header = ft.Container(
content=ft.Column(
[
ft.Text("Example Subject", size=15),
ft.Row([
ft.Text("2 "+(_("grades")), size=14),
ft.Text((_("Average"))+": 5.00", size=14),
])
],
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.START,
expand=True,
),
padding=ft.padding.all(10),
expand=True,
)
# Initialize subject-based panels and data storage
panels = []
subjects = {}
panel = ft.ExpansionPanel(
header=header,
content=ft.Column([
ft.Row([
# 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(subject, size=15), # Display subject name
ft.Row([
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,
horizontal_alignment=ft.CrossAxisAlignment.START,
expand=True,
),
padding=ft.padding.all(10),
expand=True,
)
grade_rows = []
for grade in grades_list:
grade_rows.append(
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
),
]),
]),
expand=False,
)
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(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
)

View file

@ -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
View 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