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
|
||||
|
||||
/HabitTracker.iml
|
||||
|
||||
static/profile_images
|
||||
|
||||
|
||||
72
app.py
72
app.py
@ -1,5 +1,7 @@
|
||||
import datetime
|
||||
import hashlib
|
||||
import os
|
||||
from PIL import Image
|
||||
|
||||
from flask import Flask, render_template, redirect, url_for, request
|
||||
from flask_login import login_required, LoginManager, login_user, logout_user, current_user
|
||||
@ -270,6 +272,7 @@ def profile():
|
||||
"profile.html",
|
||||
name=current_user.name,
|
||||
email=current_user.email,
|
||||
profile_image_url=current_user.profile_image,
|
||||
errors={}
|
||||
)
|
||||
|
||||
@ -292,15 +295,15 @@ def profile_change():
|
||||
|
||||
if not oldPassword:
|
||||
errors['oldPassword'] = 'Du musst dein aktuelles Passwort angeben.'
|
||||
else:
|
||||
if hashlib.sha256(oldPassword.encode()).hexdigest() != current_user.password:
|
||||
errors['oldPassword'] = 'Das Passwort ist falsch.'
|
||||
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
|
||||
)
|
||||
|
||||
@ -316,6 +319,69 @@ def profile_change():
|
||||
"profile.html",
|
||||
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
|
||||
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={}
|
||||
)
|
||||
|
||||
|
||||
@ -9,11 +9,11 @@ def con3():
|
||||
|
||||
|
||||
### 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()
|
||||
now = datetime.now().isoformat()
|
||||
query = (f"INSERT INTO users (name, email, password, created_at, updated_at) VALUES ('{name}', '{email}', "
|
||||
f"'{password}', '{now}', '{now}');")
|
||||
query = (f"INSERT INTO users (name, email, password, profile_image, created_at, updated_at) VALUES "
|
||||
f"('{name}', '{email}', '{password}', '{profile_image}', '{now}', '{now}');")
|
||||
conn = con3()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(query)
|
||||
@ -42,13 +42,14 @@ def get_user_by_email(email: str):
|
||||
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()
|
||||
if password:
|
||||
query = (f"UPDATE users SET name = '{name}', email = '{email}', password = '{password}', updated_at = '{now}' "
|
||||
f"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:
|
||||
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()
|
||||
cursor = conn.cursor()
|
||||
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):
|
||||
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.name = name
|
||||
self.email = email
|
||||
self.password = password
|
||||
self.profile_image = profile_image
|
||||
|
||||
@staticmethod
|
||||
def create(name: str, email: str, password: str):
|
||||
id = create_user(name, email, password)
|
||||
return User(id, name, email)
|
||||
def create(name: str, email: str, password: str, profile_image: str=None):
|
||||
id = create_user(name, email, password, profile_image)
|
||||
return User(id=id, name=name, email=email, profile_image=profile_image)
|
||||
|
||||
@staticmethod
|
||||
def get(id: int):
|
||||
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
|
||||
def get_by_email(email: str):
|
||||
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):
|
||||
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
|
||||
|
||||
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>
|
||||
|
||||
<!-- 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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
|
||||
|
||||
<!-- Axios Library-->
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||
</head>
|
||||
|
||||
@ -2,45 +2,91 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 class="mt-5">Account Einstellungen👤</h1>
|
||||
<div class="container mt-5">
|
||||
<h1 class="mb-4">Account Einstellungen👤</h1>
|
||||
|
||||
|
||||
<form action="/profile" method="POST">
|
||||
<div class="form-group mb-3">
|
||||
<label for="newName">Neuer Name:</label>
|
||||
<input type="text" class="form-control {% if errors.get('newName') %} is-invalid {% endif %}" id="newName" name="newName" value="{{name}}">
|
||||
<div class="invalid-feedback">
|
||||
{{ errors.get('newName', '') }}
|
||||
<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>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="newEmail">Neue E-Mail:</label>
|
||||
<input type="email" class="form-control {% if errors.get('newEmail') %} is-invalid {% endif %}" id="newEmail" name="newEmail" value="{{email}}">
|
||||
<div class="invalid-feedback">
|
||||
{{ errors.get('newEmail', '') }}
|
||||
<form action="/profile" method="POST">
|
||||
<div class="form-group mb-3">
|
||||
<label for="newName">Neuer Name:</label>
|
||||
<input type="text" class="form-control {% if errors.get('newName') %} is-invalid {% endif %}" id="newName" name="newName" value="{{name}}">
|
||||
<div class="invalid-feedback">
|
||||
{{ errors.get('newName', '') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-5">
|
||||
<label for="newPassword">Neues Passwort:</label>
|
||||
<input type="text" class="form-control {% if errors.get('newPassword') %} is-invalid {% endif %}" id="newPassword" name="newPassword">
|
||||
<div class="invalid-feedback">
|
||||
{{ errors.get('newPassword', '') }}
|
||||
<div class="form-group mb-3">
|
||||
<label for="newEmail">Neue E-Mail:</label>
|
||||
<input type="email" class="form-control {% if errors.get('newEmail') %} is-invalid {% endif %}" id="newEmail" name="newEmail" value="{{email}}">
|
||||
<div class="invalid-feedback">
|
||||
{{ errors.get('newEmail', '') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<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 class="form-group mb-5">
|
||||
<label for="newPassword">Neues Passwort:</label>
|
||||
<input type="text" class="form-control {% if errors.get('newPassword') %} is-invalid {% endif %}" id="newPassword" name="newPassword">
|
||||
<div class="invalid-feedback">
|
||||
{{ errors.get('newPassword', '') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Änderungen speichern</button>
|
||||
</form>
|
||||
<div class="form-group mb-3">
|
||||
<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 %}
|
||||
Loading…
x
Reference in New Issue
Block a user