Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow users to set custom profile pictures #2405

Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
aa9a93a
Implemented feature: Allow users to set custom profile pictures
workaryangupta Jun 14, 2024
b7ceb1e
optional profile image
workaryangupta Jun 17, 2024
0742c39
flake8
workaryangupta Jun 17, 2024
f2cb29a
enhanced image upload security and validation
workaryangupta Jun 18, 2024
0fbad83
storing image path in DB instead of image itself
workaryangupta Jun 21, 2024
51f0ccb
set limit for profile image uploads
workaryangupta Jun 21, 2024
254e47e
added new config variable to mscolab init
workaryangupta Jun 21, 2024
e805e4c
Updated logic to store relative paths for user profile image instead …
workaryangupta Jul 1, 2024
89af2d0
Refactored upload_profile_image to use early returns
workaryangupta Jul 1, 2024
cefba82
Used create_files to ensure needed paths exist
workaryangupta Jul 1, 2024
9c15bf8
refactored directory separator slashes logic
workaryangupta Jul 2, 2024
9d49c6b
flake8
workaryangupta Jul 2, 2024
f4321d4
fixed failing tests and reduced def upload_file signature
workaryangupta Jul 3, 2024
ab514c4
flake8
workaryangupta Jul 3, 2024
0b68218
removed accidentally pushed testing logs
workaryangupta Jul 3, 2024
69a9ecc
fixed more failing tests since ext is no longer a part of uploaded fi…
workaryangupta Jul 3, 2024
57bbd5a
using fs instead of os while making dir in def upload_file
workaryangupta Jul 4, 2024
b56ab25
mscolab.py changes
workaryangupta Jul 5, 2024
e0a038a
refactored functions in server.py and file_manager.py
workaryangupta Jul 5, 2024
d27b876
removed PROFILE_IMG_FOLDER config var
workaryangupta Jul 5, 2024
3141566
removed pytest.log
workaryangupta Jul 5, 2024
ce0a390
delete old user pfp when new is uploaded
workaryangupta Jul 6, 2024
a646774
removed pytest log
workaryangupta Jul 6, 2024
67a28da
fixed error
workaryangupta Jul 6, 2024
0bfe0ec
removed truthy check for user options icon and added some ToDo
workaryangupta Jul 7, 2024
8ca40f5
spelling mistake
workaryangupta Jul 7, 2024
e532435
pytest log
workaryangupta Jul 7, 2024
3b3744c
added a ToDo for adding namespace for chat attachments
workaryangupta Jul 7, 2024
4e49277
added my name to authors
workaryangupta Jul 7, 2024
07b06b6
refactored fetch_profile_image route
workaryangupta Jul 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mslib/mscolab/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
APP.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
APP.config['SQLALCHEMY_ECHO'] = mscolab_settings.SQLALCHEMY_ECHO
APP.config['UPLOAD_FOLDER'] = mscolab_settings.UPLOAD_FOLDER
APP.config['PROFILE_IMG_FOLDER'] = mscolab_settings.PROFILE_IMG_FOLDER
APP.config['MAX_CONTENT_LENGTH'] = mscolab_settings.MAX_UPLOAD_SIZE
APP.config['SECRET_KEY'] = mscolab_settings.SECRET_KEY
APP.config['SECURITY_PASSWORD_SALT'] = getattr(mscolab_settings, "SECURITY_PASSWORD_SALT", None)
Expand Down
25 changes: 0 additions & 25 deletions mslib/mscolab/chat_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@
limitations under the License.
"""
import datetime
import os
import time

import fs
from werkzeug.utils import secure_filename

from mslib.mscolab.conf import mscolab_settings
from mslib.mscolab.models import db, Message, MessageType
Expand Down Expand Up @@ -96,24 +92,3 @@ def delete_message(self, message_id):
upload_dir.remove(fs.path.join(str(message.op_id), file_name))
db.session.delete(message)
db.session.commit()

def add_attachment(self, op_id, upload_folder, file, file_token):
with fs.open_fs('/') as home_fs:
file_dir = fs.path.join(upload_folder, str(op_id))
if '\\' not in file_dir:
if not home_fs.exists(file_dir):
home_fs.makedirs(file_dir)
else:
file_dir = file_dir.replace('\\', '/')
if not os.path.exists(file_dir):
os.makedirs(file_dir)
file_name, file_ext = file.filename.rsplit('.', 1)
file_name = f'{file_name}-{time.strftime("%Y%m%dT%H%M%S")}-{file_token}.{file_ext}'
file_name = secure_filename(file_name)
file_path = fs.path.join(file_dir, file_name)
file.save(file_path)
static_dir = fs.path.basename(upload_folder)
static_dir = static_dir.replace('\\', '/')
static_file_path = os.path.join(static_dir, str(op_id), file_name)
if os.path.exists(file_path):
return static_file_path
5 changes: 4 additions & 1 deletion mslib/mscolab/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ class default_mscolab_settings:

# mscolab file upload settings
UPLOAD_FOLDER = os.path.join(DATA_DIR, 'uploads')
MAX_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB
MAX_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MiB

workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
# Sub-folder for storing user profile images
PROFILE_IMG_FOLDER = os.path.join(UPLOAD_FOLDER, 'profile')

# used to generate and parse tokens
SECRET_KEY = secrets.token_urlsafe(16)
Expand Down
58 changes: 58 additions & 0 deletions mslib/mscolab/file_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,18 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
import os
import sys
import secrets
import time
import datetime
import fs
import difflib
import logging
import git
import threading
import mimetypes
from werkzeug.utils import secure_filename
from sqlalchemy.exc import IntegrityError
from mslib.mscolab.models import db, Operation, Permission, User, Change, Message
from mslib.mscolab.conf import mscolab_settings
Expand Down Expand Up @@ -250,6 +256,58 @@ def modify_user(self, user, attribute=None, value=None, action=None):
db.session.commit()
return True

def upload_file(self, file, subfolder, identifier=None):
"""
Generic function to save files securely in specified directory with unique filename
and return the relative file path.
"""
upload_folder = mscolab_settings.UPLOAD_FOLDER
with fs.open_fs(upload_folder) as _fs:
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
file_dir = fs.path.join(upload_folder, str(subfolder) if subfolder else "")
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
if sys.platform.startswith('win'):
file_dir = file_dir.replace('\\', '/')
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
_fs.makedirs(str(subfolder) if subfolder else "", recreate=True)

# Creating unique and secure filename
file_name, _ = file.filename.rsplit('.', 1)
mime_type, _ = mimetypes.guess_type(file.filename)
file_ext = mimetypes.guess_extension(mime_type) if mime_type else '.unknown'
token = secrets.token_urlsafe()
timestamp = time.strftime("%Y%m%dT%H%M%S")

if identifier:
file_name = f'{identifier}-{timestamp}-{token}{file_ext}'
else:
file_name = f'{file_name}-{timestamp}-{token}{file_ext}'
file_name = secure_filename(file_name)

# Saving the file
file_path = fs.path.join(file_dir, file_name)
file.save(file_path)
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved

# Relative File path
upload_folder = upload_folder.replace('\\', '/')
static_dir = fs.path.basename(upload_folder)
static_file_path = os.path.join(static_dir, str(subfolder), file_name)
static_file_path = static_file_path.replace('\\', '/')
logging.debug(f'Relative Path: {static_file_path}')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This again mixes fs and os. This could be simplified to

fs.path.relativefrom(upload_folder, fs.path.join(<what was supplied to open_fs>, file_name))

when the subfolder is opened as _fs.

Copy link
Contributor Author

@workaryangupta workaryangupta Jul 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matrss I refactored the code according to this, now the static path is returned is relative to the uploads directory, which works fine for profile images,
BUT, as this function also handles chat attachment functionality, the chat attachments now return a path like
6/{filename}, which breaks the code for adding the messages to the chat as it expects a path like uploads/6/{filename}
Please suggest me what I must do.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, without seeing the exact error I can only guess, but if the chat attachment stuff breaks then that code has the same bug and also needs to be fixed.

But that is a bit more work: it would require a database migration to fix the wrong paths that are already saved and the migration infrastructure to do that properly doesn't exist yet. Also, that would be well out of scope for your project.

I think you could just introduce a keyword argument to specify if the method returns the path with or without the prefix to allow the chat attachment code to get the old wrongly-prefixed path, while using the proper path for profile images. The default should be to not add the prefix.

That can then be fixed and refactored later.

@ReimarBauer does that sound reasonable or do you have a different idea?

Copy link
Member

@ReimarBauer ReimarBauer Jul 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with the suggested solution.

for the chat window this is the operation id, there can be many images. So that needs better a subdir.

Copy link
Member

@ReimarBauer ReimarBauer Jul 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@workaryangupta please add also a ToDo here

ToDo: add a namespace for the chat, similar as for profile

https://github.com/workaryangupta/MSS/blob/feat/upload-profile-img/mslib/mscolab/file_manager.py#L300

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with a manually migration the existing chat message attachment can be seen. so that needs no additional migration, but when we implement the namespace it definitly does.


return static_file_path

def save_user_profile_image(self, user_id, image_file):
"""
Save the user's profile image path to the database.
"""
relative_file_path = self.upload_file(image_file, subfolder='profile', identifier=user_id)
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved

user = User.query.get(user_id)
if user:
user.profile_image_path = relative_file_path
db.session.commit()
return True, "Image uploaded successfully"
else:
return False, "User not found"

def update_operation(self, op_id, attribute, value, user):
"""
op_id: operation id
Expand Down
5 changes: 4 additions & 1 deletion mslib/mscolab/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,19 @@ class User(db.Model):
username = db.Column(db.String(255))
emailid = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
profile_image_path = db.Column(db.String(255), nullable=True) # relative path
registered_on = db.Column(AwareDateTime, nullable=False)
confirmed = db.Column(db.Boolean, nullable=False, default=False)
confirmed_on = db.Column(AwareDateTime, nullable=True)
permissions = db.relationship('Permission', cascade='all,delete,delete-orphan', backref='user')
authentication_backend = db.Column(db.String(255), nullable=False, default='local')

