Profile Pictures

YES YOU HEARD RIGHT!!

I ALSO DID THE HTML!

This is the most awesome most epic addition, i sold my kidney for this.
This commit is contained in:
Yapollon 2024-02-18 04:53:15 +01:00
parent e5709775bb
commit 6f363a104e
10 changed files with 222 additions and 47 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

72
app.py
View File

@ -1,5 +1,7 @@
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
@ -270,6 +272,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={}
) )
@ -292,15 +295,15 @@ 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:
return render_template( return render_template(
"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
) )
@ -316,6 +319,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={}
) )

View File

@ -9,11 +9,11 @@ def con3():
### User ### ### 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,13 +42,14 @@ def get_user_by_email(email: str):
return user return user
def update_user(id: int, name: str, email: str, password: str): 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}' " query = (f"UPDATE users SET name = '{name}', email = '{email}', password = '{password}', "
f"WHERE id = {id};") 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)

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

@ -4,31 +4,32 @@ from db.SQLiteClient import (create_user, get_user, get_user_by_email, update_us
class User(UserMixin): class User(UserMixin):
def __init__(self, id: int, name: str, email: str, password: str = 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 # 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 # Deletes the User

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,45 +2,91 @@
{% 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">
<form action="/profile" method="POST"> <div class="card-body">
<div class="form-group mb-3"> <h5 class="card-title">Profilbild</h5>
<label for="newName">Neuer Name:</label> <div class="mb-3 profile-image-container" id="profileImageContainer">
<input type="text" class="form-control {% if errors.get('newName') %} is-invalid {% endif %}" id="newName" name="newName" value="{{name}}"> <img src="{{ profile_image_url }}" alt="Profile Image" class="profile-image" id="profileImage">
<div class="invalid-feedback"> <div class="profile-image-overlay" id="profileImageOverlay">
{{ errors.get('newName', '') }} <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>
</div> </div>
<div class="form-group mb-3"> <form action="/profile" method="POST">
<label for="newEmail">Neue E-Mail:</label> <div class="form-group mb-3">
<input type="email" class="form-control {% if errors.get('newEmail') %} is-invalid {% endif %}" id="newEmail" name="newEmail" value="{{email}}"> <label for="newName">Neuer Name:</label>
<div class="invalid-feedback"> <input type="text" class="form-control {% if errors.get('newName') %} is-invalid {% endif %}" id="newName" name="newName" value="{{name}}">
{{ errors.get('newEmail', '') }} <div class="invalid-feedback">
{{ errors.get('newName', '') }}
</div>
</div> </div>
</div>
<div class="form-group mb-5"> <div class="form-group mb-3">
<label for="newPassword">Neues Passwort:</label> <label for="newEmail">Neue E-Mail:</label>
<input type="text" class="form-control {% if errors.get('newPassword') %} is-invalid {% endif %}" id="newPassword" name="newPassword"> <input type="email" class="form-control {% if errors.get('newEmail') %} is-invalid {% endif %}" id="newEmail" name="newEmail" value="{{email}}">
<div class="invalid-feedback"> <div class="invalid-feedback">
{{ errors.get('newPassword', '') }} {{ errors.get('newEmail', '') }}
</div>
</div> </div>
</div>
<div class="form-group mb-3"> <div class="form-group mb-5">
<label for="oldPassword">Altes Passwort:</label> <label for="newPassword">Neues Passwort:</label>
<input type="password" class="form-control {% if errors.get('oldPassword') %} is-invalid {% endif %}" id="oldPassword" name="oldPassword"> <input type="text" class="form-control {% if errors.get('newPassword') %} is-invalid {% endif %}" id="newPassword" name="newPassword">
<div class="invalid-feedback"> <div class="invalid-feedback">
{{ errors.get('oldPassword', '') }} {{ errors.get('newPassword', '') }}
</div>
</div> </div>
</div>
<button type="submit" class="btn btn-primary">Änderungen speichern</button> <div class="form-group mb-3">
</form> <label for="oldPassword">Altes Passwort:</label>
<input type="password" class="form-control {% if errors.get('oldPassword') %} is-invalid {% endif %}" id="oldPassword" name="oldPassword">
<div class="invalid-feedback">
{{ errors.get('oldPassword', '') }}
</div>
</div>
<button type="submit" class="btn btn-primary">Änderungen speichern</button>
</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 %}