From 684beb372f853ed23fea2723fbd3ac7b6c37f161 Mon Sep 17 00:00:00 2001 From: Yapollon Date: Tue, 20 Feb 2024 04:16:28 +0100 Subject: [PATCH] Profil upgrade and MORE Very cool isn't it, totally didn't take a lot of time --- .gitignore | 4 +- app.py | 78 +++---- db/SQLiteClient.py | 37 ++- models/User.py | 9 +- static/css/profile.css | 28 +++ static/css/symbols/eye-closed.svg | 1 + static/css/symbols/eye-opened.svg | 1 + static/profile_images/no_avatar/user.jpg | Bin 0 -> 6168 bytes templates/layouts/main.html | 15 +- templates/profile.html | 284 +++++++++++++++++++---- 10 files changed, 357 insertions(+), 100 deletions(-) create mode 100644 static/css/symbols/eye-closed.svg create mode 100644 static/css/symbols/eye-opened.svg create mode 100644 static/profile_images/no_avatar/user.jpg diff --git a/.gitignore b/.gitignore index a930a3f..f7f20ae 100644 --- a/.gitignore +++ b/.gitignore @@ -166,5 +166,5 @@ db/db.sqlite /HabitTracker.iml -static/profile_images - +static/profile_images/* +!/static/profile_images/no_avatar/ diff --git a/app.py b/app.py index 09188a7..cf07dec 100644 --- a/app.py +++ b/app.py @@ -3,7 +3,7 @@ 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, jsonify from flask_login import login_required, LoginManager, login_user, logout_user, current_user from models.Habit import Habit @@ -273,7 +273,6 @@ def profile(): name=current_user.name, email=current_user.email, profile_image_url=current_user.profile_image, - errors={} ) @@ -282,34 +281,37 @@ def profile(): def profile_change(): newName = request.form.get('newName') newEmail = request.form.get('newEmail') - newPassword = request.form.get('newPassword') - oldPassword = request.form.get('oldPassword') - - # Check for errors - errors = {} - if not newName: - errors['newName'] = 'Der Name ist erforderlich.' - - if not newEmail: - errors['newEmail'] = 'Die E-Mail Adresse ist erforderlich.' - - if not oldPassword: - errors['oldPassword'] = 'Du musst dein aktuelles Passwort angeben.' - elif hashlib.sha256(oldPassword.encode()).hexdigest() != current_user.password: - errors['oldPassword'] = 'Das Passwort ist falsch.' - - if errors: - return render_template( - "profile.html", - name=current_user.name, - email=current_user.email, - profile_image_url=current_user.profile_image, - errors=errors - ) # 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('/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() @@ -320,14 +322,12 @@ def profile_change(): name=current_user.name, 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 @@ -339,9 +339,15 @@ def save_profile_image(image_file): 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) + # 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) @@ -355,13 +361,11 @@ def save_profile_image(image_file): # 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)) + image = image.resize((256, 256)) # Save the processed image image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) - image.save(image_path) - - return filename + image.save(image_path, 'JPEG', quality=100) @app.route('/upload', methods=['POST']) def upload_profile_image(): @@ -369,12 +373,7 @@ def upload_profile_image(): 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() + save_profile_image(file) # Back to profile return render_template( @@ -440,7 +439,6 @@ def delete_habit(): habit.delete() return {} - @app.route('/reorder', methods=['POST']) @login_required def reorder_habits(): @@ -462,4 +460,4 @@ def reorder_habits(): # Run the application if __name__ == '__main__': - app.run(port=5000, debug=True) + app.run(port=5000, debug=True) \ No newline at end of file diff --git a/db/SQLiteClient.py b/db/SQLiteClient.py index c407599..fb183f3 100644 --- a/db/SQLiteClient.py +++ b/db/SQLiteClient.py @@ -1,6 +1,8 @@ from datetime import datetime, timedelta import hashlib import sqlite3 +import os +import shutil def con3(): @@ -9,7 +11,23 @@ def con3(): ### User ### -def create_user(name: str, email: str, password: str, profile_image: str): +def create_user_profile_image(user_id): + script_directory = os.path.dirname(os.path.abspath(__file__)) + source_path = os.path.join(script_directory, '../static/profile_images/no_avatar/user.jpg') + destination_directory = os.path.join(script_directory, '../static/profile_images/') + new_filename = f'user{user_id}-profile.jpg' + + destination_path = os.path.join(destination_directory, new_filename) + shutil.copyfile(source_path, destination_path) + + # Just save the part after static + static_index = destination_path.find('static') + relative_destination_path = destination_path[static_index:] + + return relative_destination_path + + +def create_user(name: str, email: str, password: str, profile_image:str = None): password = hashlib.sha256(password.encode()).hexdigest() now = datetime.now().isoformat() query = (f"INSERT INTO users (name, email, password, profile_image, created_at, updated_at) VALUES " @@ -17,9 +35,15 @@ def create_user(name: str, email: str, password: str, profile_image: str): conn = con3() cursor = conn.cursor() cursor.execute(query) + + id = cursor.lastrowid + profile_image = create_user_profile_image(id) + query2 = f"UPDATE users SET profile_image = '{profile_image}' WHERE id = '{id}';" + cursor.execute(query2) + conn.commit() conn.close() - return cursor.lastrowid + return id, profile_image def get_user(id: int): @@ -42,14 +66,13 @@ def get_user_by_email(email: str): return user -def update_user(id: int, name: str, email: str, password: str, profile_image:str): +def update_user(id: int, name: str, email: str, password: str): now = datetime.now().isoformat() if password: - query = (f"UPDATE users SET name = '{name}', email = '{email}', password = '{password}', " - f"profile_image = '{profile_image}', updated_at = '{now}' WHERE id = {id};") + query = (f"UPDATE users SET name = '{name}', email = '{email}', password = '{password}', updated_at = '{now}' " + f"WHERE id = {id};") else: - query = (f"UPDATE users SET name = '{name}', email = '{email}', profile_image = '{profile_image}'," - f" updated_at = '{now}' WHERE id = {id};") + query = f"UPDATE users SET name = '{name}', email = '{email}', updated_at = '{now}' WHERE id = {id};" conn = con3() cursor = conn.cursor() cursor.execute(query) diff --git a/models/User.py b/models/User.py index 16a24d9..e322983 100644 --- a/models/User.py +++ b/models/User.py @@ -11,9 +11,10 @@ class User(UserMixin): self.password = password self.profile_image = profile_image + @staticmethod - def create(name: str, email: str, password: str, profile_image: str=None): - id = create_user(name, email, password, profile_image) + def create(name: str, email: str, password: str): + id, profile_image = create_user(name, email, password) return User(id=id, name=name, email=email, profile_image=profile_image) @staticmethod @@ -27,9 +28,9 @@ class User(UserMixin): return User(user[0], user[1], user[2], user[3], user[4]) if user else None - # Updates: name, email, password, profile_image + # Updates: name, email, password def update(self): - update_user(self.id, self.name, self.email, self.password if self.password else None, self.profile_image) + update_user(self.id, self.name, self.email, self.password if self.password else None) # Deletes the User diff --git a/static/css/profile.css b/static/css/profile.css index 55b1d07..af7832c 100644 --- a/static/css/profile.css +++ b/static/css/profile.css @@ -1,3 +1,4 @@ + /* Profile image */ .profile-image-container { position: relative; width: 150px; /* Adjust the size as needed */ @@ -42,4 +43,31 @@ .profile-image-overlay:hover span { /* Style the text when hovering */ color: white; +} + + + /* Edit-Modal Close Button */ +.close-icon { + fill: #aaa; + cursor: pointer; +} + +.close-icon:hover { + fill: #777; /* Change the color to whatever you'd like on hover */ +} + + +.fade-out { + -webkit-animation: fadeOut 3s forwards; + animation: fadeOut 3s forwards; +} + +@keyframes fadeOut { + 0% { opacity: 1; } + 100% { opacity: 0; display: none; } +} + +@-webkit-keyframes fadeOut { + 0% { opacity: 1; } + 100% { opacity: 0; display: none; } } \ No newline at end of file diff --git a/static/css/symbols/eye-closed.svg b/static/css/symbols/eye-closed.svg new file mode 100644 index 0000000..1176af5 --- /dev/null +++ b/static/css/symbols/eye-closed.svg @@ -0,0 +1 @@ +324324Created with Sketch.Created by Sonya Nikolaevafrom the Noun Project \ No newline at end of file diff --git a/static/css/symbols/eye-opened.svg b/static/css/symbols/eye-opened.svg new file mode 100644 index 0000000..9caef02 --- /dev/null +++ b/static/css/symbols/eye-opened.svg @@ -0,0 +1 @@ +123123Created with Sketch.Created by Sonya Nikolaevafrom the Noun Project \ No newline at end of file diff --git a/static/profile_images/no_avatar/user.jpg b/static/profile_images/no_avatar/user.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dc70cadb1cc03e4315d6b10e9ff664202554ef99 GIT binary patch literal 6168 zcmbVQXIN9sww@$FD4~Q7p;tjcO3X)t%0ASD$9Q_9T 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 title }} - HabitTracker - + + + + + + + + + + + @@ -53,9 +63,6 @@
{% block content %} {% endblock %} - - -
\ No newline at end of file diff --git a/templates/profile.html b/templates/profile.html index 7ae87be..06f64bd 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -5,58 +5,99 @@

Account Einstellungen👤

+
-
-
Profilbild
-
- Profile Image -
- Profilbild bearbeiten +
+
+
Profilbild
+
+ Profile Image +
+ Profilbild aktualisieren +
+
+
+
+ +
-
-
- -
+
+
Name
+

{{ name }}

+
Email
+

{{ email }}

+
-
-
- - -
- {{ errors.get('newName', '') }} + +
+
+
Passwort ändern
+ +
+ + +
+
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + + + + {% endblock %} \ No newline at end of file