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:
parent
e5709775bb
commit
6f363a104e
3
.gitignore
vendored
3
.gitignore
vendored
@ -165,3 +165,6 @@ cython_debug/
|
|||||||
db/db.sqlite
|
db/db.sqlite
|
||||||
|
|
||||||
/HabitTracker.iml
|
/HabitTracker.iml
|
||||||
|
|
||||||
|
static/profile_images
|
||||||
|
|
||||||
|
|||||||
72
app.py
72
app.py
@ -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={}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
1
db/migrations/1708105700_delete_users_table.sql
Normal file
1
db/migrations/1708105700_delete_users_table.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE users;
|
||||||
10
db/migrations/1708105768_create_users_table.sql
Normal file
10
db/migrations/1708105768_create_users_table.sql
Normal 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
|
||||||
|
);
|
||||||
@ -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
45
static/css/profile.css
Normal 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;
|
||||||
|
}
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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 %}
|
||||||
Loading…
x
Reference in New Issue
Block a user