mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-11 10:46:24 +00:00
Code cleanup/refactoring
This commit is contained in:
2
.flake8
2
.flake8
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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?",
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
BIN
ivatar/static/img/broken.tif
Normal file
BIN
ivatar/static/img/broken.tif
Normal file
Binary file not shown.
Binary file not shown.
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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),
|
||||||
),
|
),
|
||||||
|
|||||||
179
ivatar/views.py
179
ivatar/views.py
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user