Code cleanup/refactoring

This commit is contained in:
Oliver Falk
2025-02-10 16:54:28 +01:00
parent 4a892b0c4c
commit e90604a8d3
14 changed files with 315 additions and 559 deletions

View File

@@ -1,5 +1,5 @@
[flake8] [flake8]
ignore = E501, W503, E402, C901, E231 ignore = E501, W503, E402, C901, E231, E702
max-line-length = 79 max-line-length = 79
max-complexity = 18 max-complexity = 18
select = B,C,E,F,W,T4,B9 select = B,C,E,F,W,T4,B9

View File

@@ -118,9 +118,7 @@ class UploadPhotoForm(forms.Form):
photo.ip_address = get_client_ip(request)[0] photo.ip_address = get_client_ip(request)[0]
photo.data = data.read() photo.data = data.read()
photo.save() photo.save()
if not photo.pk: return None if not photo.pk else photo
return None
return photo
class AddOpenIDForm(forms.Form): class AddOpenIDForm(forms.Form):
@@ -141,13 +139,16 @@ class AddOpenIDForm(forms.Form):
""" """
# Lowercase hostname port of the URL # Lowercase hostname port of the URL
url = urlsplit(self.cleaned_data["openid"]) url = urlsplit(self.cleaned_data["openid"])
data = urlunsplit( return urlunsplit(
(url.scheme.lower(), url.netloc.lower(), url.path, url.query, url.fragment) (
url.scheme.lower(),
url.netloc.lower(),
url.path,
url.query,
url.fragment,
)
) )
# TODO: Domain restriction as in libravatar?
return data
def save(self, user): def save(self, user):
""" """
Save the model, ensuring some safety Save the model, ensuring some safety

View File

@@ -346,7 +346,7 @@ class ConfirmedEmail(BaseAccountModel):
handle = bs.normalize_handle(handle) handle = bs.normalize_handle(handle)
avatar = bs.get_profile(handle) avatar = bs.get_profile(handle)
if not avatar: if not avatar:
raise Exception("Invalid Bluesky handle") raise ValueError("Invalid Bluesky handle")
self.bluesky_handle = handle self.bluesky_handle = handle
self.save() self.save()
@@ -499,7 +499,7 @@ class ConfirmedOpenId(BaseAccountModel):
handle = bs.normalize_handle(handle) handle = bs.normalize_handle(handle)
avatar = bs.get_profile(handle) avatar = bs.get_profile(handle)
if not avatar: if not avatar:
raise Exception("Invalid Bluesky handle") raise ValueError("Invalid Bluesky handle")
self.bluesky_handle = handle self.bluesky_handle = handle
self.save() self.save()

View File

@@ -32,8 +32,6 @@ def read_gzdata(gzdata=None):
""" """
Read gzipped data file Read gzipped data file
""" """
emails = [] # pylint: disable=invalid-name
openids = [] # pylint: disable=invalid-name
photos = [] # pylint: disable=invalid-name photos = [] # pylint: disable=invalid-name
username = None # pylint: disable=invalid-name username = None # pylint: disable=invalid-name
password = None # pylint: disable=invalid-name password = None # pylint: disable=invalid-name
@@ -45,8 +43,8 @@ def read_gzdata(gzdata=None):
content = fh.read() content = fh.read()
fh.close() fh.close()
root = xml.etree.ElementTree.fromstring(content) root = xml.etree.ElementTree.fromstring(content)
if not root.tag == "{%s}user" % SCHEMAROOT: if root.tag != "{%s}user" % SCHEMAROOT:
print("Unknown export format: %s" % root.tag) print(f"Unknown export format: {root.tag}")
exit(-1) exit(-1)
# Username # Username
@@ -56,23 +54,21 @@ def read_gzdata(gzdata=None):
if item[0] == "password": if item[0] == "password":
password = item[1] password = item[1]
# Emails emails = [
for email in root.findall("{%s}emails" % SCHEMAROOT)[0]: {"email": email.text, "photo_id": email.attrib["photo_id"]}
if email.tag == "{%s}email" % SCHEMAROOT: for email in root.findall("{%s}emails" % SCHEMAROOT)[0]
emails.append({"email": email.text, "photo_id": email.attrib["photo_id"]}) if email.tag == "{%s}email" % SCHEMAROOT
]
# OpenIDs openids = [
for openid in root.findall("{%s}openids" % SCHEMAROOT)[0]: {"openid": openid.text, "photo_id": openid.attrib["photo_id"]}
if openid.tag == "{%s}openid" % SCHEMAROOT: for openid in root.findall("{%s}openids" % SCHEMAROOT)[0]
openids.append( if openid.tag == "{%s}openid" % SCHEMAROOT
{"openid": openid.text, "photo_id": openid.attrib["photo_id"]} ]
)
# Photos # Photos
for photo in root.findall("{%s}photos" % SCHEMAROOT)[0]: for photo in root.findall("{%s}photos" % SCHEMAROOT)[0]:
if photo.tag == "{%s}photo" % SCHEMAROOT: if photo.tag == "{%s}photo" % SCHEMAROOT:
try: try:
# Safty measures to make sure we do not try to parse # Safety measures to make sure we do not try to parse
# a binary encoded string # a binary encoded string
photo.text = photo.text.strip("'") photo.text = photo.text.strip("'")
photo.text = photo.text.strip("\\n") photo.text = photo.text.strip("\\n")
@@ -80,26 +76,14 @@ def read_gzdata(gzdata=None):
data = base64.decodebytes(bytes(photo.text, "utf-8")) data = base64.decodebytes(bytes(photo.text, "utf-8"))
except binascii.Error as exc: except binascii.Error as exc:
print( print(
"Cannot decode photo; Encoding: %s, Format: %s, Id: %s: %s" f'Cannot decode photo; Encoding: {photo.attrib["encoding"]}, Format: {photo.attrib["format"]}, Id: {photo.attrib["id"]}: {exc}'
% (
photo.attrib["encoding"],
photo.attrib["format"],
photo.attrib["id"],
exc,
)
) )
continue continue
try: try:
Image.open(BytesIO(data)) Image.open(BytesIO(data))
except Exception as exc: # pylint: disable=broad-except except Exception as exc: # pylint: disable=broad-except
print( print(
"Cannot decode photo; Encoding: %s, Format: %s, Id: %s: %s" f'Cannot decode photo; Encoding: {photo.attrib["encoding"]}, Format: {photo.attrib["format"]}, Id: {photo.attrib["id"]}: {exc}'
% (
photo.attrib["encoding"],
photo.attrib["format"],
photo.attrib["id"],
exc,
)
) )
continue continue
else: else:

View File

