Merge remote-tracking branch 'origin/model'

# Conflicts:
#	db/SQLiteClient.py
#	models/Habit.py
#	models/HabitList.py
#	models/HabitTrackings.py
#	models/User.py
This commit is contained in:
Verox 2024-02-19 20:14:55 +01:00
commit 8b9340e867
18 changed files with 397 additions and 178 deletions

3
.gitignore vendored
View File

@ -165,3 +165,6 @@ cython_debug/
db/db.sqlite db/db.sqlite
/HabitTracker.iml /HabitTracker.iml
static/profile_images

BIN
ER.dia

Binary file not shown.

BIN
ER.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 95 KiB

BIN
UML.dia

Binary file not shown.

BIN
UML.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 43 KiB

76
app.py
View File

@ -1,12 +1,14 @@
import datetime import datetime
import hashlib import hashlib
import os
from PIL import Image
from flask import Flask, render_template, redirect, url_for, request from flask import Flask, render_template, redirect, url_for, request
from flask_login import login_required, LoginManager, login_user, logout_user, current_user from flask_login import login_required, LoginManager, login_user, logout_user, current_user
from models.Habit import Habit from models.Habit import Habit
from models.HabitList import HabitList from models.HabitList import HabitList
from models.HabitTrackings import HabitTrackings from models.HabitTracking import HabitTracking
from models.User import User from models.User import User
from utils import anonymous_required from utils import anonymous_required
@ -271,6 +273,7 @@ def profile():
"profile.html", "profile.html",
name=current_user.name, name=current_user.name,
email=current_user.email, email=current_user.email,
profile_image_url=current_user.profile_image,
errors={} errors={}
) )
@ -293,8 +296,7 @@ def profile_change():
if not oldPassword: if not oldPassword:
errors['oldPassword'] = 'Du musst dein aktuelles Passwort angeben.' errors['oldPassword'] = 'Du musst dein aktuelles Passwort angeben.'
else: elif hashlib.sha256(oldPassword.encode()).hexdigest() != current_user.password:
if hashlib.sha256(oldPassword.encode()).hexdigest() != current_user.password:
errors['oldPassword'] = 'Das Passwort ist falsch.' errors['oldPassword'] = 'Das Passwort ist falsch.'
if errors: if errors:
@ -302,6 +304,7 @@ def profile_change():
"profile.html", "profile.html",
name=current_user.name, name=current_user.name,
email=current_user.email, email=current_user.email,
profile_image_url=current_user.profile_image,
errors=errors errors=errors
) )
@ -317,6 +320,69 @@ def profile_change():
"profile.html", "profile.html",
name=current_user.name, name=current_user.name,
email=current_user.email, email=current_user.email,
profile_image_url=current_user.profile_image,
errors={}
)
UPLOAD_FOLDER = 'static/profile_images/' # Folder to store profile images
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
def save_profile_image(image_file):
print("we are saving...")
filename = image_file.filename
if '.' not in filename:
# Ensure the filename has an extension
raise ValueError("Invalid filename")
# Check if the file extension is allowed
allowed_extensions = {'jpg', 'jpeg', 'png', 'gif'}
file_extension = filename.rsplit('.', 1)[1].lower()
if file_extension not in allowed_extensions:
raise ValueError("Invalid file extension")
# Open the uploaded image
image = Image.open(image_file)
# Determine the size of the square image
min_dimension = min(image.size)
square_size = (min_dimension, min_dimension)
# Calculate the coordinates for cropping
left = (image.width - min_dimension) / 2
top = (image.height - min_dimension) / 2
right = (image.width + min_dimension) / 2
bottom = (image.height + min_dimension) / 2
# Crop the image to a square and resize it
image = image.crop((left, top, right, bottom))
image.thumbnail(square_size)
image = image.resize((128, 128))
# Save the processed image
image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
image.save(image_path)
return filename
@app.route('/upload', methods=['POST'])
def upload_profile_image():
if 'file' not in request.files:
return 'No file part'
file = request.files['file']
filename = save_profile_image(file)
# Update user
current_user.profile_image = url_for('static', filename=f'profile_images/{filename}')
current_user.update()
# Back to profile
return render_template(
"profile.html",
name=current_user.name,
email=current_user.email,
profile_image_url=current_user.profile_image,
errors={} errors={}
) )
@ -336,7 +402,7 @@ def check_habit():
if current_user not in users: if current_user not in users:
return {"error": "Habit does not belong to user"} return {"error": "Habit does not belong to user"}
trackings = habit.get_habitTrackings() trackings = habit.get_habitTracking()
# Check if habit has been tracked today # Check if habit has been tracked today
delete_tracking = None delete_tracking = None
@ -345,7 +411,7 @@ def check_habit():
delete_tracking = tracking delete_tracking = tracking
if not delete_tracking: if not delete_tracking:
HabitTrackings.create(habit_id, 1) HabitTracking.create(habit_id)
else: else:
delete_tracking.delete() delete_tracking.delete()

