mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-16 13:08:03 +00:00
Merge branch 'master' of git.linux-kernel.at:oliver/ivatar
This commit is contained in:
@@ -140,3 +140,5 @@ SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
|
||||
|
||||
USE_X_FORWARDED_HOST = True
|
||||
ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS = ['avatars.linux-kernel.at', 'localhost',]
|
||||
|
||||
DEFAULT_AVATAR_SIZE = 80
|
||||
|
||||
@@ -5,6 +5,8 @@ from ssl import SSLError
|
||||
from urllib.request import urlopen, HTTPError, URLError
|
||||
import hashlib
|
||||
|
||||
from .. settings import AVATAR_MAX_SIZE
|
||||
|
||||
URL_TIMEOUT = 5 # in seconds
|
||||
|
||||
|
||||
@@ -15,7 +17,7 @@ def get_photo(email):
|
||||
hash_object = hashlib.new('md5')
|
||||
hash_object.update(email.lower().encode('utf-8'))
|
||||
thumbnail_url = 'https://secure.gravatar.com/avatar/' + \
|
||||
hash_object.hexdigest() + '?s=80&d=404'
|
||||
hash_object.hexdigest() + '?s=%i&d=404' % AVATAR_MAX_SIZE
|
||||
image_url = 'https://secure.gravatar.com/avatar/' + hash_object.hexdigest(
|
||||
) + '?s=512&d=404'
|
||||
|
||||
@@ -44,8 +46,8 @@ def get_photo(email):
|
||||
return {
|
||||
'thumbnail_url': thumbnail_url,
|
||||
'image_url': image_url,
|
||||
'width': 80,
|
||||
'height': 80,
|
||||
'width': AVATAR_MAX_SIZE,
|
||||
'height': AVATAR_MAX_SIZE,
|
||||
'service_url': service_url,
|
||||
'service_name': 'Gravatar'
|
||||
}
|
||||
|
||||
18
ivatar/ivataraccount/migrations/0013_auto_20181203_1421.py
Normal file
18
ivatar/ivataraccount/migrations/0013_auto_20181203_1421.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.1.3 on 2018-12-03 14:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ivataraccount', '0012_auto_20181107_1732'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='userpreference',
|
||||
name='theme',
|
||||
field=models.CharField(choices=[('default', 'Default theme'), ('clime', 'climes theme'), ('green', 'green theme'), ('red', 'red theme')], default='default', max_length=10),
|
||||
),
|
||||
]
|
||||
@@ -70,7 +70,8 @@ class UserPreference(models.Model):
|
||||
THEMES = (
|
||||
('default', 'Default theme'),
|
||||
('clime', 'climes theme'),
|
||||
('falko', 'falkos theme'),
|
||||
('green', 'green theme'),
|
||||
('red', 'red theme'),
|
||||
)
|
||||
|
||||
theme = models.CharField(
|
||||
@@ -135,7 +136,7 @@ class Photo(BaseAccountModel):
|
||||
image_url = gravatar['image_url']
|
||||
|
||||
if service_name == 'Libravatar':
|
||||
image_url = libravatar_url(email_address)
|
||||
image_url = libravatar_url(email_address, size=AVATAR_MAX_SIZE)
|
||||
|
||||
if not image_url:
|
||||
return False # pragma: no cover
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</h3></div>
|
||||
<div class="panel-body">
|
||||
<center>
|
||||
<img src="{{ photo.thumbnail_url }}" alt="{{ photo.service_name }} image">
|
||||
<img src="{{ photo.thumbnail_url }}" style="max-width: 80px; max-height: 80px;" alt="{{ photo.service_name }} image">
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,11 +7,21 @@
|
||||
{% block content %}
|
||||
<h1>{% trans 'Account settings' %}</h1>
|
||||
|
||||
{% if has_password %}
|
||||
<p><a href="{% url 'password_change' %}" class="btn btn-default">{% trans 'Change your password' %}</a></p>
|
||||
{% else %}
|
||||
<p><a href="{% url 'password_set' %}" class="btn btn-default">{% trans 'Set a password' %}</a></p>
|
||||
{% endif %}
|
||||
<p>
|
||||
<form method="post" action="{% url 'user_preference' %}">{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<select name='theme' class="form-control" style="width:200px;">
|
||||
{% for theme in THEMES %}
|
||||
<option {% if user.userpreference.theme == theme.0 %}selected{% endif %} value="{{ theme.0 }}">{{ theme.1 }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<br/>
|
||||
<button type="submit" class="btn btn-default">{% trans 'Save' %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</p>
|
||||
|
||||
<div style="height:40px"></div>
|
||||
|
||||
<!-- <p><a href="{% url 'export' %}" class="btn btn-default">{% trans 'Export your data' %}</a></p> -->
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from django.test import Client
|
||||
from django.urls import reverse
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth import authenticate
|
||||
import hashlib
|
||||
|
||||
from libravatar import libravatar_url
|
||||
|
||||
@@ -1092,6 +1093,34 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
'Why is this not the correct size?')
|
||||
|
||||
def test_avatar_url_inexisting_mail_digest(self): # pylint: disable=invalid-name
|
||||
'''
|
||||
Test fetching avatar via inexisting mail digest
|
||||
'''
|
||||
self.test_upload_image()
|
||||
self.test_confirm_email()
|
||||
urlobj = urlsplit(
|
||||
libravatar_url(
|
||||
email=self.user.confirmedemail_set.first().email,
|
||||
size=80,
|
||||
)
|
||||
)
|
||||
# Simply delete it, then it's digest is 'correct', but
|
||||
# the hash is no longer there
|
||||
addr = self.user.confirmedemail_set.first().email
|
||||
check_hash = hashlib.md5(
|
||||
addr.strip().lower().encode('utf-8')
|
||||
).hexdigest()
|
||||
|
||||
self.user.confirmedemail_set.first().delete()
|
||||
url = '%s?%s' % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url='/gravatarproxy/%s?s=80' % check_hash,
|
||||
msg_prefix='Why does this not redirect to Gravatar?')
|
||||
# Eventually one should check if the data is the same
|
||||
|
||||
def test_avatar_url_inexisting_mail_digest_gravatarproxy_disabled(self): # pylint: disable=invalid-name
|
||||
'''
|
||||
Test fetching avatar via inexisting mail digest
|
||||
'''
|
||||
@@ -1106,7 +1135,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
# Simply delete it, then it digest is 'correct', but
|
||||
# the hash is no longer there
|
||||
self.user.confirmedemail_set.first().delete()
|
||||
url = '%s?%s' % (urlobj.path, urlobj.query)
|
||||
url = '%s?%s&gravatarproxy=n' % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
@@ -1127,6 +1156,25 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
url = '%s?%s' % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url='/gravatarproxy/1b1d0b654430c012e47e350db07c83c5?s=80',
|
||||
msg_prefix='Why does this not redirect to the default img?')
|
||||
# Eventually one should check if the data is the same
|
||||
|
||||
def test_avatar_url_inexisting_mail_digest_w_default_mm_gravatarproxy_disabled(self): # pylint: disable=invalid-name
|
||||
'''
|
||||
Test fetching avatar via inexisting mail digest and default 'mm'
|
||||
'''
|
||||
urlobj = urlsplit(
|
||||
libravatar_url(
|
||||
email='asdf@company.local',
|
||||
size=80,
|
||||
default='mm',
|
||||
)
|
||||
)
|
||||
url = '%s?%s&gravatarproxy=n' % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url='/static/img/mm/80.png',
|
||||
@@ -1145,6 +1193,24 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
url = '%s?%s' % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url='/gravatarproxy/1b1d0b654430c012e47e350db07c83c5?s=80',
|
||||
msg_prefix='Why does this not redirect to the default img?')
|
||||
# Eventually one should check if the data is the same
|
||||
|
||||
def test_avatar_url_inexisting_mail_digest_wo_default_gravatarproxy_disabled(self): # pylint: disable=invalid-name
|
||||
'''
|
||||
Test fetching avatar via inexisting mail digest and default 'mm'
|
||||
'''
|
||||
urlobj = urlsplit(
|
||||
libravatar_url(
|
||||
email='asdf@company.local',
|
||||
size=80,
|
||||
)
|
||||
)
|
||||
url = '%s?%s&gravatarproxy=n' % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url='/static/img/nobody/80.png',
|
||||
@@ -1164,6 +1230,24 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
url = '%s?%s' % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url='/gravatarproxy/fb7a6d7f11365642d44ba66dc57df56f?s=80',
|
||||
msg_prefix='Why does this not redirect to the default img?')
|
||||
|
||||
def test_avatar_url_default_gravatarproxy_disabled(self): # pylint: disable=invalid-name
|
||||
'''
|
||||
Test fetching avatar for not existing mail with default specified
|
||||
'''
|
||||
urlobj = urlsplit(
|
||||
libravatar_url(
|
||||
'xxx@xxx.xxx',
|
||||
size=80,
|
||||
default='/static/img/nobody.png',
|
||||
)
|
||||
)
|
||||
url = '%s?%s&gravatarproxy=n' % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url='/static/img/nobody.png',
|
||||
@@ -1183,6 +1267,26 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
url = '%s?%s' % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=False)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url='/gravatarproxy/fb7a6d7f11365642d44ba66dc57df56f?s=80',
|
||||
fetch_redirect_response=False,
|
||||
msg_prefix='Why does this not redirect to the default img?')
|
||||
|
||||
def test_avatar_url_default_external_gravatarproxy_disabled(self): # pylint: disable=invalid-name
|
||||
'''
|
||||
Test fetching avatar for not existing mail with external default specified
|
||||
'''
|
||||
default = 'http://host.tld/img.png'
|
||||
urlobj = urlsplit(
|
||||
libravatar_url(
|
||||
'xxx@xxx.xxx',
|
||||
size=80,
|
||||
default=default,
|
||||
)
|
||||
)
|
||||
url = '%s?%s&gravatarproxy=n' % (urlobj.path, urlobj.query)
|
||||
response = self.client.get(url, follow=False)
|
||||
self.assertRedirects(
|
||||
response=response,
|
||||
expected_url=default,
|
||||
|
||||
@@ -32,7 +32,7 @@ from openid.consumer import consumer
|
||||
from ipware import get_client_ip
|
||||
|
||||
from libravatar import libravatar_url
|
||||
from ivatar.settings import MAX_NUM_PHOTOS, MAX_PHOTO_SIZE, JPEG_QUALITY
|
||||
from ivatar.settings import MAX_NUM_PHOTOS, MAX_PHOTO_SIZE, JPEG_QUALITY, AVATAR_MAX_SIZE
|
||||
from .gravatar import get_photo as get_gravatar_photo
|
||||
|
||||
from .forms import AddEmailForm, UploadPhotoForm, AddOpenIDForm
|
||||
@@ -117,8 +117,8 @@ class AddEmailView(SuccessMessageMixin, FormView):
|
||||
def form_valid(self, form):
|
||||
if not form.save(self.request):
|
||||
return render(self.request, self.template_name, {'form': form})
|
||||
else:
|
||||
messages.success(self.request, _('Address added successfully'))
|
||||
|
||||
messages.success(self.request, _('Address added successfully'))
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
@@ -310,14 +310,13 @@ class ImportPhotoView(SuccessMessageMixin, TemplateView):
|
||||
if 'email_id' in kwargs:
|
||||
try:
|
||||
addr = ConfirmedEmail.objects.get(pk=kwargs['email_id']).email
|
||||
except ConfirmedEmail.ObjectDoesNotExist:
|
||||
except ConfirmedEmail.ObjectDoesNotExist: # pylint: disable=no-member
|
||||
messages.error(
|
||||
self.request,
|
||||
_('Address does not exist'))
|
||||
return context
|
||||
|
||||
if 'email_addr' in kwargs:
|
||||
addr = kwargs['email_addr']
|
||||
addr = kwargs.get('email_addr', None)
|
||||
|
||||
if addr:
|
||||
gravatar = get_gravatar_photo(addr)
|
||||
@@ -327,6 +326,7 @@ class ImportPhotoView(SuccessMessageMixin, TemplateView):
|
||||
libravatar_service_url = libravatar_url(
|
||||
email=addr,
|
||||
default=404,
|
||||
size=AVATAR_MAX_SIZE,
|
||||
)
|
||||
if libravatar_service_url:
|
||||
try:
|
||||
@@ -350,18 +350,10 @@ class ImportPhotoView(SuccessMessageMixin, TemplateView):
|
||||
Handle post to photo import
|
||||
'''
|
||||
|
||||
addr = None
|
||||
email_id = None
|
||||
imported = None
|
||||
|
||||
if 'email_id' in kwargs:
|
||||
email_id = kwargs['email_id']
|
||||
if 'email_id' in request.POST:
|
||||
email_id = request.POST['email_id']
|
||||
if 'email_addr' in kwargs:
|
||||
addr = kwargs['email_addr']
|
||||
if 'email_addr' in request.POST:
|
||||
addr = request.POST['email_addr']
|
||||
email_id = kwargs.get('email_id', request.POST.get('email_id', None))
|
||||
addr = kwargs.get('emali_addr', request.POST.get('email_addr', None))
|
||||
|
||||
if email_id:
|
||||
email = ConfirmedEmail.objects.filter(
|
||||
@@ -436,7 +428,7 @@ class DeletePhotoView(SuccessMessageMixin, View):
|
||||
photo = self.model.objects.get( # pylint: disable=no-member
|
||||
pk=kwargs['pk'], user=request.user)
|
||||
photo.delete()
|
||||
except (self.model.DoesNotExist, ProtectedError):
|
||||
except (self.model.DoesNotExist, ProtectedError): # pylint: disable=no-member
|
||||
messages.error(
|
||||
request,
|
||||
_('No such image or no permission to delete it'))
|
||||
@@ -519,7 +511,7 @@ class RemoveUnconfirmedOpenIDView(View):
|
||||
user=request.user, id=kwargs['openid_id'])
|
||||
openid.delete()
|
||||
messages.success(request, _('ID removed'))
|
||||
except self.model.DoesNotExist: # pragma: no cover # pylint: disable=no-member
|
||||
except self.model.DoesNotExist: # pragma: no cover pylint: disable=no-member
|
||||
messages.error(request, _('ID does not exist'))
|
||||
return HttpResponseRedirect(reverse_lazy('profile'))
|
||||
|
||||
@@ -543,9 +535,9 @@ class RemoveConfirmedOpenIDView(View):
|
||||
user_id=request.user.id,
|
||||
claimed_id=openid.openid)
|
||||
openidobj.delete()
|
||||
except:
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
# Why it is not there?
|
||||
pass
|
||||
print('How did we get here: %s' % exc)
|
||||
openid.delete()
|
||||
messages.success(request, _('ID removed'))
|
||||
except self.model.DoesNotExist: # pylint: disable=no-member
|
||||
@@ -567,7 +559,7 @@ class RedirectOpenIDView(View):
|
||||
try:
|
||||
unconfirmed = self.model.objects.get( # pylint: disable=no-member
|
||||
user=request.user, id=kwargs['openid_id'])
|
||||
except self.model.DoesNotExist: # pragma: no cover # pylint: disable=no-member
|
||||
except self.model.DoesNotExist: # pragma: no cover pylint: disable=no-member
|
||||
messages.error(request, _('ID does not exist'))
|
||||
return HttpResponseRedirect(reverse_lazy('profile'))
|
||||
|
||||
@@ -621,10 +613,12 @@ class ConfirmOpenIDView(View): # pragma: no cover
|
||||
self.request,
|
||||
_('Confirmation failed: "') + str(info.message) + '"')
|
||||
return HttpResponseRedirect(reverse_lazy('profile'))
|
||||
elif info.status == consumer.CANCEL:
|
||||
|
||||
if info.status == consumer.CANCEL:
|
||||
messages.error(self.request, _('Cancelled by user'))
|
||||
return HttpResponseRedirect(reverse_lazy('profile'))
|
||||
elif info.status != consumer.SUCCESS:
|
||||
|
||||
if info.status != consumer.SUCCESS:
|
||||
messages.error(self.request, _('Unknown verification error'))
|
||||
return HttpResponseRedirect(reverse_lazy('profile'))
|
||||
|
||||
@@ -705,7 +699,7 @@ class CropPhotoView(TemplateView):
|
||||
if 'email' in request.POST:
|
||||
try:
|
||||
email = ConfirmedEmail.objects.get(email=request.POST['email'])
|
||||
except ConfirmedEmail.DoesNotExist:
|
||||
except ConfirmedEmail.DoesNotExist: # pylint: disable=no-member
|
||||
pass # Ignore automatic assignment
|
||||
|
||||
if 'openid' in request.POST:
|
||||
@@ -728,8 +722,21 @@ class UserPreferenceView(FormView, UpdateView):
|
||||
form_class = UpdatePreferenceForm
|
||||
success_url = reverse_lazy('user_preference')
|
||||
|
||||
def post(self, request, *args, **kwargs): # pylint: disable=unused-argument
|
||||
self.request.user.userpreference.theme = request.POST['theme']
|
||||
self.request.user.userpreference.save()
|
||||
return HttpResponseRedirect(reverse_lazy('user_preference'))
|
||||
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return render(self.request, self.template_name, {
|
||||
'THEMES': UserPreference.THEMES,
|
||||
})
|
||||
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return self.request.user.userpreference
|
||||
(obj, created) = UserPreference.objects.get_or_create(user=self.request.user) # pylint: disable=no-member,unused-variable
|
||||
return obj
|
||||
|
||||
|
||||
@method_decorator(login_required, name='dispatch')
|
||||
|
||||
338
ivatar/static/css/green.css
Normal file
338
ivatar/static/css/green.css
Normal file
@@ -0,0 +1,338 @@
|
||||
/// Example theme using tortin with bg-hero:@lab-green;
|
||||
body {
|
||||
font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif;
|
||||
color: #525252;
|
||||
}
|
||||
.btn {
|
||||
border-bottom-width: 3px;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
text-transform: uppercase;
|
||||
background: #3aa850;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
-webkit-transition: all 0.3s;
|
||||
-moz-transition: all 0.3s;
|
||||
-ms-transition: all 0.3s;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.btn.btn-default {
|
||||
color: #52c368;
|
||||
border-color: #52c368;
|
||||
background: none;
|
||||
}
|
||||
.btn.btn-primary {
|
||||
border-color: #266f35;
|
||||
}
|
||||
.btn:hover,
|
||||
.btn:active,
|
||||
.btn:focus {
|
||||
background: none;
|
||||
border-color: #2d823e;
|
||||
color: #2d823e;
|
||||
}
|
||||
.btn:hover:after,
|
||||
.btn:active:after,
|
||||
.btn:focus:after {
|
||||
top: 50%;
|
||||
}
|
||||
.btn:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
width: 150%;
|
||||
height: 200%;
|
||||
top: -190%;
|
||||
left: 50%;
|
||||
background: #78d089;
|
||||
-webkit-transform: translateX(-50%) translateY(-50%) skew(0, 5deg);
|
||||
-moz-transform: translateX(-50%) translateY(-50%) skew(0, 5deg);
|
||||
-ms-transform: translateX(-50%) translateY(-50%) skew(0, 5deg);
|
||||
transform: translateX(-50%) translateY(-50%) skew(0, 5deg);
|
||||
-webkit-transition: all 0.5s ease-out;
|
||||
-moz-transition: all 0.5s ease-out;
|
||||
-ms-transition: all 0.5s ease-out;
|
||||
transition: all 0.5s ease-out;
|
||||
}
|
||||
.btn.btn-block:after {
|
||||
height: 250%;
|
||||
width: 200%;
|
||||
-webkit-transform: translateX(-50%) translateY(-50%) skew(0, 2deg);
|
||||
-moz-transform: translateX(-50%) translateY(-50%) skew(0, 2deg);
|
||||
-ms-transform: translateX(-50%) translateY(-50%) skew(0, 2deg);
|
||||
transform: translateX(-50%) translateY(-50%) skew(0, 2deg);
|
||||
}
|
||||
.hero {
|
||||
background-color: #3aa850;
|
||||
color: #fff;
|
||||
padding: 90px 0 40px;
|
||||
}
|
||||
.hero h1 {
|
||||
font-weight: 600;
|
||||
font-size: 6em;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.hero h2 {
|
||||
font-weight: 200;
|
||||
font-size: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.hero small {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.hero .btn {
|
||||
display: inline-block;
|
||||
}
|
||||
.hero .btn.btn-default {
|
||||
color: #7fd390;
|
||||
border-color: #7fd390;
|
||||
background: none;
|
||||
}
|
||||
.hero .btn.btn-primary {
|
||||
border-color: #fff;
|
||||
}
|
||||
.hero .btn:hover,
|
||||
.hero .btn:active,
|
||||
.hero .btn:focus {
|
||||
border-color: #fff;
|
||||
color: #205c2c;
|
||||
}
|
||||
.hero .btn:after {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.hero .container {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
.social {
|
||||
background-color: #3aa850;
|
||||
padding: 30px 0 140px;
|
||||
}
|
||||
.social ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.social ul li {
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
width: 100px;
|
||||
}
|
||||
.clipper,
|
||||
.clipper-footer {
|
||||
background-color: #fff;
|
||||
height: 110px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
top: -40px;
|
||||
-webkit-transform: skew(0, 2deg);
|
||||
-moz-transform: skew(0, 2deg);
|
||||
-ms-transform: skew(0, 2deg);
|
||||
transform: skew(0, 2deg);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
.clipper-footer {
|
||||
top: 0;
|
||||
}
|
||||
section.content {
|
||||
position: relative;
|
||||
top: -100px;
|
||||
margin-bottom: -100px;
|
||||
z-index: 10;
|
||||
}
|
||||
section.content h1,
|
||||
section.content h2,
|
||||
section.content h3,
|
||||
section.content h4,
|
||||
section.content h5,
|
||||
section.content h6 {
|
||||
color: #2d823e;
|
||||
}
|
||||
section.content h2 {
|
||||
font-weight: 200;
|
||||
font-size: 40px;
|
||||
}
|
||||
section.content section {
|
||||
margin-bottom: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
section.content .container > hr {
|
||||
-webkit-transform: skew(0, 2deg);
|
||||
-moz-transform: skew(0, 2deg);
|
||||
-ms-transform: skew(0, 2deg);
|
||||
transform: skew(0, 2deg);
|
||||
margin-top: 80px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
footer {
|
||||
background-color: #dddddd;
|
||||
color: #888888;
|
||||
padding: 100px 0 40px;
|
||||
margin-top: -40px;
|
||||
}
|
||||
footer .pull-left {
|
||||
margin-right: 20px;
|
||||
}
|
||||
footer .logo {
|
||||
float: left;
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
margin-top: -8px;
|
||||
}
|
||||
footer .logo .circle {
|
||||
stroke: #888888;
|
||||
stroke-width: 7;
|
||||
fill: none;
|
||||
}
|
||||
footer .logo .polygon {
|
||||
fill: #888888;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.hero {
|
||||
padding: 50px 0 30px;
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: 4em;
|
||||
}
|
||||
.social {
|
||||
padding: 30px 0 100px;
|
||||
}
|
||||
.btn {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
section.content section {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
}
|
||||
.color {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
.color.blue {
|
||||
background-color: #36b7d7;
|
||||
}
|
||||
.color.green {
|
||||
background-color: #3aa850;
|
||||
}
|
||||
.color.red {
|
||||
background-color: #f7645e;
|
||||
}
|
||||
.color.black {
|
||||
background-color: #525252;
|
||||
}
|
||||
.navbar-tortin {
|
||||
border: 0;
|
||||
background-color: #3aa850;
|
||||
color: #FFFFFF;
|
||||
border-radius: 0;
|
||||
}
|
||||
.form-control {
|
||||
border-bottom-width: 3px;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
-webkit-transition: all 0.3s;
|
||||
-moz-transition: all 0.3s;
|
||||
-ms-transition: all 0.3s;
|
||||
transition: all 0.3s;
|
||||
border-color: #52c368;
|
||||
background: none;
|
||||
}
|
||||
.form-control:focus {
|
||||
border-color: #2d823e;
|
||||
box-shadow: none;
|
||||
}
|
||||
.navbar-tortin .navbar-brand,
|
||||
.navbar-tortin .navbar-text,
|
||||
.navbar-tortin .navbar-nav > li > a,
|
||||
.navbar-tortin .navbar-link,
|
||||
.navbar-tortin .btn-link {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.navbar-tortin .navbar-nav > .active > a,
|
||||
.navbar-tortin .navbar-nav > .active > a:focus,
|
||||
.navbar-tortin .navbar-nav > .active > a:hover,
|
||||
.navbar-tortin .navbar-nav > li > a:focus,
|
||||
.navbar-tortin .navbar-nav > li > a:hover,
|
||||
.navbar-tortin .navbar-link:hover,
|
||||
.navbar-tortin .btn-link:focus,
|
||||
.navbar-tortin .btn-link:hover,
|
||||
.navbar-tortin .navbar-nav > .open > a,
|
||||
.navbar-tortin .navbar-nav > .open > a:focus,
|
||||
.navbar-tortin .navbar-nav > .open > a:hover {
|
||||
background-color: #2d823e;
|
||||
}
|
||||
.navbar-tortin .navbar-toggle {
|
||||
border-color: #FFFFFF;
|
||||
}
|
||||
.navbar-tortin .navbar-toggle:hover {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
.navbar-tortin .navbar-toggle .icon-bar {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
.navbar-tortin .navbar-toggle:hover .icon-bar {
|
||||
background-color: #3aa850;
|
||||
}
|
||||
.navbar-tortin .navbar-collapse,
|
||||
.navbar-tortin .navbar-form {
|
||||
border: 0;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.navbar-tortin .navbar-nav .open .dropdown-menu > li > a {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.navbar-tortin .navbar-nav .open .dropdown-menu > li > a:hover {
|
||||
background-color: #2d823e;
|
||||
}
|
||||
.navbar-tortin .navbar-nav .open .dropdown-menu > .active > a,
|
||||
.navbar-tortin .navbar-nav .open .dropdown-menu > .active > a:focus,
|
||||
.navbar-tortin .navbar-nav .open .dropdown-menu > .active > a:hover {
|
||||
background-color: #2d823e;
|
||||
}
|
||||
}
|
||||
.panel-tortin {
|
||||
border-color: #3aa850;
|
||||
border-bottom-width: 3px;
|
||||
}
|
||||
.panel-tortin > .panel-heading {
|
||||
color: #fff;
|
||||
background-color: #3aa850;
|
||||
border-color: #3aa850;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
}
|
||||
.panel-tortin > .panel-heading + .panel-collapse > .panel-body {
|
||||
border-top-color: #3aa850;
|
||||
}
|
||||
.panel-tortin > .panel-heading .badge {
|
||||
color: #3aa850;
|
||||
background-color: #fff;
|
||||
}
|
||||
.panel-tortin > .panel-footer + .panel-collapse > .panel-body {
|
||||
border-bottom-color: #3aa850;
|
||||
}
|
||||
.alert.alert-danger {
|
||||
background-color: #FFFFFF;
|
||||
color: #f7645e;
|
||||
border-color: #f7645e;
|
||||
border-bottom-width: 3px;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.input-group-addon {
|
||||
border-bottom-width: 3px;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border-color: #52c368;
|
||||
background: none;
|
||||
width: auto;
|
||||
height: 36px;
|
||||
}
|
||||
337
ivatar/static/css/red.css
Normal file
337
ivatar/static/css/red.css
Normal file
@@ -0,0 +1,337 @@
|
||||
body {
|
||||
font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif;
|
||||
color: #525252;
|
||||
}
|
||||
.btn {
|
||||
border-bottom-width: 3px;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
text-transform: uppercase;
|
||||
background: #f7645e;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
-webkit-transition: all 0.3s;
|
||||
-moz-transition: all 0.3s;
|
||||
-ms-transition: all 0.3s;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.btn.btn-default {
|
||||
color: #f9938f;
|
||||
border-color: #f9938f;
|
||||
background: none;
|
||||
}
|
||||
.btn.btn-primary {
|
||||
border-color: #f31e15;
|
||||
}
|
||||
.btn:hover,
|
||||
.btn:active,
|
||||
.btn:focus {
|
||||
background: none;
|
||||
border-color: #f5352d;
|
||||
color: #f5352d;
|
||||
}
|
||||
.btn:hover:after,
|
||||
.btn:active:after,
|
||||
.btn:focus:after {
|
||||
top: 50%;
|
||||
}
|
||||
.btn:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
width: 150%;
|
||||
height: 200%;
|
||||
top: -190%;
|
||||
left: 50%;
|
||||
background: #fcc2bf;
|
||||
-webkit-transform: translateX(-50%) translateY(-50%) skew(0, 5deg);
|
||||
-moz-transform: translateX(-50%) translateY(-50%) skew(0, 5deg);
|
||||
-ms-transform: translateX(-50%) translateY(-50%) skew(0, 5deg);
|
||||
transform: translateX(-50%) translateY(-50%) skew(0, 5deg);
|
||||
-webkit-transition: all 0.5s ease-out;
|
||||
-moz-transition: all 0.5s ease-out;
|
||||
-ms-transition: all 0.5s ease-out;
|
||||
transition: all 0.5s ease-out;
|
||||
}
|
||||
.btn.btn-block:after {
|
||||
height: 250%;
|
||||
width: 200%;
|
||||
-webkit-transform: translateX(-50%) translateY(-50%) skew(0, 2deg);
|
||||
-moz-transform: translateX(-50%) translateY(-50%) skew(0, 2deg);
|
||||
-ms-transform: translateX(-50%) translateY(-50%) skew(0, 2deg);
|
||||
transform: translateX(-50%) translateY(-50%) skew(0, 2deg);
|
||||
}
|
||||
.hero {
|
||||
background-color: #f7645e;
|
||||
color: #fff;
|
||||
padding: 90px 0 40px;
|
||||
}
|
||||
.hero h1 {
|
||||
font-weight: 600;
|
||||
font-size: 6em;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.hero h2 {
|
||||
font-weight: 200;
|
||||
font-size: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.hero small {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.hero .btn {
|
||||
display: inline-block;
|
||||
}
|
||||
.hero .btn.btn-default {
|
||||
color: #fccbc9;
|
||||
border-color: #fccbc9;
|
||||
background: none;
|
||||
}
|
||||
.hero .btn.btn-primary {
|
||||
border-color: #fff;
|
||||
}
|
||||
.hero .btn:hover,
|
||||
.hero .btn:active,
|
||||
.hero .btn:focus {
|
||||
border-color: #fff;
|
||||
color: #e4140b;
|
||||
}
|
||||
.hero .btn:after {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.hero .container {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
.social {
|
||||
background-color: #f7645e;
|
||||
padding: 30px 0 140px;
|
||||
}
|
||||
.social ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.social ul li {
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
width: 100px;
|
||||
}
|
||||
.clipper,
|
||||
.clipper-footer {
|
||||
background-color: #fff;
|
||||
height: 110px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
top: -40px;
|
||||
-webkit-transform: skew(0, 2deg);
|
||||
-moz-transform: skew(0, 2deg);
|
||||
-ms-transform: skew(0, 2deg);
|
||||
transform: skew(0, 2deg);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
.clipper-footer {
|
||||
top: 0;
|
||||
}
|
||||
section.content {
|
||||
position: relative;
|
||||
top: -100px;
|
||||
margin-bottom: -100px;
|
||||
z-index: 10;
|
||||
}
|
||||
section.content h1,
|
||||
section.content h2,
|
||||
section.content h3,
|
||||
section.content h4,
|
||||
section.content h5,
|
||||
section.content h6 {
|
||||
color: #f5352d;
|
||||
}
|
||||
section.content h2 {
|
||||
font-weight: 200;
|
||||
font-size: 40px;
|
||||
}
|
||||
section.content section {
|
||||
margin-bottom: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
section.content .container > hr {
|
||||
-webkit-transform: skew(0, 2deg);
|
||||
-moz-transform: skew(0, 2deg);
|
||||
-ms-transform: skew(0, 2deg);
|
||||
transform: skew(0, 2deg);
|
||||
margin-top: 80px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
footer {
|
||||
background-color: #dddddd;
|
||||
color: #888888;
|
||||
padding: 100px 0 40px;
|
||||
margin-top: -40px;
|
||||
}
|
||||
footer .pull-left {
|
||||
margin-right: 20px;
|
||||
}
|
||||
footer .logo {
|
||||
float: left;
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
margin-top: -8px;
|
||||
}
|
||||
footer .logo .circle {
|
||||
stroke: #888888;
|
||||
stroke-width: 7;
|
||||
fill: none;
|
||||
}
|
||||
footer .logo .polygon {
|
||||
fill: #888888;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.hero {
|
||||
padding: 50px 0 30px;
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: 4em;
|
||||
}
|
||||
.social {
|
||||
padding: 30px 0 100px;
|
||||
}
|
||||
.btn {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
section.content section {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
}
|
||||
.color {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
.color.blue {
|
||||
background-color: #36b7d7;
|
||||
}
|
||||
.color.green {
|
||||
background-color: #3aa850;
|
||||
}
|
||||
.color.red {
|
||||
background-color: #f7645e;
|
||||
}
|
||||
.color.black {
|
||||
background-color: #525252;
|
||||
}
|
||||
.navbar-tortin {
|
||||
border: 0;
|
||||
background-color: #f7645e;
|
||||
color: #FFFFFF;
|
||||
border-radius: 0;
|
||||
}
|
||||
.form-control {
|
||||
border-bottom-width: 3px;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
-webkit-transition: all 0.3s;
|
||||
-moz-transition: all 0.3s;
|
||||
-ms-transition: all 0.3s;
|
||||
transition: all 0.3s;
|
||||
border-color: #f9938f;
|
||||
background: none;
|
||||
}
|
||||
.form-control:focus {
|
||||
border-color: #f5352d;
|
||||
box-shadow: none;
|
||||
}
|
||||
.navbar-tortin .navbar-brand,
|
||||
.navbar-tortin .navbar-text,
|
||||
.navbar-tortin .navbar-nav > li > a,
|
||||
.navbar-tortin .navbar-link,
|
||||
.navbar-tortin .btn-link {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.navbar-tortin .navbar-nav > .active > a,
|
||||
.navbar-tortin .navbar-nav > .active > a:focus,
|
||||
.navbar-tortin .navbar-nav > .active > a:hover,
|
||||
.navbar-tortin .navbar-nav > li > a:focus,
|
||||
.navbar-tortin .navbar-nav > li > a:hover,
|
||||
.navbar-tortin .navbar-link:hover,
|
||||
.navbar-tortin .btn-link:focus,
|
||||
.navbar-tortin .btn-link:hover,
|
||||
.navbar-tortin .navbar-nav > .open > a,
|
||||
.navbar-tortin .navbar-nav > .open > a:focus,
|
||||
.navbar-tortin .navbar-nav > .open > a:hover {
|
||||
background-color: #f5352d;
|
||||
}
|
||||
.navbar-tortin .navbar-toggle {
|
||||
border-color: #FFFFFF;
|
||||
}
|
||||
.navbar-tortin .navbar-toggle:hover {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
.navbar-tortin .navbar-toggle .icon-bar {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
.navbar-tortin .navbar-toggle:hover .icon-bar {
|
||||
background-color: #f7645e;
|
||||
}
|
||||
.navbar-tortin .navbar-collapse,
|
||||
.navbar-tortin .navbar-form {
|
||||
border: 0;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.navbar-tortin .navbar-nav .open .dropdown-menu > li > a {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.navbar-tortin .navbar-nav .open .dropdown-menu > li > a:hover {
|
||||
background-color: #f5352d;
|
||||
}
|
||||
.navbar-tortin .navbar-nav .open .dropdown-menu > .active > a,
|
||||
.navbar-tortin .navbar-nav .open .dropdown-menu > .active > a:focus,
|
||||
.navbar-tortin .navbar-nav .open .dropdown-menu > .active > a:hover {
|
||||
background-color: #f5352d;
|
||||
}
|
||||
}
|
||||
.panel-tortin {
|
||||
border-color: #f7645e;
|
||||
border-bottom-width: 3px;
|
||||
}
|
||||
.panel-tortin > .panel-heading {
|
||||
color: #fff;
|
||||
background-color: #f7645e;
|
||||
border-color: #f7645e;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
}
|
||||
.panel-tortin > .panel-heading + .panel-collapse > .panel-body {
|
||||
border-top-color: #f7645e;
|
||||
}
|
||||
.panel-tortin > .panel-heading .badge {
|
||||
color: #f7645e;
|
||||
background-color: #fff;
|
||||
}
|
||||
.panel-tortin > .panel-footer + .panel-collapse > .panel-body {
|
||||
border-bottom-color: #f7645e;
|
||||
}
|
||||
.alert.alert-danger {
|
||||
background-color: #FFFFFF;
|
||||
color: #f7645e;
|
||||
border-color: #f7645e;
|
||||
border-bottom-width: 3px;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.input-group-addon {
|
||||
border-bottom-width: 3px;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border-color: #f9938f;
|
||||
background: none;
|
||||
width: auto;
|
||||
height: 36px;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -258,6 +258,17 @@ background-color:@bg-hero;
|
||||
.navbar-tortin .navbar-collapse, .navbar-tortin .navbar-form {
|
||||
border:0;
|
||||
}
|
||||
.dropdown-menu {
|
||||
background-color:@bg-hero;
|
||||
border:1px solid darken(@bg-hero, 10%);
|
||||
}
|
||||
.dropdown-menu>li>a {
|
||||
color:#FFFFFF;
|
||||
}
|
||||
.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover {
|
||||
background-color:darken(@bg-hero, 10%);
|
||||
color:#FFFFFF;
|
||||
}
|
||||
@media (max-width:767px) {
|
||||
.navbar-tortin .navbar-nav .open .dropdown-menu > li > a {
|
||||
color:#FFFFFF
|
||||
@@ -310,4 +321,4 @@ border-color: lighten(@bg-hero, 10%);
|
||||
background: none;
|
||||
width:auto;
|
||||
height:36px;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ from django.conf.urls import url
|
||||
from django.conf.urls.static import static
|
||||
from django.views.generic import TemplateView, RedirectView
|
||||
from ivatar import settings
|
||||
from . views import AvatarImageView
|
||||
from . views import AvatarImageView, GravatarProxyView
|
||||
|
||||
urlpatterns = [ # pylint: disable=invalid-name
|
||||
path('admin/', admin.site.urls),
|
||||
@@ -21,12 +21,15 @@ urlpatterns = [ # pylint: disable=invalid-name
|
||||
r'avatar/(?P<digest>\w{32})',
|
||||
AvatarImageView.as_view(), name='avatar_view'),
|
||||
url(
|
||||
r'avatar/(?P<digest>\w)',
|
||||
r'avatar/(?P<digest>\w*)',
|
||||
TemplateView.as_view(
|
||||
template_name='error.html',
|
||||
extra_context={
|
||||
'errormessage': 'Incorrect digest length',
|
||||
})),
|
||||
url(
|
||||
r'gravatarproxy/(?P<digest>\w*)',
|
||||
GravatarProxyView.as_view(), name='gravatarproxy'),
|
||||
url('description/', TemplateView.as_view(template_name='description.html'), name='description'),
|
||||
# The following two are TODO TODO TODO TODO TODO
|
||||
url('run_your_own/', TemplateView.as_view(template_name='run_your_own.html'), name='run_your_own'),
|
||||
|
||||
135
ivatar/views.py
135
ivatar/views.py
@@ -5,17 +5,45 @@ from io import BytesIO
|
||||
from os import path
|
||||
import hashlib
|
||||
from PIL import Image
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.base import TemplateView, View
|
||||
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from urllib.request import urlopen
|
||||
from urllib.error import HTTPError, URLError
|
||||
from ssl import SSLError
|
||||
|
||||
from monsterid.id import build_monster as BuildMonster
|
||||
from pydenticon import Generator as IdenticonGenerator
|
||||
|
||||
from ivatar.settings import AVATAR_MAX_SIZE, JPEG_QUALITY
|
||||
from ivatar.settings import AVATAR_MAX_SIZE, JPEG_QUALITY, DEFAULT_AVATAR_SIZE
|
||||
from . ivataraccount.models import ConfirmedEmail, ConfirmedOpenId
|
||||
from . ivataraccount.models import pil_format
|
||||
from . ivataraccount.models import pil_format, file_format
|
||||
|
||||
URL_TIMEOUT = 5 # in seconds
|
||||
|
||||
|
||||
def get_size(request, size=DEFAULT_AVATAR_SIZE):
|
||||
sizetemp = None
|
||||
if 's' in request.GET:
|
||||
sizetemp = request.GET['s']
|
||||
if 'size' in request.GET:
|
||||
sizetemp = request.GET['size']
|
||||
if sizetemp:
|
||||
if sizetemp != '' and sizetemp is not None and sizetemp != '0':
|
||||
try:
|
||||
if int(sizetemp) > 0:
|
||||
size = int(sizetemp)
|
||||
# Should we receive something we cannot convert to int, leave
|
||||
# the user with the default value of 80
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if size > int(AVATAR_MAX_SIZE):
|
||||
size = int(AVATAR_MAX_SIZE)
|
||||
return size
|
||||
|
||||
|
||||
class AvatarImageView(TemplateView):
|
||||
@@ -29,11 +57,13 @@ class AvatarImageView(TemplateView):
|
||||
Override get from parent class
|
||||
'''
|
||||
model = ConfirmedEmail
|
||||
size = 80
|
||||
size = get_size(request)
|
||||
imgformat = 'png'
|
||||
obj = None
|
||||
default = None
|
||||
forcedefault = False
|
||||
gravatarredirect = False
|
||||
gravatarproxy = True
|
||||
|
||||
if 'd' in request.GET:
|
||||
default = request.GET['d']
|
||||
@@ -47,34 +77,13 @@ class AvatarImageView(TemplateView):
|
||||
if request.GET['forcedefault'] == 'y':
|
||||
forcedefault = True
|
||||
|
||||
sizetemp = None
|
||||
if 's' in request.GET:
|
||||
sizetemp = request.GET['s']
|
||||
if 'size' in request.GET:
|
||||
sizetemp = request.GET['size']
|
||||
if sizetemp:
|
||||
if sizetemp != '' and sizetemp is not None and sizetemp != '0':
|
||||
try:
|
||||
if int(sizetemp) > 0:
|
||||
size = int(sizetemp)
|
||||
# Should we receive something we cannot convert to int, leave
|
||||
# the user with the default value of 80
|
||||
except ValueError:
|
||||
pass
|
||||
if 'gravatarredirect' in request.GET:
|
||||
if request.GET['gravatarredirect'] == 'y':
|
||||
gravatarredirect = True
|
||||
|
||||
if size > int(AVATAR_MAX_SIZE):
|
||||
size = int(AVATAR_MAX_SIZE)
|
||||
if len(kwargs['digest']) == 32:
|
||||
# Fetch by digest from mail
|
||||
pass
|
||||
elif len(kwargs['digest']) == 64:
|
||||
if ConfirmedOpenId.objects.filter( # pylint: disable=no-member
|
||||
digest=kwargs['digest']).count():
|
||||
# Fetch by digest from OpenID
|
||||
model = ConfirmedOpenId
|
||||
else: # pragma: no cover
|
||||
# We should actually never ever reach this code...
|
||||
raise Exception('Digest provided is wrong: %s' % kwargs['digest'])
|
||||
if 'gravatarproxy' in request.GET:
|
||||
if request.GET['gravatarproxy'] == 'n':
|
||||
gravatarproxy = False
|
||||
|
||||
try:
|
||||
obj = model.objects.get(digest=kwargs['digest'])
|
||||
@@ -86,6 +95,20 @@ class AvatarImageView(TemplateView):
|
||||
|
||||
# If that mail/openid doesn't exist, or has no photo linked to it
|
||||
if not obj or not obj.photo or forcedefault:
|
||||
gravatar_url = 'https://secure.gravatar.com/avatar/' + kwargs['digest'] \
|
||||
+ '?s=%i' % size
|
||||
|
||||
# If we have redirection to Gravatar enabled, this overrides all
|
||||
# default= settings, except forcedefault!
|
||||
if gravatarredirect and not forcedefault:
|
||||
return HttpResponseRedirect(gravatar_url)
|
||||
|
||||
# Request to proxy Gravatar image - only if not forcedefault
|
||||
if gravatarproxy and not forcedefault:
|
||||
url = reverse_lazy('gravatarproxy', args=[kwargs['digest']]) \
|
||||
+ '?s=%i' % size
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Return the default URL, as specified, or 404 Not Found, if default=404
|
||||
if default:
|
||||
if str(default) == str(404):
|
||||
@@ -158,3 +181,53 @@ class AvatarImageView(TemplateView):
|
||||
return HttpResponse(
|
||||
data,
|
||||
content_type='image/%s' % imgformat)
|
||||
|
||||
class GravatarProxyView(View):
|
||||
'''
|
||||
Proxy request to Gravatar and return the image from there
|
||||
'''
|
||||
# TODO: Do cache images!! Memcached?
|
||||
|
||||
def get(self, request, *args, **kwargs): # pylint: disable=too-many-branches,too-many-statements,too-many-locals
|
||||
'''
|
||||
Override get from parent class
|
||||
'''
|
||||
size = get_size(request)
|
||||
gravatarimagedata = None
|
||||
|
||||
gravatar_url = 'https://secure.gravatar.com/avatar/' + kwargs['digest'] \
|
||||
+ '?s=%i' % size
|
||||
|
||||
try:
|
||||
gravatarimagedata = urlopen(gravatar_url, timeout=URL_TIMEOUT)
|
||||
except HTTPError as exc:
|
||||
if exc.code != 404 and exc.code != 503:
|
||||
print(
|
||||
'Gravatar fetch failed with an unexpected %s HTTP error' %
|
||||
exc.code)
|
||||
pass
|
||||
except URLError as exc:
|
||||
print(
|
||||
'Gravatar fetch failed with URL error: %s' %
|
||||
exc.reason)
|
||||
pass
|
||||
except SSLError as exc:
|
||||
print(
|
||||
'Gravatar fetch failed with SSL error: %s' %
|
||||
exc.reason)
|
||||
pass
|
||||
try:
|
||||
data = BytesIO(gravatarimagedata.read())
|
||||
img = Image.open(data)
|
||||
data.seek(0)
|
||||
return HttpResponse(
|
||||
data.read(),
|
||||
content_type='image/%s' % file_format(img.format))
|
||||
|
||||
except ValueError as exc:
|
||||
print('Value error: %s' % exc)
|
||||
pass
|
||||
|
||||
# TODO: In case anything strange happens, we need to redirect to the default
|
||||
url = reverse_lazy('avatar_view', args=[kwargs['digest']]) + '?s=%i' % size + '&forcedefault=y'
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
@@ -11,24 +11,22 @@
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
|
||||
{% if request.user.is_authenticated %}
|
||||
<li><a href="{% url 'profile' %}"><i class="fa fa-image" aria-hidden="true"></i> {% trans 'Profile' %}</a></li>
|
||||
<!--
|
||||
<li><a href="{% url 'user_preference' %}"><i class="fa fa-cog" aria-hidden="true"></i> {% trans 'Preferences' %}</a></li>
|
||||
-->
|
||||
<li><a href="{% url 'import_photo' %}"><i class="fa fa-envelope-square" aria-hidden="true"></i> {% trans 'Import photo via mail address' %}</a></li>
|
||||
<li><a href="{% url 'upload_export' %}"><i class="fa fa-file-archive-o" aria-hidden="true"></i> {% trans 'Import libravatar XML export' %}</a></li>
|
||||
<li><a href="{% url 'password_change' %}"><i class="fa fa-key" aria-hidden="true"></i> {% trans 'Change password' %}</a></li>
|
||||
<li><a href="{% url 'password_reset' %}"><i class="fa fa-unlock-alt" aria-hidden="true"></i> {% trans 'Reset password' %}</a></li>
|
||||
<li><a href="{% url 'logout' %}"><i class="fa fa-sign-out" aria-hidden="true"></i> {% trans 'Logout' %}</a></li>
|
||||
<li><a href="{% url 'profile' %}"><i class="fa fa-fw fa-image" aria-hidden="true"></i> {% trans 'Profile' %}</a></li>
|
||||
<li><a href="{% url 'user_preference' %}"><i class="fa fa-fw fa-cog" aria-hidden="true"></i> {% trans 'Preferences' %}</a></li>
|
||||
<li><a href="{% url 'import_photo' %}"><i class="fa fa-fw fa-envelope-square" aria-hidden="true"></i> {% trans 'Import photo via mail address' %}</a></li>
|
||||
<li><a href="{% url 'upload_export' %}"><i class="fa fa-fw fa-file-archive-o" aria-hidden="true"></i> {% trans 'Import libravatar XML export' %}</a></li>
|
||||
<li><a href="{% url 'password_change' %}"><i class="fa fa-fw fa-key" aria-hidden="true"></i> {% trans 'Change password' %}</a></li>
|
||||
<li><a href="{% url 'password_reset' %}"><i class="fa fa-fw fa-unlock-alt" aria-hidden="true"></i> {% trans 'Reset password' %}</a></li>
|
||||
<li><a href="{% url 'logout' %}"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> {% trans 'Logout' %}</a></li>
|
||||
{% else %}
|
||||
<li><a href="{% url 'login' %}"><i class="fa fa-sign-in" aria-hidden="true"></i> {% trans 'Local' %}</a></li>
|
||||
<li><a href="{% url 'new_account' %}"><i class="fa fa-user-plus" aria-hidden="true"></i> {% trans 'Create account' %}</a></li>
|
||||
<li><a href="{% url 'login' %}"><i class="fa fa-fw fa-sign-in" aria-hidden="true"></i> {% trans 'Local' %}</a></li>
|
||||
<li><a href="{% url 'new_account' %}"><i class="fa fa-fw fa-user-plus" aria-hidden="true"></i> {% trans 'Create account' %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
{% if user.is_staff %}
|
||||
<li>
|
||||
<a href="{% url 'admin:index' %}" target="_new"><i class="fa fa-user-secret" aria-hidden="true"></i> {% trans 'Admin' %}</a>
|
||||
<a href="{% url 'admin:index' %}" target="_new"><i class="fa fa-fw fa-user-secret" aria-hidden="true"></i> {% trans 'Admin' %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="tools_dropdown">
|
||||
<li><a id="tools-check" href="{% url 'tools_check' %}">
|
||||
<i class="fa fa-check-square" aria-hidden="true"></i> {% trans 'Check' %}
|
||||
<i class="fa fa-fw fa-check-square" aria-hidden="true"></i> {% trans 'Check' %}
|
||||
</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -89,4 +89,3 @@
|
||||
|
||||
<script src="{% static '/js/bootstrap.min.js' %}"></script>
|
||||
<script src="{% static '/js/ivatar.js' %}"></script>
|
||||
{{ settings }}
|
||||
|
||||
@@ -16,4 +16,3 @@
|
||||
|
||||
<script src="{% static '/js/bootstrap.min.js' %}"></script>
|
||||
<script src="{% static '/js/ivatar.js' %}"></script>
|
||||
{{ settings }}
|
||||
|
||||
Reference in New Issue
Block a user