mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-13 11:46:22 +00:00
Compare commits
22 Commits
django-4.1
...
1.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8292b5404 | ||
|
|
5730c2dabf | ||
|
|
dddd24e57f | ||
|
|
a6c5899f44 | ||
|
|
ba6f46c6eb | ||
|
|
ddfc1e7824 | ||
|
|
2761e801df | ||
|
|
555a8b0523 | ||
|
|
6d984a486a | ||
|
|
9dceb7a696 | ||
|
|
64575a9b99 | ||
|
|
a94954d58c | ||
|
|
f359532c30 | ||
|
|
a76d5b9225 | ||
|
|
71d69dde53 | ||
|
|
0b5271424c | ||
|
|
a492995836 | ||
|
|
ad39324650 | ||
|
|
ef02feed3b | ||
|
|
1a10861d2f | ||
|
|
b64f939344 | ||
|
|
ddaf6a6d8a |
@@ -13,7 +13,7 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.3.0
|
rev: v4.4.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
- id: check-ast
|
- id: check-ast
|
||||||
@@ -38,7 +38,7 @@ repos:
|
|||||||
- id: sort-simple-yaml
|
- id: sort-simple-yaml
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: 5.0.4
|
rev: 6.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
- repo: local
|
- repo: local
|
||||||
|
|||||||
@@ -41,12 +41,14 @@ def file_format(image_type):
|
|||||||
"""
|
"""
|
||||||
Helper method returning a short image type
|
Helper method returning a short image type
|
||||||
"""
|
"""
|
||||||
if image_type == "JPEG":
|
if image_type in ("JPEG", "MPO"):
|
||||||
return "jpg"
|
return "jpg"
|
||||||
elif image_type == "PNG":
|
elif image_type == "PNG":
|
||||||
return "png"
|
return "png"
|
||||||
elif image_type == "GIF":
|
elif image_type == "GIF":
|
||||||
return "gif"
|
return "gif"
|
||||||
|
elif image_type == "WEBP":
|
||||||
|
return "webp"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -54,12 +56,14 @@ def pil_format(image_type):
|
|||||||
"""
|
"""
|
||||||
Helper method returning the 'encoder name' for PIL
|
Helper method returning the 'encoder name' for PIL
|
||||||
"""
|
"""
|
||||||
if image_type == "jpg" or image_type == "jpeg":
|
if image_type in ("jpg", "jpeg", "mpo"):
|
||||||
return "JPEG"
|
return "JPEG"
|
||||||
elif image_type == "png":
|
elif image_type == "png":
|
||||||
return "PNG"
|
return "PNG"
|
||||||
elif image_type == "gif":
|
elif image_type == "gif":
|
||||||
return "GIF"
|
return "GIF"
|
||||||
|
elif image_type == "webp":
|
||||||
|
return "WEBP"
|
||||||
|
|
||||||
logger.info("Unsupported file format: %s", image_type)
|
logger.info("Unsupported file format: %s", image_type)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -748,6 +748,47 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
|||||||
response = self.client.get(url, follow=True)
|
response = self.client.get(url, follow=True)
|
||||||
self.assertEqual(response.status_code, 200, "unable to fetch avatar?")
|
self.assertEqual(response.status_code, 200, "unable to fetch avatar?")
|
||||||
|
|
||||||
|
def test_upload_webp_image(self):
|
||||||
|
"""
|
||||||
|
Test if webp is correctly detected and can be viewed
|
||||||
|
"""
|
||||||
|
self.login()
|
||||||
|
url = reverse("upload_photo")
|
||||||
|
# rb => Read binary
|
||||||
|
# Broken is _not_ broken - it's just an 'x' :-)
|
||||||
|
with open(
|
||||||
|
os.path.join(settings.STATIC_ROOT, "img", "broken.webp"), "rb"
|
||||||
|
) as photo:
|
||||||
|
response = self.client.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
"photo": photo,
|
||||||
|
"not_porn": True,
|
||||||
|
"can_distribute": True,
|
||||||
|
},
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
str(list(response.context[0]["messages"])[0]),
|
||||||
|
"Successfully uploaded",
|
||||||
|
"WEBP upload failed?!",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.user.photo_set.first().format,
|
||||||
|
"webp",
|
||||||
|
"Format must be webp, since we uploaded a webp!",
|
||||||
|
)
|
||||||
|
self.test_confirm_email()
|
||||||
|
self.user.confirmedemail_set.first().photo = self.user.photo_set.first()
|
||||||
|
urlobj = urlsplit(
|
||||||
|
libravatar_url(
|
||||||
|
email=self.user.confirmedemail_set.first().email,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
url = "%s?%s" % (urlobj.path, urlobj.query)
|
||||||
|
response = self.client.get(url, follow=True)
|
||||||
|
self.assertEqual(response.status_code, 200, "unable to fetch avatar?")
|
||||||
|
|
||||||
def test_upload_unsupported_tif_image(self): # pylint: disable=invalid-name
|
def test_upload_unsupported_tif_image(self): # pylint: disable=invalid-name
|
||||||
"""
|
"""
|
||||||
Test if unsupported format is correctly detected
|
Test if unsupported format is correctly detected
|
||||||
|
|||||||
BIN
ivatar/static/img/broken.webp
Normal file
BIN
ivatar/static/img/broken.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
@@ -37,6 +37,7 @@ class Tester(TestCase):
|
|||||||
self.assertEqual(pil_format("jpeg"), "JPEG")
|
self.assertEqual(pil_format("jpeg"), "JPEG")
|
||||||
self.assertEqual(pil_format("png"), "PNG")
|
self.assertEqual(pil_format("png"), "PNG")
|
||||||
self.assertEqual(pil_format("gif"), "GIF")
|
self.assertEqual(pil_format("gif"), "GIF")
|
||||||
|
self.assertEqual(pil_format("webp"), "WEBP")
|
||||||
self.assertEqual(pil_format("abc"), None)
|
self.assertEqual(pil_format("abc"), None)
|
||||||
|
|
||||||
def test_userprefs_str(self):
|
def test_userprefs_str(self):
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ Simple module providing reusable random_string function
|
|||||||
"""
|
"""
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
from PIL import Image, ImageDraw
|
from io import BytesIO
|
||||||
|
from PIL import Image, ImageDraw, ImageSequence
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
|
||||||
@@ -158,3 +159,25 @@ def is_trusted_url(url, url_filters):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def resize_animated_gif(input_pil: Image, size: list) -> BytesIO:
|
||||||
|
def _thumbnail_frames(image):
|
||||||
|
for frame in ImageSequence.Iterator(image):
|
||||||
|
new_frame = frame.copy()
|
||||||
|
new_frame.thumbnail(size)
|
||||||
|
yield new_frame
|
||||||
|
|
||||||
|
frames = list(_thumbnail_frames(input_pil))
|
||||||
|
output = BytesIO()
|
||||||
|
output_image = frames[0]
|
||||||
|
output_image.save(
|
||||||
|
output,
|
||||||
|
format="gif",
|
||||||
|
save_all=True,
|
||||||
|
optimize=False,
|
||||||
|
append_images=frames[1:],
|
||||||
|
disposal=input_pil.disposal_method,
|
||||||
|
**input_pil.info,
|
||||||
|
)
|
||||||
|
return output
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ from .ivataraccount.models import ConfirmedEmail, ConfirmedOpenId
|
|||||||
from .ivataraccount.models import UnconfirmedEmail, UnconfirmedOpenId
|
from .ivataraccount.models import UnconfirmedEmail, UnconfirmedOpenId
|
||||||
from .ivataraccount.models import Photo
|
from .ivataraccount.models import Photo
|
||||||
from .ivataraccount.models import pil_format, file_format
|
from .ivataraccount.models import pil_format, file_format
|
||||||
from .utils import is_trusted_url, mm_ng
|
from .utils import is_trusted_url, mm_ng, resize_animated_gif
|
||||||
|
|
||||||
URL_TIMEOUT = 5 # in seconds
|
URL_TIMEOUT = 5 # in seconds
|
||||||
|
|
||||||
@@ -151,7 +151,8 @@ class AvatarImageView(TemplateView):
|
|||||||
|
|
||||||
if not trusted_url:
|
if not trusted_url:
|
||||||
print(
|
print(
|
||||||
"Default URL is not in trusted URLs: '%s' ; Kicking it!" % default
|
"Default URL is not in trusted URLs: '%s' ; Kicking it!"
|
||||||
|
% default
|
||||||
)
|
)
|
||||||
default = None
|
default = None
|
||||||
|
|
||||||
@@ -318,14 +319,23 @@ class AvatarImageView(TemplateView):
|
|||||||
|
|
||||||
imgformat = obj.photo.format
|
imgformat = obj.photo.format
|
||||||
photodata = Image.open(BytesIO(obj.photo.data))
|
photodata = Image.open(BytesIO(obj.photo.data))
|
||||||
|
|
||||||
|
data = BytesIO()
|
||||||
|
|
||||||
|
# Animated GIFs need additional handling
|
||||||
|
if imgformat == "gif" and photodata.is_animated:
|
||||||
|
# Debug only
|
||||||
|
# print("Object is animated and has %i frames" % photodata.n_frames)
|
||||||
|
data = resize_animated_gif(photodata, (size, size))
|
||||||
|
else:
|
||||||
# If the image is smaller than what was requested, we need
|
# If the image is smaller than what was requested, we need
|
||||||
# to use the function resize
|
# to use the function resize
|
||||||
if photodata.size[0] < size or photodata.size[1] < size:
|
if photodata.size[0] < size or photodata.size[1] < size:
|
||||||
photodata = photodata.resize((size, size), Image.ANTIALIAS)
|
photodata = photodata.resize((size, size), Image.ANTIALIAS)
|
||||||
else:
|
else:
|
||||||
photodata.thumbnail((size, size), Image.ANTIALIAS)
|
photodata.thumbnail((size, size), Image.ANTIALIAS)
|
||||||
data = BytesIO()
|
|
||||||
photodata.save(data, pil_format(imgformat), quality=JPEG_QUALITY)
|
photodata.save(data, pil_format(imgformat), quality=JPEG_QUALITY)
|
||||||
|
|
||||||
data.seek(0)
|
data.seek(0)
|
||||||
obj.photo.access_count += 1
|
obj.photo.access_count += 1
|
||||||
obj.photo.save()
|
obj.photo.save()
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
<button type="submit" class="button">{% trans 'Login' %}</button>
|
<button type="submit" class="button">{% trans 'Login' %}</button>
|
||||||
<input type="hidden" name="next" value="{{ request.build_absolute_uri }}{% url 'profile' %}" />
|
<input type="hidden" name="next" value="{% url 'profile' %}" />
|
||||||
|
|
||||||
<button type="reset" class="button" onclick="window.history.back();">{% trans 'Cancel' %}</button>
|
<button type="reset" class="button" onclick="window.history.back();">{% trans 'Cancel' %}</button>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user