feat(usermodel): add default profile picture

This commit is contained in:
Julian Lobbes 2022-11-15 15:10:38 +01:00
parent 65de54cfad
commit 8c1f8775c4
3 changed files with 163 additions and 12 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="512"
height="512"
viewBox="0 0 512 512"
version="1.1"
id="svg5"
sodipodi:docname="default_user_icon.svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14, custom)"
inkscape:export-filename="default_user_icon.svg.jpg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="false"
inkscape:deskcolor="#505050"
inkscape:document-units="px"
showgrid="false"
inkscape:zoom="0.4639112"
inkscape:cx="253.28123"
inkscape:cy="156.27991"
inkscape:window-width="1366"
inkscape:window-height="712"
inkscape:window-x="0"
inkscape:window-y="28"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<rect
x="171.97761"
y="250.66449"
width="344.04429"
height="325.81057"
id="rect1513" />
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#ffffff;fill-opacity:1"
id="rect9171"
width="512"
height="512"
x="0"
y="0" />
<circle
style="fill:#f8c655;fill-opacity:1;stroke:none;stroke-width:0.511802"
id="path111"
cx="256"
cy="91.079361"
r="84.022392" />
<path
style="fill:#f8c655;fill-opacity:1;stroke:none;stroke-width:0.997777"
id="path927"
sodipodi:type="arc"
sodipodi:cx="-256"
sodipodi:cy="-330.88705"
sodipodi:rx="136.01785"
sodipodi:ry="138.84554"
sodipodi:start="0"
sodipodi:end="3.1415927"
sodipodi:open="true"
sodipodi:arc-type="arc"
d="m -119.98215,-330.88705 a 136.01785,138.84554 0 0 1 -68.00893,120.24376 136.01785,138.84554 0 0 1 -136.01785,-1e-5 136.01785,138.84554 0 0 1 -68.00892,-120.24375"
transform="scale(-1)" />
<path
id="rect1005"
d="m 119.98215,328.05939 h 272.0357 v 99.18975 c 0,93.50289 -272.0357,97.29136 -272.0357,0 z"
sodipodi:nodetypes="ccssc"
style="fill:#f8c655;fill-opacity:1;stroke-width:0.80019" />
<text
xml:space="preserve"
id="text1511"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:266.667px;font-family:'Hack Nerd Font Mono';-inkscape-font-specification:'Hack Nerd Font Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;shape-inside:url(#rect1513);shape-padding:4.89372;display:inline;fill:#cfad64;fill-opacity:1"
inkscape:label="text1511"
transform="translate(-4.4648406,-44.051506)"><tspan
x="176.87109"
y="491.49638"
id="tspan9525">?</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -1,10 +1,12 @@
"""Provides the application-internal class-based models for users and groups."""
from string import ascii_lowercase, ascii_uppercase, digits, whitespace
from base64 import b64decode
from base64 import b64encode, b64decode
import hashlib
from binascii import Error as Base64DecodeError
from PIL.Image import Image
from PIL import Image
from flask import current_app
class User:
"""Class model for a user.
@ -187,7 +189,7 @@ class User:
@staticmethod
def is_valid_picture(input_image: Image) -> bool:
def is_valid_picture(input_image: Image.Image) -> bool:
"""Checks whether the input image is a valid Image object.
TBD - unsure which formats and filesizes to allow here.
@ -208,18 +210,64 @@ class User:
If input_image is not of type PIL.Image.
"""
if not isinstance(input_image, Image):
if not isinstance(input_image, Image.Image):
raise TypeError(f"Expected a PIL Image but got: '{type(input_image)}'.")
# TODO implement
# TODO implement some integrity checks
# TODO implement some filesize restrictions
return True
@staticmethod
def generate_password_hash(password: str) -> str:
"""Generates a base64-encoded SHA512 hash of the input string.
Parameters
----------
password : str
The plaintext password for which to generate a hash.
Returns
-------
str
A base64-encoded SHA512 hash digest of the input string.
Raises
------
TypeError
If the input is not of type string.
ValueError
If the input string is empty.
"""
if not isinstance(password, str):
raise TypeError(f"Expected a string but got: '{type(password)}'.")
if not len(password):
raise ValueError("Input string cannot be empty.")
hash_bytes = hashlib.sha512()
hash_bytes.update(bytes(password, "UTF-8"))
return b64encode(hash_bytes.digest()).decode("ASCII")
def _get_default_picture() -> Image.Image:
"""Returns the default user picture as a PIL Image object.
Returns
-------
PIL.Image.Image
The default user profile picture.
"""
image_path = f"{current_app.static_folder}/assets/default_user_icon.jpg"
return Image.open(image_path)
def __init__(
self,
username: str, password_hash: str, email: str,
first_name: str, last_name: str, display_name: str,
picture: Image,
first_name: str, last_name: str, display_name = None,
picture = None,
):
if not User.is_valid_username(username):
@ -234,16 +282,25 @@ class User:
raise ValueError(f"Not a valid email address: '{email}'.")
self.email = email
for name in [first_name, last_name, display_name]:
for name in [first_name, last_name]:
if not User.is_valid_person_name(name):
raise ValueError(f"Not a valid name: '{name}'.")
self.first_name = first_name
self.last_name = last_name
self.display_name = display_name
if not User.is_valid_picture(picture):
raise ValueError(f"Not a valid image: '{picture}'.")
self.picture = picture
if display_name is not None:
if not User.is_valid_person_name(display_name):
raise ValueError(f"Not a valid display name: '{display_name}'.")
self.display_name = display_name
else:
self.display_name = first_name
if picture is not None:
if not User.is_valid_picture(picture):
raise ValueError(f"Not a valid image: '{picture}'.")
self.picture = picture
else:
self.picture = User._get_default_picture()
def __eq__(self, other):