def __init__(self, emailid, username, password, confirmed=False, confirmed_on=None, authentication_backend='local'):
def __init__(self, emailid, username, password, profile_image_path=None, confirmed=False,
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
confirmed_on=None, authentication_backend='local'):
self.username = username
self.emailid = emailid
self.hash_password(password)
self.profile_image_path = profile_image_path
self.registered_on = datetime.datetime.now(tz=datetime.timezone.utc)
self.confirmed = confirmed
self.confirmed_on = confirmed_on
Expand Down
38 changes: 36 additions & 2 deletions mslib/mscolab/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
import os
import functools
import json
import logging
Expand Down Expand Up @@ -351,6 +352,40 @@ def get_user():
return json.dumps({'user': {'id': g.user.id, 'username': g.user.username}})


@APP.route('/upload_profile_image', methods=["POST"])
@verify_user
def upload_profile_image():
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
user_id = request.form['user_id']
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
file = request.files['image']
if not file:
return jsonify({'message': 'No file provided or invalid file type'}), 400
if not file.mimetype.startswith('image/'):
return jsonify({'message': 'Invalid file type'}), 400
if file.content_length > mscolab_settings.MAX_UPLOAD_SIZE:
return jsonify({'message': 'File too large'}), 413

success, message = fm.save_user_profile_image(user_id, file)
if success:
return jsonify({'message': message}), 200
else:
return jsonify({'message': message}), 400


