diff --git a/Dockerfile b/Dockerfile
index 553cb64..7338327 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,10 +1,12 @@
FROM python:3.7-alpine
-RUN mkdir -p /var/matemat/db /var/matemat/upload
-RUN apk --update add libmagic
ADD . /
-RUN pip3 install -r /requirements.txt
+RUN mkdir -p /var/matemat/db /var/matemat/upload \
+ && apk --update add libmagic zlib jpeg zlib-dev jpeg-dev build-base \
+ && pip3 install -r /requirements.txt \
+ && apk del zlib-dev jpeg-dev build-base \
+ && rm -rf /var/cache/apk /root/.cache/pip
EXPOSE 80/tcp
CMD [ "/run.sh" ]
diff --git a/README.md b/README.md
index 278c59e..1d75157 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ This project intends to provide a well-tested and maintainable alternative to
- Python dependencies:
- file-magic
- jinja2
+ - Pillow
## Usage
diff --git a/matemat/webserver/pagelets/admin.py b/matemat/webserver/pagelets/admin.py
index d1866b2..7e4f95e 100644
--- a/matemat/webserver/pagelets/admin.py
+++ b/matemat/webserver/pagelets/admin.py
@@ -1,9 +1,13 @@
from typing import Any, Dict, Union
import os
+from shutil import copyfile
import magic
+from io import BytesIO
+from PIL import Image
from matemat.webserver import pagelet, RequestArguments, PageletResponse, RedirectResponse, TemplateResponse
+from matemat.util.currency_format import parse_chf
from matemat.db import MatematDatabase
from matemat.db.primitives import User, ReceiptPreference
from matemat.exceptions import DatabaseConsistencyError, HttpException
@@ -124,16 +128,20 @@ def handle_change(args: RequestArguments, user: User, db: MatematDatabase, confi
return
# Detect the MIME type
filemagic: magic.FileMagic = magic.detect_from_content(avatar)
- # Currently, only image/png is supported, don't process any other formats
- if filemagic.mime_type != 'image/png':
- # TODO: Optionally convert to png
+ if not filemagic.mime_type.startswith('image/'):
return
# Create the absolute path of the upload directory
abspath: str = os.path.join(os.path.abspath(config['UploadDir']), 'thumbnails/users/')
os.makedirs(abspath, exist_ok=True)
- # Write the image to the file
- with open(os.path.join(abspath, f'{user.id}.png'), 'wb') as f:
- f.write(avatar)
+ try:
+ # Parse the image data
+ image: Image = Image.open(BytesIO(avatar))
+ # Resize the image to 150x150
+ image.thumbnail((150, 150), Image.LANCZOS)
+ # Write the image to the file
+ image.save(os.path.join(abspath, f'{user.id}.png'), 'PNG')
+ except OSError:
+ return
except UnicodeDecodeError:
raise ValueError('an argument not a string')
@@ -166,7 +174,18 @@ def handle_admin_change(args: RequestArguments, db: MatematDatabase, config: Dic
is_member = 'ismember' in args
is_admin = 'isadmin' in args
# Create the user in the database
- db.create_user(username, password, email, member=is_member, admin=is_admin)
+ newuser: User = db.create_user(username, password, email, member=is_member, admin=is_admin)
+
+ # If a default avatar is set, copy it to the user's avatar path
+
+ # Create the absolute path of the upload directory
+ abspath: str = os.path.join(os.path.abspath(config['UploadDir']), 'thumbnails/users/')
+ # Derive the individual paths
+ default: str = os.path.join(abspath, 'default.png')
+ userimg: str = os.path.join(abspath, f'{newuser.id}.png')
+ # Copy the default image, if it exists
+ if os.path.exists(default):
+ copyfile(default, userimg, follow_symlinks=True)
# The user requested to create a new product
elif change == 'newproduct':
@@ -175,29 +194,41 @@ def handle_admin_change(args: RequestArguments, db: MatematDatabase, config: Dic
return
# Read the properties from the request arguments
name = str(args.name)
- price_member = int(str(args.pricemember))
- price_non_member = int(str(args.pricenonmember))
+ price_member = parse_chf(str(args.pricemember))
+ price_non_member = parse_chf(str(args.pricenonmember))
# Create the user in the database
newproduct = db.create_product(name, price_member, price_non_member)
# If a new product image was uploaded, process it
- if 'image' in args:
+ if 'image' in args and len(bytes(args.image)) > 0:
# Read the raw image data from the request
- image = bytes(args.image)
- # Only process the image, if its size is more than zero. Zero size means no new image was uploaded
- if len(image) == 0:
- return
+ avatar = bytes(args.image)
# Detect the MIME type
- filemagic: magic.FileMagic = magic.detect_from_content(image)
- # Currently, only image/png is supported, don't process any other formats
- if filemagic.mime_type != 'image/png':
- # TODO: Optionally convert to png
+ filemagic: magic.FileMagic = magic.detect_from_content(avatar)
+ if not filemagic.mime_type.startswith('image/'):
return
# Create the absolute path of the upload directory
abspath: str = os.path.join(os.path.abspath(config['UploadDir']), 'thumbnails/products/')
os.makedirs(abspath, exist_ok=True)
- # Write the image to the file
- with open(os.path.join(abspath, f'{newproduct.id}.png'), 'wb') as f:
- f.write(image)
+ try:
+ # Parse the image data
+ image: Image = Image.open(BytesIO(avatar))
+ # Resize the image to 150x150
+ image.thumbnail((150, 150), Image.LANCZOS)
+ # Write the image to the file
+ image.save(os.path.join(abspath, f'{newproduct.id}.png'), 'PNG')
+ except OSError:
+ return
+ else:
+ # If no image was uploaded and a default avatar is set, copy it to the product's avatar path
+
+ # Create the absolute path of the upload directory
+ abspath: str = os.path.join(os.path.abspath(config['UploadDir']), 'thumbnails/products/')
+ # Derive the individual paths
+ default: str = os.path.join(abspath, 'default.png')
+ userimg: str = os.path.join(abspath, f'{newproduct.id}.png')
+ # Copy the default image, if it exists
+ if os.path.exists(default):
+ copyfile(default, userimg, follow_symlinks=True)
# The user requested to restock a product
elif change == 'restock':
@@ -212,5 +243,33 @@ def handle_admin_change(args: RequestArguments, db: MatematDatabase, config: Dic
# Write the new stock count to the database
db.restock(product, amount)
+ # The user requested to set default images
+ elif change == 'defaultimg':
+ # Iterate the possible images to set
+ for category in 'users', 'products':
+ if category not in args:
+ continue
+ # Read the raw image data from the request
+ default: bytes = bytes(args[category])
+ # Only process the image, if its size is more than zero. Zero size means no new image was uploaded
+ if len(default) == 0:
+ continue
+ # Detect the MIME type
+ filemagic: magic.FileMagic = magic.detect_from_content(default)
+ if not filemagic.mime_type.startswith('image/'):
+ continue
+ # Create the absolute path of the upload directory
+ abspath: str = os.path.join(os.path.abspath(config['UploadDir']), f'thumbnails/{category}/')
+ os.makedirs(abspath, exist_ok=True)
+ try:
+ # Parse the image data
+ image: Image = Image.open(BytesIO(default))
+ # Resize the image to 150x150
+ image.thumbnail((150, 150), Image.LANCZOS)
+ # Write the image to the file
+ image.save(os.path.join(abspath, f'default.png'), 'PNG')
+ except OSError:
+ return
+
except UnicodeDecodeError:
raise ValueError('an argument not a string')
diff --git a/matemat/webserver/pagelets/modproduct.py b/matemat/webserver/pagelets/modproduct.py
index ef6acbd..7ba9c3e 100644
--- a/matemat/webserver/pagelets/modproduct.py
+++ b/matemat/webserver/pagelets/modproduct.py
@@ -2,6 +2,8 @@ from typing import Any, Dict, Union
import os
import magic
+from PIL import Image
+from io import BytesIO
from matemat.webserver import pagelet, RequestArguments, PageletResponse, RedirectResponse, TemplateResponse
from matemat.db import MatematDatabase
@@ -101,19 +103,23 @@ def handle_change(args: RequestArguments, product: Product, db: MatematDatabase,
# If a new product image was uploaded, process it
if 'image' in args:
# Read the raw image data from the request
- image = bytes(args.image)
+ avatar = bytes(args.image)
# Only process the image, if its size is more than zero. Zero size means no new image was uploaded
- if len(image) == 0:
+ if len(avatar) == 0:
return
# Detect the MIME type
- filemagic: magic.FileMagic = magic.detect_from_content(image)
- # Currently, only image/png is supported, don't process any other formats
- if filemagic.mime_type != 'image/png':
- # TODO: Optionally convert to png
+ filemagic: magic.FileMagic = magic.detect_from_content(avatar)
+ if not filemagic.mime_type.startswith('image/'):
return
# Create the absolute path of the upload directory
abspath: str = os.path.join(os.path.abspath(config['UploadDir']), 'thumbnails/products/')
os.makedirs(abspath, exist_ok=True)
- # Write the image to the file
- with open(os.path.join(abspath, f'{product.id}.png'), 'wb') as f:
- f.write(image)
+ try:
+ # Parse the image data
+ image: Image = Image.open(BytesIO(avatar))
+ # Resize the image to 150x150
+ image.thumbnail((150, 150), Image.LANCZOS)
+ # Write the image to the file
+ image.save(os.path.join(abspath, f'{product.id}.png'), 'PNG')
+ except OSError:
+ return
diff --git a/matemat/webserver/pagelets/moduser.py b/matemat/webserver/pagelets/moduser.py
index 8be086c..94bfa84 100644
--- a/matemat/webserver/pagelets/moduser.py
+++ b/matemat/webserver/pagelets/moduser.py
@@ -2,6 +2,8 @@ from typing import Any, Dict, Optional, Union
import os
import magic
+from PIL import Image
+from io import BytesIO
from matemat.webserver import pagelet, RequestArguments, PageletResponse, RedirectResponse, TemplateResponse
from matemat.db import MatematDatabase
@@ -130,13 +132,17 @@ def handle_change(args: RequestArguments, user: User, authuser: User, db: Matema
return
# Detect the MIME type
filemagic: magic.FileMagic = magic.detect_from_content(avatar)
- # Currently, only image/png is supported, don't process any other formats
- if filemagic.mime_type != 'image/png':
- # TODO: Optionally convert to png
+ if not filemagic.mime_type.startswith('image/'):
return
# Create the absolute path of the upload directory
abspath: str = os.path.join(os.path.abspath(config['UploadDir']), 'thumbnails/users/')
os.makedirs(abspath, exist_ok=True)
- # Write the image to the file
- with open(os.path.join(abspath, f'{user.id}.png'), 'wb') as f:
- f.write(avatar)
+ try:
+ # Parse the image data
+ image: Image = Image.open(BytesIO(avatar))
+ # Resize the image to 150x150
+ image.thumbnail((150, 150), Image.LANCZOS)
+ # Write the image to the file
+ image.save(os.path.join(abspath, f'{user.id}.png'), 'PNG')
+ except OSError:
+ return
diff --git a/requirements.txt b/requirements.txt
index 4913234..0da0f5a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,3 @@
file-magic
jinja2
+Pillow
diff --git a/templates/admin_all.html b/templates/admin_all.html
index b637cc7..d1aa1f2 100644
--- a/templates/admin_all.html
+++ b/templates/admin_all.html
@@ -34,7 +34,7 @@
-
+
diff --git a/templates/admin_restricted.html b/templates/admin_restricted.html
index 36152c3..72a262c 100644
--- a/templates/admin_restricted.html
+++ b/templates/admin_restricted.html
@@ -44,13 +44,13 @@
-
+ CHF
-
+ CHF
-
+
@@ -87,4 +87,22 @@
-
\ No newline at end of file
+
+
+
+