@@ -461,17 +461,8 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
}, },
follow=True, follow=True,
) # Create test addresses + 1 too much ) # Create test addresses + 1 too much
# Check the response context for form errors return self._check_form_validity(
self.assertTrue( response, "Too many unconfirmed mail addresses!", "__all__"
hasattr(response, "context"), "Response does not have a context"
)
form = response.context.get("form")
self.assertIsNotNone(form, "No form found in response context")
# Verify form errors
self.assertFalse(form.is_valid(), "Form should not be valid")
self.assertIn(
"Too many unconfirmed mail addresses!", form.errors.get("__all__", [])
) )
def test_add_mail_address_twice(self): def test_add_mail_address_twice(self):
@@ -491,17 +482,8 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
}, },
follow=True, follow=True,
) )
# Check the response context for form errors return self._check_form_validity(
self.assertTrue( response, "Address already added, currently unconfirmed", "email"
hasattr(response, "context"), "Response does not have a context"
)
form = response.context.get("form")
self.assertIsNotNone(form, "No form found in response context")
# Verify form errors
self.assertFalse(form.is_valid(), "Form should not be valid")
self.assertIn(
"Address already added, currently unconfirmed", form.errors.get("email", [])
) )
def test_add_already_confirmed_email_self(self): # pylint: disable=invalid-name def test_add_already_confirmed_email_self(self): # pylint: disable=invalid-name
@@ -520,17 +502,8 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
follow=True, follow=True,
) )
# Check the response context for form errors return self._check_form_validity(
self.assertTrue( response, "Address already confirmed (by you)", "email"
hasattr(response, "context"), "Response does not have a context"
)
form = response.context.get("form")
self.assertIsNotNone(form, "No form found in response context")
# Verify form errors
self.assertFalse(form.is_valid(), "Form should not be valid")
self.assertIn(
"Address already confirmed (by you)", form.errors.get("email", [])
) )
def test_add_already_confirmed_email_other(self): # pylint: disable=invalid-name def test_add_already_confirmed_email_other(self): # pylint: disable=invalid-name
@@ -556,17 +529,8 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
follow=True, follow=True,
) )
# Check the response context for form errors return self._check_form_validity(
self.assertTrue( response, "Address already confirmed (by someone else)", "email"
hasattr(response, "context"), "Response does not have a context"
)
form = response.context.get("form")
self.assertIsNotNone(form, "No form found in response context")
# Verify form errors
self.assertFalse(form.is_valid(), "Form should not be valid")
self.assertIn(
"Address already confirmed (by someone else)", form.errors.get("email", [])
) )
def test_remove_unconfirmed_non_existing_email( def test_remove_unconfirmed_non_existing_email(
@@ -712,120 +676,59 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
""" """
Test if gif is correctly detected and can be viewed Test if gif is correctly detected and can be viewed
""" """
self.login() self._extracted_from_test_upload_webp_image_5(
url = reverse("upload_photo") "broken.gif",
# rb => Read binary
# Broken is _not_ broken - it's just an 'x' :-)
with open(
os.path.join(settings.STATIC_ROOT, "img", "broken.gif"), "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",
"GIF upload failed?!", "GIF upload failed?!",
)
self.assertEqual(
self.user.photo_set.first().format,
"gif", "gif",
"Format must be gif, since we uploaded a GIF!", "Format must be gif, since we uploaded a GIF!",
) )
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 = f"{urlobj.path}?{urlobj.query}"
response = self.client.get(url, follow=True)
self.assertEqual(response.status_code, 200, "unable to fetch avatar?")
def test_upload_jpg_image(self): def test_upload_jpg_image(self):
""" """
Test if jpg is correctly detected and can be viewed Test if jpg is correctly detected and can be viewed
""" """
self.login() self._extracted_from_test_upload_webp_image_5(
url = reverse("upload_photo") "broken.jpg",
# rb => Read binary
# Broken is _not_ broken - it's just an 'x' :-)
with open(
os.path.join(settings.STATIC_ROOT, "img", "broken.jpg"), "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",
"JPEG upload failed?!", "JPEG upload failed?!",
)
self.assertEqual(
self.user.photo_set.first().format,
"jpg", "jpg",
"Format must be jpeg, since we uploaded a jpeg!", "Format must be jpeg, since we uploaded a jpeg!",
) )
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 = f"{urlobj.path}?{urlobj.query}"
response = self.client.get(url, follow=True)
self.assertEqual(response.status_code, 200, "unable to fetch avatar?")
def test_upload_webp_image(self): def test_upload_webp_image(self):
""" """
Test if webp is correctly detected and can be viewed Test if webp is correctly detected and can be viewed
""" """
self._extracted_from_test_upload_webp_image_5(
"broken.webp",
"WEBP upload failed?!",
"webp",
"Format must be webp, since we uploaded a webp!",
)
def _extracted_from_test_upload_webp_image_5(
self, filename, message1, format, message2
):
"""
Helper function for common checks for gif, jpg, webp
"""
self.login() self.login()
url = reverse("upload_photo") url = reverse("upload_photo")
# rb => Read binary with open(os.path.join(settings.STATIC_ROOT, "img", filename), "rb") as photo:
# 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( response = self.client.post(
url, url,
{ {"photo": photo, "not_porn": True, "can_distribute": True},
"photo": photo,
"not_porn": True,
"can_distribute": True,
},
follow=True, follow=True,
) )
self.assertEqual( self.assertEqual(
str(list(response.context[0]["messages"])[0]), str(list(response.context[0]["messages"])[0]),
"Successfully uploaded", "Successfully uploaded",
"WEBP upload failed?!", message1,
)
self.assertEqual(
self.user.photo_set.first().format,
"webp",
"Format must be webp, since we uploaded a webp!",
) )
self.assertEqual(self.user.photo_set.first().format, format, message2)
self.test_confirm_email() self.test_confirm_email()
self.user.confirmedemail_set.first().photo = self.user.photo_set.first() self.user.confirmedemail_set.first().photo = self.user.photo_set.first()
urlobj = urlsplit( urlobj = urlsplit(
libravatar_url( libravatar_url(email=self.user.confirmedemail_set.first().email)
email=self.user.confirmedemail_set.first().email,
)
) )
url = f"{urlobj.path}?{urlobj.query}" url = f"{urlobj.path}?{urlobj.query}"
response = self.client.get(url, follow=True) response = self.client.get(url, follow=True)
@@ -839,7 +742,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
url = reverse("upload_photo") url = reverse("upload_photo")
# rb => Read binary # rb => Read binary
with open( with open(
os.path.join(settings.STATIC_ROOT, "img", "hackergotchi_test.tif"), "rb" os.path.join(settings.STATIC_ROOT, "img", "broken.tif"), "rb"
) as photo: ) as photo:
response = self.client.post( response = self.client.post(
url, url,
@@ -1062,8 +965,6 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
if confirm: if confirm:
self._manual_confirm() self._manual_confirm()
# TODO Rename this here and in `test_add_openid`
def test_add_openid_twice(self): def test_add_openid_twice(self):
""" """
Test if adding OpenID a second time works - it shouldn't Test if adding OpenID a second time works - it shouldn't
@@ -1095,20 +996,9 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"There must only be one unconfirmed ID!", "There must only be one unconfirmed ID!",
) )
# Check the response context for form errors self._check_form_validity(
self.assertTrue( response, "OpenID already added, but not confirmed yet!", "openid"
hasattr(response, "context"), "Response does not have a context"
) )
form = response.context.get("form")
self.assertIsNotNone(form, "No form found in response context")
# Verify form errors
self.assertFalse(form.is_valid(), "Form should not be valid")
self.assertIn(
"OpenID already added, but not confirmed yet!",
form.errors.get("openid", []),
)
# Manual confirm, since testing is _really_ hard! # Manual confirm, since testing is _really_ hard!
unconfirmed = self.user.unconfirmedopenid_set.first() unconfirmed = self.user.unconfirmedopenid_set.first()
confirmed = ConfirmedOpenId() confirmed = ConfirmedOpenId()
@@ -1127,18 +1017,24 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
follow=True, follow=True,
) )
# Check the response context for form errors return self._check_form_validity(
response, "OpenID already added and confirmed!", "openid"
)
def _check_form_validity(self, response, message, field):
"""
Helper method to check form, used in several test functions,
deduplicating code
"""
self.assertTrue( self.assertTrue(
hasattr(response, "context"), "Response does not have a context" hasattr(response, "context"), "Response does not have a context"
) )
form = response.context.get("form") result = response.context.get("form")
self.assertIsNotNone(form, "No form found in response context") self.assertIsNotNone(result, "No form found in response context")
self.assertFalse(result.is_valid(), "Form should not be valid")
# Verify form errors self.assertIn(message, result.errors.get(field, []))
self.assertFalse(form.is_valid(), "Form should not be valid") return result
self.assertIn(
"OpenID already added and confirmed!", form.errors.get("openid", [])
)
def test_assign_photo_to_openid(self): def test_assign_photo_to_openid(self):
""" """
@@ -2023,37 +1919,10 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
fh_gzip = gzip.open(BytesIO(response.content), "rb") fh_gzip = gzip.open(BytesIO(response.content), "rb")
fh = BytesIO(response.content) fh = BytesIO(response.content)
response = self.client.post( response = self._uploading_export_check(
reverse("upload_export"), fh_gzip, "Unable to parse file: Not a gzipped file"
data={"not_porn": "on", "can_distribute": "on", "export_file": fh_gzip},
follow=True,
)
fh_gzip.close()
self.assertEqual(response.status_code, 200, "Upload worked")
self.assertContains(
response,
"Unable to parse file: Not a gzipped file",
1,
200,
"Upload didn't work?",
)
# Second test - correctly gzipped content
response = self.client.post(
reverse("upload_export"),
data={"not_porn": "on", "can_distribute": "on", "export_file": fh},
follow=True,
)
fh.close()
self.assertEqual(response.status_code, 200, "Upload worked")
self.assertContains(
response,
"Choose items to be imported",
1,
200,
"Upload didn't work?",
) )
response = self._uploading_export_check(fh, "Choose items to be imported")
self.assertContains( self.assertContains(
response, response,
"asdf@asdf.local", "asdf@asdf.local",
@@ -2062,6 +1931,21 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"Upload didn't work?", "Upload didn't work?",
) )
def _uploading_export_check(self, fh, message):
"""
Helper function to upload an export
"""
result = self.client.post(
reverse("upload_export"),
data={"not_porn": "on", "can_distribute": "on", "export_file": fh},
follow=True,
)
fh.close()
self.assertEqual(result.status_code, 200, "Upload worked")
self.assertContains(result, message, 1, 200, "Upload didn't work?")
return result
def test_preferences_page(self): def test_preferences_page(self):
""" """
Test if preferences page works Test if preferences page works

View File