@APP.route('/fetch_profile_image', methods=["GET"])
@verify_user
def fetch_profile_image():
user_id = request.form['user_id']
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
user = User.query.get(user_id)
if user and user.profile_image_path:
# base_directory = mscolab_settings.UPLOAD_FOLDER
# base_path = os.path.join(base_directory, 'profile')
base_path = mscolab_settings.PROFILE_IMG_FOLDER
filename = os.path.basename(user.profile_image_path)
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
return send_from_directory(base_path, filename)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point the whole fs abstraction breaks down, because send_from_directory expects os.path semantics and we shouldn't really use anything else. Until here, everything should be treated as fs, and then can be somewhat cleanly converted to something send_from_directory understands with send_from_directory(_fs.getsyspath(""), user.profile_image_path) (_fs would need to be opened first).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added this to #2103

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@workaryangupta
Here a ToDo can be added, e.g. see open Discussion about PyFilesystem2 on ticket
or it can be solved as @matrss wrote.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree, this shouldn't be a todo, this is a bug that has already bitten us in other places. The fix is trivial, so it can be done here:

        base_path = mscolab_settings.UPLOAD_FOLDER
        if sys.platform.startswith('win'):
            base_path = base_path.replace('\\', '/')
        filename = user.profile_image_path
        with fs.open_fs(base_path) as _fs:
            return send_from_directory(_fs.getsyspath(""), filename)

