HabitTracker/app.py
2024-03-07 17:54:06 +01:00

780 lines
20 KiB
Python

import datetime
import hashlib
import os
from PIL import Image
from flask import Flask, render_template, redirect, url_for, request, jsonify
from flask_login import login_required, LoginManager, login_user, logout_user, current_user
from models.Habit import Habit
from models.HabitList import HabitList
from models.HabitTracking import HabitTracking
from models.User import User
from utils import anonymous_required
# Create a new Flask instance
app = Flask(__name__)
app.secret_key = 'PSSSSSHHHT!'
UPLOAD_FOLDER = 'static/profile_images/' # Folder to store profile images
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
# Initialize the Flask-Login extension
login_manager = LoginManager()
login_manager.login_view = 'login'
login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
@app.context_processor
def inject_user():
return dict(user=current_user)
@app.route('/login')
@anonymous_required
def login():
return render_template('auth/login.html', errors={})
@app.route('/signup')
@anonymous_required
def signup():
return render_template('auth/signup.html', errors={})
@app.route('/login', methods=['POST'])
def login_post():
email = request.form.get('email')
password = request.form.get('password')
# Check for errors
errors = {}
if not email:
errors['email'] = 'Die E-Mail Adresse ist erforderlich.'
if not password:
errors['password'] = 'Das Passwort ist erforderlich.'
# Check if user exists
user = User.get_by_email(email)
if not user:
errors['email'] = 'E-Mail Adresse nicht gefunden.'
elif user.password is None or hashlib.sha256(password.encode()).hexdigest() != user.password:
errors['password'] = 'Das Passwort ist falsch.'
if errors:
return render_template(
'auth/login.html',
email=email,
password=password,
errors=errors
)
login_user(user)
# Redirect to login page
return redirect(url_for('index'))
@app.route('/signup', methods=['POST'])
def signup_post():
email = request.form.get('email')
name = request.form.get('name')
password = request.form.get('password')
# Check for errors
errors = {}
if not email:
errors['email'] = 'Die E-Mail Adresse ist erforderlich.'
if not name:
errors['name'] = 'Der Name ist erforderlich.'
if not password:
errors['password'] = 'Das Passwort ist erforderlich.'
# Check if email is already in use
if User.get_by_email(email):
errors['email'] = 'E-Mail Adresse bereits in Benutzung.'
if errors:
return render_template(
'auth/signup.html',
email=email,
name=name,
password=password,
errors=errors
)
# Save user to database. Maybe log the user in directly.
user = User.create(name, email, password)
login_user(user)
# Redirect to login page
return redirect(url_for('index'))
@app.route('/logout')
@login_required
def logout():
# Log out functionality
logout_user()
return redirect(url_for('index'))
# Create a new route
@app.route('/')
def index():
if current_user.is_authenticated:
habit_lists = current_user.get_habitLists()
name = "Hallo " + current_user.name
heatmap_values, day = current_user.get_heatmap()
else:
habit_lists = []
name = "Bitte melde dich an."
heatmap_values = []
day = None
# Sort habits by whether they have been checked today and then by slot
for habit_list in habit_lists:
habit_list.habits = sorted(habit_list.get_habits(), key=lambda habit: (not habit.checked, habit.slot))
days = {"Monday": "Montag", "Tuesday": "Dienstag", "Wednesday": "Mittwoch", "Thursday": "Donnerstag",
"Friday": "Freitag", "Saturday": "Samstag", "Sunday": "Sonntag"}
date = datetime.datetime.now().strftime("%d.%m.%Y %H:%M ") + days[datetime.datetime.now().strftime("%A")]
color = '255, 0, 255'
return render_template(
'index.html',
title=name,
utc_dt=date,
habit_lists=habit_lists,
heatmap_values=heatmap_values,
day=day,
color=color,
errors={}
)
@app.route('/habit')
@login_required
def habit_creation():
return render_template(
'habit.html',
title='Erstelle ein Habit',
unit="Woche",
errors={},
)
@app.route('/habit', methods=['POST'])
@login_required
def habit_create():
name = request.form.get('name')
note = request.form.get('note')
times = request.form.get('times')
unit = request.form.get('unit')
list_id = request.form.get('list_query')
# Check for errors
errors = {}
if not name:
errors['name'] = 'Der Name ist erforderlich.'
if not times:
errors['times'] = 'Die Anzahl ist erforderlich.'
if not note:
note = ''
if not unit:
errors['unit'] = 'Die Einheit ist erforderlich.'
if not list_id:
errors['list_query'] = 'Die Habitliste ist erforderlich.'
# Check if times is an integer
try:
times = int(times)
# Check that times is greater than 0
if times <= 0:
errors['times'] = 'Die Anzahl muss größer als 0 sein.'
except ValueError:
errors['times'] = 'Die Anzahl muss eine Zahl sein.'
# Check that unit is valid
if unit not in ['Tag', 'Woche', 'Monat', 'Jahr']:
errors['unit'] = 'Die Einheit ist ungültig.'
# check if list_id is an int
try:
list_id = int(list_id)
except ValueError:
errors['list_query'] = 'Die Anzahl muss eine Zahl sein.'
# Check if times is possible to achieve
if unit == 'Tag' and times != 1:
errors['times'] = 'Die Anzahl muss 1 sein, wenn das Habit täglich ist.'
if unit == 'Woche' and times > 7:
errors['times'] = 'Die Anzahl darf höchstens 7 sein, wenn das Habit wöchentlich ist.'
if unit == 'Monat' and times > 31:
errors['times'] = 'Die Anzahl darf höchstens 31 sein, wenn das Habit monatlich ist.'
if unit == 'Jahr' and times > 365:
errors['times'] = 'Die Anzahl darf höchstens 365 sein, wenn das Habit jährlich ist.'
if errors:
return render_template(
'habit.html',
title='Erstelle ein Habit',
name=name,
note=note,
times=times,
unit=unit,
errors=errors,
list_id=list_id
)
# Map unit to integer
if unit == 'Tag':
unit = 0
elif unit == 'Woche':
unit = 1
elif unit == 'Monat':
unit = 2
elif unit == 'Jahr':
unit = 3
else:
unit = 1
# Save habit to database
Habit.create(list_id, name, times, note, unit)
# Back to index
return redirect(url_for('index'))
@app.route('/habit-list')
@login_required
def habit_list_creation():
return render_template(
'habit-list.html',
title='Erstelle eine Habitliste',
errors={},
)
@app.route('/habit-list', methods=['POST'])
@login_required
def habit_list_create():
name = request.form.get('name')
description = request.form.get('description')
# Check for errors
errors = {}
if not name:
errors['name'] = 'Der Name ist erforderlich.'
if not description:
description = ''
if errors:
return render_template(
'habit-list.html',
title='Erstelle eine Habitliste',
name=name,
description=description,
errors=errors
)
# Save habit to database
HabitList.create(current_user.id, name, description)
# Back to index
return redirect(url_for('index'))
@app.route('/profile')
@login_required
def profile():
return render_template(
"profile.html",
name=current_user.name,
email=current_user.email,
profile_image_url=current_user.profile_image,
title="Profil",
)
@app.route('/profile', methods=['POST'])
@login_required
def profile_change():
newName = request.form.get('newName')
newEmail = request.form.get('newEmail')
# Update user
current_user.name = newName
current_user.email = newEmail
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,
)
@app.route('/edit-habit')
@login_required
def edit_habit():
habit_id = int(request.args.get("habit"))
habit = Habit.get(habit_id)
units = ["Tag", "Woche", "Monat", "Jahr"]
return render_template(
"edit-habit.html",
title=habit.name,
habit=habit.id,
name=habit.name,
note=habit.note,
times=habit.times,
unit=units[habit.unit],
errors={}
)
@app.route('/edit-habit', methods=['POST'])
@login_required
def edit_habit_change():
units = ["Tag", "Woche", "Monat", "Jahr"]
name = request.form.get('name')
note = request.form.get('note')
times = request.form.get('times')
unit = request.form.get('unit')
list_id = request.form.get('habit')
habit = Habit.get(list_id)
# Check for errors
errors = {}
if not name:
errors['name'] = 'Der Name ist erforderlich.'
if not times:
errors['times'] = 'Die Anzahl ist erforderlich.'
if not note:
note = ''
if not unit:
errors['unit'] = 'Die Einheit ist erforderlich.'
if not list_id:
errors['habit'] = 'Das Habit ist erforderlich.'
# Check if times is an integer
try:
print(times)
times = int(times)
# Check that times is greater than 0
if times <= 0:
errors['times'] = 'Die Anzahl muss größer als 0 sein.'
except ValueError:
errors['times'] = 'Die Anzahl muss eine Zahl sein.'
# Check that unit is valid
if unit not in ['Tag', 'Woche', 'Monat', 'Jahr']:
errors['unit'] = 'Die Einheit ist ungültig.'
# check if list_id is an int
try:
int(list_id)
except ValueError:
errors['list_query'] = 'Die Anzahl muss eine Zahl sein.'
# Check if times is possible to achieve
if unit == 'Tag' and times != 1:
errors['times'] = 'Die Anzahl muss 1 sein, wenn das Habit täglich ist.'
if unit == 'Woche' and times > 7:
errors['times'] = 'Die Anzahl darf höchstens 7 sein, wenn das Habit wöchentlich ist.'
if unit == 'Monat' and times > 31:
errors['times'] = 'Die Anzahl darf höchstens 31 sein, wenn das Habit monatlich ist.'
if unit == 'Jahr' and times > 365:
errors['times'] = 'Die Anzahl darf höchstens 365 sein, wenn das Habit jährlich ist.'
if errors:
return render_template(
"edit-habit.html",
title=habit.name,
habit=habit.id,
name=habit.name,
note=habit.note,
times=habit.times,
unit=units[habit.unit],
errors=errors
)
# Map unit to integer
if unit == 'Tag':
unit = 0
elif unit == 'Woche':
unit = 1
elif unit == 'Monat':
unit = 2
elif unit == 'Jahr':
unit = 3
else:
unit = 1
# Save habit to database
print(name, note, times, unit)
habit.name, habit.note, habit.times, habit.unit = name, note, times, unit
habit.update()
# Back to index
return redirect(url_for('index'))
@app.route('/check_password', methods=['POST'])
@login_required
def check_password():
# Get the password sent from the client
password = request.json.get('password')
if hashlib.sha256(password.encode()).hexdigest() == current_user.password:
return jsonify({"valid": True})
else:
return jsonify({"valid": False})
@app.route('/password', methods=['POST'])
@login_required
def password_change():
newPassword = request.form.get('newPassword')
# Update user
if newPassword:
current_user.password = hashlib.sha256(newPassword.encode()).hexdigest()
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,
)
def save_profile_image(image_file):
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")
# Get the filename from the image path saved in the user
filename = os.path.basename(current_user.profile_image)
# Check if the image is an animated gif
if file_extension == 'gif':
save_profile_animated(image_file, filename)
return
# Open the uploaded image
image = Image.open(image_file)
# Convert the image to RGB mode (required for JPEG)
image = image.convert('RGB')
# 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((256, 256))
# Save the processed image
image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename.replace(".gif", ".jpg"))
image.save(image_path, 'JPEG', quality=100)
current_user.update_profile_image(image_path)
def save_profile_animated(image_file, filename):
# Save the GIF temporarily
filename = filename.replace(".jpg", ".gif")
gif_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
image_file.save(gif_path)
current_user.update_profile_image(gif_path)
@app.route('/upload', methods=['POST'])
def upload_profile_image():
if 'file' not in request.files:
return 'No file part'
file = request.files['file']
save_profile_image(file)
# Back to profile
return redirect(url_for('profile'))
@app.route('/check', methods=['POST'])
@login_required
def check_habit():
habit_id = request.get_json()["habitId"]
habit = Habit.get(habit_id)
if habit is None:
return {"error": "Habit not found"}
# Check if habit belongs to user
users = habit.habit_list().get_users()
if current_user not in users:
return {"error": "Habit does not belong to user"}
trackings = habit.get_habitTrackings()
# Check if habit has been tracked today
delete_tracking = None
for tracking in trackings:
if tracking.created_at.date() == datetime.date.today():
delete_tracking = tracking
if not delete_tracking:
HabitTracking.create(habit_id)
habit.fill_statistics()
else:
delete_tracking.delete()
habit.reset_statistics()
habit.load_statistics()
heatmap_values, day = current_user.get_heatmap()
return {
"habitId": habit_id,
"unchecked": not delete_tracking,
"percentage": habit.percentage,
"streak": habit.streak,
"heatmap": heatmap_values,
"day": day,
}
@app.route('/delete', methods=['POST'])
@login_required
def delete_habit():
habit_id = request.get_json()["habitId"]
habit = Habit.get(habit_id)
if habit is None:
return {"error": "Habit not found"}
# Check if habit belongs to user
if current_user not in habit.habit_list().get_users():
return {"error": "Habit does not belong to user"}
habit.delete()
return {}
@app.route('/delete-list', methods=['POST'])
@login_required
def delete_list():
list_id = request.get_json()["listId"]
habit_list = HabitList.get(list_id)
if habit_list is None:
return {"error": "List not found"}
# Check if habit belongs to user
if current_user not in habit_list.get_users():
return {"error": "List does not belong to user"}
habit_list.delete(current_user.id)
return {}
@app.route('/reorder', methods=['POST'])
@login_required
def reorder_habits():
new_index = request.get_json()["newIndex"] + 1
habit = Habit.get(request.get_json()["habitId"])
if habit is None:
return {"error": "Habit not found"}
# Check if habit belongs to user
users = habit.habit_list().get_users()
if current_user not in users:
return {"error": "Habit does not belong to user"}
habit.update_slot(new_index)
return {}
@app.route('/users')
@login_required
def users():
habit_list_id = request.args.get('habit_list', current_user.get_habitLists()[0].id)
habit_list = HabitList.get(int(habit_list_id))
users = habit_list.get_users()
# Remove the current user from the list
users = [user for user in users if user.id != current_user.id]
return render_template(
'users.html',
title='Teilnehmer',
habit_list=habit_list,
users=users,
errors={},
)
@app.route('/users-edit')
@login_required
def edit_users():
habit_list_id = request.args.get('habit_list', current_user.get_habitLists()[0].id)
habit_list = HabitList.get(int(habit_list_id))
users = habit_list.get_users()
# Remove the current user from the list
users = [user for user in users if user.id != current_user.id]
return render_template(
'users-edit.html',
title='Teilnehmer bearbeiten',
habit_list=habit_list,
users=users,
errors={},
)
@app.route('/user-delete', methods=['POST'])
@login_required
def delete_user_from_list():
habit_list_id = request.form.get('habit_list_id')
habit_list = HabitList.get(int(habit_list_id))
habit_user_id = request.form.get('habit_user_id')
habit_user = User.get(int(habit_user_id))
users = habit_list.get_users()
# Remove the current user from the list
users = [user for user in users if user.id != current_user.id]
# Check for errors
errors = {}
if not habit_list_id:
errors['habit_list'] = 'Die Habitliste ist erforderlich.'
if not habit_list_id:
errors['habit_user'] = 'Ein User ist erforderlich.'
if errors:
return render_template(
'users-edit.html',
title='Teilnehmer bearbeiten',
habit_list=habit_list,
users=users,
errors={},
)
# delete user from habit list
id = int(habit_user_id)
habit_list.delete(id)
return render_template(
'users-edit.html',
title='Teilnehmer bearbeiten',
habit_list=habit_list,
users=users,
errors={},
)
@app.route('/users', methods=['POST'])
@login_required
def add_user():
email = request.form.get('email')
habit_list_id = request.form.get('habit_list_id')
habit_list = HabitList.get(int(habit_list_id))
# Check for errors
errors = {}
if not email:
errors['email'] = 'Die E-Mail Adresse ist erforderlich.'
if not habit_list_id:
errors['habit_list'] = 'Die Habitliste ist erforderlich.'
if errors:
return render_template(
'users.html',
title='Teilnehmer',
email=email,
habit_list=habit_list,
errors=errors,
users=habit_list.get_users(),
)
# Check if user exists
user = User.get_by_email(email)
if not user:
errors['email'] = 'E-Mail Adresse nicht gefunden.'
if user and user.id == current_user.id:
errors['email'] = 'Du kannst dich nicht selbst hinzufügen.'
# Check if user is already in the habit list
already = False
if user:
for u in habit_list.get_users():
if u.id == user.id:
already = True
break
if already:
errors['email'] = 'Teilnehmer ist bereits in der Liste.'
if errors:
return render_template(
'users.html',
title='Teilnehmer',
email=email,
habit_list=habit_list,
errors=errors,
users=habit_list.get_users(),
)
# Add user to habit list
habit_list = HabitList.get(int(habit_list_id))
habit_list.add_user(user)
return redirect(url_for('index', habit_list=habit_list.id))
# Run the application
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000, debug=True)