@@ -179,23 +179,10 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
""" """
self.login() self.login()
confirmed = self.create_confirmed_openid() confirmed = self.create_confirmed_openid()
url = reverse("assign_bluesky_handle_to_openid", args=[confirmed.id]) self._assign_handle_to(
response = self.client.post( "assign_bluesky_handle_to_openid",
url, confirmed,
{ "Adding Bluesky handle to OpenID fails?",
"bluesky_handle": self.bsky_test_account,
},
follow=True,
)
self.assertEqual(
response.status_code, 200, "Adding Bluesky handle to OpenID fails?"
)
# Fetch object again, as it has changed because of the request
confirmed.refresh_from_db(fields=["bluesky_handle"])
self.assertEqual(
confirmed.bluesky_handle,
self.bsky_test_account,
"Setting Bluesky handle doesn't work?",
) )
def test_assign_bluesky_handle_to_email(self): def test_assign_bluesky_handle_to_email(self):
@@ -205,18 +192,22 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
""" """
self.login() self.login()
confirmed = self.create_confirmed_email() confirmed = self.create_confirmed_email()
url = reverse("assign_bluesky_handle_to_email", args=[confirmed.id]) self._assign_handle_to(
"assign_bluesky_handle_to_email",
confirmed,
"Adding Bluesky handle to Email fails?",
)
def _assign_handle_to(self, endpoint, confirmed, message):
"""
Helper method to assign a handle to reduce code duplication
Since the endpoints are similar, we can reuse the code
"""
url = reverse(endpoint, args=[confirmed.id])
response = self.client.post( response = self.client.post(
url, url, {"bluesky_handle": self.bsky_test_account}, follow=True
{
"bluesky_handle": self.bsky_test_account,
},
follow=True,
) )
self.assertEqual( self.assertEqual(response.status_code, 200, message)
response.status_code, 200, "Adding Bluesky handle to Email fails?"
)
# Fetch object again, as it has changed because of the request
confirmed.refresh_from_db(fields=["bluesky_handle"]) confirmed.refresh_from_db(fields=["bluesky_handle"])
self.assertEqual( self.assertEqual(
confirmed.bluesky_handle, confirmed.bluesky_handle,
@@ -230,26 +221,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
""" """
self.login() self.login()
confirmed = self.create_confirmed_email() confirmed = self.create_confirmed_email()
confirmed.bluesky_handle = self.bsky_test_account self._assign_bluesky_handle(confirmed, "assign_photo_email")
confirmed.save()
url = reverse("assign_photo_email", args=[confirmed.id])
response = self.client.post(
url,
{
"photoNone": True,
},
follow=True,
)
self.assertEqual(response.status_code, 200, "Unassigning Photo doesn't work?")
# Fetch object again, as it has changed because of the request
confirmed.refresh_from_db(fields=["bluesky_handle"])
self.assertEqual(
confirmed.bluesky_handle,
None,
"Removing Bluesky handle doesn't work?",
)
def test_assign_photo_to_openid_removes_bluesky_handle(self): def test_assign_photo_to_openid_removes_bluesky_handle(self):
""" """
@@ -257,23 +229,19 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
""" """
self.login() self.login()
confirmed = self.create_confirmed_openid() confirmed = self.create_confirmed_openid()
self._assign_bluesky_handle(confirmed, "assign_photo_openid")
def _assign_bluesky_handle(self, confirmed, endpoint):
"""
Helper method to assign a Bluesky handle
Since the endpoints are similar, we can reuse the code
"""
confirmed.bluesky_handle = self.bsky_test_account confirmed.bluesky_handle = self.bsky_test_account
confirmed.save() confirmed.save()
url = reverse(endpoint, args=[confirmed.id])
url = reverse("assign_photo_openid", args=[confirmed.id]) response = self.client.post(url, {"photoNone": True}, follow=True)
response = self.client.post(
url,
{
"photoNone": True,
},
follow=True,
)
self.assertEqual(response.status_code, 200, "Unassigning Photo doesn't work?") self.assertEqual(response.status_code, 200, "Unassigning Photo doesn't work?")
# Fetch object again, as it has changed because of the request
confirmed.refresh_from_db(fields=["bluesky_handle"]) confirmed.refresh_from_db(fields=["bluesky_handle"])
self.assertEqual( self.assertEqual(
confirmed.bluesky_handle, confirmed.bluesky_handle, None, "Removing Bluesky handle doesn't work?"
None,
"Removing Bluesky handle doesn't work?",
) )

View File

@@ -2,10 +2,12 @@
""" """
View classes for ivatar/ivataraccount/ View classes for ivatar/ivataraccount/
""" """
from io import BytesIO from io import BytesIO
from ivatar.utils import urlopen, Bluesky from ivatar.utils import urlopen, Bluesky
import base64 import base64
import binascii import binascii
import contextlib
from xml.sax import saxutils from xml.sax import saxutils
import gzip import gzip
@@ -87,23 +89,8 @@ class CreateView(SuccessMessageMixin, FormView):
# If the username looks like a mail address, automagically # If the username looks like a mail address, automagically
# add it as unconfirmed mail and set it also as user's # add it as unconfirmed mail and set it also as user's
# email address # email address
try: with contextlib.suppress(Exception):
# This will error out if it's not a valid address self._extracted_from_form_valid_(form, user)
valid = validate_email(form.cleaned_data["username"])
user.email = valid.email
user.save()
# The following will also error out if it already exists
unconfirmed = UnconfirmedEmail()
unconfirmed.email = valid.email
unconfirmed.user = user
unconfirmed.save()
unconfirmed.send_confirmation_mail(
url=self.request.build_absolute_uri("/")[:-1]
)
# In any exception cases, we just skip it
except Exception: # pylint: disable=broad-except
pass
login(self.request, user) login(self.request, user)
pref = UserPreference.objects.create( pref = UserPreference.objects.create(
user_id=user.pk user_id=user.pk
@@ -112,13 +99,26 @@ class CreateView(SuccessMessageMixin, FormView):
return HttpResponseRedirect(reverse_lazy("profile")) return HttpResponseRedirect(reverse_lazy("profile"))
return HttpResponseRedirect(reverse_lazy("login")) # pragma: no cover return HttpResponseRedirect(reverse_lazy("login")) # pragma: no cover
def _extracted_from_form_valid_(self, form, user):
# This will error out if it's not a valid address
valid = validate_email(form.cleaned_data["username"])
user.email = valid.email
user.save()
# The following will also error out if it already exists
unconfirmed = UnconfirmedEmail()
unconfirmed.email = valid.email
unconfirmed.user = user
unconfirmed.save()
unconfirmed.send_confirmation_mail(
url=self.request.build_absolute_uri("/")[:-1]
)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
""" """
Handle get for create view Handle get for create view
""" """
if request.user: if request.user and request.user.is_authenticated:
if request.user.is_authenticated: return HttpResponseRedirect(reverse_lazy("profile"))
return HttpResponseRedirect(reverse_lazy("profile"))
return super().get(self, request, args, kwargs) return super().get(self, request, args, kwargs)
@@ -379,9 +379,7 @@ class AssignBlueskyHandleToEmailView(SuccessMessageMixin, TemplateView):
bs.get_avatar(bluesky_handle) bs.get_avatar(bluesky_handle)
except Exception as e: except Exception as e:
messages.error( messages.error(request, _(f"Handle '{bluesky_handle}' not found: {e}"))
request, _("Handle '%s' not found: %s" % (bluesky_handle, e))
)
return HttpResponseRedirect(reverse_lazy("profile")) return HttpResponseRedirect(reverse_lazy("profile"))
email.set_bluesky_handle(bluesky_handle) email.set_bluesky_handle(bluesky_handle)
email.photo = None email.photo = None
@@ -425,9 +423,7 @@ class AssignBlueskyHandleToOpenIdView(SuccessMessageMixin, TemplateView):
bs.get_avatar(bluesky_handle) bs.get_avatar(bluesky_handle)
except Exception as e: except Exception as e:
messages.error( messages.error(request, _(f"Handle '{bluesky_handle}' not found: {e}"))
request, _("Handle '%s' not found: %s" % (bluesky_handle, e))
)
return HttpResponseRedirect( return HttpResponseRedirect(
reverse_lazy( reverse_lazy(
"assign_photo_openid", kwargs={"openid_id": int(kwargs["open_id"])} "assign_photo_openid", kwargs={"openid_id": int(kwargs["open_id"])}
@@ -436,7 +432,7 @@ class AssignBlueskyHandleToOpenIdView(SuccessMessageMixin, TemplateView):
try: try:
openid.set_bluesky_handle(bluesky_handle) openid.set_bluesky_handle(bluesky_handle)
except Exception as e: except Exception as e:
messages.error(request, _("Error: %s" % (e))) messages.error(request, _(f"Error: {e}"))
return HttpResponseRedirect( return HttpResponseRedirect(
reverse_lazy( reverse_lazy(
"assign_photo_openid", kwargs={"openid_id": int(kwargs["open_id"])} "assign_photo_openid", kwargs={"openid_id": int(kwargs["open_id"])}
@@ -474,29 +470,25 @@ class ImportPhotoView(SuccessMessageMixin, TemplateView):
messages.error(self.request, _("Address does not exist")) messages.error(self.request, _("Address does not exist"))
return context return context
addr = kwargs.get("email_addr", None) if addr := kwargs.get("email_addr", None):
if gravatar := get_gravatar_photo(addr):
if addr:
gravatar = get_gravatar_photo(addr)
if gravatar:
context["photos"].append(gravatar) context["photos"].append(gravatar)
libravatar_service_url = libravatar_url( if libravatar_service_url := libravatar_url(
email=addr, email=addr,
default=404, default=404,
size=AVATAR_MAX_SIZE, size=AVATAR_MAX_SIZE,
) ):
if libravatar_service_url:
try: try:
urlopen(libravatar_service_url) urlopen(libravatar_service_url)
except OSError as exc: except OSError as exc:
print("Exception caught during photo import: {}".format(exc)) print(f"Exception caught during photo import: {exc}")
else: else:
context["photos"].append( context["photos"].append(
{ {
"service_url": libravatar_service_url, "service_url": libravatar_service_url,
"thumbnail_url": libravatar_service_url + "&s=80", "thumbnail_url": f"{libravatar_service_url}&s=80",
"image_url": libravatar_service_url + "&s=512", "image_url": f"{libravatar_service_url}&s=512",
"width": 80, "width": 80,
"height": 80, "height": 80,
"service_name": "Libravatar", "service_name": "Libravatar",
@@ -515,7 +507,7 @@ class ImportPhotoView(SuccessMessageMixin, TemplateView):
imported = None imported = None
email_id = kwargs.get("email_id", request.POST.get("email_id", None)) email_id = kwargs.get("email_id", request.POST.get("email_id", None))
addr = kwargs.get("emali_addr", request.POST.get("email_addr", None)) addr = kwargs.get("email", request.POST.get("email_addr", None))
if email_id: if email_id:
email = ConfirmedEmail.objects.filter(id=email_id, user=request.user) email = ConfirmedEmail.objects.filter(id=email_id, user=request.user)
@@ -565,9 +557,9 @@ class RawImageView(DetailView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
photo = self.model.objects.get(pk=kwargs["pk"]) # pylint: disable=no-member photo = self.model.objects.get(pk=kwargs["pk"]) # pylint: disable=no-member
if not photo.user.id == request.user.id and not request.user.is_staff: if photo.user.id != request.user.id and not request.user.is_staff:
return HttpResponseRedirect(reverse_lazy("home")) return HttpResponseRedirect(reverse_lazy("home"))
return HttpResponse(BytesIO(photo.data), content_type="image/%s" % photo.format) return HttpResponse(BytesIO(photo.data), content_type=f"image/{photo.format}")
@method_decorator(login_required, name="dispatch") @method_decorator(login_required, name="dispatch")
@@ -643,17 +635,16 @@ class AddOpenIDView(SuccessMessageMixin, FormView):
success_url = reverse_lazy("profile") success_url = reverse_lazy("profile")
def form_valid(self, form): def form_valid(self, form):
openid_id = form.save(self.request.user) if openid_id := form.save(self.request.user):
if not openid_id: # At this point we have an unconfirmed OpenID, but
# we do not add the message, that we successfully added it,
# since this is misleading
return HttpResponseRedirect(
reverse_lazy("openid_redirection", args=[openid_id])
)
else:
return render(self.request, self.template_name, {"form": form}) return render(self.request, self.template_name, {"form": form})
# At this point we have an unconfirmed OpenID, but
# we do not add the message, that we successfully added it,
# since this is misleading
return HttpResponseRedirect(
reverse_lazy("openid_redirection", args=[openid_id])
)
@method_decorator(login_required, name="dispatch") @method_decorator(login_required, name="dispatch")
class RemoveUnconfirmedOpenIDView(View): class RemoveUnconfirmedOpenIDView(View):
@@ -703,7 +694,7 @@ class RemoveConfirmedOpenIDView(View):
openidobj.delete() openidobj.delete()
except Exception as exc: # pylint: disable=broad-except except Exception as exc: # pylint: disable=broad-except
# Why it is not there? # Why it is not there?
print("How did we get here: %s" % exc) print(f"How did we get here: {exc}")
openid.delete() openid.delete()
messages.success(request, _("ID removed")) messages.success(request, _("ID removed"))
except self.model.DoesNotExist: # pylint: disable=no-member except self.model.DoesNotExist: # pylint: disable=no-member
@@ -740,7 +731,7 @@ class RedirectOpenIDView(View):
try: try:
auth_request = openid_consumer.begin(user_url) auth_request = openid_consumer.begin(user_url)
except consumer.DiscoveryFailure as exc: except consumer.DiscoveryFailure as exc:
messages.error(request, _("OpenID discovery failed: %s" % exc)) messages.error(request, _(f"OpenID discovery failed: {exc}"))
return HttpResponseRedirect(reverse_lazy("profile")) return HttpResponseRedirect(reverse_lazy("profile"))
except UnicodeDecodeError as exc: # pragma: no cover except UnicodeDecodeError as exc: # pragma: no cover
msg = _( msg = _(
@@ -752,7 +743,7 @@ class RedirectOpenIDView(View):
"message": exc, "message": exc,
} }
) )
print("message: %s" % msg) print(f"message: {msg}")
messages.error(request, msg) messages.error(request, msg)
if auth_request is None: # pragma: no cover if auth_request is None: # pragma: no cover
@@ -886,19 +877,13 @@ class CropPhotoView(TemplateView):
} }
email = openid = None email = openid = None
if "email" in request.POST: if "email" in request.POST:
try: with contextlib.suppress(ConfirmedEmail.DoesNotExist):
email = ConfirmedEmail.objects.get(email=request.POST["email"]) email = ConfirmedEmail.objects.get(email=request.POST["email"])
except ConfirmedEmail.DoesNotExist: # pylint: disable=no-member
pass # Ignore automatic assignment
if "openid" in request.POST: if "openid" in request.POST:
try: with contextlib.suppress(ConfirmedOpenId.DoesNotExist):
openid = ConfirmedOpenId.objects.get( # pylint: disable=no-member openid = ConfirmedOpenId.objects.get( # pylint: disable=no-member
openid=request.POST["openid"] openid=request.POST["openid"]
) )
except ConfirmedOpenId.DoesNotExist: # pylint: disable=no-member
pass # Ignore automatic assignment
return photo.perform_crop(request, dimensions, email, openid) return photo.perform_crop(request, dimensions, email, openid)
@@ -934,14 +919,14 @@ class UserPreferenceView(FormView, UpdateView):
if request.POST["email"] not in addresses: if request.POST["email"] not in addresses:
messages.error( messages.error(
self.request, self.request,
_("Mail address not allowed: %s" % request.POST["email"]), _(f'Mail address not allowed: {request.POST["email"]}'),
) )
else: else:
self.request.user.email = request.POST["email"] self.request.user.email = request.POST["email"]
self.request.user.save() self.request.user.save()
messages.info(self.request, _("Mail address changed.")) messages.info(self.request, _("Mail address changed."))
except Exception as e: # pylint: disable=broad-except except Exception as e: # pylint: disable=broad-except
messages.error(self.request, _("Error setting new mail address: %s" % e)) messages.error(self.request, _(f"Error setting new mail address: {e}"))
try: try:
if request.POST["first_name"] or request.POST["last_name"]: if request.POST["first_name"] or request.POST["last_name"]:
@@ -953,7 +938,7 @@ class UserPreferenceView(FormView, UpdateView):
messages.info(self.request, _("Last name changed.")) messages.info(self.request, _("Last name changed."))
self.request.user.save() self.request.user.save()
except Exception as e: # pylint: disable=broad-except except Exception as e: # pylint: disable=broad-except
messages.error(self.request, _("Error setting names: %s" % e)) messages.error(self.request, _(f"Error setting names: {e}"))
return HttpResponseRedirect(reverse_lazy("user_preference")) return HttpResponseRedirect(reverse_lazy("user_preference"))
@@ -1021,15 +1006,14 @@ class UploadLibravatarExportView(SuccessMessageMixin, FormView):
except Exception as exc: # pylint: disable=broad-except except Exception as exc: # pylint: disable=broad-except
# DEBUG # DEBUG
print( print(
"Exception during adding mail address (%s): %s" f"Exception during adding mail address ({email}): {exc}"
% (email, exc)
) )
if arg.startswith("photo"): if arg.startswith("photo"):
try: try:
data = base64.decodebytes(bytes(request.POST[arg], "utf-8")) data = base64.decodebytes(bytes(request.POST[arg], "utf-8"))
except binascii.Error as exc: except binascii.Error as exc:
print("Cannot decode photo: %s" % exc) print(f"Cannot decode photo: {exc}")
continue continue
try: try:
pilobj = Image.open(BytesIO(data)) pilobj = Image.open(BytesIO(data))
@@ -1043,7 +1027,7 @@ class UploadLibravatarExportView(SuccessMessageMixin, FormView):
photo.data = out.read() photo.data = out.read()
photo.save() photo.save()
except Exception as exc: # pylint: disable=broad-except except Exception as exc: # pylint: disable=broad-except
print("Exception during save: %s" % exc) print(f"Exception during save: {exc}")
continue continue
return HttpResponseRedirect(reverse_lazy("profile")) return HttpResponseRedirect(reverse_lazy("profile"))
@@ -1063,7 +1047,7 @@ class UploadLibravatarExportView(SuccessMessageMixin, FormView):
}, },
) )
except Exception as e: except Exception as e:
messages.error(self.request, _("Unable to parse file: %s" % e)) messages.error(self.request, _(f"Unable to parse file: {e}"))
return HttpResponseRedirect(reverse_lazy("upload_export")) return HttpResponseRedirect(reverse_lazy("upload_export"))
@@ -1089,13 +1073,12 @@ class ResendConfirmationMailView(View):
try: try:
email.send_confirmation_mail(url=request.build_absolute_uri("/")[:-1]) email.send_confirmation_mail(url=request.build_absolute_uri("/")[:-1])
messages.success( messages.success(
request, "%s: %s" % (_("Confirmation mail sent to"), email.email) request, f'{_("Confirmation mail sent to")}: {email.email}'
) )
except Exception as exc: # pylint: disable=broad-except except Exception as exc: # pylint: disable=broad-except
messages.error( messages.error(
request, request,
"%s %s: %s" f'{_("Unable to send confirmation email for")} {email.email}: {exc}',
% (_("Unable to send confirmation email for"), email.email, exc),
) )
return HttpResponseRedirect(reverse_lazy("profile")) return HttpResponseRedirect(reverse_lazy("profile"))
@@ -1129,12 +1112,9 @@ class ProfileView(TemplateView):
if "profile_username" in kwargs: if "profile_username" in kwargs:
if not request.user.is_staff: if not request.user.is_staff:
return HttpResponseRedirect(reverse_lazy("profile")) return HttpResponseRedirect(reverse_lazy("profile"))
try: with contextlib.suppress(Exception):
u = User.objects.get(username=kwargs["profile_username"]) u = User.objects.get(username=kwargs["profile_username"])
request.user = u request.user = u
except Exception: # pylint: disable=broad-except
pass
self._confirm_claimed_openid() self._confirm_claimed_openid()
return super().get(self, request, args, kwargs) return super().get(self, request, args, kwargs)
@@ -1165,7 +1145,7 @@ class ProfileView(TemplateView):
openid=openids.first().claimed_id openid=openids.first().claimed_id
).exists(): ).exists():
return return
print("need to confirm: %s" % openids.first()) print(f"need to confirm: {openids.first()}")
confirmed = ConfirmedOpenId() confirmed = ConfirmedOpenId()
confirmed.user = self.request.user confirmed.user = self.request.user
confirmed.ip_address = get_client_ip(self.request)[0] confirmed.ip_address = get_client_ip(self.request)[0]
@@ -1183,7 +1163,7 @@ class PasswordResetView(PasswordResetViewOriginal):
Since we have the mail addresses in ConfirmedEmail model, Since we have the mail addresses in ConfirmedEmail model,
we need to set the email on the user object in order for the we need to set the email on the user object in order for the
PasswordResetView class to pick up the correct user. PasswordResetView class to pick up the correct user.
In case we have the mail address in the User objecct, we still In case we have the mail address in the User object, we still
need to assign a random password in order for PasswordResetView need to assign a random password in order for PasswordResetView
class to pick up the user - else it will silently do nothing. class to pick up the user - else it will silently do nothing.
""" """
@@ -1200,16 +1180,13 @@ class PasswordResetView(PasswordResetViewOriginal):
# If we find the user there, we need to set the mail # If we find the user there, we need to set the mail
# attribute on the user object accordingly # attribute on the user object accordingly
if not user: if not user:
try: with contextlib.suppress(ObjectDoesNotExist):
confirmed_email = ConfirmedEmail.objects.get( confirmed_email = ConfirmedEmail.objects.get(
email=request.POST["email"] email=request.POST["email"]
) )
user = confirmed_email.user user = confirmed_email.user
user.email = confirmed_email.email user.email = confirmed_email.email
user.save() user.save()
except ObjectDoesNotExist:
pass
# If we found the user, set a random password. Else, the # If we found the user, set a random password. Else, the
# ResetPasswordView class will silently ignore the password # ResetPasswordView class will silently ignore the password
# reset request # reset request
@@ -1249,7 +1226,6 @@ class DeleteAccountView(SuccessMessageMixin, FormView):
messages.error(request, _("No password given")) messages.error(request, _("No password given"))
return HttpResponseRedirect(reverse_lazy("delete")) return HttpResponseRedirect(reverse_lazy("delete"))
raise _("No password given")
# should delete all confirmed/unconfirmed/photo objects # should delete all confirmed/unconfirmed/photo objects
request.user.delete() request.user.delete()
return super().post(self, request, args, kwargs) return super().post(self, request, args, kwargs)
@@ -1272,7 +1248,7 @@ class ExportView(SuccessMessageMixin, TemplateView):
Handle real export Handle real export
""" """
SCHEMA_ROOT = "https://www.libravatar.org/schemas/export/0.2" SCHEMA_ROOT = "https://www.libravatar.org/schemas/export/0.2"
SCHEMA_XSD = "%s/export.xsd" % SCHEMA_ROOT SCHEMA_XSD = f"{SCHEMA_ROOT}/export.xsd"
def xml_header(): def xml_header():
return ( return (
@@ -1354,8 +1330,8 @@ class ExportView(SuccessMessageMixin, TemplateView):
bytesobj.seek(0) bytesobj.seek(0)
response = HttpResponse(content_type="application/gzip") response = HttpResponse(content_type="application/gzip")
response["Content-Disposition"] = ( response[
'attachment; filename="libravatar-export_%s.xml.gz"' % user.username "Content-Disposition"
) ] = f'attachment; filename="libravatar-export_{user.username}.xml.gz"'
response.write(bytesobj.read()) response.write(bytesobj.read())
return response return response

Binary file not shown.

View File

@@ -100,6 +100,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
""" """
Bluesky client needs credentials, so it's limited with testing here now Bluesky client needs credentials, so it's limited with testing here now
""" """
if BLUESKY_APP_PASSWORD and BLUESKY_IDENTIFIER: if BLUESKY_APP_PASSWORD and BLUESKY_IDENTIFIER:
b = Bluesky() b = Bluesky()
profile = b.get_profile("ofalk.bsky.social") profile = b.get_profile("ofalk.bsky.social")

View File

@@ -33,10 +33,9 @@ class CheckDomainView(FormView):
success_url = reverse("tools_check_domain") success_url = reverse("tools_check_domain")
def form_valid(self, form): def form_valid(self, form):
result = {}
super().form_valid(form) super().form_valid(form)
domain = form.cleaned_data["domain"] domain = form.cleaned_data["domain"]
result["avatar_server_http"] = lookup_avatar_server(domain, False) result = {"avatar_server_http": lookup_avatar_server(domain, False)}
if result["avatar_server_http"]: if result["avatar_server_http"]:
result["avatar_server_http_ipv4"] = lookup_ip_address( result["avatar_server_http_ipv4"] = lookup_ip_address(
result["avatar_server_http"], False result["avatar_server_http"], False
@@ -80,8 +79,6 @@ class CheckView(FormView):
mail_hash = None mail_hash = None
mail_hash256 = None mail_hash256 = None
openid_hash = None openid_hash = None
size = 80
super().form_valid(form) super().form_valid(form)
if form.cleaned_data["default_url"]: if form.cleaned_data["default_url"]:
@@ -94,8 +91,7 @@ class CheckView(FormView):
else: else:
default_url = None default_url = None
if "size" in form.cleaned_data: size = form.cleaned_data["size"] if "size" in form.cleaned_data else 80
size = form.cleaned_data["size"]
if form.cleaned_data["mail"]: if form.cleaned_data["mail"]:
mailurl = libravatar_url( mailurl = libravatar_url(
email=form.cleaned_data["mail"], size=size, default=default_url email=form.cleaned_data["mail"], size=size, default=default_url
@@ -121,7 +117,7 @@ class CheckView(FormView):
if not form.cleaned_data["openid"].startswith( if not form.cleaned_data["openid"].startswith(
"http://" "http://"
) and not form.cleaned_data["openid"].startswith("https://"): ) and not form.cleaned_data["openid"].startswith("https://"):
form.cleaned_data["openid"] = "http://%s" % form.cleaned_data["openid"] form.cleaned_data["openid"] = f'http://{form.cleaned_data["openid"]}'
openidurl = libravatar_url( openidurl = libravatar_url(
openid=form.cleaned_data["openid"], size=size, default=default_url openid=form.cleaned_data["openid"], size=size, default=default_url
) )
@@ -139,34 +135,33 @@ class CheckView(FormView):
openid=form.cleaned_data["openid"], email=None openid=form.cleaned_data["openid"], email=None
)[0] )[0]
if "DEVELOPMENT" in SITE_NAME: if "DEVELOPMENT" in SITE_NAME and DEBUG:
if DEBUG: if mailurl:
if mailurl: mailurl = mailurl.replace(
mailurl = mailurl.replace( "https://avatars.linux-kernel.at",
"https://avatars.linux-kernel.at", f"http://{self.request.get_host()}",
"http://" + self.request.get_host(), )
) if mailurl_secure:
if mailurl_secure: mailurl_secure = mailurl_secure.replace(
mailurl_secure = mailurl_secure.replace( "https://avatars.linux-kernel.at",
"https://avatars.linux-kernel.at", f"http://{self.request.get_host()}",
"http://" + self.request.get_host(), )
) if mailurl_secure_256:
if mailurl_secure_256: mailurl_secure_256 = mailurl_secure_256.replace(
mailurl_secure_256 = mailurl_secure_256.replace( "https://avatars.linux-kernel.at",
"https://avatars.linux-kernel.at", f"http://{self.request.get_host()}",
"http://" + self.request.get_host(), )
)
if openidurl: if openidurl:
openidurl = openidurl.replace( openidurl = openidurl.replace(
"https://avatars.linux-kernel.at", "https://avatars.linux-kernel.at",
"http://" + self.request.get_host(), f"http://{self.request.get_host()}",
) )
if openidurl_secure: if openidurl_secure:
openidurl_secure = openidurl_secure.replace( openidurl_secure = openidurl_secure.replace(
"https://avatars.linux-kernel.at", "https://avatars.linux-kernel.at",
"http://" + self.request.get_host(), f"http://{self.request.get_host()}",
) )
print(mailurl, openidurl, mailurl_secure, mailurl_secure_256, openidurl_secure) print(mailurl, openidurl, mailurl_secure, mailurl_secure_256, openidurl_secure)
return render( return render(
@@ -202,15 +197,15 @@ def lookup_avatar_server(domain, https):
service_name = None service_name = None
if https: if https:
service_name = "_avatars-sec._tcp.%s" % domain service_name = f"_avatars-sec._tcp.{domain}"
else: else:
service_name = "_avatars._tcp.%s" % domain service_name = f"_avatars._tcp.{domain}"
DNS.DiscoverNameServers() DNS.DiscoverNameServers()
try: try:
dns_request = DNS.Request(name=service_name, qtype="SRV").req() dns_request = DNS.Request(name=service_name, qtype="SRV").req()
except DNS.DNSError as message: except DNS.DNSError as message:
print("DNS Error: %s (%s)" % (message, domain)) print(f"DNS Error: {message} ({domain})")
return None return None
if dns_request.header["status"] == "NXDOMAIN": if dns_request.header["status"] == "NXDOMAIN":
@@ -218,7 +213,7 @@ def lookup_avatar_server(domain, https):
return None return None
if dns_request.header["status"] != "NOERROR": if dns_request.header["status"] != "NOERROR":
print("DNS Error: status=%s (%s)" % (dns_request.header["status"], domain)) print(f'DNS Error: status={dns_request.header["status"]} ({domain})')
return None return None
records = [] records = []
@@ -243,7 +238,7 @@ def lookup_avatar_server(domain, https):
target, port = srv_hostname(records) target, port = srv_hostname(records)
if target and ((https and port != 443) or (not https and port != 80)): if target and ((https and port != 443) or (not https and port != 80)):
return "%s:%s" % (target, port) return f"{target}:{port}"
return target return target
@@ -273,7 +268,7 @@ def srv_hostname(records):
# Take care - this if is only a if, if the above if # Take care - this if is only a if, if the above if
# uses continue at the end. else it should be an elsif # uses continue at the end. else it should be an elsif
if ret["priority"] < top_priority: if ret["priority"] < top_priority:
# reset the aretay (ret has higher priority) # reset the priority (ret has higher priority)
top_priority = ret["priority"] top_priority = ret["priority"]
total_weight = 0 total_weight = 0
priority_records = [] priority_records = []
@@ -283,7 +278,7 @@ def srv_hostname(records):
if ret["weight"] > 0: if ret["weight"] > 0:
priority_records.append((total_weight, ret)) priority_records.append((total_weight, ret))
else: else:
# zero-weigth elements must come first # zero-weight elements must come first
priority_records.insert(0, (0, ret)) priority_records.insert(0, (0, ret))
if len(priority_records) == 1: if len(priority_records) == 1:
@@ -315,11 +310,11 @@ def lookup_ip_address(hostname, ipv6):
else: else:
dns_request = DNS.Request(name=hostname, qtype=DNS.Type.A).req() dns_request = DNS.Request(name=hostname, qtype=DNS.Type.A).req()
except DNS.DNSError as message: except DNS.DNSError as message:
print("DNS Error: %s (%s)" % (message, hostname)) print(f"DNS Error: {message} ({hostname})")
return None return None
if dns_request.header["status"] != "NOERROR": if dns_request.header["status"] != "NOERROR":
print("DNS Error: status=%s (%s)" % (dns_request.header["status"], hostname)) print(f'DNS Error: status={dns_request.header["status"]} ({hostname})')
return None return None
for answer in dns_request.answers: for answer in dns_request.answers:
@@ -330,9 +325,5 @@ def lookup_ip_address(hostname, ipv6):
): ):
continue # skip CNAME records continue # skip CNAME records
if ipv6: return inet_ntop(AF_INET6, answer["data"]) if ipv6 else answer["data"]
return inet_ntop(AF_INET6, answer["data"])
return answer["data"]
return None return None

View File

@@ -2,6 +2,8 @@
""" """
ivatar URL configuration ivatar URL configuration
""" """
import contextlib
from django.contrib import admin from django.contrib import admin
from django.urls import path, include, re_path from django.urls import path, include, re_path
from django.conf.urls.static import static from django.conf.urls.static import static
@@ -69,12 +71,9 @@ urlpatterns = [ # pylint: disable=invalid-name
] ]
MAINTENANCE = False MAINTENANCE = False
try: with contextlib.suppress(Exception):
if settings.MAINTENANCE: if settings.MAINTENANCE:
MAINTENANCE = True MAINTENANCE = True
except Exception: # pylint: disable=bare-except
pass
if MAINTENANCE: if MAINTENANCE:
urlpatterns.append( urlpatterns.append(
path("", TemplateView.as_view(template_name="maintenance.html"), name="home") path("", TemplateView.as_view(template_name="maintenance.html"), name="home")

View File

@@ -2,6 +2,8 @@
""" """
Simple module providing reusable random_string function Simple module providing reusable random_string function
""" """
import contextlib
import random import random
import string import string
from io import BytesIO from io import BytesIO
@@ -13,10 +15,8 @@ from urllib.request import urlopen as urlopen_orig
BLUESKY_IDENTIFIER = None BLUESKY_IDENTIFIER = None
BLUESKY_APP_PASSWORD = None BLUESKY_APP_PASSWORD = None
try: with contextlib.suppress(Exception):
from ivatar.settings import BLUESKY_IDENTIFIER, BLUESKY_APP_PASSWORD from ivatar.settings import BLUESKY_IDENTIFIER, BLUESKY_APP_PASSWORD
except Exception: # pylint: disable=broad-except
pass
def urlopen(url, timeout=URL_TIMEOUT): def urlopen(url, timeout=URL_TIMEOUT):
@@ -66,8 +66,7 @@ class Bluesky:
Return the normalized handle for given handle Return the normalized handle for given handle
""" """
# Normalize Bluesky handle in case someone enters an '@' at the beginning # Normalize Bluesky handle in case someone enters an '@' at the beginning
if handle.startswith("@"): handle = handle.removeprefix("@")
handle = handle[1:]
# Remove trailing spaces or spaces at the beginning # Remove trailing spaces or spaces at the beginning
while handle.startswith(" "): while handle.startswith(" "):
handle = handle[1:] handle = handle[1:]
@@ -88,9 +87,7 @@ class Bluesky:
) )
profile_response.raise_for_status() profile_response.raise_for_status()
except Exception as exc: except Exception as exc:
print( print(f"Bluesky profile fetch failed with HTTP error: {exc}")
"Bluesky profile fetch failed with HTTP error: %s" % exc
) # pragma: no cover
return None return None
return profile_response.json() return profile_response.json()
@@ -126,12 +123,12 @@ def openid_variations(openid):
if openid.startswith("https://"): if openid.startswith("https://"):
openid = openid.replace("https://", "http://") openid = openid.replace("https://", "http://")
if openid[-1] != "/": if openid[-1] != "/":
openid = openid + "/" openid = f"{openid}/"
# http w/o trailing slash # http w/o trailing slash
var1 = openid[0:-1] var1 = openid[:-1]
var2 = openid.replace("http://", "https://") var2 = openid.replace("http://", "https://")
var3 = var2[0:-1] var3 = var2[:-1]
return (openid, var1, var2, var3) return (openid, var1, var2, var3)
@@ -149,43 +146,43 @@ def mm_ng(
idhash = "e0" idhash = "e0"
# How large is the circle? # How large is the circle?
circlesize = size * 0.6 circle_size = size * 0.6
# Coordinates for the circle # Coordinates for the circle
start_x = int(size * 0.2) start_x = int(size * 0.2)
end_x = start_x + circlesize end_x = start_x + circle_size
start_y = int(size * 0.05) start_y = int(size * 0.05)
end_y = start_y + circlesize end_y = start_y + circle_size
# All are the same, based on the input hash # All are the same, based on the input hash
# this should always result in a "gray-ish" background # this should always result in a "gray-ish" background
red = idhash[0:2] red = idhash[:2]
green = idhash[0:2] green = idhash[:2]
blue = idhash[0:2] blue = idhash[:2]
# Add some red (i/a) and make sure it's not over 255 # Add some red (i/a) and make sure it's not over 255
red = hex(int(red, 16) + add_red).replace("0x", "") red = hex(int(red, 16) + add_red).replace("0x", "")
if int(red, 16) > 255: if int(red, 16) > 255:
red = "ff" red = "ff"
if len(red) == 1: if len(red) == 1:
red = "0%s" % red red = f"0{red}"
# Add some green (i/a) and make sure it's not over 255 # Add some green (i/a) and make sure it's not over 255
green = hex(int(green, 16) + add_green).replace("0x", "") green = hex(int(green, 16) + add_green).replace("0x", "")
if int(green, 16) > 255: if int(green, 16) > 255:
green = "ff" green = "ff"
if len(green) == 1: if len(green) == 1:
green = "0%s" % green green = f"0{green}"
# Add some blue (i/a) and make sure it's not over 255 # Add some blue (i/a) and make sure it's not over 255
blue = hex(int(blue, 16) + add_blue).replace("0x", "") blue = hex(int(blue, 16) + add_blue).replace("0x", "")
if int(blue, 16) > 255: if int(blue, 16) > 255:
blue = "ff" blue = "ff"
if len(blue) == 1: if len(blue) == 1:
blue = "0%s" % blue blue = f"0{blue}"
# Assemable the bg color "string" in webnotation. Eg. '#d3d3d3' # Assemble the bg color "string" in web notation. Eg. '#d3d3d3'
bg_color = "#" + red + green + blue bg_color = f"#{red}{green}{blue}"
# Image # Image
image = Image.new("RGB", (size, size)) image = Image.new("RGB", (size, size))
@@ -200,7 +197,7 @@ def mm_ng(
# Draw MMs 'body' # Draw MMs 'body'
draw.polygon( draw.polygon(
( (
(start_x + circlesize / 2, size / 2.5), (start_x + circle_size / 2, size / 2.5),
(size * 0.15, size), (size * 0.15, size),
(size - size * 0.15, size), (size - size * 0.15, size),
), ),

View File

@@ -2,6 +2,8 @@
""" """
views under / views under /
""" """
import contextlib
from io import BytesIO from io import BytesIO
from os import path from os import path
import hashlib import hashlib
@@ -47,17 +49,11 @@ def get_size(request, size=DEFAULT_AVATAR_SIZE):
if "size" in request.GET: if "size" in request.GET:
sizetemp = request.GET["size"] sizetemp = request.GET["size"]
if sizetemp: if sizetemp:
if sizetemp != "" and sizetemp is not None and sizetemp != "0": if sizetemp not in ["", "0"]:
try: with contextlib.suppress(ValueError):
if int(sizetemp) > 0: if int(sizetemp) > 0:
size = int(sizetemp) size = int(sizetemp)
# Should we receive something we cannot convert to int, leave size = min(size, int(AVATAR_MAX_SIZE))
# the user with the default value of 80
except ValueError:
pass
if size > int(AVATAR_MAX_SIZE):
size = int(AVATAR_MAX_SIZE)
return size return size
@@ -119,8 +115,7 @@ class AvatarImageView(TemplateView):
# Check the cache first # Check the cache first
if CACHE_RESPONSE: if CACHE_RESPONSE:
centry = caches["filesystem"].get(uri) if centry := caches["filesystem"].get(uri):
if centry:
# For DEBUG purpose only # For DEBUG purpose only
# print('Cached entry for %s' % uri) # print('Cached entry for %s' % uri)
return HttpResponse( return HttpResponse(
@@ -150,8 +145,7 @@ class AvatarImageView(TemplateView):
if not trusted_url: if not trusted_url:
print( print(
"Default URL is not in trusted URLs: '%s' ; Kicking it!" f"Default URL is not in trusted URLs: '{default}'; Kicking it!"
% default
) )
default = None default = None
@@ -177,20 +171,17 @@ class AvatarImageView(TemplateView):
obj = model.objects.get(digest_sha256=kwargs["digest"]) obj = model.objects.get(digest_sha256=kwargs["digest"])
except ObjectDoesNotExist: except ObjectDoesNotExist:
model = ConfirmedOpenId model = ConfirmedOpenId
try: with contextlib.suppress(Exception):
d = kwargs["digest"] # pylint: disable=invalid-name d = kwargs["digest"] # pylint: disable=invalid-name
# OpenID is tricky. http vs. https, versus trailing slash or not # OpenID is tricky. http vs. https, versus trailing slash or not
# However, some users eventually have added their variations already # However, some users eventually have added their variations already
# and therfore we need to use filter() and first() # and therefore we need to use filter() and first()
obj = model.objects.filter( obj = model.objects.filter(
Q(digest=d) Q(digest=d)
| Q(alt_digest1=d) | Q(alt_digest1=d)
| Q(alt_digest2=d) | Q(alt_digest2=d)
| Q(alt_digest3=d) | Q(alt_digest3=d)
).first() ).first()
except Exception: # pylint: disable=bare-except
pass
# Handle the special case of Bluesky # Handle the special case of Bluesky
if obj: if obj:
if obj.bluesky_handle: if obj.bluesky_handle:
@@ -218,7 +209,7 @@ class AvatarImageView(TemplateView):
) )
# Ensure we do not convert None to string 'None' # Ensure we do not convert None to string 'None'
if default: if default:
url += "&default=%s" % default url += f"&default={default}"
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
# Return the default URL, as specified, or 404 Not Found, if default=404 # Return the default URL, as specified, or 404 Not Found, if default=404
@@ -228,7 +219,7 @@ class AvatarImageView(TemplateView):
url = ( url = (
reverse_lazy("gravatarproxy", args=[kwargs["digest"]]) reverse_lazy("gravatarproxy", args=[kwargs["digest"]])
+ "?s=%i" % size + "?s=%i" % size
+ "&default=%s&f=y" % default + f"&default={default}&f=y"
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@@ -238,46 +229,25 @@ class AvatarImageView(TemplateView):
if str(default) == "monsterid": if str(default) == "monsterid":
monsterdata = BuildMonster(seed=kwargs["digest"], size=(size, size)) monsterdata = BuildMonster(seed=kwargs["digest"], size=(size, size))
data = BytesIO() data = BytesIO()
monsterdata.save(data, "PNG", quality=JPEG_QUALITY) return self._return_cached_png(monsterdata, data, uri)
data.seek(0)
response = CachingHttpResponse(uri, data, content_type="image/png")
response["Cache-Control"] = "max-age=%i" % CACHE_IMAGES_MAX_AGE
return response
if str(default) == "robohash": if str(default) == "robohash":
roboset = "any" roboset = request.GET.get("robohash") or "any"
if request.GET.get("robohash"):
roboset = request.GET.get("robohash")
robohash = Robohash(kwargs["digest"]) robohash = Robohash(kwargs["digest"])
robohash.assemble(roboset=roboset, sizex=size, sizey=size) robohash.assemble(roboset=roboset, sizex=size, sizey=size)
data = BytesIO() data = BytesIO()
robohash.img.save(data, format="png") robohash.img.save(data, format="png")
data.seek(0) return self._return_cached_response(data, uri)
response = CachingHttpResponse(uri, data, content_type="image/png")
response["Cache-Control"] = "max-age=%i" % CACHE_IMAGES_MAX_AGE
return response
if str(default) == "retro": if str(default) == "retro":
identicon = Identicon.render(kwargs["digest"]) identicon = Identicon.render(kwargs["digest"])
data = BytesIO() data = BytesIO()
img = Image.open(BytesIO(identicon)) img = Image.open(BytesIO(identicon))
img = img.resize((size, size), Image.LANCZOS) img = img.resize((size, size), Image.LANCZOS)
img.save(data, "PNG", quality=JPEG_QUALITY) return self._return_cached_png(img, data, uri)
data.seek(0)
response = CachingHttpResponse(uri, data, content_type="image/png")
response["Cache-Control"] = "max-age=%i" % CACHE_IMAGES_MAX_AGE
return response
if str(default) == "pagan": if str(default) == "pagan":
paganobj = pagan.Avatar(kwargs["digest"]) paganobj = pagan.Avatar(kwargs["digest"])
data = BytesIO() data = BytesIO()
img = paganobj.img.resize((size, size), Image.LANCZOS) img = paganobj.img.resize((size, size), Image.LANCZOS)
img.save(data, "PNG", quality=JPEG_QUALITY) return self._return_cached_png(img, data, uri)
data.seek(0)
response = CachingHttpResponse(uri, data, content_type="image/png")
response["Cache-Control"] = "max-age=%i" % CACHE_IMAGES_MAX_AGE
return response
if str(default) == "identicon": if str(default) == "identicon":
p = Pydenticon5() # pylint: disable=invalid-name p = Pydenticon5() # pylint: disable=invalid-name
# In order to make use of the whole 32 bytes digest, we need to redigest them. # In order to make use of the whole 32 bytes digest, we need to redigest them.
@@ -286,42 +256,16 @@ class AvatarImageView(TemplateView):
).hexdigest() ).hexdigest()
img = p.draw(newdigest, size, 0) img = p.draw(newdigest, size, 0)
data = BytesIO() data = BytesIO()
img.save(data, "PNG", quality=JPEG_QUALITY) return self._return_cached_png(img, data, uri)
data.seek(0)
response = CachingHttpResponse(uri, data, content_type="image/png")
response["Cache-Control"] = "max-age=%i" % CACHE_IMAGES_MAX_AGE
return response
if str(default) == "mmng": if str(default) == "mmng":
mmngimg = mm_ng(idhash=kwargs["digest"], size=size) mmngimg = mm_ng(idhash=kwargs["digest"], size=size)
data = BytesIO() data = BytesIO()
mmngimg.save(data, "PNG", quality=JPEG_QUALITY) return self._return_cached_png(mmngimg, data, uri)
data.seek(0) if str(default) in {"mm", "mp"}:
response = CachingHttpResponse(uri, data, content_type="image/png") return self._redirect_static_w_size("mm", size)
response["Cache-Control"] = "max-age=%i" % CACHE_IMAGES_MAX_AGE
return response
if str(default) == "mm" or str(default) == "mp":
# If mm is explicitly given, we need to catch that
static_img = path.join(
"static", "img", "mm", "%s%s" % (str(size), ".png")
)
if not path.isfile(static_img):
# We trust this exists!!!
static_img = path.join("static", "img", "mm", "512.png")
# We trust static/ is mapped to /static/
return HttpResponseRedirect("/" + static_img)
return HttpResponseRedirect(default) return HttpResponseRedirect(default)
static_img = path.join( return self._redirect_static_w_size("nobody", size)
"static", "img", "nobody", "%s%s" % (str(size), ".png")
)
if not path.isfile(static_img):
# We trust this exists!!!
static_img = path.join("static", "img", "nobody", "512.png")
# We trust static/ is mapped to /static/
return HttpResponseRedirect("/" + static_img)
imgformat = obj.photo.format imgformat = obj.photo.format
photodata = Image.open(BytesIO(obj.photo.data)) photodata = Image.open(BytesIO(obj.photo.data))
@@ -348,10 +292,32 @@ class AvatarImageView(TemplateView):
obj.save() obj.save()
if imgformat == "jpg": if imgformat == "jpg":
imgformat = "jpeg" imgformat = "jpeg"
response = CachingHttpResponse(uri, data, content_type="image/%s" % imgformat) response = CachingHttpResponse(uri, data, content_type=f"image/{imgformat}")
response["Cache-Control"] = "max-age=%i" % CACHE_IMAGES_MAX_AGE response["Cache-Control"] = "max-age=%i" % CACHE_IMAGES_MAX_AGE
return response return response
def _redirect_static_w_size(self, arg0, size):
"""
Helper method to redirect to static image with size i/a
"""
# If mm is explicitly given, we need to catch that
static_img = path.join("static", "img", arg0, f"{str(size)}.png")
if not path.isfile(static_img):
# We trust this exists!!!
static_img = path.join("static", "img", arg0, "512.png")
# We trust static/ is mapped to /static/
return HttpResponseRedirect(f"/{static_img}")
def _return_cached_response(self, data, uri):
data.seek(0)
response = CachingHttpResponse(uri, data, content_type="image/png")
response["Cache-Control"] = "max-age=%i" % CACHE_IMAGES_MAX_AGE
return response
def _return_cached_png(self, arg0, data, uri):
arg0.save(data, "PNG", quality=JPEG_QUALITY)
return self._return_cached_response(data, uri)
class GravatarProxyView(View): class GravatarProxyView(View):
""" """
@@ -374,19 +340,16 @@ class GravatarProxyView(View):
+ "&forcedefault=y" + "&forcedefault=y"
) )
if default is not None: if default is not None:
url += "&default=%s" % default url += f"&default={default}"
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
size = get_size(request) size = get_size(request)
gravatarimagedata = None gravatarimagedata = None
default = None default = None
try: with contextlib.suppress(Exception):
if str(request.GET["default"]) != "None": if str(request.GET["default"]) != "None":
default = request.GET["default"] default = request.GET["default"]
except Exception: # pylint: disable=bare-except
pass
if str(default) != "wavatar": if str(default) != "wavatar":
# This part is special/hackish # This part is special/hackish
# Check if the image returned by Gravatar is their default image, if so, # Check if the image returned by Gravatar is their default image, if so,
@@ -406,35 +369,34 @@ class GravatarProxyView(View):
if exc.code == 404: if exc.code == 404:
cache.set(gravatar_test_url, "default", 60) cache.set(gravatar_test_url, "default", 60)
else: else:
print("Gravatar test url fetch failed: %s" % exc) print(f"Gravatar test url fetch failed: {exc}")
return redir_default(default) return redir_default(default)
gravatar_url = ( gravatar_url = (
"https://secure.gravatar.com/avatar/" + kwargs["digest"] + "?s=%i" % size "https://secure.gravatar.com/avatar/" + kwargs["digest"] + "?s=%i" % size
) )
if default: if default:
gravatar_url += "&d=%s" % default gravatar_url += f"&d={default}"
try: try:
if cache.get(gravatar_url) == "err": if cache.get(gravatar_url) == "err":
print("Cached Gravatar fetch failed with URL error: %s" % gravatar_url) print(f"Cached Gravatar fetch failed with URL error: {gravatar_url}")
return redir_default(default) return redir_default(default)
gravatarimagedata = urlopen(gravatar_url) gravatarimagedata = urlopen(gravatar_url)
except HTTPError as exc: except HTTPError as exc:
if exc.code != 404 and exc.code != 503: if exc.code not in [404, 503]:
print( print(
"Gravatar fetch failed with an unexpected %s HTTP error: %s" f"Gravatar fetch failed with an unexpected {exc.code} HTTP error: {gravatar_url}"
% (exc.code, gravatar_url)
) )
cache.set(gravatar_url, "err", 30) cache.set(gravatar_url, "err", 30)
return redir_default(default) return redir_default(default)
except URLError as exc: except URLError as exc:
print("Gravatar fetch failed with URL error: %s" % exc.reason) print(f"Gravatar fetch failed with URL error: {exc.reason}")
cache.set(gravatar_url, "err", 30) cache.set(gravatar_url, "err", 30)
return redir_default(default) return redir_default(default)
except SSLError as exc: except SSLError as exc:
print("Gravatar fetch failed with SSL error: %s" % exc.reason) print(f"Gravatar fetch failed with SSL error: {exc.reason}")
cache.set(gravatar_url, "err", 30) cache.set(gravatar_url, "err", 30)
return redir_default(default) return redir_default(default)
try: try:
@@ -442,13 +404,13 @@ class GravatarProxyView(View):
img = Image.open(data) img = Image.open(data)
data.seek(0) data.seek(0)
response = HttpResponse( response = HttpResponse(
data.read(), content_type="image/%s" % file_format(img.format) data.read(), content_type=f"image/{file_format(img.format)}"
) )
response["Cache-Control"] = "max-age=%i" % CACHE_IMAGES_MAX_AGE response["Cache-Control"] = "max-age=%i" % CACHE_IMAGES_MAX_AGE
return response return response
except ValueError as exc: except ValueError as exc:
print("Value error: %s" % exc) print(f"Value error: {exc}")
return redir_default(default) return redir_default(default)
# We shouldn't reach this point... But make sure we do something # We shouldn't reach this point... But make sure we do something
@@ -474,7 +436,7 @@ class BlueskyProxyView(View):
+ "&forcedefault=y" + "&forcedefault=y"
) )
if default is not None: if default is not None:
url += "&default=%s" % default url += f"&default={default}"
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
size = get_size(request) size = get_size(request)
@@ -482,12 +444,9 @@ class BlueskyProxyView(View):
blueskyimagedata = None blueskyimagedata = None
default = None default = None
try: with contextlib.suppress(Exception):
if str(request.GET["default"]) != "None": if str(request.GET["default"]) != "None":
default = request.GET["default"] default = request.GET["default"]
except Exception: # pylint: disable=bare-except
pass
identity = None identity = None
# First check for email, as this is the most common # First check for email, as this is the most common
@@ -517,12 +476,9 @@ class BlueskyProxyView(View):
bs = Bluesky() bs = Bluesky()
bluesky_url = None bluesky_url = None
# Try with the cache first # Try with the cache first
try: with contextlib.suppress(Exception):
if cache.get(identity.bluesky_handle): if cache.get(identity.bluesky_handle):
bluesky_url = cache.get(identity.bluesky_handle) bluesky_url = cache.get(identity.bluesky_handle)
except Exception: # pylint: disable=bare-except
pass
if not bluesky_url: if not bluesky_url:
try: try:
bluesky_url = bs.get_avatar(identity.bluesky_handle) bluesky_url = bs.get_avatar(identity.bluesky_handle)
@@ -532,30 +488,29 @@ class BlueskyProxyView(View):
try: try:
if cache.get(bluesky_url) == "err": if cache.get(bluesky_url) == "err":
print("Cached Bluesky fetch failed with URL error: %s" % bluesky_url) print(f"Cached Bluesky fetch failed with URL error: {bluesky_url}")
return redir_default(default) return redir_default(default)
blueskyimagedata = urlopen(bluesky_url) blueskyimagedata = urlopen(bluesky_url)
except HTTPError as exc: except HTTPError as exc:
if exc.code != 404 and exc.code != 503: if exc.code not in [404, 503]:
print( print(
"Bluesky fetch failed with an unexpected %s HTTP error: %s" f"Bluesky fetch failed with an unexpected {exc.code} HTTP error: {bluesky_url}"
% (exc.code, bluesky_url)
) )
cache.set(bluesky_url, "err", 30) cache.set(bluesky_url, "err", 30)
return redir_default(default) return redir_default(default)
except URLError as exc: except URLError as exc:
print("Bluesky fetch failed with URL error: %s" % exc.reason) print(f"Bluesky fetch failed with URL error: {exc.reason}")
cache.set(bluesky_url, "err", 30) cache.set(bluesky_url, "err", 30)
return redir_default(default) return redir_default(default)
except SSLError as exc: except SSLError as exc:
print("Bluesky fetch failed with SSL error: %s" % exc.reason) print(f"Bluesky fetch failed with SSL error: {exc.reason}")
cache.set(bluesky_url, "err", 30) cache.set(bluesky_url, "err", 30)
return redir_default(default) return redir_default(default)
try: try:
data = BytesIO(blueskyimagedata.read()) data = BytesIO(blueskyimagedata.read())
img = Image.open(data) img = Image.open(data)
format = img.format img_format = img.format
if max(img.size) > size: if max(img.size) > size:
aspect = img.size[0] / float(img.size[1]) aspect = img.size[0] / float(img.size[1])
if aspect > 1: if aspect > 1:
@@ -564,16 +519,16 @@ class BlueskyProxyView(View):
new_size = (int(size * aspect), size) new_size = (int(size * aspect), size)
img = img.resize(new_size) img = img.resize(new_size)
data = BytesIO() data = BytesIO()
img.save(data, format=format) img.save(data, format=img_format)
data.seek(0) data.seek(0)
response = HttpResponse( response = HttpResponse(
data.read(), content_type="image/%s" % file_format(format) data.read(), content_type=f"image/{file_format(format)}"
) )
response["Cache-Control"] = "max-age=%i" % CACHE_IMAGES_MAX_AGE response["Cache-Control"] = "max-age=%i" % CACHE_IMAGES_MAX_AGE
return response return response
except ValueError as exc: except ValueError as exc:
print("Value error: %s" % exc) print(f"Value error: {exc}")
return redir_default(default) return redir_default(default)
# We shouldn't reach this point... But make sure we do something # We shouldn't reach this point... But make sure we do something