mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-17 05:28:03 +00:00
Bluesky integration
* Centralize the our urlopen for consistency. * Fix a few tests
This commit is contained in:
12
config.py
12
config.py
@@ -268,9 +268,7 @@ TRUSTED_DEFAULT_URLS = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
# This MUST BE THE LAST!
|
URL_TIMEOUT = 10
|
||||||
if os.path.isfile(os.path.join(BASE_DIR, "config_local.py")):
|
|
||||||
from config_local import * # noqa # flake8: noqa # NOQA # pragma: no cover
|
|
||||||
|
|
||||||
|
|
||||||
def map_legacy_config(trusted_url):
|
def map_legacy_config(trusted_url):
|
||||||
@@ -286,3 +284,11 @@ def map_legacy_config(trusted_url):
|
|||||||
|
|
||||||
# Backward compability for legacy behavior
|
# Backward compability for legacy behavior
|
||||||
TRUSTED_DEFAULT_URLS = list(map(map_legacy_config, TRUSTED_DEFAULT_URLS))
|
TRUSTED_DEFAULT_URLS = list(map(map_legacy_config, TRUSTED_DEFAULT_URLS))
|
||||||
|
|
||||||
|
# Bluesky settings
|
||||||
|
BLUESKY_IDENTIFIER = os.environ.get("BLUESKY_IDENTIFIER", None)
|
||||||
|
BLUESKY_APP_PASSWORD = os.environ.get("BLUESKY_APP_PASSWORD", None)
|
||||||
|
|
||||||
|
# This MUST BE THE LAST!
|
||||||
|
if os.path.isfile(os.path.join(BASE_DIR, "config_local.py")):
|
||||||
|
from config_local import * # noqa # flake8: noqa # NOQA # pragma: no cover
|
||||||
|
|||||||
@@ -3,13 +3,12 @@
|
|||||||
Helper method to fetch Gravatar image
|
Helper method to fetch Gravatar image
|
||||||
"""
|
"""
|
||||||
from ssl import SSLError
|
from ssl import SSLError
|
||||||
from urllib.request import urlopen, HTTPError, URLError
|
from urllib.request import HTTPError, URLError
|
||||||
|
from ivatar.utils import urlopen
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
from ..settings import AVATAR_MAX_SIZE
|
from ..settings import AVATAR_MAX_SIZE
|
||||||
|
|
||||||
URL_TIMEOUT = 5 # in seconds
|
|
||||||
|
|
||||||
|
|
||||||
def get_photo(email):
|
def get_photo(email):
|
||||||
"""
|
"""
|
||||||
@@ -30,7 +29,7 @@ def get_photo(email):
|
|||||||
service_url = "http://www.gravatar.com/" + hash_object.hexdigest()
|
service_url = "http://www.gravatar.com/" + hash_object.hexdigest()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
urlopen(image_url, timeout=URL_TIMEOUT)
|
urlopen(image_url)
|
||||||
except HTTPError as exc:
|
except HTTPError as exc:
|
||||||
if exc.code != 404 and exc.code != 503:
|
if exc.code != 404 and exc.code != 503:
|
||||||
print( # pragma: no cover
|
print( # pragma: no cover
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 5.1.5 on 2025-01-27 10:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("ivataraccount", "0018_alter_photo_format"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="confirmedemail",
|
||||||
|
name="bluesky_handle",
|
||||||
|
field=models.CharField(blank=True, max_length=256, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 5.1.5 on 2025-01-27 13:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("ivataraccount", "0019_confirmedemail_bluesky_handle"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="confirmedopenid",
|
||||||
|
name="bluesky_handle",
|
||||||
|
field=models.CharField(blank=True, max_length=256, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -9,7 +9,7 @@ import time
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from os import urandom
|
from os import urandom
|
||||||
from urllib.error import HTTPError, URLError
|
from urllib.error import HTTPError, URLError
|
||||||
from urllib.request import urlopen
|
from ivatar.utils import urlopen, Bluesky
|
||||||
from urllib.parse import urlsplit, urlunsplit
|
from urllib.parse import urlsplit, urlunsplit
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
@@ -322,6 +322,8 @@ class ConfirmedEmail(BaseAccountModel):
|
|||||||
null=True,
|
null=True,
|
||||||
on_delete=models.deletion.SET_NULL,
|
on_delete=models.deletion.SET_NULL,
|
||||||
)
|
)
|
||||||
|
# Alternative assignment - use Bluesky handle
|
||||||
|
bluesky_handle = models.CharField(max_length=256, null=True, blank=True)
|
||||||
digest = models.CharField(max_length=32)
|
digest = models.CharField(max_length=32)
|
||||||
digest_sha256 = models.CharField(max_length=64)
|
digest_sha256 = models.CharField(max_length=64)
|
||||||
objects = ConfirmedEmailManager()
|
objects = ConfirmedEmailManager()
|
||||||
@@ -342,6 +344,18 @@ class ConfirmedEmail(BaseAccountModel):
|
|||||||
self.photo = photo
|
self.photo = photo
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
def set_bluesky_handle(self, handle):
|
||||||
|
"""
|
||||||
|
Helper method to set Bluesky handle
|
||||||
|
"""
|
||||||
|
bs = Bluesky()
|
||||||
|
avatar = bs.get_profile(handle)
|
||||||
|
if not avatar:
|
||||||
|
raise Exception("Invalid Bluesky handle")
|
||||||
|
return
|
||||||
|
self.bluesky_handle = handle
|
||||||
|
self.save()
|
||||||
|
|
||||||
def save(
|
def save(
|
||||||
self, force_insert=False, force_update=False, using=None, update_fields=None
|
self, force_insert=False, force_update=False, using=None, update_fields=None
|
||||||
):
|
):
|
||||||
@@ -463,6 +477,8 @@ class ConfirmedOpenId(BaseAccountModel):
|
|||||||
alt_digest2 = models.CharField(max_length=64, null=True, blank=True, default=None)
|
alt_digest2 = models.CharField(max_length=64, null=True, blank=True, default=None)
|
||||||
# https://<id> - https w/o trailing slash
|
# https://<id> - https w/o trailing slash
|
||||||
alt_digest3 = models.CharField(max_length=64, null=True, blank=True, default=None)
|
alt_digest3 = models.CharField(max_length=64, null=True, blank=True, default=None)
|
||||||
|
# Alternative assignment - use Bluesky handle
|
||||||
|
bluesky_handle = models.CharField(max_length=256, null=True, blank=True)
|
||||||
|
|
||||||
access_count = models.BigIntegerField(default=0, editable=False)
|
access_count = models.BigIntegerField(default=0, editable=False)
|
||||||
|
|
||||||
@@ -481,6 +497,18 @@ class ConfirmedOpenId(BaseAccountModel):
|
|||||||
self.photo = photo
|
self.photo = photo
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
def set_bluesky_handle(self, handle):
|
||||||
|
"""
|
||||||
|
Helper method to set Bluesky handle
|
||||||
|
"""
|
||||||
|
bs = Bluesky()
|
||||||
|
avatar = bs.get_profile(handle)
|
||||||
|
if not avatar:
|
||||||
|
raise Exception("Invalid Bluesky handle")
|
||||||
|
return
|
||||||
|
self.bluesky_handle = handle
|
||||||
|
self.save()
|
||||||
|
|
||||||
def save(
|
def save(
|
||||||
self, force_insert=False, force_update=False, using=None, update_fields=None
|
self, force_insert=False, force_update=False, using=None, update_fields=None
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -30,39 +30,68 @@ outline: inherit;
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
{% for photo in user.photo_set.all %}
|
{% for photo in user.photo_set.all %}
|
||||||
<form action="{% url 'assign_photo_email' view.kwargs.email_id %}" method="post" style="float:left;margin-left:20px">{% csrf_token %}
|
<form action="{% url 'assign_photo_email' view.kwargs.email_id %}" method="post" style="float:left;margin-left:20px">{% csrf_token %}
|
||||||
<input type="hidden" name="photo_id" value="{{ photo.id }}">
|
<input type="hidden" name="photo_id" value="{{ photo.id }}">
|
||||||
<button type="submit" name="photo{{ photo.id }}" class="nobutton">
|
<button type="submit" name="photo{{ photo.id }}" class="nobutton">
|
||||||
<div class="panel panel-tortin" style="width:132px;margin:0">
|
<div class="panel panel-tortin" style="width:132px;margin:0">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title">{% if email.photo.id == photo.id %}<i class="fa fa-check"></i>{% endif %} {% trans 'Image' %} {{ forloop.counter }}</h3>
|
<h3 class="panel-title">{% if email.photo.id == photo.id %}<i class="fa fa-check"></i>{% endif %} {% trans 'Image' %} {{ forloop.counter }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body" style="height:130px">
|
<div class="panel-body" style="height:130px">
|
||||||
<center>
|
<center>
|
||||||
<img style="max-height:100px;max-width:100px" src="{% url 'raw_image' photo.id %}">
|
<img style="max-height:100px;max-width:100px" src="{% url 'raw_image' photo.id %}">
|
||||||
</center>
|
</center>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<form action="{% url 'assign_photo_email' view.kwargs.email_id %}" method="post" style="float:left;margin-left:20px">{% csrf_token %}
|
<form action="{% url 'assign_photo_email' view.kwargs.email_id %}" method="post" style="float:left;margin-left:20px">{% csrf_token %}
|
||||||
<button type="submit" name="photoNone" class="nobutton">
|
<button type="submit" name="photoNone" class="nobutton">
|
||||||
<div class="panel panel-tortin" style="width:132px;margin:0">
|
<div class="panel panel-tortin" style="width:132px;margin:0">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title">{% if email.photo.id == photo.id %}<i class="fa fa-check"></i>{% endif %} {% trans 'No image' %}</h3>
|
<h3 class="panel-title">{% if email.photo.id == photo.id %}{% if not email.bluesky_handle %}<i class="fa fa-check"></i>{% endif %}{% endif %} {% trans 'No image' %}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body" style="height:130px">
|
<div class="panel-body" style="height:130px">
|
||||||
<center>
|
<center>
|
||||||
<img style="max-height:100px;max-width:100px" src="/static/img/nobody/100.png">
|
<img style="max-height:100px;max-width:100px" src="/static/img/nobody/100.png">
|
||||||
</center>
|
</center>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
{% if email.bluesky_handle %}
|
||||||
|
<form action="" style="float:left;margin-left:20px">
|
||||||
|
<div class="panel panel-tortin" style="width:132px;margin:0">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title"><i class="fa fa-check"></i> {% trans "Bluesky" %}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body" style="height:130px">
|
||||||
|
<center>
|
||||||
|
<img style="max-height:100px;max-width:100px" src="{% url "blueskyproxy" email.digest %}?size=100">
|
||||||
|
</center>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div style="height:8px"></div>
|
<div style="height:8px"></div>
|
||||||
<a href="{% url 'upload_photo' %}" class="button">{% blocktrans %}Upload a new one{% endblocktrans %}</a>
|
<a href="{% url 'upload_photo' %}" class="button">{% blocktrans %}Upload a new one{% endblocktrans %}</a>
|
||||||
<a href="{% url 'import_photo' email.pk %}" class="button">{% blocktrans %}Import from other services{% endblocktrans %}</a>
|
<a href="{% url 'import_photo' email.pk %}" class="button">{% blocktrans %}Import from other services{% endblocktrans %}</a>
|
||||||
|
|
||||||
|
<div style="height:8px"></div>
|
||||||
|
<form action="{% url 'assign_bluesky_handle_to_email' view.kwargs.email_id %}" method="post" style="float:left;margin-left:20px">{% csrf_token %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="id_bluesky_handle">{% trans "Bluesky handle" %}:</label>
|
||||||
|
{% if email.bluesky_handle %}
|
||||||
|
<input type="text" name="bluesky_handle" required value="{{ email.bluesky_handle }}" class="form-control" id="id_bluesky_handle">
|
||||||
|
{% else %}
|
||||||
|
<input type="text" name="bluesky_handle" required value="" placeholder="{% trans 'Bluesky handle' %}" class="form-control" id="id_bluesky_handle">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="button">{% trans 'Assign Bluesky handle' %}</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div style="height:40px"></div>
|
<div style="height:40px"></div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@@ -63,6 +63,19 @@ outline: inherit;
|
|||||||
<div style="height:8px"></div>
|
<div style="height:8px"></div>
|
||||||
<a href="{% url 'upload_photo' %}" class="button">{% blocktrans %}upload a new one{% endblocktrans %}</a>
|
<a href="{% url 'upload_photo' %}" class="button">{% blocktrans %}upload a new one{% endblocktrans %}</a>
|
||||||
|
|
||||||
|
<div style="height:8px"></div>
|
||||||
|
<form action="{% url 'assign_bluesky_handle_to_openid' view.kwargs.openid_id %}" method="post" style="float:left;margin-left:20px">{% csrf_token %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="id_bluesky_handle">{% trans "Bluesky handle" %}:</label>
|
||||||
|
{% if openid.bluesky_handle %}
|
||||||
|
<input type="text" name="bluesky_handle" required value="{{ openid.bluesky_handle }}" class="form-control" id="id_bluesky_handle">
|
||||||
|
{% else %}
|
||||||
|
<input type="text" name="bluesky_handle" required value="" placeholder="{% trans 'Bluesky handle' %}" class="form-control" id="id_bluesky_handle">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="button">{% trans 'Assign Bluesky handle' %}</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div style="height:40px"></div>
|
<div style="height:40px"></div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@@ -101,7 +101,15 @@
|
|||||||
<form action="{% url 'remove_confirmed_email' email.id %}" method="post">
|
<form action="{% url 'remove_confirmed_email' email.id %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div id="email-conf-{{ forloop.counter }}" class="profile-container active">
|
<div id="email-conf-{{ forloop.counter }}" class="profile-container active">
|
||||||
<img title="{% trans 'Access count' %}: {{ email.access_count }}" src="{% if email.photo %}{% url 'raw_image' email.photo.id %}{% else %}{% static '/img/nobody/120.png' %}{% endif %}">
|
<img title="{% trans 'Access count' %}: {{ email.access_count }}"
|
||||||
|
src="
|
||||||
|
{% if email.photo %}
|
||||||
|
{% url 'raw_image' email.photo.id %}
|
||||||
|
{% elif email.bluesky_handle %}
|
||||||
|
{% url 'blueskyproxy' email.digest %}
|
||||||
|
{% else %}
|
||||||
|
{% static '/img/nobody/120.png' %}
|
||||||
|
{% endif %}">
|
||||||
<h3 class="panel-title email-profile" title="{{ email.email }}">
|
<h3 class="panel-title email-profile" title="{{ email.email }}">
|
||||||
{{ email.email }}
|
{{ email.email }}
|
||||||
</h3>
|
</h3>
|
||||||
@@ -123,7 +131,15 @@
|
|||||||
<form action="{% url 'remove_confirmed_email' email.id %}" method="post">
|
<form action="{% url 'remove_confirmed_email' email.id %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div id="email-conf-{{ forloop.counter }}" class="profile-container" onclick="add_active('email-conf-{{ forloop.counter }}')">
|
<div id="email-conf-{{ forloop.counter }}" class="profile-container" onclick="add_active('email-conf-{{ forloop.counter }}')">
|
||||||
<img title="{% trans 'Access count' %}: {{ email.access_count }}" src="{% if email.photo %}{% url 'raw_image' email.photo.id %}{% else %}{% static '/img/nobody/120.png' %}{% endif %}">
|
<img title="{% trans 'Access count' %}: {{ email.access_count }}"
|
||||||
|
src="
|
||||||
|
{% if email.photo %}
|
||||||
|
{% url 'raw_image' email.photo.id %}
|
||||||
|
{% elif email.bluesky_handle %}
|
||||||
|
{% url 'blueskyproxy' email.digest %}
|
||||||
|
{% else %}
|
||||||
|
{% static '/img/nobody/120.png' %}
|
||||||
|
{% endif %}">
|
||||||
<h3 class="panel-title email-profile" title="{{ email.email }}">
|
<h3 class="panel-title email-profile" title="{{ email.email }}">
|
||||||
{{ email.email }}
|
{{ email.email }}
|
||||||
</h3>
|
</h3>
|
||||||
@@ -148,7 +164,15 @@
|
|||||||
<form action="{% url 'remove_confirmed_openid' openid.id %}" method="post">{% csrf_token %}
|
<form action="{% url 'remove_confirmed_openid' openid.id %}" method="post">{% csrf_token %}
|
||||||
<div>
|
<div>
|
||||||
<div id="id-conf-{{ forloop.counter }}" class="profile-container active">
|
<div id="id-conf-{{ forloop.counter }}" class="profile-container active">
|
||||||
<img title="{% trans 'Access count' %}: {{ openid.access_count }}" src="{% if openid.photo %}{% url 'raw_image' openid.photo.id %}{% else %}{% static '/img/nobody/120.png' %}{% endif %}">
|
<img title="{% trans 'Access count' %}: {{ openid.access_count }}"
|
||||||
|
src="
|
||||||
|
{% if openid.photo %}
|
||||||
|
{% url 'raw_image' openid.photo.id %}
|
||||||
|
{% elif openid.bluesky_handle %}
|
||||||
|
{% url 'blueskyproxy' openid.digest %}
|
||||||
|
{% else %}
|
||||||
|
{% static '/img/nobody/120.png' %}
|
||||||
|
{% endif %}">
|
||||||
<h3 class="panel-title email-profile" title="{{ openid.openid }}">
|
<h3 class="panel-title email-profile" title="{{ openid.openid }}">
|
||||||
{{ openid.openid }}
|
{{ openid.openid }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|||||||
@@ -456,11 +456,18 @@ 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
|
||||||
# TODO: This test isn't super criticial, but needs to be fixed
|
# Check the response context for form errors
|
||||||
# Currently issues an error with an unbound form
|
self.assertTrue(
|
||||||
# self.assertFormError(
|
hasattr(response, "context"), "Response does not have a context"
|
||||||
# response, "form", None, "Too many unconfirmed mail addresses!"
|
)
|
||||||
# )
|
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):
|
||||||
"""
|
"""
|
||||||
@@ -479,11 +486,18 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
|||||||
},
|
},
|
||||||
follow=True,
|
follow=True,
|
||||||
)
|
)
|
||||||
# TODO: This test isn't super criticial, but needs to be fixed
|
# Check the response context for form errors
|
||||||
# Currently issues an error with an unbound form
|
self.assertTrue(
|
||||||
# self.assertFormError(
|
hasattr(response, "context"), "Response does not have a context"
|
||||||
# response, "form", "email", "Address already added, currently unconfirmed"
|
)
|
||||||
# )
|
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
|
||||||
"""
|
"""
|
||||||
@@ -500,11 +514,19 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
|||||||
},
|
},
|
||||||
follow=True,
|
follow=True,
|
||||||
)
|
)
|
||||||
# TODO: This test isn't super criticial, but needs to be fixed
|
|
||||||
# Currently issues an error with an unbound form
|
# Check the response context for form errors
|
||||||
# self.assertFormError(
|
self.assertTrue(
|
||||||
# response, "form", "email", "Address already confirmed (by you)"
|
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
|
||||||
"""
|
"""
|
||||||
@@ -528,11 +550,19 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
|||||||
},
|
},
|
||||||
follow=True,
|
follow=True,
|
||||||
)
|
)
|
||||||
# TODO: This test isn't super criticial, but needs to be fixed
|
|
||||||
# Currently issues an error with an unbound form
|
# Check the response context for form errors
|
||||||
# self.assertFormError(
|
self.assertTrue(
|
||||||
# response, "form", "email", "Address already confirmed (by someone)"
|
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(
|
||||||
self,
|
self,
|
||||||
@@ -1052,11 +1082,19 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
|||||||
"There must only be one unconfirmed ID!",
|
"There must only be one unconfirmed ID!",
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: This test isn't super criticial, but needs to be fixed
|
# Check the response context for form errors
|
||||||
# Currently issues an error with an unbound form
|
self.assertTrue(
|
||||||
# self.assertFormError(
|
hasattr(response, "context"), "Response does not have a context"
|
||||||
# response, "form", "openid", "OpenID already added, but not confirmed yet!"
|
)
|
||||||
# )
|
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()
|
||||||
@@ -1075,11 +1113,19 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
|||||||
},
|
},
|
||||||
follow=True,
|
follow=True,
|
||||||
)
|
)
|
||||||
# TODO: This test isn't super criticial, but needs to be fixed
|
|
||||||
# Currently issues an error with an unbound form
|
# Check the response context for form errors
|
||||||
# self.assertFormError(
|
self.assertTrue(
|
||||||
# response, "form", "openid", "OpenID already added and confirmed!"
|
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", [])
|
||||||
|
)
|
||||||
|
|
||||||
def test_assign_photo_to_openid(self):
|
def test_assign_photo_to_openid(self):
|
||||||
"""
|
"""
|
||||||
@@ -1529,9 +1575,6 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
|||||||
"""
|
"""
|
||||||
Test fetching avatar for not existing mail with default specified
|
Test fetching avatar for not existing mail with default specified
|
||||||
"""
|
"""
|
||||||
# TODO - Find a new way
|
|
||||||
# Do not run this test, since static serving isn't allowed in testing mode
|
|
||||||
return
|
|
||||||
urlobj = urlsplit(
|
urlobj = urlsplit(
|
||||||
libravatar_url(
|
libravatar_url(
|
||||||
"xxx@xxx.xxx",
|
"xxx@xxx.xxx",
|
||||||
@@ -1540,11 +1583,13 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
url = "%s?%s" % (urlobj.path, urlobj.query)
|
url = "%s?%s" % (urlobj.path, urlobj.query)
|
||||||
|
url += "&gravatarproxy=n"
|
||||||
response = self.client.get(url, follow=False)
|
response = self.client.get(url, follow=False)
|
||||||
self.assertRedirects(
|
self.assertEqual(response.status_code, 302, "Doesn't redirect with 302?")
|
||||||
response=response,
|
self.assertEqual(
|
||||||
expected_url="/static/img/nobody.png",
|
response["Location"],
|
||||||
msg_prefix="Why does this not redirect to nobody img?",
|
"/static/img/nobody.png",
|
||||||
|
"Doesn't redirect to static img?",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_avatar_url_default_gravatarproxy_disabled(
|
def test_avatar_url_default_gravatarproxy_disabled(
|
||||||
@@ -1553,9 +1598,6 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
|||||||
"""
|
"""
|
||||||
Test fetching avatar for not existing mail with default specified
|
Test fetching avatar for not existing mail with default specified
|
||||||
"""
|
"""
|
||||||
# TODO - Find a new way
|
|
||||||
# Do not run this test, since static serving isn't allowed in testing mode
|
|
||||||
return
|
|
||||||
urlobj = urlsplit(
|
urlobj = urlsplit(
|
||||||
libravatar_url(
|
libravatar_url(
|
||||||
"xxx@xxx.xxx",
|
"xxx@xxx.xxx",
|
||||||
@@ -1565,10 +1607,10 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
|||||||
)
|
)
|
||||||
url = "%s?%s&gravatarproxy=n" % (urlobj.path, urlobj.query)
|
url = "%s?%s&gravatarproxy=n" % (urlobj.path, urlobj.query)
|
||||||
response = self.client.get(url, follow=True)
|
response = self.client.get(url, follow=True)
|
||||||
self.assertRedirects(
|
self.assertEqual(
|
||||||
response=response,
|
response.redirect_chain[0][0],
|
||||||
expected_url="/static/img/nobody.png",
|
"/static/img/nobody.png",
|
||||||
msg_prefix="Why does this not redirect to the default img?",
|
"Doesn't redirect to static?",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_avatar_url_default_external(self): # pylint: disable=invalid-name
|
def test_avatar_url_default_external(self): # pylint: disable=invalid-name
|
||||||
|
|||||||
284
ivatar/ivataraccount/test_views_bluesky.py
Normal file
284
ivatar/ivataraccount/test_views_bluesky.py
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Test our views in ivatar.ivataraccount.views and ivatar.views
|
||||||
|
"""
|
||||||
|
# pylint: disable=too-many-lines
|
||||||
|
import os
|
||||||
|
import django
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test import Client
|
||||||
|
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
# from django.contrib.auth import authenticate
|
||||||
|
|
||||||
|
os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings"
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
# pylint: disable=wrong-import-position
|
||||||
|
from ivatar import settings
|
||||||
|
from ivatar.ivataraccount.models import ConfirmedOpenId, ConfirmedEmail
|
||||||
|
from ivatar.utils import random_string
|
||||||
|
|
||||||
|
from libravatar import libravatar_url
|
||||||
|
|
||||||
|
|
||||||
|
class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||||
|
"""
|
||||||
|
Main test class
|
||||||
|
"""
|
||||||
|
|
||||||
|
client = Client()
|
||||||
|
user = None
|
||||||
|
username = random_string()
|
||||||
|
password = random_string()
|
||||||
|
email = "%s@%s.%s" % (username, random_string(), random_string(2))
|
||||||
|
# Dunno why random tld doesn't work, but I'm too lazy now to investigate
|
||||||
|
openid = "http://%s.%s.%s/" % (username, random_string(), "org")
|
||||||
|
first_name = random_string()
|
||||||
|
last_name = random_string()
|
||||||
|
bsky_test_account = "ofalk.bsky.social"
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
"""
|
||||||
|
Login as user
|
||||||
|
"""
|
||||||
|
self.client.login(username=self.username, password=self.password)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
Prepare for tests.
|
||||||
|
- Create user
|
||||||
|
"""
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
username=self.username,
|
||||||
|
password=self.password,
|
||||||
|
first_name=self.first_name,
|
||||||
|
last_name=self.last_name,
|
||||||
|
)
|
||||||
|
settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
|
||||||
|
|
||||||
|
def create_confirmed_openid(self):
|
||||||
|
"""
|
||||||
|
Create a confirmed openid
|
||||||
|
"""
|
||||||
|
confirmed = ConfirmedOpenId.objects.create(
|
||||||
|
user=self.user,
|
||||||
|
ip_address="127.0.0.1",
|
||||||
|
openid=self.openid,
|
||||||
|
)
|
||||||
|
|
||||||
|
return confirmed
|
||||||
|
|
||||||
|
def create_confirmed_email(self):
|
||||||
|
"""
|
||||||
|
Create a confirmed email
|
||||||
|
"""
|
||||||
|
confirmed = ConfirmedEmail.objects.create(
|
||||||
|
email=self.email,
|
||||||
|
user=self.user,
|
||||||
|
)
|
||||||
|
|
||||||
|
return confirmed
|
||||||
|
|
||||||
|
# The following tests need to be moved over to the model tests
|
||||||
|
# and real web UI tests added
|
||||||
|
def test_bluesky_handle_for_mail_via_model_handle_doesnt_exist(self):
|
||||||
|
"""
|
||||||
|
Add Bluesky handle to a confirmed mail address
|
||||||
|
"""
|
||||||
|
self.login()
|
||||||
|
confirmed = self.create_confirmed_email()
|
||||||
|
confirmed.set_bluesky_handle(self.bsky_test_account)
|
||||||
|
|
||||||
|
try:
|
||||||
|
confirmed.set_bluesky_handle(self.bsky_test_account + "1")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self.assertNotEqual(
|
||||||
|
confirmed.bluesky_handle,
|
||||||
|
self.bsky_test_account + "1",
|
||||||
|
"Setting Bluesky handle that doesn exist works?",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_bluesky_handle_for_mail_via_model_handle_exists(self):
|
||||||
|
"""
|
||||||
|
Add Bluesky handle to a confirmed mail address
|
||||||
|
"""
|
||||||
|
self.login()
|
||||||
|
confirmed = self.create_confirmed_email()
|
||||||
|
confirmed.set_bluesky_handle(self.bsky_test_account)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
confirmed.bluesky_handle,
|
||||||
|
self.bsky_test_account,
|
||||||
|
"Setting Bluesky handle doesn't work?",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_bluesky_handle_for_openid_via_model_handle_doesnt_exist(self):
|
||||||
|
"""
|
||||||
|
Add Bluesky handle to a confirmed openid address
|
||||||
|
"""
|
||||||
|
self.login()
|
||||||
|
confirmed = self.create_confirmed_openid()
|
||||||
|
confirmed.set_bluesky_handle(self.bsky_test_account)
|
||||||
|
|
||||||
|
try:
|
||||||
|
confirmed.set_bluesky_handle(self.bsky_test_account + "1")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self.assertNotEqual(
|
||||||
|
confirmed.bluesky_handle,
|
||||||
|
self.bsky_test_account + "1",
|
||||||
|
"Setting Bluesky handle that doesn exist works?",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_bluesky_handle_for_openid_via_model_handle_exists(self):
|
||||||
|
"""
|
||||||
|
Add Bluesky handle to a confirmed openid address
|
||||||
|
"""
|
||||||
|
self.login()
|
||||||
|
confirmed = self.create_confirmed_openid()
|
||||||
|
confirmed.set_bluesky_handle(self.bsky_test_account)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
confirmed.bluesky_handle,
|
||||||
|
self.bsky_test_account,
|
||||||
|
"Setting Bluesky handle doesn't work?",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_bluesky_fetch_mail(self):
|
||||||
|
"""
|
||||||
|
Check if we can successfully fetch a Bluesky avatar via email
|
||||||
|
"""
|
||||||
|
self.login()
|
||||||
|
confirmed = self.create_confirmed_email()
|
||||||
|
confirmed.set_bluesky_handle(self.bsky_test_account)
|
||||||
|
lu = libravatar_url(confirmed.email, https=True)
|
||||||
|
lu = lu.replace("https://seccdn.libravatar.org/", reverse("home"))
|
||||||
|
|
||||||
|
response = self.client.get(lu)
|
||||||
|
# This is supposed to redirect to the Bluesky proxy
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response["Location"], "/blueskyproxy/%s" % confirmed.digest)
|
||||||
|
|
||||||
|
def test_bluesky_fetch_openid(self):
|
||||||
|
"""
|
||||||
|
Check if we can successfully fetch a Bluesky avatar via OpenID
|
||||||
|
"""
|
||||||
|
self.login()
|
||||||
|
confirmed = self.create_confirmed_openid()
|
||||||
|
confirmed.set_bluesky_handle(self.bsky_test_account)
|
||||||
|
lu = libravatar_url(openid=confirmed.openid, https=True)
|
||||||
|
lu = lu.replace("https://seccdn.libravatar.org/", reverse("home"))
|
||||||
|
|
||||||
|
response = self.client.get(lu)
|
||||||
|
# This is supposed to redirect to the Bluesky proxy
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response["Location"], "/blueskyproxy/%s" % confirmed.digest)
|
||||||
|
|
||||||
|
def test_assign_bluesky_handle_to_openid(self):
|
||||||
|
"""
|
||||||
|
Assign a Bluesky handle to an OpenID
|
||||||
|
"""
|
||||||
|
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?",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_assign_bluesky_handle_to_email(self):
|
||||||
|
"""
|
||||||
|
Assign a Bluesky handle to an email
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.login()
|
||||||
|
confirmed = self.create_confirmed_email()
|
||||||
|
url = reverse("assign_bluesky_handle_to_email", 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 Email 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_photo_to_mail_removes_bluesky_handle(self):
|
||||||
|
"""
|
||||||
|
Assign a Photo to a mail, removes Bluesky handle
|
||||||
|
"""
|
||||||
|
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?",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_assign_photo_to_openid_removes_bluesky_handle(self):
|
||||||
|
"""
|
||||||
|
Assign a Photo to a OpenID, removes Bluesky handle
|
||||||
|
"""
|
||||||
|
self.login()
|
||||||
|
confirmed = self.create_confirmed_openid()
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
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?",
|
||||||
|
)
|
||||||
@@ -20,6 +20,7 @@ from .views import RemoveUnconfirmedOpenIDView, RemoveConfirmedOpenIDView
|
|||||||
from .views import ImportPhotoView, RawImageView, DeletePhotoView
|
from .views import ImportPhotoView, RawImageView, DeletePhotoView
|
||||||
from .views import UploadPhotoView, AssignPhotoOpenIDView
|
from .views import UploadPhotoView, AssignPhotoOpenIDView
|
||||||
from .views import AddOpenIDView, RedirectOpenIDView, ConfirmOpenIDView
|
from .views import AddOpenIDView, RedirectOpenIDView, ConfirmOpenIDView
|
||||||
|
from .views import AssignBlueskyHandleToEmailView, AssignBlueskyHandleToOpenIdView
|
||||||
from .views import CropPhotoView
|
from .views import CropPhotoView
|
||||||
from .views import UserPreferenceView, UploadLibravatarExportView
|
from .views import UserPreferenceView, UploadLibravatarExportView
|
||||||
from .views import ResendConfirmationMailView
|
from .views import ResendConfirmationMailView
|
||||||
@@ -125,6 +126,16 @@ urlpatterns = [ # pylint: disable=invalid-name
|
|||||||
AssignPhotoOpenIDView.as_view(),
|
AssignPhotoOpenIDView.as_view(),
|
||||||
name="assign_photo_openid",
|
name="assign_photo_openid",
|
||||||
),
|
),
|
||||||
|
re_path(
|
||||||
|
r"assign_bluesky_handle_to_email/(?P<email_id>\d+)",
|
||||||
|
AssignBlueskyHandleToEmailView.as_view(),
|
||||||
|
name="assign_bluesky_handle_to_email",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
r"assign_bluesky_handle_to_openid/(?P<open_id>\d+)",
|
||||||
|
AssignBlueskyHandleToOpenIdView.as_view(),
|
||||||
|
name="assign_bluesky_handle_to_openid",
|
||||||
|
),
|
||||||
re_path(r"import_photo/$", ImportPhotoView.as_view(), name="import_photo"),
|
re_path(r"import_photo/$", ImportPhotoView.as_view(), name="import_photo"),
|
||||||
re_path(
|
re_path(
|
||||||
r"import_photo/(?P<email_addr>[\w.+-]+@[\w.]+.[\w.]+)",
|
r"import_photo/(?P<email_addr>[\w.+-]+@[\w.]+.[\w.]+)",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
View classes for ivatar/ivataraccount/
|
View classes for ivatar/ivataraccount/
|
||||||
"""
|
"""
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from urllib.request import urlopen
|
from ivatar.utils import urlopen, Bluesky
|
||||||
import base64
|
import base64
|
||||||
import binascii
|
import binascii
|
||||||
from xml.sax import saxutils
|
from xml.sax import saxutils
|
||||||
@@ -288,6 +288,7 @@ class AssignPhotoEmailView(SuccessMessageMixin, TemplateView):
|
|||||||
messages.error(request, _("Photo does not exist"))
|
messages.error(request, _("Photo does not exist"))
|
||||||
return HttpResponseRedirect(reverse_lazy("profile"))
|
return HttpResponseRedirect(reverse_lazy("profile"))
|
||||||
email.photo = photo
|
email.photo = photo
|
||||||
|
email.bluesky_handle = None
|
||||||
email.save()
|
email.save()
|
||||||
|
|
||||||
messages.success(request, _("Successfully changed photo"))
|
messages.success(request, _("Successfully changed photo"))
|
||||||
@@ -337,6 +338,7 @@ class AssignPhotoOpenIDView(SuccessMessageMixin, TemplateView):
|
|||||||
messages.error(request, _("Photo does not exist"))
|
messages.error(request, _("Photo does not exist"))
|
||||||
return HttpResponseRedirect(reverse_lazy("profile"))
|
return HttpResponseRedirect(reverse_lazy("profile"))
|
||||||
openid.photo = photo
|
openid.photo = photo
|
||||||
|
openid.bluesky_handle = None
|
||||||
openid.save()
|
openid.save()
|
||||||
|
|
||||||
messages.success(request, _("Successfully changed photo"))
|
messages.success(request, _("Successfully changed photo"))
|
||||||
@@ -350,6 +352,96 @@ class AssignPhotoOpenIDView(SuccessMessageMixin, TemplateView):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_required, name="dispatch")
|
||||||
|
class AssignBlueskyHandleToEmailView(SuccessMessageMixin, TemplateView):
|
||||||
|
"""
|
||||||
|
View class for assigning a Bluesky handle to an email address
|
||||||
|
"""
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs): # pylint: disable=unused-argument
|
||||||
|
"""
|
||||||
|
Handle post request - assign bluesky handle to email
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
email = ConfirmedEmail.objects.get(user=request.user, id=kwargs["email_id"])
|
||||||
|
except ConfirmedEmail.DoesNotExist: # pylint: disable=no-member
|
||||||
|
messages.error(request, _("Invalid request"))
|
||||||
|
return HttpResponseRedirect(reverse_lazy("profile"))
|
||||||
|
|
||||||
|
if "bluesky_handle" not in request.POST:
|
||||||
|
messages.error(request, _("Invalid request [bluesky_handle] missing"))
|
||||||
|
return HttpResponseRedirect(reverse_lazy("profile"))
|
||||||
|
bluesky_handle = request.POST["bluesky_handle"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
bs = Bluesky()
|
||||||
|
|
||||||
|
bs.get_avatar(bluesky_handle)
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(
|
||||||
|
request, _("Handle '%s' not found: %s" % (bluesky_handle, e))
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(reverse_lazy("profile"))
|
||||||
|
email.set_bluesky_handle(bluesky_handle)
|
||||||
|
email.photo = None
|
||||||
|
email.save()
|
||||||
|
|
||||||
|
messages.success(request, _("Successfully assigned Bluesky handle"))
|
||||||
|
return HttpResponseRedirect(reverse_lazy("profile"))
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
data = super().get_context_data(**kwargs)
|
||||||
|
data["email"] = ConfirmedEmail.objects.get(pk=kwargs["email_id"])
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_required, name="dispatch")
|
||||||
|
class AssignBlueskyHandleToOpenIdView(SuccessMessageMixin, TemplateView):
|
||||||
|
"""
|
||||||
|
View class for assigning a Bluesky handle to an email address
|
||||||
|
"""
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs): # pylint: disable=unused-argument
|
||||||
|
"""
|
||||||
|
Handle post request - assign bluesky handle to email
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
openid = ConfirmedOpenId.objects.get(
|
||||||
|
user=request.user, id=kwargs["open_id"]
|
||||||
|
)
|
||||||
|
except ConfirmedOpenId.DoesNotExist: # pylint: disable=no-member
|
||||||
|
messages.error(request, _("Invalid request"))
|
||||||
|
return HttpResponseRedirect(reverse_lazy("profile"))
|
||||||
|
|
||||||
|
if "bluesky_handle" not in request.POST:
|
||||||
|
messages.error(request, _("Invalid request [bluesky_handle] missing"))
|
||||||
|
return HttpResponseRedirect(reverse_lazy("profile"))
|
||||||
|
bluesky_handle = request.POST["bluesky_handle"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
bs = Bluesky()
|
||||||
|
|
||||||
|
bs.get_avatar(bluesky_handle)
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(
|
||||||
|
request, _("Handle '%s' not found: %s" % (bluesky_handle, e))
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(reverse_lazy("profile"))
|
||||||
|
openid.set_bluesky_handle(bluesky_handle)
|
||||||
|
openid.photo = None
|
||||||
|
openid.save()
|
||||||
|
|
||||||
|
messages.success(request, _("Successfully assigned Bluesky handle"))
|
||||||
|
return HttpResponseRedirect(reverse_lazy("profile"))
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
data = super().get_context_data(**kwargs)
|
||||||
|
data["openid"] = ConfirmedOpenId.objects.get(pk=kwargs["open_id"])
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(login_required, name="dispatch")
|
@method_decorator(login_required, name="dispatch")
|
||||||
class ImportPhotoView(SuccessMessageMixin, TemplateView):
|
class ImportPhotoView(SuccessMessageMixin, TemplateView):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -10,8 +10,14 @@ from django.urls import reverse
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test import Client
|
from django.test import Client
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from ivatar.utils import random_string, Bluesky
|
||||||
|
|
||||||
from ivatar.utils import random_string
|
BLUESKY_APP_PASSWORD = None
|
||||||
|
BLUESKY_IDENTIFIER = None
|
||||||
|
try:
|
||||||
|
from settings import BLUESKY_APP_PASSWORD, BLUESKY_IDENTIFIER
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
pass
|
||||||
|
|
||||||
os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings"
|
os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings"
|
||||||
django.setup()
|
django.setup()
|
||||||
@@ -89,3 +95,18 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
|||||||
)
|
)
|
||||||
response = self.client.post(reverse("logout"), follow=True)
|
response = self.client.post(reverse("logout"), follow=True)
|
||||||
self.assertEqual(response.status_code, 200, "logout with post should logout")
|
self.assertEqual(response.status_code, 200, "logout with post should logout")
|
||||||
|
|
||||||
|
def test_Bluesky_client(self):
|
||||||
|
"""
|
||||||
|
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")
|
||||||
|
self.assertEqual(profile["handle"], "ofalk.bsky.social")
|
||||||
|
# As long as I don't change my avatar, this should stay the same
|
||||||
|
self.assertEqual(
|
||||||
|
profile["avatar"],
|
||||||
|
"https://cdn.bsky.app/img/avatar/plain/did:plc:35jdu26cjgsc5vdbsaqiuw4a/bafkreidgtubihcdwcr72s5nag2ohcnwhhbg2zabw4jtxlhmtekrm6t5f4y@jpeg",
|
||||||
|
)
|
||||||
|
self.assertEqual(True, True)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from libravatar import libravatar_url, parse_user_identity
|
|||||||
from libravatar import SECURE_BASE_URL as LIBRAVATAR_SECURE_BASE_URL
|
from libravatar import SECURE_BASE_URL as LIBRAVATAR_SECURE_BASE_URL
|
||||||
from libravatar import BASE_URL as LIBRAVATAR_BASE_URL
|
from libravatar import BASE_URL as LIBRAVATAR_BASE_URL
|
||||||
|
|
||||||
from ivatar.settings import SECURE_BASE_URL, BASE_URL
|
from ivatar.settings import SECURE_BASE_URL, BASE_URL, SITE_NAME, DEBUG
|
||||||
from .forms import (
|
from .forms import (
|
||||||
CheckDomainForm,
|
CheckDomainForm,
|
||||||
CheckForm,
|
CheckForm,
|
||||||
@@ -139,6 +139,36 @@ 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 DEBUG:
|
||||||
|
if mailurl:
|
||||||
|
mailurl = mailurl.replace(
|
||||||
|
"https://avatars.linux-kernel.at",
|
||||||
|
"http://" + self.request.get_host(),
|
||||||
|
)
|
||||||
|
if mailurl_secure:
|
||||||
|
mailurl_secure = mailurl_secure.replace(
|
||||||
|
"https://avatars.linux-kernel.at",
|
||||||
|
"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(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if openidurl:
|
||||||
|
openidurl = openidurl.replace(
|
||||||
|
"https://avatars.linux-kernel.at",
|
||||||
|
"http://" + self.request.get_host(),
|
||||||
|
)
|
||||||
|
if openidurl_secure:
|
||||||
|
openidurl_secure = openidurl_secure.replace(
|
||||||
|
"https://avatars.linux-kernel.at",
|
||||||
|
"http://" + self.request.get_host(),
|
||||||
|
)
|
||||||
|
print(mailurl, openidurl, mailurl_secure, mailurl_secure_256, openidurl_secure)
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
self.request,
|
self.request,
|
||||||
self.template_name,
|
self.template_name,
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ from django.urls import path, include, re_path
|
|||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.views.generic import TemplateView, RedirectView
|
from django.views.generic import TemplateView, RedirectView
|
||||||
from ivatar import settings
|
from ivatar import settings
|
||||||
from .views import AvatarImageView, GravatarProxyView, StatsView
|
from .views import AvatarImageView, StatsView
|
||||||
|
from .views import GravatarProxyView, BlueskyProxyView
|
||||||
|
|
||||||
urlpatterns = [ # pylint: disable=invalid-name
|
urlpatterns = [ # pylint: disable=invalid-name
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
@@ -31,6 +32,11 @@ urlpatterns = [ # pylint: disable=invalid-name
|
|||||||
GravatarProxyView.as_view(),
|
GravatarProxyView.as_view(),
|
||||||
name="gravatarproxy",
|
name="gravatarproxy",
|
||||||
),
|
),
|
||||||
|
re_path(
|
||||||
|
r"blueskyproxy/(?P<digest>\w*)",
|
||||||
|
BlueskyProxyView.as_view(),
|
||||||
|
name="blueskyproxy",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"description/",
|
"description/",
|
||||||
TemplateView.as_view(template_name="description.html"),
|
TemplateView.as_view(template_name="description.html"),
|
||||||
|
|||||||
@@ -7,6 +7,82 @@ import string
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from PIL import Image, ImageDraw, ImageSequence
|
from PIL import Image, ImageDraw, ImageSequence
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
import requests
|
||||||
|
from ivatar.settings import DEBUG, URL_TIMEOUT
|
||||||
|
from urllib.request import urlopen as urlopen_orig
|
||||||
|
|
||||||
|
BLUESKY_IDENTIFIER = None
|
||||||
|
BLUESKY_APP_PASSWORD = None
|
||||||
|
try:
|
||||||
|
from ivatar.settings import BLUESKY_IDENTIFIER, BLUESKY_APP_PASSWORD
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def urlopen(url, timeout=URL_TIMEOUT):
|
||||||
|
ctx = None
|
||||||
|
if DEBUG:
|
||||||
|
import ssl
|
||||||
|
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
return urlopen_orig(url, timeout=timeout, context=ctx)
|
||||||
|
|
||||||
|
|
||||||
|
class Bluesky:
|
||||||
|
"""
|
||||||
|
Handle Bluesky client access
|
||||||
|
"""
|
||||||
|
|
||||||
|
identifier = ""
|
||||||
|
app_password = ""
|
||||||
|
service = "https://bsky.social"
|
||||||
|
session = None
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
identifier: str = BLUESKY_IDENTIFIER,
|
||||||
|
app_password: str = BLUESKY_APP_PASSWORD,
|
||||||
|
service: str = "https://bsky.social",
|
||||||
|
):
|
||||||
|
self.identifier = identifier
|
||||||
|
self.app_password = app_password
|
||||||
|
self.service = service
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
"""
|
||||||
|
Login to Bluesky
|
||||||
|
"""
|
||||||
|
auth_response = requests.post(
|
||||||
|
f"{self.service}/xrpc/com.atproto.server.createSession",
|
||||||
|
json={"identifier": self.identifier, "password": self.app_password},
|
||||||
|
)
|
||||||
|
auth_response.raise_for_status()
|
||||||
|
self.session = auth_response.json()
|
||||||
|
|
||||||
|
def get_profile(self, handle: str):
|
||||||
|
if not self.session:
|
||||||
|
self.login()
|
||||||
|
profile_response = None
|
||||||
|
try:
|
||||||
|
profile_response = requests.get(
|
||||||
|
f"{self.service}/xrpc/app.bsky.actor.getProfile",
|
||||||
|
headers={"Authorization": f'Bearer {self.session["accessJwt"]}'},
|
||||||
|
params={"actor": handle},
|
||||||
|
)
|
||||||
|
profile_response.raise_for_status()
|
||||||
|
except Exception as exc:
|
||||||
|
print(
|
||||||
|
"Bluesky profile fetch failed with HTTP error: %s" % exc
|
||||||
|
) # pragma: no cover
|
||||||
|
return None
|
||||||
|
|
||||||
|
return profile_response.json()
|
||||||
|
|
||||||
|
def get_avatar(self, handle: str):
|
||||||
|
profile = self.get_profile(handle)
|
||||||
|
return profile["avatar"] if profile else None
|
||||||
|
|
||||||
|
|
||||||
def random_string(length=10):
|
def random_string(length=10):
|
||||||
|
|||||||
142
ivatar/views.py
142
ivatar/views.py
@@ -5,7 +5,7 @@ views under /
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from os import path
|
from os import path
|
||||||
import hashlib
|
import hashlib
|
||||||
from urllib.request import urlopen
|
from ivatar.utils import urlopen, Bluesky
|
||||||
from urllib.error import HTTPError, URLError
|
from urllib.error import HTTPError, URLError
|
||||||
from ssl import SSLError
|
from ssl import SSLError
|
||||||
from django.views.generic.base import TemplateView, View
|
from django.views.generic.base import TemplateView, View
|
||||||
@@ -36,8 +36,6 @@ from .ivataraccount.models import Photo
|
|||||||
from .ivataraccount.models import pil_format, file_format
|
from .ivataraccount.models import pil_format, file_format
|
||||||
from .utils import is_trusted_url, mm_ng, resize_animated_gif
|
from .utils import is_trusted_url, mm_ng, resize_animated_gif
|
||||||
|
|
||||||
URL_TIMEOUT = 5 # in seconds
|
|
||||||
|
|
||||||
|
|
||||||
def get_size(request, size=DEFAULT_AVATAR_SIZE):
|
def get_size(request, size=DEFAULT_AVATAR_SIZE):
|
||||||
"""
|
"""
|
||||||
@@ -123,7 +121,8 @@ class AvatarImageView(TemplateView):
|
|||||||
if CACHE_RESPONSE:
|
if CACHE_RESPONSE:
|
||||||
centry = caches["filesystem"].get(uri)
|
centry = caches["filesystem"].get(uri)
|
||||||
if centry:
|
if centry:
|
||||||
# For DEBUG purpose only print('Cached entry for %s' % uri)
|
# For DEBUG purpose only
|
||||||
|
# print('Cached entry for %s' % uri)
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
centry["content"],
|
centry["content"],
|
||||||
content_type=centry["content_type"],
|
content_type=centry["content_type"],
|
||||||
@@ -192,6 +191,12 @@ class AvatarImageView(TemplateView):
|
|||||||
except Exception: # pylint: disable=bare-except
|
except Exception: # pylint: disable=bare-except
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Handle the special case of Bluesky
|
||||||
|
if obj:
|
||||||
|
if obj.bluesky_handle:
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
reverse_lazy("blueskyproxy", args=[kwargs["digest"]])
|
||||||
|
)
|
||||||
# If that mail/openid doesn't exist, or has no photo linked to it
|
# If that mail/openid doesn't exist, or has no photo linked to it
|
||||||
if not obj or not obj.photo or forcedefault:
|
if not obj or not obj.photo or forcedefault:
|
||||||
gravatar_url = (
|
gravatar_url = (
|
||||||
@@ -396,7 +401,7 @@ class GravatarProxyView(View):
|
|||||||
# print("Cached Gravatar response: Default.")
|
# print("Cached Gravatar response: Default.")
|
||||||
return redir_default(default)
|
return redir_default(default)
|
||||||
try:
|
try:
|
||||||
urlopen(gravatar_test_url, timeout=URL_TIMEOUT)
|
urlopen(gravatar_test_url)
|
||||||
except HTTPError as exc:
|
except HTTPError as exc:
|
||||||
if exc.code == 404:
|
if exc.code == 404:
|
||||||
cache.set(gravatar_test_url, "default", 60)
|
cache.set(gravatar_test_url, "default", 60)
|
||||||
@@ -415,7 +420,7 @@ class GravatarProxyView(View):
|
|||||||
print("Cached Gravatar fetch failed with URL error: %s" % gravatar_url)
|
print("Cached Gravatar fetch failed with URL error: %s" % gravatar_url)
|
||||||
return redir_default(default)
|
return redir_default(default)
|
||||||
|
|
||||||
gravatarimagedata = urlopen(gravatar_url, timeout=URL_TIMEOUT)
|
gravatarimagedata = urlopen(gravatar_url)
|
||||||
except HTTPError as exc:
|
except HTTPError as exc:
|
||||||
if exc.code != 404 and exc.code != 503:
|
if exc.code != 404 and exc.code != 503:
|
||||||
print(
|
print(
|
||||||
@@ -450,6 +455,131 @@ class GravatarProxyView(View):
|
|||||||
return redir_default(default)
|
return redir_default(default)
|
||||||
|
|
||||||
|
|
||||||
|
class BlueskyProxyView(View):
|
||||||
|
"""
|
||||||
|
Proxy request to Bluesky and return the image from there
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get(
|
||||||
|
self, request, *args, **kwargs
|
||||||
|
): # pylint: disable=too-many-branches,too-many-statements,too-many-locals,no-self-use,unused-argument,too-many-return-statements
|
||||||
|
"""
|
||||||
|
Override get from parent class
|
||||||
|
"""
|
||||||
|
|
||||||
|
def redir_default(default=None):
|
||||||
|
url = (
|
||||||
|
reverse_lazy("avatar_view", args=[kwargs["digest"]])
|
||||||
|
+ "?s=%i" % size
|
||||||
|
+ "&forcedefault=y"
|
||||||
|
)
|
||||||
|
if default is not None:
|
||||||
|
url += "&default=%s" % default
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
size = get_size(request)
|
||||||
|
print(size)
|
||||||
|
blueskyimagedata = None
|
||||||
|
default = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
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
|
||||||
|
try:
|
||||||
|
identity = ConfirmedEmail.objects.filter(
|
||||||
|
Q(digest=kwargs["digest"]) | Q(digest_sha256=kwargs["digest"])
|
||||||
|
).first()
|
||||||
|
except Exception as exc:
|
||||||
|
print(exc)
|
||||||
|
|
||||||
|
# If no identity is found in the email table, try the openid table
|
||||||
|
if not identity:
|
||||||
|
try:
|
||||||
|
identity = ConfirmedOpenId.objects.filter(
|
||||||
|
Q(digest=kwargs["digest"])
|
||||||
|
| Q(alt_digest1=kwargs["digest"])
|
||||||
|
| Q(alt_digest2=kwargs["digest"])
|
||||||
|
| Q(alt_digest3=kwargs["digest"])
|
||||||
|
).first()
|
||||||
|
except Exception as exc:
|
||||||
|
print(exc)
|
||||||
|
|
||||||
|
# If still no identity is found, redirect to the default
|
||||||
|
if not identity:
|
||||||
|
return redir_default(default)
|
||||||
|
|
||||||
|
bs = Bluesky()
|
||||||
|
bluesky_url = None
|
||||||
|
# Try with the cache first
|
||||||
|
try:
|
||||||
|
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)
|
||||||
|
cache.set(identity.bluesky_handle, bluesky_url)
|
||||||
|
except Exception: # pylint: disable=bare-except
|
||||||
|
return redir_default(default)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if cache.get(bluesky_url) == "err":
|
||||||
|
print("Cached Bluesky fetch failed with URL error: %s" % bluesky_url)
|
||||||
|
return redir_default(default)
|
||||||
|
|
||||||
|
blueskyimagedata = urlopen(bluesky_url)
|
||||||
|
except HTTPError as exc:
|
||||||
|
if exc.code != 404 and exc.code != 503:
|
||||||
|
print(
|
||||||
|
"Bluesky fetch failed with an unexpected %s HTTP error: %s"
|
||||||
|
% (exc.code, 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)
|
||||||
|
cache.set(bluesky_url, "err", 30)
|
||||||
|
return redir_default(default)
|
||||||
|
except SSLError as exc:
|
||||||
|
print("Bluesky fetch failed with SSL error: %s" % exc.reason)
|
||||||
|
cache.set(bluesky_url, "err", 30)
|
||||||
|
return redir_default(default)
|
||||||
|
try:
|
||||||
|
data = BytesIO(blueskyimagedata.read())
|
||||||
|
img = Image.open(data)
|
||||||
|
format = img.format
|
||||||
|
if max(img.size) > size:
|
||||||
|
aspect = img.size[0] / float(img.size[1])
|
||||||
|
if aspect > 1:
|
||||||
|
new_size = (size, int(size / aspect))
|
||||||
|
else:
|
||||||
|
new_size = (int(size * aspect), size)
|
||||||
|
img = img.resize(new_size)
|
||||||
|
data = BytesIO()
|
||||||
|
img.save(data, format=format)
|
||||||
|
|
||||||
|
data.seek(0)
|
||||||
|
response = HttpResponse(
|
||||||
|
data.read(), content_type="image/%s" % file_format(format)
|
||||||
|
)
|
||||||
|
response["Cache-Control"] = "max-age=%i" % CACHE_IMAGES_MAX_AGE
|
||||||
|
return response
|
||||||
|
except ValueError as exc:
|
||||||
|
print("Value error: %s" % exc)
|
||||||
|
return redir_default(default)
|
||||||
|
|
||||||
|
# We shouldn't reach this point... But make sure we do something
|
||||||
|
return redir_default(default)
|
||||||
|
|
||||||
|
|
||||||
class StatsView(TemplateView, JsonResponse):
|
class StatsView(TemplateView, JsonResponse):
|
||||||
"""
|
"""
|
||||||
Return stats
|
Return stats
|
||||||
|
|||||||
Reference in New Issue
Block a user