feat(usermodel): add default profile picture
This commit is contained in:
parent
65de54cfad
commit
8c1f8775c4
BIN
lumi2/static/assets/default_user_icon.jpg
Normal file
BIN
lumi2/static/assets/default_user_icon.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
94
lumi2/static/assets/default_user_icon.svg
Normal file
94
lumi2/static/assets/default_user_icon.svg
Normal 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 |
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user