Skip to content

Commit b2d1f7f

Browse files
authored
Merge pull request #2049 from wger-project/feature/image-validation
Adds validator for (exercise) images
2 parents 98da44b + 16aa8c7 commit b2d1f7f

File tree

3 files changed

+60
-19
lines changed

3 files changed

+60
-19
lines changed

wger/exercises/api/views.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,17 @@
1717
import logging
1818
from uuid import UUID
1919

20+
# Third Party
21+
import bleach
22+
from actstream import action as actstream_action
23+
from bleach.css_sanitizer import CSSSanitizer
2024
# Django
2125
from django.conf import settings
2226
from django.contrib.postgres.search import TrigramSimilarity
2327
from django.db.models import Q
2428
from django.utils.decorators import method_decorator
2529
from django.utils.translation import gettext as _
2630
from django.views.decorators.cache import cache_page
27-
28-
# Third Party
29-
import bleach
30-
from actstream import action as actstream_action
31-
from bleach.css_sanitizer import CSSSanitizer
3231
from drf_spectacular.types import OpenApiTypes
3332
from drf_spectacular.utils import (
3433
OpenApiParameter,
@@ -90,7 +89,6 @@
9089
from wger.utils.db import is_postgres_db
9190
from wger.utils.language import load_language
9291

93-
9492
logger = logging.getLogger(__name__)
9593

9694

@@ -298,9 +296,9 @@ def search(request):
298296
try:
299297
thumbnail = t.get_thumbnail(aliases.get('micro_cropped')).url
300298
except InvalidImageFormatError as e:
301-
logger.info(f'InvalidImageFormatError while processing a thumbnail: {e}')
299+
logger.warning(f'InvalidImageFormatError while processing a thumbnail: {e}')
302300
except OSError as e:
303-
logger.info(f'OSError while processing a thumbnail: {e}')
301+
logger.warning(f'OSError while processing a thumbnail: {e}')
304302

305303
result_json = {
306304
'value': translation.name,

wger/exercises/models/image.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@
2121
# Django
2222
from django.db import models
2323
from django.utils.translation import gettext_lazy as _
24-
2524
# Third Party
2625
from simple_history.models import HistoricalRecords
2726

2827
# wger
2928
from wger.exercises.models import Exercise
3029
from wger.utils.cache import reset_exercise_api_cache
3130
from wger.utils.helpers import BaseImage
31+
from wger.utils.images import validate_image_static_no_animation
3232
from wger.utils.models import (
3333
AbstractHistoryMixin,
3434
AbstractLicenseModel,
@@ -78,39 +78,34 @@ class ExerciseImage(AbstractLicenseModel, AbstractHistoryMixin, models.Model, Ba
7878

7979
image = models.ImageField(
8080
verbose_name=_('Image'),
81-
help_text=_('Only PNG and JPEG formats are supported'),
81+
help_text='Only PNG and JPEG formats are supported',
8282
upload_to=exercise_image_upload_dir,
83+
validators=[validate_image_static_no_animation],
8384
)
8485
"""Uploaded image"""
8586

8687
is_main = models.BooleanField(
8788
verbose_name=_('Main picture'),
8889
default=False,
89-
help_text=_(
90-
'Tick the box if you want to set this image as the '
91-
'main one for the exercise (will be shown e.g. in '
92-
'the search). The first image is automatically '
93-
'marked by the system.'
94-
),
9590
)
9691
"""A flag indicating whether the image is the exercise's main image"""
9792

9893
style = models.CharField(
99-
help_text=_('The art style of your image'),
94+
help_text='The art style of your image',
10095
max_length=1,
10196
choices=STYLE,
10297
default=PHOTO,
10398
)
10499
"""The art style of the image"""
105100

106101
created = models.DateTimeField(
107-
_('Date'),
102+
'Date',
108103
auto_now_add=True,
109104
)
110105
"""The creation time"""
111106

112107
last_update = models.DateTimeField(
113-
_('Date'),
108+
'Date',
114109
auto_now=True,
115110
)
116111
"""Datetime of last modification"""

wger/utils/images.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# This file is part of wger Workout Manager.
2+
#
3+
# wger Workout Manager is free software: you can redistribute it and/or modify
4+
# it under the terms of the GNU Affero General Public License as published by
5+
# the Free Software Foundation, either version 3 of the License, or
6+
# (at your option) any later version.
7+
#
8+
# wger Workout Manager is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU Affero General Public License
14+
15+
# Third Party
16+
from PIL import (
17+
Image,
18+
UnidentifiedImageError,
19+
)
20+
# Django
21+
from django.core.exceptions import ValidationError
22+
23+
MAX_FILE_SIZE_MB = 20
24+
25+
26+
def validate_image_static_no_animation(value):
27+
# File size check
28+
if value.size > 1024 * 1024 * MAX_FILE_SIZE_MB:
29+
raise ValidationError(f'The maximum image file size is {MAX_FILE_SIZE_MB}MB.')
30+
31+
# Try opening the file with PIL
32+
try:
33+
value.open()
34+
img = Image.open(value)
35+
img_format = img.format.lower()
36+
except UnidentifiedImageError:
37+
raise ValidationError('File is not a valid image.')
38+
39+
# Supported types
40+
allowed_formats = {'jpeg', 'jpg', 'png', 'webp'}
41+
if img_format not in allowed_formats:
42+
raise ValidationError(
43+
f'File type is not supported. Allowed formats: {", ".join(allowed_formats)}.'
44+
)
45+
46+
# Check for animation
47+
if img_format == 'webp' and getattr(img, 'is_animated'):
48+
raise ValidationError('Animated images are not supported.')

0 commit comments

Comments
 (0)