else:
return 404
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved


@APP.route("/delete_own_account", methods=["POST"])
@verify_user
def delete_own_account():
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -381,15 +416,14 @@ def message_attachment():
user = g.user
op_id = request.form.get("op_id", None)
if fm.is_member(user.id, op_id):
file_token = secrets.token_urlsafe(16)
file = request.files['file']
message_type = MessageType(int(request.form.get("message_type")))
user = g.user
users = fm.fetch_users_without_permission(int(op_id), user.id)
if users is False:
return jsonify({"success": False, "message": "Could not send message. No file uploaded."})
if file is not None:
static_file_path = cm.add_attachment(op_id, APP.config['UPLOAD_FOLDER'], file, file_token)
static_file_path = fm.upload_file(file, subfolder=str(op_id))
if static_file_path is not None:
new_message = cm.add_message(user, static_file_path, op_id, message_type)
new_message_dict = get_message_dict(new_message)
Expand Down
1 change: 1 addition & 0 deletions mslib/mscolab/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,5 @@ def os_fs_create_dir(directory_path):
def create_files():
os_fs_create_dir(mscolab_settings.MSCOLAB_DATA_DIR)
os_fs_create_dir(mscolab_settings.UPLOAD_FOLDER)
os_fs_create_dir(mscolab_settings.PROFILE_IMG_FOLDER)
os_fs_create_dir(mscolab_settings.MSCOLAB_SSO_DIR)
70 changes: 66 additions & 4 deletions mslib/msui/mscolab.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
limitations under the License.
"""
import os
import io
import sys
import json
import hashlib
Expand All @@ -54,6 +55,8 @@

import PyQt5
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5.QtGui import QPixmap

from mslib.utils.auth import get_password_from_keyring, save_password_to_keyring
from mslib.utils.verify_user_token import verify_user_token
Expand Down Expand Up @@ -675,7 +678,7 @@ def after_login(self, emailid, url, r):
self.ui.usernameLabel.setText(f"{self.user['username']}")
self.ui.usernameLabel.show()
self.ui.userOptionsTb.show()
self.fetch_gravatar()
self.fetch_profile_image()
# enable add operation menu action
self.ui.actionAddOperation.setEnabled(True)

Expand All @@ -693,7 +696,31 @@ def after_login(self, emailid, url, r):

self.signal_login_mscolab.emit(self.mscolab_server_url, self.token)

def fetch_gravatar(self, refresh=False):
def fetch_profile_image(self, refresh=False):
# Display custom profile picture if exists
url = urljoin(self.mscolab_server_url, 'fetch_profile_image')
data = {
"user_id": str(self.user["id"]),
"token": self.token
}
response = requests.get(url, data=data)
if response.status_code == 200:
img_data = response.content
pixmap = QPixmap()
pixmap.loadFromData(img_data)

resized_pixmap = pixmap.scaled(64, 64)

if hasattr(self, 'profile_dialog'):
self.profile_dialog.gravatarLabel.setPixmap(resized_pixmap)
icon = QtGui.QIcon()
icon.addPixmap(resized_pixmap, QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.ui.userOptionsTb.setIcon(icon)
else:
self.fetch_gravatar(refresh)

def fetch_gravatar(self, refresh):
# Display default gravatar if custom profile image is not set
email_hash = hashlib.md5(bytes(self.email.encode('utf-8')).lower()).hexdigest()
email_in_config = self.email in config_loader(dataset="gravatar_ids")
gravatar_img_path = fs.path.join(constants.GRAVATAR_DIR_PATH, f"{email_hash}.png")
Expand Down Expand Up @@ -798,16 +825,51 @@ def on_context_menu(point):
self.profile_dialog.mscolabURLLabel_2.setText(self.mscolab_server_url)
self.profile_dialog.emailLabel_2.setText(self.email)
self.profile_dialog.deleteAccountBtn.clicked.connect(self.delete_account)
self.profile_dialog.uploadImageBtn.clicked.connect(self.upload_image)

# add context menu for right click on image
self.gravatar_menu = QtWidgets.QMenu()
self.gravatar_menu.addAction('Fetch Gravatar', lambda: self.fetch_gravatar(refresh=True))
self.gravatar_menu.addAction('Fetch Gravatar', lambda: self.fetch_profile_image(refresh=True))
self.gravatar_menu.addAction('Remove Gravatar', lambda: self.remove_gravatar())
self.profile_dialog.gravatarLabel.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.profile_dialog.gravatarLabel.customContextMenuRequested.connect(on_context_menu)

self.prof_diag.show()
self.fetch_gravatar()
self.fetch_profile_image()

def upload_image(self):
file_name, _ = QFileDialog.getOpenFileName(self.prof_diag, "Open Image", "", "Image Files (*.png *.jpg *.jpeg)")
if file_name:
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
# Resize the image and load it into QPixmap
image = Image.open(file_name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs an try: except PIL.UnidentifiedImageError: because one can have wrong extensions used, e.g. renamed a mp4 to a jpg

currently this gives a traceback:

Fatal error in MSS 9.0.0 on Linux-6.5.0-10043-tuxedo-x86_64-with-glibc2.35
Python 3.11.6 | packaged by conda-forge | (main, Oct 3 2023, 10:40:35) [GCC 12.3.0]

Please report bugs in MSS to https://github.com/Open-MSS/MSS


Information about the fatal error:

Traceback (most recent call last):
  File "/home/user/PycharmProjects/2024/workaryangupta/MSS/mslib/msui/mscolab.py", line 850, in upload_image
    image = Image.open(file_name)
            ^^^^^^^^^^^^^^^^^^^^^
  File "/home/user/Miniforge/envs/mssdev/lib/python3.11/site-packages/PIL/Image.py", line 3283, in open
    raise UnidentifiedImageError(msg)
PIL.UnidentifiedImageError: cannot identify image file '/home/user/tmp/1.jpg'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renaming a gif to a png and uploading works, so gif can maybe added to the list of extensions

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renaming a gif to a jpg and uploading gives

Please report bugs in MSS to https://github.com/Open-MSS/MSS

Information about the fatal error:

Traceback (most recent call last):
  File "/home/user/Miniforge/envs/mssdev/lib/python3.11/site-packages/PIL/JpegImagePlugin.py", line 643, in _save
    rawmode = RAWMODE[im.mode]
              ~~~~~~~^^^^^^^^^
KeyError: 'P'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/user/PycharmProjects/2024/workaryangupta/MSS/mslib/msui/mscolab.py", line 853, in upload_image
    image.save(img_byte_arr, format=file_format)
  File "/home/user/Miniforge/envs/mssdev/lib/python3.11/site-packages/PIL/Image.py", line 2431, in save
    save_handler(self, fp, filename)
  File "/home/user/Miniforge/envs/mssdev/lib/python3.11/site-packages/PIL/JpegImagePlugin.py", line 646, in _save
    raise OSError(msg) from e
OSError: cannot write mode P as JPEG

So also catch OSError

Becasue this is not server code it is not critical, we help here the user and maybe get no issues when someone has files like this.

image = image.resize((64, 64), Image.ANTIALIAS)
img_byte_arr = io.BytesIO()
image.save(img_byte_arr, format='PNG')
img_byte_arr.seek(0)

pixmap = QPixmap()
pixmap.loadFromData(img_byte_arr.getvalue())
self.profile_dialog.gravatarLabel.setPixmap(pixmap.scaled(64, 64))
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved

# Prepare the file data for upload
try:
img_byte_arr.seek(0) # Reset buffer position
files = {'image': (os.path.basename(file_name), img_byte_arr, 'image/png')}
data = {
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
"user_id": str(self.user["id"]),
"token": self.token
}
url = urljoin(self.mscolab_server_url, 'upload_profile_image')
response = requests.post(url, files=files, data=data)

# Check response status
if response.status_code == 200:
QMessageBox.information(self.prof_diag, "Success", "Image uploaded successfully")
else:
QMessageBox.critical(self.prof_diag, "Error", f"Failed to upload image: {response.text}")

except requests.exceptions.RequestException as e:
QMessageBox.critical(self.prof_diag, "Error", f"Error occurred: {e}")

def delete_account(self):
# ToDo rename to delete_own_account
Expand Down
Loading
Loading