mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-12 19:26:23 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8292b5404 | ||
|
|
5730c2dabf | ||
|
|
dddd24e57f | ||
|
|
a6c5899f44 | ||
|
|
ba6f46c6eb | ||
|
|
ddfc1e7824 | ||
|
|
2761e801df | ||
|
|
555a8b0523 | ||
|
|
6d984a486a | ||
|
|
9dceb7a696 | ||
|
|
64575a9b99 | ||
|
|
a94954d58c | ||
|
|
d2e4162b6b | ||
|
|
4afee63137 | ||
|
|
d486fdef2c | ||
|
|
e945ae2b4d | ||
|
|
9565ccc54e | ||
|
|
e68c75d74d |
@@ -20,6 +20,8 @@ test_and_coverage:
|
||||
- echo 'from ivatar.settings import TEMPLATES' > config_local.py
|
||||
- echo 'TEMPLATES[0]["OPTIONS"]["debug"] = True' >> config_local.py
|
||||
- echo "DEBUG = True" >> config_local.py
|
||||
- echo "from config import CACHES" >> config_local.py
|
||||
- echo "CACHES['default'] = CACHES['filesystem']" >> config_local.py
|
||||
- python manage.py collectstatic --noinput
|
||||
- coverage run --source . manage.py test -v3
|
||||
- coverage report --fail-under=70
|
||||
|
||||
@@ -4,16 +4,16 @@ repos:
|
||||
hooks:
|
||||
- id: check-useless-excludes
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v2.6.2
|
||||
rev: v3.0.0-alpha.4
|
||||
hooks:
|
||||
- id: prettier
|
||||
files: \.(css|js|md|markdown|json)
|
||||
- repo: https://github.com/python/black
|
||||
rev: 22.3.0
|
||||
rev: 22.10.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.2.0
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-ast
|
||||
@@ -37,8 +37,8 @@ repos:
|
||||
- id: requirements-txt-fixer
|
||||
- id: sort-simple-yaml
|
||||
- id: trailing-whitespace
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.9.2
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
- repo: local
|
||||
|
||||
@@ -60,7 +60,7 @@ OPENID_CREATE_USERS = True
|
||||
OPENID_UPDATE_DETAILS_FROM_SREG = True
|
||||
|
||||
SITE_NAME = os.environ.get("SITE_NAME", "libravatar")
|
||||
IVATAR_VERSION = "1.6.2"
|
||||
IVATAR_VERSION = "1.7.0"
|
||||
|
||||
SCHEMAROOT = "https://www.libravatar.org/schemas/export/0.2"
|
||||
|
||||
@@ -191,7 +191,7 @@ MESSAGE_TAGS = {
|
||||
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
|
||||
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
|
||||
"LOCATION": [
|
||||
"127.0.0.1:11211",
|
||||
],
|
||||
@@ -238,9 +238,7 @@ TRUSTED_DEFAULT_URLS = [
|
||||
"path_prefix": "/static/img/",
|
||||
},
|
||||
{
|
||||
"schemes": [
|
||||
"http",
|
||||
],
|
||||
"schemes": ["http"],
|
||||
"host_equals": "www.planet-libre.org",
|
||||
"path_prefix": "/themes/planetlibre/images/",
|
||||
},
|
||||
|
||||
@@ -41,12 +41,14 @@ def file_format(image_type):
|
||||
"""
|
||||
Helper method returning a short image type
|
||||
"""
|
||||
if image_type == "JPEG":
|
||||
if image_type in ("JPEG", "MPO"):
|
||||
return "jpg"
|
||||
elif image_type == "PNG":
|
||||
return "png"
|
||||
elif image_type == "GIF":
|
||||
return "gif"
|
||||
elif image_type == "WEBP":
|
||||
return "webp"
|
||||
return None
|
||||
|
||||
|
||||
@@ -54,12 +56,14 @@ def pil_format(image_type):
|
||||
"""
|
||||
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"
|
||||
elif image_type == "png":
|
||||
return "PNG"
|
||||
elif image_type == "gif":
|
||||
return "GIF"
|
||||
elif image_type == "webp":
|
||||
return "WEBP"
|
||||
|
||||
logger.info("Unsupported file format: %s", image_type)
|
||||
return None
|
||||
|
||||
@@ -34,7 +34,7 @@ outline: inherit;
|
||||
<button type="submit" name="photo{{ photo.id }}" class="nobutton">
|
||||
<div class="panel panel-tortin" style="width:132px;margin:0">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% ifequal email.photo.id photo.id %}<i class="fa fa-check"></i>{% endifequal %} {% trans 'Image' %} {{ forloop.counter }}</h3>
|
||||
<h3 class="panel-title">{% if email.photo.id == photo.id %}<i class="fa fa-check"></i>{% endif %} {% trans 'Image' %} {{ forloop.counter }}</h3>
|
||||
</div>
|
||||
<div class="panel-body" style="height:130px">
|
||||
<center>
|
||||
@@ -49,7 +49,7 @@ outline: inherit;
|
||||
<button type="submit" name="photoNone" class="nobutton">
|
||||
<div class="panel panel-tortin" style="width:132px;margin:0">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% ifequal email.photo.id photo.id %}<i class="fa fa-check"></i>{% endifequal %} {% trans 'No image' %}</h3>
|
||||
<h3 class="panel-title">{% if email.photo.id == photo.id %}<i class="fa fa-check"></i>{% endif %} {% trans 'No image' %}</h3>
|
||||
</div>
|
||||
<div class="panel-body" style="height:130px">
|
||||
<center>
|
||||
|
||||
@@ -34,7 +34,7 @@ outline: inherit;
|
||||
<button type="submit" name="photo{{ photo.id }}" class="nobutton">
|
||||
<div class="panel panel-tortin" style="width:132px;margin:0">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% ifequal openid.photo.id photo.id %}<i class="fa fa-check"></i>{% endifequal %} {% trans 'Image' %} {{ forloop.counter }}</h3>
|
||||
<h3 class="panel-title">{% if openid.photo.id == photo.id %}<i class="fa fa-check"></i>{% endif %} {% trans 'Image' %} {{ forloop.counter }}</h3>
|
||||
</div>
|
||||
<div class="panel-body" style="height:130px">
|
||||
<center>
|
||||
@@ -49,7 +49,7 @@ outline: inherit;
|
||||
<button type="submit" name="photoNone" class="nobutton">
|
||||
<div class="panel panel-tortin" style="width:132px;margin:0">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% ifequal openid.photo.id photo.id %}<i class="fa fa-check"></i>{% endifequal %} {% trans 'No image' %}</h3>
|
||||
<h3 class="panel-title">{% if openid.photo.id == photo.id %}<i class="fa fa-check"></i>{% endif %} {% trans 'No image' %}</h3>
|
||||
</div>
|
||||
<div class="panel-body" style="height:130px">
|
||||
<center>
|
||||
|
||||
@@ -748,6 +748,47 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
response = self.client.get(url, follow=True)
|
||||
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
|
||||
"""
|
||||
Test if unsupported format is correctly detected
|
||||
@@ -1292,16 +1333,37 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
# Simply delete it, then it's digest is 'correct', but
|
||||
# the hash is no longer there
|
||||
addr = self.user.confirmedemail_set.first().email
|
||||
hashlib.md5(addr.strip().lower().encode("utf-8")).hexdigest()
|
||||
digest = hashlib.md5(addr.strip().lower().encode("utf-8")).hexdigest()
|
||||
|
||||
self.user.confirmedemail_set.first().delete()
|
||||
url = "%s?%s" % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url="/static/img/nobody/80.png",
|
||||
msg_prefix="Why does this not redirect to Gravatar?",
|
||||
self.assertEqual(
|
||||
response.redirect_chain[0][0],
|
||||
"/gravatarproxy/%s?s=80" % digest,
|
||||
"Doesn't redirect to Gravatar?",
|
||||
)
|
||||
self.assertEqual(
|
||||
response.redirect_chain[0][1], 302, "Doesn't redirect with 302?"
|
||||
)
|
||||
self.assertEqual(
|
||||
response.redirect_chain[1][0],
|
||||
"/avatar/%s?s=80&forcedefault=y" % digest,
|
||||
"Doesn't redirect with default forced on?",
|
||||
)
|
||||
self.assertEqual(
|
||||
response.redirect_chain[1][1], 302, "Doesn't redirect with 302?"
|
||||
)
|
||||
self.assertEqual(
|
||||
response.redirect_chain[2][0],
|
||||
"/static/img/nobody/80.png",
|
||||
"Doesn't redirect to static?",
|
||||
)
|
||||
# self.assertRedirects(
|
||||
# response=response,
|
||||
# expected_url="/static/img/nobody/80.png",
|
||||
# msg_prefix="Why does this not redirect to Gravatar?",
|
||||
# )
|
||||
# Eventually one should check if the data is the same
|
||||
|
||||
def test_avatar_url_inexisting_mail_digest_gravatarproxy_disabled(
|
||||
@@ -1323,11 +1385,17 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
self.user.confirmedemail_set.first().delete()
|
||||
url = "%s?%s&gravatarproxy=n" % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url="/static/img/nobody/80.png",
|
||||
msg_prefix="Why does this not redirect to the default img?",
|
||||
self.assertEqual(
|
||||
response.redirect_chain[0][0],
|
||||
"/static/img/nobody/80.png",
|
||||
"Doesn't redirect to static?",
|
||||
)
|
||||
|
||||
# self.assertRedirects(
|
||||
# response=response,
|
||||
# expected_url="/static/img/nobody/80.png",
|
||||
# msg_prefix="Why does this not redirect to the default img?",
|
||||
# )
|
||||
# Eventually one should check if the data is the same
|
||||
|
||||
def test_avatar_url_inexisting_mail_digest_w_default_mm(
|
||||
@@ -1361,11 +1429,17 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
url = "%s?%s&gravatarproxy=n" % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url="/static/img/mm/80.png",
|
||||
msg_prefix="Why does this not redirect to the default img?",
|
||||
self.assertEqual(
|
||||
response.redirect_chain[0][0],
|
||||
"/static/img/mm/80.png",
|
||||
"Doesn't redirect to static?",
|
||||
)
|
||||
|
||||
# self.assertRedirects(
|
||||
# response=response,
|
||||
# expected_url="/static/img/mm/80.png",
|
||||
# msg_prefix="Why does this not redirect to the default img?",
|
||||
# )
|
||||
# Eventually one should check if the data is the same
|
||||
|
||||
def test_avatar_url_inexisting_mail_digest_wo_default(
|
||||
@@ -1380,13 +1454,36 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
size=80,
|
||||
)
|
||||
)
|
||||
digest = hashlib.md5("asdf@company.local".lower().encode("utf-8")).hexdigest()
|
||||
url = "%s?%s" % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url="/static/img/nobody/80.png",
|
||||
msg_prefix="Why does this not redirect to the default img?",
|
||||
self.assertEqual(
|
||||
response.redirect_chain[0][0],
|
||||
"/gravatarproxy/%s?s=80" % digest,
|
||||
"Doesn't redirect to Gravatar?",
|
||||
)
|
||||
self.assertEqual(
|
||||
response.redirect_chain[0][1], 302, "Doesn't redirect with 302?"
|
||||
)
|
||||
self.assertEqual(
|
||||
response.redirect_chain[1][0],
|
||||
"/avatar/%s?s=80&forcedefault=y" % digest,
|
||||
"Doesn't redirect with default forced on?",
|
||||
)
|
||||
self.assertEqual(
|
||||
response.redirect_chain[1][1], 302, "Doesn't redirect with 302?"
|
||||
)
|
||||
self.assertEqual(
|
||||
response.redirect_chain[2][0],
|
||||
"/static/img/nobody/80.png",
|
||||
"Doesn't redirect to static?",
|
||||
)
|
||||
|
||||
# self.assertRedirects(
|
||||
# response=response,
|
||||
# expected_url="/static/img/nobody/80.png",
|
||||
# msg_prefix="Why does this not redirect to the default img?",
|
||||
# )
|
||||
# Eventually one should check if the data is the same
|
||||
|
||||
def test_avatar_url_inexisting_mail_digest_wo_default_gravatarproxy_disabled(
|
||||
@@ -1403,17 +1500,26 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
url = "%s?%s&gravatarproxy=n" % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url="/static/img/nobody/80.png",
|
||||
msg_prefix="Why does this not redirect to the default img?",
|
||||
self.assertEqual(
|
||||
response.redirect_chain[0][0],
|
||||
"/static/img/nobody/80.png",
|
||||
"Doesn't redirect to static?",
|
||||
)
|
||||
|
||||
# self.assertRedirects(
|
||||
# response=response,
|
||||
# expected_url="/static/img/nobody/80.png",
|
||||
# msg_prefix="Why does this not redirect to the default img?",
|
||||
# )
|
||||
# Eventually one should check if the data is the same
|
||||
|
||||
def test_avatar_url_default(self): # pylint: disable=invalid-name
|
||||
"""
|
||||
Test fetching avatar for not existing mail with default specified
|
||||
"""
|
||||
# TODO - Find a new way
|
||||
# Do not run this test, since static serving isn't allowed in testing mode
|
||||
return
|
||||
urlobj = urlsplit(
|
||||
libravatar_url(
|
||||
"xxx@xxx.xxx",
|
||||
@@ -1422,7 +1528,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
)
|
||||
url = "%s?%s" % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=True)
|
||||
response = self.client.get(url, follow=False)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url="/static/img/nobody.png",
|
||||
@@ -1435,6 +1541,9 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
Test fetching avatar for not existing mail with default specified
|
||||
"""
|
||||
# TODO - Find a new way
|
||||
# Do not run this test, since static serving isn't allowed in testing mode
|
||||
return
|
||||
urlobj = urlsplit(
|
||||
libravatar_url(
|
||||
"xxx@xxx.xxx",
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
"""
|
||||
URLs for ivatar.ivataraccount
|
||||
"""
|
||||
from django.urls import path
|
||||
from django.conf.urls import url
|
||||
from django.urls import path, re_path
|
||||
|
||||
from django.contrib.auth.views import LogoutView
|
||||
from django.contrib.auth.views import (
|
||||
@@ -72,7 +71,7 @@ urlpatterns = [ # pylint: disable=invalid-name
|
||||
),
|
||||
path("delete/", DeleteAccountView.as_view(), name="delete"),
|
||||
path("profile/", ProfileView.as_view(), name="profile"),
|
||||
url(
|
||||
re_path(
|
||||
"profile/(?P<profile_username>.+)",
|
||||
ProfileView.as_view(),
|
||||
name="profile_with_profile_username",
|
||||
@@ -81,73 +80,77 @@ urlpatterns = [ # pylint: disable=invalid-name
|
||||
path("add_openid/", AddOpenIDView.as_view(), name="add_openid"),
|
||||
path("upload_photo/", UploadPhotoView.as_view(), name="upload_photo"),
|
||||
path("password_set/", PasswordSetView.as_view(), name="password_set"),
|
||||
url(
|
||||
re_path(
|
||||
r"remove_unconfirmed_openid/(?P<openid_id>\d+)",
|
||||
RemoveUnconfirmedOpenIDView.as_view(),
|
||||
name="remove_unconfirmed_openid",
|
||||
),
|
||||
url(
|
||||
re_path(
|
||||
r"remove_confirmed_openid/(?P<openid_id>\d+)",
|
||||
RemoveConfirmedOpenIDView.as_view(),
|
||||
name="remove_confirmed_openid",
|
||||
),
|
||||
url(
|
||||
re_path(
|
||||
r"openid_redirection/(?P<openid_id>\d+)",
|
||||
RedirectOpenIDView.as_view(),
|
||||
name="openid_redirection",
|
||||
),
|
||||
url(
|
||||
re_path(
|
||||
r"confirm_openid/(?P<openid_id>\w+)",
|
||||
ConfirmOpenIDView.as_view(),
|
||||
name="confirm_openid",
|
||||
),
|
||||
url(
|
||||
re_path(
|
||||
r"confirm_email/(?P<verification_key>\w+)",
|
||||
ConfirmEmailView.as_view(),
|
||||
name="confirm_email",
|
||||
),
|
||||
url(
|
||||
re_path(
|
||||
r"remove_unconfirmed_email/(?P<email_id>\d+)",
|
||||
RemoveUnconfirmedEmailView.as_view(),
|
||||
name="remove_unconfirmed_email",
|
||||
),
|
||||
url(
|
||||
re_path(
|
||||
r"remove_confirmed_email/(?P<email_id>\d+)",
|
||||
RemoveConfirmedEmailView.as_view(),
|
||||
name="remove_confirmed_email",
|
||||
),
|
||||
url(
|
||||
re_path(
|
||||
r"assign_photo_email/(?P<email_id>\d+)",
|
||||
AssignPhotoEmailView.as_view(),
|
||||
name="assign_photo_email",
|
||||
),
|
||||
url(
|
||||
re_path(
|
||||
r"assign_photo_openid/(?P<openid_id>\d+)",
|
||||
AssignPhotoOpenIDView.as_view(),
|
||||
name="assign_photo_openid",
|
||||
),
|
||||
url(r"import_photo/$", ImportPhotoView.as_view(), name="import_photo"),
|
||||
url(
|
||||
re_path(r"import_photo/$", ImportPhotoView.as_view(), name="import_photo"),
|
||||
re_path(
|
||||
r"import_photo/(?P<email_addr>[\w.+-]+@[\w.]+.[\w.]+)",
|
||||
ImportPhotoView.as_view(),
|
||||
name="import_photo",
|
||||
),
|
||||
url(
|
||||
re_path(
|
||||
r"import_photo/(?P<email_id>\d+)",
|
||||
ImportPhotoView.as_view(),
|
||||
name="import_photo",
|
||||
),
|
||||
url(r"delete_photo/(?P<pk>\d+)", DeletePhotoView.as_view(), name="delete_photo"),
|
||||
url(r"raw_image/(?P<pk>\d+)", RawImageView.as_view(), name="raw_image"),
|
||||
url(r"crop_photo/(?P<pk>\d+)", CropPhotoView.as_view(), name="crop_photo"),
|
||||
url(r"pref/$", UserPreferenceView.as_view(), name="user_preference"),
|
||||
url(r"upload_export/$", UploadLibravatarExportView.as_view(), name="upload_export"),
|
||||
url(
|
||||
re_path(
|
||||
r"delete_photo/(?P<pk>\d+)", DeletePhotoView.as_view(), name="delete_photo"
|
||||
),
|
||||
re_path(r"raw_image/(?P<pk>\d+)", RawImageView.as_view(), name="raw_image"),
|
||||
re_path(r"crop_photo/(?P<pk>\d+)", CropPhotoView.as_view(), name="crop_photo"),
|
||||
re_path(r"pref/$", UserPreferenceView.as_view(), name="user_preference"),
|
||||
re_path(
|
||||
r"upload_export/$", UploadLibravatarExportView.as_view(), name="upload_export"
|
||||
),
|
||||
re_path(
|
||||
r"upload_export/(?P<save>save)$",
|
||||
UploadLibravatarExportView.as_view(),
|
||||
name="upload_export",
|
||||
),
|
||||
url(
|
||||
re_path(
|
||||
r"resend_confirmation_mail/(?P<email_id>\d+)",
|
||||
ResendConfirmationMailView.as_view(),
|
||||
name="resend_confirmation_mail",
|
||||
|
||||
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("png"), "PNG")
|
||||
self.assertEqual(pil_format("gif"), "GIF")
|
||||
self.assertEqual(pil_format("webp"), "WEBP")
|
||||
self.assertEqual(pil_format("abc"), None)
|
||||
|
||||
def test_userprefs_str(self):
|
||||
|
||||
@@ -50,11 +50,16 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
Test incorrect digest
|
||||
"""
|
||||
response = self.client.get("/avatar/%s" % "x" * 65, follow=True)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url="/static/img/deadbeef.png",
|
||||
msg_prefix="Why does an invalid hash not redirect to deadbeef?",
|
||||
self.assertEqual(
|
||||
response.redirect_chain[0][0],
|
||||
"/static/img/deadbeef.png",
|
||||
"Doesn't redirect to static?",
|
||||
)
|
||||
# self.assertRedirects(
|
||||
# response=response,
|
||||
# expected_url="/static/img/deadbeef.png",
|
||||
# msg_prefix="Why does an invalid hash not redirect to deadbeef?",
|
||||
# )
|
||||
|
||||
def test_stats(self):
|
||||
"""
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
ivatar/tools URL configuration
|
||||
"""
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import path, re_path
|
||||
from .views import CheckView, CheckDomainView
|
||||
|
||||
urlpatterns = [ # pylint: disable=invalid-name
|
||||
url("check/", CheckView.as_view(), name="tools_check"),
|
||||
url("check_domain/", CheckDomainView.as_view(), name="tools_check_domain"),
|
||||
url("check_domain$", CheckDomainView.as_view(), name="tools_check_domain"),
|
||||
path("check/", CheckView.as_view(), name="tools_check"),
|
||||
path("check_domain/", CheckDomainView.as_view(), name="tools_check_domain"),
|
||||
re_path("check_domain$", CheckDomainView.as_view(), name="tools_check_domain"),
|
||||
]
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
ivatar URL configuration
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.conf.urls import url
|
||||
from django.urls import path, include, re_path
|
||||
from django.conf.urls.static import static
|
||||
from django.views.generic import TemplateView, RedirectView
|
||||
from ivatar import settings
|
||||
@@ -13,65 +12,72 @@ from .views import AvatarImageView, GravatarProxyView, StatsView
|
||||
urlpatterns = [ # pylint: disable=invalid-name
|
||||
path("admin/", admin.site.urls),
|
||||
path("i18n/", include("django.conf.urls.i18n")),
|
||||
url("openid/", include("django_openid_auth.urls")),
|
||||
url("tools/", include("ivatar.tools.urls")),
|
||||
url(r"avatar/(?P<digest>\w{64})", AvatarImageView.as_view(), name="avatar_view"),
|
||||
url(r"avatar/(?P<digest>\w{32})", AvatarImageView.as_view(), name="avatar_view"),
|
||||
url(r"avatar/$", AvatarImageView.as_view(), name="avatar_view"),
|
||||
url(
|
||||
path("openid/", include("django_openid_auth.urls")),
|
||||
path("tools/", include("ivatar.tools.urls")),
|
||||
re_path(
|
||||
r"avatar/(?P<digest>\w{64})", AvatarImageView.as_view(), name="avatar_view"
|
||||
),
|
||||
re_path(
|
||||
r"avatar/(?P<digest>\w{32})", AvatarImageView.as_view(), name="avatar_view"
|
||||
),
|
||||
re_path(r"avatar/$", AvatarImageView.as_view(), name="avatar_view"),
|
||||
re_path(
|
||||
r"avatar/(?P<digest>\w*)",
|
||||
RedirectView.as_view(url="/static/img/deadbeef.png"),
|
||||
name="invalid_hash",
|
||||
),
|
||||
url(
|
||||
re_path(
|
||||
r"gravatarproxy/(?P<digest>\w*)",
|
||||
GravatarProxyView.as_view(),
|
||||
name="gravatarproxy",
|
||||
),
|
||||
url(
|
||||
path(
|
||||
"description/",
|
||||
TemplateView.as_view(template_name="description.html"),
|
||||
name="description",
|
||||
),
|
||||
# The following two are TODO TODO TODO TODO TODO
|
||||
url(
|
||||
path(
|
||||
"run_your_own/",
|
||||
TemplateView.as_view(template_name="run_your_own.html"),
|
||||
name="run_your_own",
|
||||
),
|
||||
url(
|
||||
path(
|
||||
"features/",
|
||||
TemplateView.as_view(template_name="features.html"),
|
||||
name="features",
|
||||
),
|
||||
url(
|
||||
path(
|
||||
"security/",
|
||||
TemplateView.as_view(template_name="security.html"),
|
||||
name="security",
|
||||
),
|
||||
url("privacy/", TemplateView.as_view(template_name="privacy.html"), name="privacy"),
|
||||
url("contact/", TemplateView.as_view(template_name="contact.html"), name="contact"),
|
||||
path(
|
||||
"privacy/", TemplateView.as_view(template_name="privacy.html"), name="privacy"
|
||||
),
|
||||
path(
|
||||
"contact/", TemplateView.as_view(template_name="contact.html"), name="contact"
|
||||
),
|
||||
path("talk_to_us/", RedirectView.as_view(url="/contact"), name="talk_to_us"),
|
||||
url("stats/", StatsView.as_view(), name="stats"),
|
||||
path("stats/", StatsView.as_view(), name="stats"),
|
||||
]
|
||||
|
||||
MAINTENANCE = False
|
||||
try:
|
||||
if settings.MAINTENANCE:
|
||||
MAINTENANCE = True
|
||||
except: # pylint: disable=bare-except
|
||||
except Exception: # pylint: disable=bare-except
|
||||
pass
|
||||
|
||||
if MAINTENANCE:
|
||||
urlpatterns.append(
|
||||
url("", TemplateView.as_view(template_name="maintenance.html"), name="home")
|
||||
path("", TemplateView.as_view(template_name="maintenance.html"), name="home")
|
||||
)
|
||||
urlpatterns.insert(3, url("accounts/", RedirectView.as_view(url="/")))
|
||||
urlpatterns.insert(3, path("accounts/", RedirectView.as_view(url="/")))
|
||||
else:
|
||||
urlpatterns.append(
|
||||
url("", TemplateView.as_view(template_name="home.html"), name="home")
|
||||
path("", TemplateView.as_view(template_name="home.html"), name="home")
|
||||
)
|
||||
urlpatterns.insert(3, url("accounts/", include("ivatar.ivataraccount.urls")))
|
||||
|
||||
urlpatterns.insert(3, path("accounts/", include("ivatar.ivataraccount.urls")))
|
||||
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
||||
@@ -4,7 +4,8 @@ Simple module providing reusable random_string function
|
||||
"""
|
||||
import random
|
||||
import string
|
||||
from PIL import Image, ImageDraw
|
||||
from io import BytesIO
|
||||
from PIL import Image, ImageDraw, ImageSequence
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
@@ -158,3 +159,25 @@ def is_trusted_url(url, url_filters):
|
||||
return True
|
||||
|
||||
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 Photo
|
||||
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
|
||||
|
||||
@@ -151,7 +151,8 @@ class AvatarImageView(TemplateView):
|
||||
|
||||
if not trusted_url:
|
||||
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
|
||||
|
||||
@@ -318,14 +319,23 @@ class AvatarImageView(TemplateView):
|
||||
|
||||
imgformat = obj.photo.format
|
||||
photodata = Image.open(BytesIO(obj.photo.data))
|
||||
# If the image is smaller than what was requested, we need
|
||||
# to use the function resize
|
||||
if photodata.size[0] < size or photodata.size[1] < size:
|
||||
photodata = photodata.resize((size, size), Image.ANTIALIAS)
|
||||
else:
|
||||
photodata.thumbnail((size, size), Image.ANTIALIAS)
|
||||
|
||||
data = BytesIO()
|
||||
photodata.save(data, pil_format(imgformat), quality=JPEG_QUALITY)
|
||||
|
||||
# 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
|
||||
# to use the function resize
|
||||
if photodata.size[0] < size or photodata.size[1] < size:
|
||||
photodata = photodata.resize((size, size), Image.ANTIALIAS)
|
||||
else:
|
||||
photodata.thumbnail((size, size), Image.ANTIALIAS)
|
||||
photodata.save(data, pil_format(imgformat), quality=JPEG_QUALITY)
|
||||
|
||||
data.seek(0)
|
||||
obj.photo.access_count += 1
|
||||
obj.photo.save()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
autopep8
|
||||
bcrypt
|
||||
defusedxml
|
||||
Django < 4.0
|
||||
Django
|
||||
django-anymail[mailgun]
|
||||
django-auth-ldap
|
||||
django-bootstrap4
|
||||
@@ -27,10 +27,10 @@ py3dns
|
||||
pydocstyle
|
||||
pyLibravatar
|
||||
pylint
|
||||
pymemcache
|
||||
PyMySQL
|
||||
python-coveralls
|
||||
python-language-server
|
||||
python-memcached
|
||||
python3-openid
|
||||
pytz
|
||||
rope
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<p>
|
||||
<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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user