1
0
Fork 0
forked from s3lph/matemat

Merge branch 'staging' into 'master'

Default user/product avatars

See merge request s3lph/matemat!45
This commit is contained in:
s3lph 2018-09-09 23:36:47 +00:00
commit 15327162d1
10 changed files with 140 additions and 47 deletions

View file

@ -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" ]

View file

@ -20,6 +20,7 @@ This project intends to provide a well-tested and maintainable alternative to
- Python dependencies:
- file-magic
- jinja2
- Pillow
## Usage

View file

@ -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)
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
with open(os.path.join(abspath, f'{user.id}.png'), 'wb') as f:
f.write(avatar)
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)
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
with open(os.path.join(abspath, f'{newproduct.id}.png'), 'wb') as f:
f.write(image)
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')

View file

@ -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)
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
with open(os.path.join(abspath, f'{product.id}.png'), 'wb') as f:
f.write(image)
image.save(os.path.join(abspath, f'{product.id}.png'), 'PNG')
except OSError:
return

View file

@ -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)
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
with open(os.path.join(abspath, f'{user.id}.png'), 'wb') as f:
f.write(avatar)
image.save(os.path.join(abspath, f'{user.id}.png'), 'PNG')
except OSError:
return

View file

@ -1,2 +1,3 @@
file-magic
jinja2
Pillow

View file

@ -34,7 +34,7 @@
<img src="/upload/thumbnails/users/{{ authuser.id }}.png" alt="Avatar of {{ authuser.name }}" /><br/>
<label for="admin-avatar-avatar">Upload new file: </label>
<input id="admin-avatar-avatar" type="file" name="avatar" accept="image/png" /><br/>
<input id="admin-avatar-avatar" type="file" name="avatar" accept="image/*" /><br/>
<input type="submit" value="Save changes" />
</form>

View file

@ -44,13 +44,13 @@
<input id="admin-newproduct-name" type="text" name="name" /><br/>
<label for="admin-newproduct-price-member">Member price: </label>
<input id="admin-newproduct-price-member" type="number" min="0" name="pricemember" /><br/>
CHF <input id="admin-newproduct-price-member" type="number" step="0.01" name="pricemember" value="0" /><br/>
<label for="admin-newproduct-price-non-member">Non-member price: </label>
<input id="admin-newproduct-price-non-member" type="number" min="0" name="pricenonmember" /><br/>
CHF <input id="admin-newproduct-price-non-member" type="number" step="0.01" name="pricenonmember" value="0" /><br/>
<label for="admin-newproduct-image">Image: </label>
<input id="admin-newproduct-image" type="file" accept="image/png" /><br/>
<input id="admin-newproduct-image" name="image" type="file" accept="image/*" /><br/>
<input type="submit" value="Create Product" />
</form>
@ -88,3 +88,21 @@
<input type="submit" value="Go">
</form>
</section>
<section id="admin-restricted-default-images">
<h2>Set default images</h2>
<form id="admin-default-images-form" method="post" action="/admin?adminchange=defaultimg" enctype="multipart/form-data" accept-charset="UTF-8">
<label for="admin-default-images-user">
<img src="/upload/thumbnails/users/default.png" alt="Default user avatar" />
</label><br/>
<input id="admin-default-images-user" type="file" name="users" accept="image/*" /><br/>
<label for="admin-default-images-product">
<img src="/upload/thumbnails/products/default.png" alt="Default product avatar" />
</label><br/>
<input id="admin-default-images-product" type="file" name="products" accept="image/*" /><br/>
<input type="submit" value="Save changes">
</form>
</section>

View file

@ -26,7 +26,7 @@
<label for="modproduct-image">
<img height="150" src="/upload/thumbnails/products/{{ product.id }}.png" alt="Image of {{ product.name }}" />
</label><br/>
<input id="modproduct-image" type="file" name="image" accept="image/png" /><br/>
<input id="modproduct-image" type="file" name="image" accept="image/*" /><br/>
<input id="modproduct-productid" type="hidden" name="productid" value="{{ product.id }}" /><br/>

View file

@ -42,9 +42,9 @@
<input id="moduser-account-balance-reason" type="text" name="reason" placeholder="Shows up on receipt" /><br/>
<label for="moduser-account-avatar">
<img height="150" src="/upload/thumbnails/users/{{ user.id }}.png" alt="Avatar of {{ user.name }}" />
<img src="/upload/thumbnails/users/{{ user.id }}.png" alt="Avatar of {{ user.name }}" />
</label><br/>
<input id="moduser-account-avatar" type="file" name="avatar" accept="image/png" /><br/>
<input id="moduser-account-avatar" type="file" name="avatar" accept="image/*" /><br/>
<input id="moduser-account-userid" type="hidden" name="userid" value="{{ user.id }}" /><br/>