View File

@ -8,12 +8,12 @@ def con3():
return conn return conn
### User.py ### ### User ###
def create_user(name: str, email: str, password: str): def create_user(name: str, email: str, password: str, profile_image: str):
password = hashlib.sha256(password.encode()).hexdigest() password = hashlib.sha256(password.encode()).hexdigest()
now = datetime.now().isoformat() now = datetime.now().isoformat()
query = (f"INSERT INTO users (name, email, password, created_at, updated_at) VALUES ('{name}', '{email}', " query = (f"INSERT INTO users (name, email, password, profile_image, created_at, updated_at) VALUES "
f"'{password}', '{now}', '{now}');") f"('{name}', '{email}', '{password}', '{profile_image}', '{now}', '{now}');")
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(query) cursor.execute(query)
@ -42,12 +42,14 @@ def get_user_by_email(email: str):
return user return user
def update_user(id: int, name: str, email: str, password: str = None): def update_user(id: int, name: str, email: str, password: str, profile_image:str):
now = datetime.now().isoformat() now = datetime.now().isoformat()
if password: if password:
query = f"UPDATE users SET name = '{name}', email = '{email}', password = '{password}', updated_at = '{now}' WHERE id = {id};" query = (f"UPDATE users SET name = '{name}', email = '{email}', password = '{password}', "
f"profile_image = '{profile_image}', updated_at = '{now}' WHERE id = {id};")
else: else:
query = f"UPDATE users SET name = '{name}', email = '{email}', updated_at = '{now}' WHERE id = {id};" query = (f"UPDATE users SET name = '{name}', email = '{email}', profile_image = '{profile_image}',"
f" updated_at = '{now}' WHERE id = {id};")
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(query) cursor.execute(query)
@ -57,22 +59,20 @@ def update_user(id: int, name: str, email: str, password: str = None):
def delete_user(id: int): def delete_user(id: int):
query = f"DELETE FROM habit_lists WHERE (SELECT list_id FROM habit_users WHERE user_id = {id}) = id;" query = f"DELETE FROM users WHERE id = {id};"
query2 = f"DELETE FROM users WHERE id = {id};"
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(query) cursor.execute(query)
cursor.execute(query2)
conn.commit() conn.commit()
conn.close() conn.close()
return cursor.lastrowid return cursor.lastrowid
### Habit.py ### ### Habit ###
def create_habit(list_id: int, name: str, times: int, unit: int, slot: int, note: str | None=None): def create_habit(list_id: int, name: str, note: str, times: int, unit: int, slot: int):
now = datetime.now().isoformat() now = datetime.now().isoformat()
query = (f"INSERT INTO habits (list_id, name, note, times, unit, slot, created_at, updated_at) VALUES ('{list_id}', " query = (f"INSERT INTO habits (list_id, name, note, times, unit, slot, created_at, updated_at) "
f"'{name}', '{note}', '{times}', '{unit}', '{slot}', '{now}', '{now}');") f"VALUES ('{list_id}', '{name}', '{note}', '{times}', '{unit}', '{slot}', '{now}', '{now}');")
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(query) cursor.execute(query)
@ -168,7 +168,8 @@ def update_slot(id: int, slot: int):
def update_habit(id: int, name: str, note: str, times: int, unit: int): def update_habit(id: int, name: str, note: str, times: int, unit: int):
now = datetime.now().isoformat() now = datetime.now().isoformat()
query = f"UPDATE habits SET name = {name}, note = {note}, times = {times}, unit = {unit}, updated_at = '{now}' WHERE id = {id};" query = (f"UPDATE habits SET name = {name}, note = {note}, times = {times}, unit = {unit}, updated_at = '{now}' "
f"WHERE id = {id};")
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(query) cursor.execute(query)
@ -186,8 +187,8 @@ def delete_habit(id: int):
conn.close() conn.close()
### HabitTrackings.py ### ### HabitTracking ###
def create_habitTrackings(habit_id: int): def create_habitTracking(habit_id: int):
now = datetime.now().isoformat() now = datetime.now().isoformat()
query = f"INSERT INTO habit_trackings (habit_id, created_at) VALUES ('{habit_id}','{now}');" query = f"INSERT INTO habit_trackings (habit_id, created_at) VALUES ('{habit_id}','{now}');"
conn = con3() conn = con3()
@ -198,7 +199,7 @@ def create_habitTrackings(habit_id: int):
return cursor.lastrowid return cursor.lastrowid
def get_habitTrackings(id: int): def get_habitTracking(id: int):
query = f"SELECT * FROM habit_trackings WHERE id = {id};" query = f"SELECT * FROM habit_trackings WHERE id = {id};"
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
@ -208,7 +209,7 @@ def get_habitTrackings(id: int):
return habit_tracking return habit_tracking
def get_habitTrackings_by_habit_id(habit_id: int): def get_habitTrackings(habit_id: int):
query = f"SELECT * FROM habit_trackings WHERE habit_id = {habit_id};" query = f"SELECT * FROM habit_trackings WHERE habit_id = {habit_id};"
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
@ -218,7 +219,7 @@ def get_habitTrackings_by_habit_id(habit_id: int):
return habit_trackings return habit_trackings
def delete_habitTrackings(id: int): def delete_habitTracking(id: int):
query = f"DELETE FROM habit_trackings WHERE id = {id};" query = f"DELETE FROM habit_trackings WHERE id = {id};"
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
@ -227,7 +228,7 @@ def delete_habitTrackings(id: int):
conn.close() conn.close()
### HabitList.py ### ### HabitList ###
def create_habitList(user_id: int, name: str, description: str): def create_habitList(user_id: int, name: str, description: str):
now = datetime.now().isoformat() now = datetime.now().isoformat()
query = (f"INSERT INTO habit_lists (name, description, created_at, updated_at) " query = (f"INSERT INTO habit_lists (name, description, created_at, updated_at) "
@ -264,6 +265,37 @@ def get_habitLists(user_id: int):
return habit_lists return habit_lists
def get_users(list_id: int):
query = (f"SELECT users.* FROM users JOIN habit_users ON users.id = habit_users.user_id WHERE "
f"habit_users.list_id = {list_id};")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
users = cursor.fetchall()
conn.close()
return users
def add_user(list_id: int, user_id: int):
now = datetime.now().isoformat()
query = (f"INSERT INTO habit_users (user_id, list_id, created_at, updated_at)"
f" VALUES ('{user_id}', '{list_id}', '{now}', '{now}');")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
conn.commit()
conn.close()
def remove_user(list_id: int, user_id: int):
query = f"DELETE FROM habit_lists WHERE user_id = {user_id} AND list_id = {list_id};"
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
conn.commit()
conn.close()
def update_habitList(id: int, name: str, description: str): def update_habitList(id: int, name: str, description: str):
now = datetime.now().isoformat() now = datetime.now().isoformat()
query = f"UPDATE habit_lists SET name = {name}, description = {description}, updated_at = '{now}' WHERE id = {id};" query = f"UPDATE habit_lists SET name = {name}, description = {description}, updated_at = '{now}' WHERE id = {id};"
@ -284,16 +316,6 @@ def delete_habitList(id: int):
conn.close() conn.close()
def get_users(list_id: int):
query = f"SELECT users.* FROM users JOIN habit_users ON users.id = habit_users.user_id WHERE habit_users.list_id = {list_id};"
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
users = cursor.fetchall()
conn.close()
return users
if __name__ == "__main__": if __name__ == "__main__":
habits = get_habits(1) habits = get_habits(1)
for habit in habits: for habit in habits:

View File

@ -0,0 +1 @@
DROP TABLE users;

View File

@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS users
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL,
password TEXT NOT NULL,
profile_image TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);

View File

@ -2,16 +2,16 @@ import json
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from models.HabitTrackings import HabitTrackings from models.HabitTracking import HabitTracking
from db.SQLiteClient import update_slot, create_habit, get_habit, delete_habit, get_next_slot, \ from db.SQLiteClient import (create_habit, get_habit, update_habit, delete_habit, get_next_slot, get_slots, update_slot,
get_habitTrackings_by_habit_id, get_slots, update_habit, get_habitList get_habitTrackings, get_habitList)
# Unit wird als Integers wie folgt gemessen: # unit will be represented by integers like this:
# 0: Tag # 0: day
# 1: Woche (Default) # 1: week (default)
# 2: Monat # 2: month
# 3: Jahr # 3: year
@dataclass @dataclass
class Habit: class Habit:
@ -28,82 +28,74 @@ class Habit:
self.fill_statistics() self.fill_statistics()
@staticmethod @staticmethod
def create(list_id: int, name: str, times: int, note: str | None = None, unit: int | None = 1): def create(list_id: int, name: str, times: int, note: str = None, unit: int = 1):
slot = get_next_slot(list_id) slot = get_next_slot(list_id)
id = create_habit(list_id, name, times, unit, slot, note) id = create_habit(list_id, name, note, times, unit, slot)
return Habit(id, list_id, name, note, times, unit, slot) return Habit(id, list_id, name, note, times, unit, slot)
@staticmethod @staticmethod
def get(id: int): def get(id: int):
habit = get_habit(id) habit = get_habit(id)
habit = Habit(habit[0], habit[1], habit[2], habit[3], habit[4], habit[5], habit[6]) if habit else None return Habit(habit[0], habit[1], habit[2], habit[3], habit[4], habit[5], habit[6]) if habit else None
return habit
def update(self, name: str=None, note: str=None, times: int=None, unit: int=None): # Updates: name, note, times, unit
update_habit(self.id, name, note, times, unit) def update(self):
if name is not None: update_habit(self.id, self.name, self.note, self.times, self.unit)
self.name = name
if note is not None:
self.note = note
if times is not None:
self.times = times
if unit is not None:
self.unit = unit
# Updates the slot and reorders the HabitList accordingly
def update_slot(self, new_slot: int): def update_slot(self, new_slot: int):
# Fetches a list with the following structure [(id, slot), (id, slot), ...]
slots = get_slots(self.list_id) slots = get_slots(self.list_id)
if new_slot > self.slot:
slots = slots[self.slot:new_slot] # Splits the list depending on whether the new slot is higher or lower than the current one
if new_slot > self.slot: # Example self.slot=1 new_slot=4
slots = slots[self.slot:new_slot] # Expected list: [(id, 2), (id, 3), (id, 4)]
for slot in slots: for slot in slots:
update_slot(slot[0], slot[1]-1) update_slot(slot[0], slot[1]-1)
if new_slot < self.slot: if new_slot < self.slot: # Example self.slot=4 new_slot=1
slots = slots[new_slot-1:self.slot-1] slots = slots[new_slot-1:self.slot-1] # Expected list: [(id, 1), (id, 2), (id, 3)]
for slot in slots: for slot in slots:
update_slot(slot[0], slot[1]+1) update_slot(slot[0], slot[1]+1)
# Update the slot of the current habit
update_slot(self.id, new_slot) update_slot(self.id, new_slot)
# Deletes the Habit
def delete(self): def delete(self):
# Reorders the slots
slots = get_slots(self.list_id)[self.slot+1:] slots = get_slots(self.list_id)[self.slot+1:]
for slot in slots: for slot in slots:
update_slot(slot[0], slot[1] - 1) update_slot(slot[0], slot[1] - 1)
# Deletes all track-records associated with the Habit
trackings = self.get_habitTrackings()
for tracking in trackings:
tracking.delete()
# Deletes the current Habit
delete_habit(self.id) delete_habit(self.id)
def get_habitTrackings(self) -> list[HabitTrackings]:
# Returns all track-records for a Habit
def get_habitTrackings(self) -> list:
trackings = [] trackings = []
for rawTracking in get_habitTrackings_by_habit_id(self.id): for rawTracking in get_habitTrackings(self.id):
trackings.append(HabitTrackings(rawTracking[0], rawTracking[1], trackings.append(HabitTracking(rawTracking[0], rawTracking[1],
datetime.strptime(rawTracking[2], "%Y-%m-%dT%H:%M:%S.%f"))) datetime.strptime(rawTracking[2], "%Y-%m-%dT%H:%M:%S.%f")))
return trackings return trackings
def getStreak(self):
streak = 0
trackings = []
for rawTracking in get_habitTrackings_by_habit_id(self.id):
trackings.append(HabitTrackings(datetime.strptime(rawTracking[2], "%Y-%m-%d")))
trackings.sort(reverse=True)
if current_date == tracking[0]:
streak += 1
for habitdate in trackings[1:]:
future_date = habitdate - relativedelta(day=1)
if future_date - habitdate == 0-0-1:
streak + 1
future_date = habitdate - relativedelta(day=1)
for habitdate in trackings:
current_date = date.today()
if current_date == habitdate or current_date == habitdate - relativedelta(day=1):
streak += 1
future_date = habitdate - relativedelta(day=1)
return streak
# Returns the HabitList in which the Habit is located
def habit_list(self) -> list:
from models.HabitList import HabitList
raw_habitLists = get_habitList(self.list_id)
return HabitList(raw_habitLists[0], raw_habitLists[1], raw_habitLists[2]) if raw_habitLists else None
# Saves the progress of the Habit in the attribute percentage
def fill_statistics(self): def fill_statistics(self):
count = 0 count = 0
self.checked = False self.checked = False
@ -130,13 +122,7 @@ class Habit:
self.percentage = int(count / self.times * 100) self.percentage = int(count / self.times * 100)
# Converts the Habit data to a json format
def to_json(self): def to_json(self):
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
def habit_list(self):
from models.HabitList import HabitList
raw_habitLists = get_habitList(self.list_id)
return HabitList(raw_habitLists[0], raw_habitLists[1], raw_habitLists[2],
datetime.strptime(raw_habitLists[3], "%Y-%m-%dT%H:%M:%S.%f"),
datetime.strptime(raw_habitLists[4], "%Y-%m-%dT%H:%M:%S.%f")) \
if raw_habitLists else None

View File

@ -1,9 +1,9 @@
from dataclasses import dataclass from dataclasses import dataclass
from datetime import date, datetime
from db.SQLiteClient import delete_habitList, create_habitList, get_habitList, get_habits, get_users
from models.Habit import Habit from models.Habit import Habit
from models.User import User from models.User import User
from db.SQLiteClient import (create_habitList, get_habitList, get_habits, get_users, add_user, remove_user,
update_habitList, delete_habitList)
@dataclass @dataclass
@ -11,24 +11,34 @@ class HabitList:
id: int id: int
name: str name: str
description: str description: str
created_at: date habits: list = None #? unclear usage
updated_at: date
habits: list = None
@staticmethod @staticmethod
def create(user_id: int, name: str, description: str): def create(user_id: int, name: str, description: str):
id = create_habitList(user_id, name, description) id = create_habitList(user_id, name, description)
return HabitList(id, name, description, datetime.now(), datetime.now()) return HabitList(id, name, description)
@staticmethod @staticmethod
def get(id: int): def get(id: int):
habitList = get_habitList(id) habitList = get_habitList(id)
return HabitList(habitList[0], habitList[1], habitList[2], datetime.strptime(habitList[3], "%Y-%m-%dT%H:%M:%S.%f"), datetime.strptime(habitList[4], "%Y-%m-%dT%H:%M:%S.%f")) if habitList else None return HabitList(habitList[0], habitList[1], habitList[2]) if habitList else None
def delete(self):
# Updates: name, description
def update(self):
update_habitList(self.id, self.name, self.description)
# Deletes the HabitList | The id of the current user is necessary
def delete(self, user_id):
if len(get_users) > 1:
self.remove_user(user_id)
else:
delete_habitList(self.id) delete_habitList(self.id)
def get_habits(self):
# Returns the Habits connected with the HabitList
def get_habits(self) -> list:
raw_habits = get_habits(self.id) raw_habits = get_habits(self.id)
habits = [] habits = []
for habit in raw_habits: for habit in raw_habits:
@ -37,7 +47,9 @@ class HabitList:
return habits return habits
def get_users(self):
# Returns the Users connected with the HabitList
def get_users(self) -> list:
raw_users = get_users(self.id) raw_users = get_users(self.id)
users = [] users = []
for user in raw_users: for user in raw_users:
@ -45,3 +57,14 @@ class HabitList:
users.append(user) users.append(user)
return users return users
# Adds a User by email to the HabitList
def add_user(self, email: str):
user = User.get_by_email(email)
add_user(self.id, user.id)
# Removes a User from the HabitList
def remove_user(self, user_id):
remove_user(self.id, user_id)

26
models/HabitTracking.py Normal file
View File

@ -0,0 +1,26 @@
from datetime import date, datetime
from dataclasses import dataclass
from db.SQLiteClient import create_habitTracking, get_habitTracking, delete_habitTracking
@dataclass
class HabitTracking:
id: int
habit_id: int
created_at: date
@staticmethod
def create(habit_id: int):
id = create_habitTracking(habit_id)
return HabitTracking(id, habit_id, datetime.now())
@staticmethod
def get(id: int):
habitTrackings = get_habitTracking(id)
return HabitTracking(habitTrackings[0], habitTrackings[1],
datetime.strptime(habitTrackings[2], "%Y-%m-%dT%H:%M:%S.%f")) \
if habitTrackings else None
# Deletes the HabitTracking
def delete(self):
delete_habitTracking(self.id)

View File

@ -1,24 +0,0 @@
from dataclasses import dataclass
from datetime import date, datetime
from db.SQLiteClient import create_habitTrackings, get_habitTrackings, delete_habitTrackings
@dataclass
class HabitTrackings:
id: int
habit_id: int
created_at: date
@staticmethod
def create(habit_id: int, times: int):
id = create_habitTrackings(habit_id)
return HabitTrackings(id, habit_id, datetime.now())
@staticmethod
def get(id: int):
habitTrackings = get_habitTrackings(id)
return HabitTrackings(habitTrackings[0], habitTrackings[1], datetime.strptime(habitTrackings[2], "%Y-%m-%dT%H:%M:%S.%f")) if habitTrackings else None
def delete(self):
delete_habitTrackings(self.id)

View File

@ -1,50 +1,63 @@
from datetime import datetime
from flask_login import UserMixin from flask_login import UserMixin
from db.SQLiteClient import create_user, get_user, get_user_by_email, delete_user, update_user, \ from db.SQLiteClient import (create_user, get_user, get_user_by_email, update_user, delete_user,
get_habitLists, get_heatmap_value get_habitLists, get_heatmap_value)
class User(UserMixin): class User(UserMixin):
def __init__(self, id: int, name: str, email: str, password: str | None = None): def __init__(self, id: int, name: str, email: str, password: str = None, profile_image:str = None):
self.id = id self.id = id
self.name = name self.name = name
self.email = email self.email = email
self.password = password self.password = password
self.profile_image = profile_image
@staticmethod @staticmethod
def create(name: str, email: str, password: str): def create(name: str, email: str, password: str, profile_image: str=None):
id = create_user(name, email, password) id = create_user(name, email, password, profile_image)
return User(id, name, email) return User(id=id, name=name, email=email, profile_image=profile_image)
@staticmethod @staticmethod
def get(id: int): def get(id: int):
user = get_user(id) user = get_user(id)
return User(user[0], user[1], user[2], user[3]) if user else None return User(user[0], user[1], user[2], user[3], user[4]) if user else None
@staticmethod @staticmethod
def get_by_email(email: str): def get_by_email(email: str):
user = get_user_by_email(email) user = get_user_by_email(email)
return User(user[0], user[1], user[2], user[3]) if user else None return User(user[0], user[1], user[2], user[3], user[4]) if user else None
# Updates: name, email, password, profile_image
def update(self): def update(self):
update_user(self.id, self.name, self.email, self.password if self.password else None) update_user(self.id, self.name, self.email, self.password if self.password else None, self.profile_image)
# Deletes the User
def delete(self): def delete(self):
# calls the deletion of the users habitLists
habitLists = self.get_habitLists()
for habitList in habitLists:
habitList.delete(self.id)
# deletes the user
delete_user(self.id) delete_user(self.id)
def get_habitLists(self):
# Returns all HabitLists connected with the user
def get_habitLists(self) -> list:
from models.HabitList import HabitList from models.HabitList import HabitList
raw_habitLists = get_habitLists(self.id) raw_habitLists = get_habitLists(self.id)
habitLists = [] habitLists = []
for habitList in raw_habitLists: for habitList in raw_habitLists:
habitList = HabitList(habitList[0], habitList[1], habitList[2], datetime.strptime(habitList[3], "%Y-%m-%dT%H:%M:%S.%f"), datetime.strptime(habitList[4], "%Y-%m-%dT%H:%M:%S.%f")) habitList = HabitList(habitList[0], habitList[1], habitList[2])
habitLists.append(habitList) habitLists.append(habitList)
return habitLists return habitLists
def get_heatmap(self):
# Returns all heatmap-values from the last 28 days
def get_heatmap(self) -> list:
heatmap = [] heatmap = []
for day in range (0, 28): for day in range (0, 28):
value = get_heatmap_value(self.id, day) value = get_heatmap_value(self.id, day)

45
static/css/profile.css Normal file
View File

@ -0,0 +1,45 @@
.profile-image-container {
position: relative;
width: 150px; /* Adjust the size as needed */
height: 150px; /* Adjust the size as needed */
}
.profile-image {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.profile-image-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* Grey overlay */
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 16px;
opacity: 0; /* Initially hidden */
transition: opacity 0.3s ease;
cursor: pointer;
}
.profile-image-overlay:hover {
opacity: 1; /* Show overlay on hover */
}
.profile-image-overlay span {
/* Center the text and make it bold */
text-align: center;
font-weight: bold;
}
.profile-image-overlay:hover span {
/* Style the text when hovering */
color: white;
}

View File

@ -6,10 +6,12 @@
<title>{{ title }} - HabitTracker</title> <title>{{ title }} - HabitTracker</title>
<!-- CSS --> <!-- CSS -->
<link rel="stylesheet" href="/static/main.css"> <link rel="stylesheet" type="text/css" href="/css/background.css">
<link rel="stylesheet" type="text/css" href="../../static/css/profile.css">
<link href="https://cdn.jsdelivr.net/npm/fastbootstrap@2.2.0/dist/css/fastbootstrap.min.css" rel="stylesheet" integrity="sha256-V6lu+OdYNKTKTsVFBuQsyIlDiRWiOmtC8VQ8Lzdm2i4=" crossorigin="anonymous"> <link href="https://cdn.jsdelivr.net/npm/fastbootstrap@2.2.0/dist/css/fastbootstrap.min.css" rel="stylesheet" integrity="sha256-V6lu+OdYNKTKTsVFBuQsyIlDiRWiOmtC8VQ8Lzdm2i4=" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<!-- Axios Library--> <!-- Axios Library-->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head> </head>

View File

@ -2,8 +2,25 @@
{% block content %} {% block content %}
<h1 class="mt-5">Account Einstellungen👤</h1> <div class="container mt-5">
<h1 class="mb-4">Account Einstellungen👤</h1>
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title">Profilbild</h5>
<div class="mb-3 profile-image-container" id="profileImageContainer">
<img src="{{ profile_image_url }}" alt="Profile Image" class="profile-image" id="profileImage">
<div class="profile-image-overlay" id="profileImageOverlay">
<span>Profilbild bearbeiten</span>
</div>
</div>
<div class="mb-3">
<form id="uploadForm" action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" class="form-control-file" id="profileImageInput" name="file" style="display: none;">
</form>
</div>
</div>
</div>
<form action="/profile" method="POST"> <form action="/profile" method="POST">
<div class="form-group mb-3"> <div class="form-group mb-3">
@ -40,7 +57,36 @@
<button type="submit" class="btn btn-primary">Änderungen speichern</button> <button type="submit" class="btn btn-primary">Änderungen speichern</button>
</form> </form>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Get elements
const profileImage = document.getElementById("profileImage");
const profileImageOverlay = document.getElementById("profileImageOverlay");
const profileImageInput = document.getElementById("profileImageInput");
const uploadForm = document.getElementById("uploadForm");
// Open file input when profile image is clicked
profileImageOverlay.addEventListener("click", function() {
profileImageInput.click();
});
// Change profile image when a new file is selected
profileImageInput.addEventListener("change", function() {
const file = this.files[0];
const reader = new FileReader();
reader.onload = function(e) {
profileImage.src = e.target.result;
};
reader.readAsDataURL(file);
// Submit the form
uploadForm.submit();
});
});
</script>
{% endblock %} {% endblock %}