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]
ignore = E501, W503, E402, C901, E231
ignore = E501, W503, E402, C901, E231, E702
max-line-length = 79
max-complexity = 18
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.data = data.read()
photo.save()
if not photo.pk:
return None
return photo
return None if not photo.pk else photo
class AddOpenIDForm(forms.Form):
@@ -141,12 +139,15 @@ class AddOpenIDForm(forms.Form):
"""
# Lowercase hostname port of the URL
url = urlsplit(self.cleaned_data["openid"])
data = urlunsplit(
(url.scheme.lower(), url.netloc.lower(), url.path, url.query, url.fragment)
return urlunsplit(
(
url.scheme.lower(),
url.netloc.lower(),
url.path,
url.query,
url.fragment,
)
)
# TODO: Domain restriction as in libravatar?
return data
def save(self, user):
"""

View File

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

View File

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

View File

@@ -461,17 +461,8 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
},
follow=True,
) # Create test addresses + 1 too much
# Check the response context for form errors
self.assertTrue(
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__", [])
return self._check_form_validity(
response, "Too many unconfirmed mail addresses!", "__all__"
)
def test_add_mail_address_twice(self):
@@ -491,17 +482,8 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
},
follow=True,
)
# Check the response context for form errors
self.assertTrue(
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", [])
return self._check_form_validity(
response, "Address already added, currently unconfirmed", "email"
)
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,
)
# Check the response context for form errors
self.assertTrue(
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", [])
return self._check_form_validity(
response, "Address already confirmed (by you)", "email"
)
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,
)
# Check the response context for form errors
self.assertTrue(
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", [])
return self._check_form_validity(
response, "Address already confirmed (by someone else)", "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
"""
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.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",
self._extracted_from_test_upload_webp_image_5(
"broken.gif",
"GIF upload failed?!",
)
self.assertEqual(
self.user.photo_set.first().format,
"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):
"""
Test if jpg 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.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",
self._extracted_from_test_upload_webp_image_5(
"broken.jpg",
"JPEG upload failed?!",
)
self.assertEqual(
self.user.photo_set.first().format,
"jpg",
"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):
"""
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()
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:
with open(os.path.join(settings.STATIC_ROOT, "img", filename), "rb") as photo:
response = self.client.post(
url,
{
"photo": photo,
"not_porn": True,
"can_distribute": True,
},
{"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!",
message1,
)
self.assertEqual(self.user.photo_set.first().format, format, message2)
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,
)
libravatar_url(email=self.user.confirmedemail_set.first().email)
)
url = f"{urlobj.path}?{urlobj.query}"
response = self.client.get(url, follow=True)
@@ -839,7 +742,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
url = reverse("upload_photo")
# rb => Read binary
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:
response = self.client.post(
url,
@@ -1062,8 +965,6 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
if confirm:
self._manual_confirm()
# TODO Rename this here and in `test_add_openid`
def test_add_openid_twice(self):
"""
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!",
)
# Check the response context for form errors
self.assertTrue(
hasattr(response, "context"), "Response does not have a context"
self._check_form_validity(
response, "OpenID already added, but not confirmed yet!", "openid"
)
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!
unconfirmed = self.user.unconfirmedopenid_set.first()
confirmed = ConfirmedOpenId()
@@ -1127,18 +1017,24 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
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(
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 and confirmed!", form.errors.get("openid", [])
)
result = response.context.get("form")
self.assertIsNotNone(result, "No form found in response context")
self.assertFalse(result.is_valid(), "Form should not be valid")
self.assertIn(message, result.errors.get(field, []))
return result
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 = BytesIO(response.content)
response = self.client.post(
reverse("upload_export"),
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_gzip, "Unable to parse file: Not a gzipped file"
)
response = self._uploading_export_check(fh, "Choose items to be imported")
self.assertContains(
response,
"asdf@asdf.local",
@@ -2062,6 +1931,21 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"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):
"""
Test if preferences page works

View File

@@ -179,23 +179,10 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"""
self.login()
confirmed = self.create_confirmed_openid()
url = reverse("assign_bluesky_handle_to_openid", args=[confirmed.id])
response = self.client.post(
url,
{
"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?",
self._assign_handle_to(
"assign_bluesky_handle_to_openid",
confirmed,
"Adding Bluesky handle to OpenID fails?",
)
def test_assign_bluesky_handle_to_email(self):
@@ -205,18 +192,22 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"""
self.login()
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(
url,
{
"bluesky_handle": self.bsky_test_account,
},
follow=True,
url, {"bluesky_handle": self.bsky_test_account}, follow=True
)
self.assertEqual(
response.status_code, 200, "Adding Bluesky handle to Email fails?"
)
# Fetch object again, as it has changed because of the request
self.assertEqual(response.status_code, 200, message)
confirmed.refresh_from_db(fields=["bluesky_handle"])
self.assertEqual(
confirmed.bluesky_handle,
@@ -230,26 +221,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"""
self.login()
confirmed = self.create_confirmed_email()
confirmed.bluesky_handle = self.bsky_test_account
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?",
)
self._assign_bluesky_handle(confirmed, "assign_photo_email")
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()
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.save()
url = reverse("assign_photo_openid", args=[confirmed.id])
response = self.client.post(
url,
{
"photoNone": True,
},
follow=True,
)
url = reverse(endpoint, 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?",
confirmed.bluesky_handle, None, "Removing Bluesky handle doesn't work?"
)

View File

@@ -2,10 +2,12 @@
"""
View classes for ivatar/ivataraccount/
"""
from io import BytesIO
from ivatar.utils import urlopen, Bluesky
import base64
import binascii
import contextlib
from xml.sax import saxutils
import gzip
@@ -87,7 +89,17 @@ class CreateView(SuccessMessageMixin, FormView):
# If the username looks like a mail address, automagically
# add it as unconfirmed mail and set it also as user's
# email address
try:
with contextlib.suppress(Exception):
self._extracted_from_form_valid_(form, user)
login(self.request, user)
pref = UserPreference.objects.create(
user_id=user.pk
) # pylint: disable=no-member
pref.save()
return HttpResponseRedirect(reverse_lazy("profile"))
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
@@ -100,24 +112,12 @@ class CreateView(SuccessMessageMixin, FormView):
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)
pref = UserPreference.objects.create(
user_id=user.pk
) # pylint: disable=no-member
pref.save()
return HttpResponseRedirect(reverse_lazy("profile"))
return HttpResponseRedirect(reverse_lazy("login")) # pragma: no cover
def get(self, request, *args, **kwargs):
"""
Handle get for create view
"""
if request.user:
if request.user.is_authenticated:
if request.user and request.user.is_authenticated:
return HttpResponseRedirect(reverse_lazy("profile"))
return super().get(self, request, args, kwargs)
@@ -379,9 +379,7 @@ class AssignBlueskyHandleToEmailView(SuccessMessageMixin, TemplateView):
bs.get_avatar(bluesky_handle)
except Exception as e:
messages.error(
request, _("Handle '%s' not found: %s" % (bluesky_handle, e))
)
messages.error(request, _(f"Handle '{bluesky_handle}' not found: {e}"))
return HttpResponseRedirect(reverse_lazy("profile"))
email.set_bluesky_handle(bluesky_handle)
email.photo = None
@@ -425,9 +423,7 @@ class AssignBlueskyHandleToOpenIdView(SuccessMessageMixin, TemplateView):
bs.get_avatar(bluesky_handle)
except Exception as e:
messages.error(
request, _("Handle '%s' not found: %s" % (bluesky_handle, e))
)
messages.error(request, _(f"Handle '{bluesky_handle}' not found: {e}"))
return HttpResponseRedirect(
reverse_lazy(
"assign_photo_openid", kwargs={"openid_id": int(kwargs["open_id"])}
@@ -436,7 +432,7 @@ class AssignBlueskyHandleToOpenIdView(SuccessMessageMixin, TemplateView):
try:
openid.set_bluesky_handle(bluesky_handle)
except Exception as e:
messages.error(request, _("Error: %s" % (e)))
messages.error(request, _(f"Error: {e}"))
return HttpResponseRedirect(
reverse_lazy(
"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"))
return context
addr = kwargs.get("email_addr", None)
if addr:
gravatar = get_gravatar_photo(addr)
if gravatar:
if addr := kwargs.get("email_addr", None):
if gravatar := get_gravatar_photo(addr):
context["photos"].append(gravatar)
libravatar_service_url = libravatar_url(
if libravatar_service_url := libravatar_url(
email=addr,
default=404,
size=AVATAR_MAX_SIZE,
)
if libravatar_service_url:
):
try:
urlopen(libravatar_service_url)
except OSError as exc:
print("Exception caught during photo import: {}".format(exc))
print(f"Exception caught during photo import: {exc}")
else:
context["photos"].append(
{
"service_url": libravatar_service_url,
"thumbnail_url": libravatar_service_url + "&s=80",
"image_url": libravatar_service_url + "&s=512",
"thumbnail_url": f"{libravatar_service_url}&s=80",
"image_url": f"{libravatar_service_url}&s=512",
"width": 80,
"height": 80,
"service_name": "Libravatar",
@@ -515,7 +507,7 @@ class ImportPhotoView(SuccessMessageMixin, TemplateView):
imported = 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:
email = ConfirmedEmail.objects.filter(id=email_id, user=request.user)
@@ -565,9 +557,9 @@ class RawImageView(DetailView):
def get(self, request, *args, **kwargs):
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 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")
@@ -643,16 +635,15 @@ class AddOpenIDView(SuccessMessageMixin, FormView):
success_url = reverse_lazy("profile")
def form_valid(self, form):
openid_id = form.save(self.request.user)
if not openid_id:
return render(self.request, self.template_name, {"form": form})
if openid_id := form.save(self.request.user):
# 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})
@method_decorator(login_required, name="dispatch")
@@ -703,7 +694,7 @@ class RemoveConfirmedOpenIDView(View):
openidobj.delete()
except Exception as exc: # pylint: disable=broad-except
# Why it is not there?
print("How did we get here: %s" % exc)
print(f"How did we get here: {exc}")
openid.delete()
messages.success(request, _("ID removed"))
except self.model.DoesNotExist: # pylint: disable=no-member
@@ -740,7 +731,7 @@ class RedirectOpenIDView(View):
try:
auth_request = openid_consumer.begin(user_url)
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"))
except UnicodeDecodeError as exc: # pragma: no cover
msg = _(
@@ -752,7 +743,7 @@ class RedirectOpenIDView(View):
"message": exc,
}
)
print("message: %s" % msg)
print(f"message: {msg}")
messages.error(request, msg)
if auth_request is None: # pragma: no cover
@@ -886,19 +877,13 @@ class CropPhotoView(TemplateView):
}
email = openid = None
if "email" in request.POST:
try:
with contextlib.suppress(ConfirmedEmail.DoesNotExist):
email = ConfirmedEmail.objects.get(email=request.POST["email"])
except ConfirmedEmail.DoesNotExist: # pylint: disable=no-member
pass # Ignore automatic assignment
if "openid" in request.POST:
try:
with contextlib.suppress(ConfirmedOpenId.DoesNotExist):
openid = ConfirmedOpenId.objects.get( # pylint: disable=no-member
openid=request.POST["openid"]
)
except ConfirmedOpenId.DoesNotExist: # pylint: disable=no-member
pass # Ignore automatic assignment
return photo.perform_crop(request, dimensions, email, openid)
@@ -934,14 +919,14 @@ class UserPreferenceView(FormView, UpdateView):
if request.POST["email"] not in addresses:
messages.error(
self.request,
_("Mail address not allowed: %s" % request.POST["email"]),
_(f'Mail address not allowed: {request.POST["email"]}'),
)
else:
self.request.user.email = request.POST["email"]
self.request.user.save()
messages.info(self.request, _("Mail address changed."))
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:
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."))
self.request.user.save()
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"))
@@ -1021,15 +1006,14 @@ class UploadLibravatarExportView(SuccessMessageMixin, FormView):
except Exception as exc: # pylint: disable=broad-except
# DEBUG
print(
"Exception during adding mail address (%s): %s"
% (email, exc)
f"Exception during adding mail address ({email}): {exc}"
)
if arg.startswith("photo"):
try:
data = base64.decodebytes(bytes(request.POST[arg], "utf-8"))
except binascii.Error as exc:
print("Cannot decode photo: %s" % exc)
print(f"Cannot decode photo: {exc}")
continue
try:
pilobj = Image.open(BytesIO(data))
@@ -1043,7 +1027,7 @@ class UploadLibravatarExportView(SuccessMessageMixin, FormView):
photo.data = out.read()
photo.save()
except Exception as exc: # pylint: disable=broad-except
print("Exception during save: %s" % exc)
print(f"Exception during save: {exc}")
continue
return HttpResponseRedirect(reverse_lazy("profile"))
@@ -1063,7 +1047,7 @@ class UploadLibravatarExportView(SuccessMessageMixin, FormView):
},
)
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"))
@@ -1089,13 +1073,12 @@ class ResendConfirmationMailView(View):
try:
email.send_confirmation_mail(url=request.build_absolute_uri("/")[:-1])
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
messages.error(
request,
"%s %s: %s"
% (_("Unable to send confirmation email for"), email.email, exc),
f'{_("Unable to send confirmation email for")} {email.email}: {exc}',
)
return HttpResponseRedirect(reverse_lazy("profile"))
@@ -1129,12 +1112,9 @@ class ProfileView(TemplateView):
if "profile_username" in kwargs:
if not request.user.is_staff:
return HttpResponseRedirect(reverse_lazy("profile"))
try:
with contextlib.suppress(Exception):
u = User.objects.get(username=kwargs["profile_username"])
request.user = u
except Exception: # pylint: disable=broad-except
pass
self._confirm_claimed_openid()
return super().get(self, request, args, kwargs)
@@ -1165,7 +1145,7 @@ class ProfileView(TemplateView):
openid=openids.first().claimed_id
).exists():
return
print("need to confirm: %s" % openids.first())
print(f"need to confirm: {openids.first()}")
confirmed = ConfirmedOpenId()
confirmed.user = self.request.user
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,
we need to set the email on the user object in order for the
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
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
# attribute on the user object accordingly
if not user:
try:
with contextlib.suppress(ObjectDoesNotExist):
confirmed_email = ConfirmedEmail.objects.get(
email=request.POST["email"]
)
user = confirmed_email.user
user.email = confirmed_email.email
user.save()
except ObjectDoesNotExist:
pass
# If we found the user, set a random password. Else, the
# ResetPasswordView class will silently ignore the password
# reset request
@@ -1249,7 +1226,6 @@ class DeleteAccountView(SuccessMessageMixin, FormView):
messages.error(request, _("No password given"))
return HttpResponseRedirect(reverse_lazy("delete"))
raise _("No password given")
# should delete all confirmed/unconfirmed/photo objects
request.user.delete()
return super().post(self, request, args, kwargs)
@@ -1272,7 +1248,7 @@ class ExportView(SuccessMessageMixin, TemplateView):
Handle real export
"""
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():
return (
@@ -1354,8 +1330,8 @@ class ExportView(SuccessMessageMixin, TemplateView):
bytesobj.seek(0)
response = HttpResponse(content_type="application/gzip")
response["Content-Disposition"] = (
'attachment; filename="libravatar-export_%s.xml.gz"' % user.username
)
response[
"Content-Disposition"
] = f'attachment; filename="libravatar-export_{user.username}.xml.gz"'
response.write(bytesobj.read())
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
"""
if BLUESKY_APP_PASSWORD and BLUESKY_IDENTIFIER:
b = Bluesky()
profile = b.get_profile("ofalk.bsky.social")

View File

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

View File

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

View File

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

View File

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