Add support for serving users' avatars through the Libravatar API
This commit is contained in:
parent
6fb289727c
commit
43348a89da
8 changed files with 152 additions and 37 deletions
1
Pipfile
1
Pipfile
|
@ -47,6 +47,7 @@ django-cors-headers = "*"
|
||||||
pytest-django = "*"
|
pytest-django = "*"
|
||||||
"argon2-cffi" = "*"
|
"argon2-cffi" = "*"
|
||||||
python-baseconv = "*"
|
python-baseconv = "*"
|
||||||
|
django-computed-property = "*"
|
||||||
|
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
|
|
88
Pipfile.lock
generated
88
Pipfile.lock
generated
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "a824f0af4df1d0088d5dd66db92329358a5cf0095c71f1cb59f6e60583b912f5"
|
"sha256": "0b68c7a2821e8063bc9d7bae759a9d8dd57785e341e6d23d6421ad93310c7d1b"
|
||||||
},
|
},
|
||||||
"host-environment-markers": {
|
"host-environment-markers": {
|
||||||
"implementation_name": "cpython",
|
"implementation_name": "cpython",
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
"platform_python_implementation": "CPython",
|
"platform_python_implementation": "CPython",
|
||||||
"platform_release": "17.5.0",
|
"platform_release": "17.5.0",
|
||||||
"platform_system": "Darwin",
|
"platform_system": "Darwin",
|
||||||
"platform_version": "Darwin Kernel Version 17.5.0: Mon Mar 5 22:58:50 PST 2018; root:xnu-4570.51.1~2/RELEASE_X86_64",
|
"platform_version": "Darwin Kernel Version 17.5.0: Tue Mar 13 20:39:15 PDT 2018; root:xnu-4570.51.1~36/RELEASE_X86_64",
|
||||||
"python_full_version": "3.6.4",
|
"python_full_version": "3.6.4",
|
||||||
"python_version": "3.6",
|
"python_version": "3.6",
|
||||||
"sys_platform": "darwin"
|
"sys_platform": "darwin"
|
||||||
|
@ -206,6 +206,12 @@
|
||||||
],
|
],
|
||||||
"version": "==2.2"
|
"version": "==2.2"
|
||||||
},
|
},
|
||||||
|
"django-computed-property": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:31aa6453a5c504ce196ba9ae3bacbe0557cadf7ae89e25431b90bf206febd3b3"
|
||||||
|
],
|
||||||
|
"version": "==0.2.1"
|
||||||
|
},
|
||||||
"django-cors-headers": {
|
"django-cors-headers": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0e9532628b3aa8806442d4d0b15e56112e6cfbef3735e13401935c98b842a2b4",
|
"sha256:0e9532628b3aa8806442d4d0b15e56112e6cfbef3735e13401935c98b842a2b4",
|
||||||
|
@ -323,36 +329,36 @@
|
||||||
},
|
},
|
||||||
"lxml": {
|
"lxml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0af9c9267b1257319d49e9c1e9abbf92a99f965bee3c4733e0f0f7578985182d",
|
"sha256:65a272821d5d8194358d6b46f3ca727fa56a6b63981606eac737c86d27309cdd",
|
||||||
"sha256:62bfcd0629991e1c1257ffd28df2ab31a5c44da4c06823c26ec0f472723a84ca",
|
"sha256:abbd2fb4a5a04c11b5e04eb146659a0cf67bb237dd3d7ca3b9994d3a9f826e55",
|
||||||
"sha256:1d1e45584353e4d563685874707fc8c85cdd11b0ef3b79d77bb38046134d68a9",
|
"sha256:3682a17fbf72d56d7e46db2e80ca23850b79c28cfe75dcd9b82f58808f730909",
|
||||||
"sha256:29697224b2df76edf7c2de9bcd90a26dd28fe85c5fd7f0171cae84f8383b227e",
|
"sha256:75322a531504d4f383264391d89993a42e286da8821ddc5ac315e57305cb84f0",
|
||||||
"sha256:988d55112f196e12341b7c5138841c2b4f21f871eaa8f138c6ac4c46f28899f9",
|
"sha256:01c45df6d90497c20aa2a07789a41941f9a1029faa30bf725fc7f6d515b1afe9",
|
||||||
"sha256:57be98177ce784495dff53f40620995ad0a56456246ed9d51977e595de58e12e",
|
"sha256:34d49d0f72dd82b9530322c48b70ac78cca0911275da741c3b1d2f3603c5f295",
|
||||||
"sha256:36ffb216e2f361a5a0a7e219aea6cd44da11c64061baed273944aae21223186c",
|
"sha256:1b46f37927fa6cd1f3fe34b54f1a23bd5bea1d905657289e08e1297069a1a597",
|
||||||
"sha256:d0dc3e5737adcc9a23fd3d3d3072b887fefb48143309563f412ef7b0ebdfdb30",
|
"sha256:5b653c9379ce29ce271fbe1010c5396670f018e78b643e21beefbb3dc6d291de",
|
||||||
"sha256:7769ac9203ebe6d8db16904c54d57d77360fcc1926ed7afaa86b04050e4afa5b",
|
"sha256:3cf2830b9a6ad7f6e965fa53a768d4d2372a7856f20ffa6ce43d2fe9c0d34b19",
|
||||||
"sha256:4c21d7304d37715e6aed756e4d0c374c99c9bb1fa8d64f546b95474b17ac23de",
|
"sha256:1b164bba1320b14905dcff77da10d5ce9c411ac4acc4fb4ed9a2a4d10fae38c9",
|
||||||
"sha256:0aa44ffdeaaf6ba45d61980bb2c07e87d4dcac7a8b5b9d458124bc1adcda5233",
|
"sha256:c557ad647facb3c0027a9d0af58853f905e85a0a2f04dcb73f8e665272fcdc3a",
|
||||||
"sha256:71ac6dac6835de75aaf531cae9ffa447dae0783ba1f43bf6eaccfad3680a5b9c",
|
"sha256:6b6379495d3baacf7ed755ac68547c8dff6ce5d37bf370f0b7678888dc1283f9",
|
||||||
"sha256:88583c6565c9299f617238a500f1a47510bac54daff7872d6a343f13361b659e",
|
"sha256:8523fbde9c2216f3f2b950cb01ebe52e785eaa8a07ffeb456dd3576ca1b4fb9b",
|
||||||
"sha256:2812bc45a7f53f366217b76a1c53e6728fbfa7f7524d16a321ea8f7131428bd1",
|
"sha256:a7182ea298cc3555ea56ffbb0748fe0d5e0d81451e2bc16d7f4645cd01b1ca70",
|
||||||
"sha256:124a9d529eec5e10f307eb237df3efc43dd1fb7ebdb5da5e480c4ed372648b6b",
|
"sha256:7f457cbda964257f443bac861d3a36732dcba8183149e7818ee2fb7c86901b94",
|
||||||
"sha256:f04b184984c23e0caac3c55eac2fe2dbb88726a5a1b35e23715eff6f29a4705c",
|
"sha256:0c9fef4f8d444e337df96c54544aeb85b7215b2ed7483bb6c35de97ac99f1bcd",
|
||||||
"sha256:d06260e6102b2f18dbee3736185cd6a2e1c88c0fad782bf8e9d7a7a1b24e02b0",
|
"sha256:7ff1fc76d8804e0f870c343a72007ff587090c218b0f92d8ee784ac2b6eaf5b9",
|
||||||
"sha256:0cddc6cde79e1932efc71d9974a4418184ad0b8ca46c633ad772b2c5eaf36b3c",
|
"sha256:accc9f6b77bed0a6f267b4fae120f6008a951193d548cdbe9b61fc98a08b1cf8",
|
||||||
"sha256:9e08918b744b89d30750eca8598f37ae75b16202870db678fde970d85afed3e3",
|
"sha256:bd88c8ce0d1504fdfd96a35911dd4f3edfb2e560d7cfdb5a3d09aa571ae5fbae",
|
||||||
"sha256:b46f31e806f6884bd1053ad1d78ecaca6d1bc5dd94a1b783a6ff0bb4b3a60962",
|
"sha256:defabb7fbb99f9f7b3e0b24b286a46855caef4776495211b066e9e6592d12b04",
|
||||||
"sha256:dd98d4f88ce0abda2b02c1542d1de22dd342023f3ba09874bd95841283f29433",
|
"sha256:231047b05907315ae9a9b6925751f9fd2c479cf7b100fff62485a25e382ca0d4",
|
||||||
"sha256:8f52c4c8f1cf15419193026e731f34a3260a3ce7977b875ba1eb2517b8a3f660",
|
"sha256:0e7996e9b46b4d8b4ac1c329a00e2d10edcd8380b95d2a676fccabf4c1dd0512",
|
||||||
"sha256:c18f316cad969111b1ff9e84c82fbc9ae6f25f35701118182d384585940cdf80",
|
"sha256:691f2cd97cf026c611df1ea5055755eec7f878f2d4f4330dc8686583de6fc5fd",
|
||||||
"sha256:95b82fdfdaac71640b281da6b9a2c3700177ba5190a786881b184de744ad55de",
|
"sha256:1858b1933d483ec5727549d3fe166eeb54229fbd6a9d3d7ea26d2c8a28048058",
|
||||||
"sha256:cef79715f2335bfc1ef7082bcb8b2bac87271431653455221a9127fde146208c",
|
"sha256:0e3cd94c95d30ba9ca3cff40e9b2a14e1a10a4fd8131105b86c6b61648f57e4b",
|
||||||
"sha256:4626d699551f66687e5f7e7f9b79bfce611e12edebfb9fec276e2df8ec46541e",
|
"sha256:28f0c6652c1b130f1e576b60532f84b19379485eb8da6185c29bd8c9c9bc97bf",
|
||||||
"sha256:cf63f590090404c52f179b7ceacb7cd549de3a1697bcfe2f79be180b2801d109",
|
"sha256:8f37627f16e026523fca326f1b5c9a43534862fede6c3e99c2ba6a776d75c1ab",
|
||||||
"sha256:7d96fbb5f23a62300aa9bef7d286cd61aca8902357619c8708c0290aba5df73f"
|
"sha256:e2629cdbcad82b83922a3488937632a4983ecc0fed3e5cfbf430d069382eeb9b"
|
||||||
],
|
],
|
||||||
"version": "==4.2.0"
|
"version": "==4.2.1"
|
||||||
},
|
},
|
||||||
"markdown": {
|
"markdown": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -373,6 +379,14 @@
|
||||||
],
|
],
|
||||||
"version": "==0.5.0"
|
"version": "==0.5.0"
|
||||||
},
|
},
|
||||||
|
"more-itertools": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e",
|
||||||
|
"sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea",
|
||||||
|
"sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44"
|
||||||
|
],
|
||||||
|
"version": "==4.1.0"
|
||||||
|
},
|
||||||
"msgpack-python": {
|
"msgpack-python": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:378cc8a6d3545b532dfd149da715abae4fda2a3adb6d74e525d0d5e51f46909b"
|
"sha256:378cc8a6d3545b532dfd149da715abae4fda2a3adb6d74e525d0d5e51f46909b"
|
||||||
|
@ -465,10 +479,10 @@
|
||||||
},
|
},
|
||||||
"py": {
|
"py": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:8cca5c229d225f8c1e3085be4fcf306090b00850fefad892f9d96c7b6e2f310f",
|
"sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a",
|
||||||
"sha256:ca18943e28235417756316bfada6cd96b23ce60dd532642690dcfdaba988a76d"
|
"sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881"
|
||||||
],
|
],
|
||||||
"version": "==1.5.2"
|
"version": "==1.5.3"
|
||||||
},
|
},
|
||||||
"pycparser": {
|
"pycparser": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -506,10 +520,10 @@
|
||||||
},
|
},
|
||||||
"pytest": {
|
"pytest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:062027955bccbc04d2fcd5d79690947e018ba31abe4c90b2c6721abec734261b",
|
"sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c",
|
||||||
"sha256:117bad36c1a787e1a8a659df35de53ba05f9f3398fb9e4ac17e80ad5903eb8c5"
|
"sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1"
|
||||||
],
|
],
|
||||||
"version": "==3.4.2"
|
"version": "==3.5.0"
|
||||||
},
|
},
|
||||||
"pytest-django": {
|
"pytest-django": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
|
@ -72,6 +72,7 @@ INSTALLED_APPS = [
|
||||||
'analytical',
|
'analytical',
|
||||||
'annoying',
|
'annoying',
|
||||||
'compressor',
|
'compressor',
|
||||||
|
'computed_property',
|
||||||
'corsheaders',
|
'corsheaders',
|
||||||
'debug_toolbar',
|
'debug_toolbar',
|
||||||
'django_activeurl',
|
'django_activeurl',
|
||||||
|
|
|
@ -36,6 +36,7 @@ maps = {'sitemaps': sections}
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url('', include('home.urls')),
|
url('', include('home.urls')),
|
||||||
url('', include('entries.urls')),
|
url('', include('entries.urls')),
|
||||||
|
url('', include('users.urls')),
|
||||||
url('^.well-known/', include('wellknowns.urls')),
|
url('^.well-known/', include('wellknowns.urls')),
|
||||||
url('^admin/', otp_admin_site.urls),
|
url('^admin/', otp_admin_site.urls),
|
||||||
url('^auth/', include('lemonauth.urls')),
|
url('^auth/', include('lemonauth.urls')),
|
||||||
|
|
24
users/migrations/0013_auto_20180323_1200.py
Normal file
24
users/migrations/0013_auto_20180323_1200.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 2.0.3 on 2018-03-23 01:00
|
||||||
|
|
||||||
|
import computed_property.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0012_auto_20180129_1614'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='email_md5',
|
||||||
|
field=computed_property.fields.ComputedCharField(compute_from='calc_email_md5', default='', editable=False, max_length=32, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='email_sha256',
|
||||||
|
field=computed_property.fields.ComputedCharField(compute_from='calc_email_sha256', default='', editable=False, max_length=64, unique=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,7 +1,9 @@
|
||||||
|
from computed_property import ComputedCharField
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import AbstractUser, UserManager as DjangoUserManager
|
from django.contrib.auth.models import AbstractUser, UserManager as DjangoUserManager
|
||||||
from django.contrib.sites.models import Site as DjangoSite
|
from django.contrib.sites.models import Site as DjangoSite
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
from hashlib import md5, sha256
|
||||||
from meta.models import ModelMeta
|
from meta.models import ModelMeta
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
from lemoncurry import utils
|
from lemoncurry import utils
|
||||||
|
@ -46,6 +48,21 @@ class User(ModelMeta, AbstractUser):
|
||||||
# This is gonna need to change if I ever decide to add multiple-user support ;)
|
# This is gonna need to change if I ever decide to add multiple-user support ;)
|
||||||
url = '/'
|
url = '/'
|
||||||
|
|
||||||
|
email_md5 = ComputedCharField(
|
||||||
|
compute_from='calc_email_md5', max_length=32, unique=True
|
||||||
|
)
|
||||||
|
email_sha256 = ComputedCharField(
|
||||||
|
compute_from='calc_email_sha256', max_length=64, unique=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def calc_email_md5(self):
|
||||||
|
return md5(self.email.lower().encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def calc_email_sha256(self):
|
||||||
|
return sha256(self.email.lower().encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return '{0} {1}'.format(self.first_name, self.last_name)
|
return '{0} {1}'.format(self.first_name, self.last_name)
|
||||||
|
|
8
users/urls.py
Normal file
8
users/urls.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from .views import libravatar
|
||||||
|
|
||||||
|
app_name = 'users'
|
||||||
|
urlpatterns = (
|
||||||
|
url('^avatar/(?P<hash>[a-z0-9]+)$', libravatar, name='libravatar'),
|
||||||
|
)
|
49
users/views.py
Normal file
49
users/views.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from lemoncurry import utils
|
||||||
|
from .models import User
|
||||||
|
|
||||||
|
|
||||||
|
def try_libravatar_org(hash, get):
|
||||||
|
url = 'https://seccdn.libravatar.org/avatar/' + hash
|
||||||
|
if get:
|
||||||
|
url += '?' + get.urlencode()
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
|
||||||
|
def libravatar(request, hash):
|
||||||
|
g = request.GET
|
||||||
|
size = g.get('s', g.get('size', 80))
|
||||||
|
try:
|
||||||
|
size = int(size)
|
||||||
|
except ValueError:
|
||||||
|
return utils.bad_req('size parameter must be an integer')
|
||||||
|
if not 1 <= size <= 128:
|
||||||
|
return utils.bad_req('size parameter must be between 1 and 128')
|
||||||
|
|
||||||
|
if len(hash) == 32:
|
||||||
|
where = {'email_md5': hash}
|
||||||
|
elif len(hash) == 64:
|
||||||
|
where = {'email_sha256': hash}
|
||||||
|
else:
|
||||||
|
return utils.bad_req('hash must be either md5 or sha256')
|
||||||
|
|
||||||
|
# If the user doesn't exist or lacks an avatar, see if libravatar.org has
|
||||||
|
# one for them - libravatar.org falls back to Gravatar when possible (only
|
||||||
|
# for MD5 hashes, since Gravatar doesn't support SHA-256), so this ensures
|
||||||
|
# all the most likely places are checked.
|
||||||
|
try:
|
||||||
|
user = User.objects.get(**where)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return try_libravatar_org(hash, g)
|
||||||
|
|
||||||
|
if not user.avatar:
|
||||||
|
return try_libravatar_org(hash, g)
|
||||||
|
|
||||||
|
im = Image.open(user.avatar)
|
||||||
|
im_resized = im.resize((size, size))
|
||||||
|
|
||||||
|
response = HttpResponse(content_type='image/'+im.format.lower())
|
||||||
|
im_resized.save(response, im.format)
|
||||||
|
return response
|
Loading…
Reference in a new issue