mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-17 13:38:03 +00:00
WIP: Happy lint
This commit is contained in:
@@ -2,8 +2,8 @@
|
|||||||
Default: useful variables for the base page templates.
|
Default: useful variables for the base page templates.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ivatar.settings import IVATAR_VERSION, SITE_NAME
|
|
||||||
from ipware import get_client_ip
|
from ipware import get_client_ip
|
||||||
|
from ivatar.settings import IVATAR_VERSION, SITE_NAME
|
||||||
|
|
||||||
|
|
||||||
def basepage(request):
|
def basepage(request):
|
||||||
@@ -15,7 +15,7 @@ def basepage(request):
|
|||||||
if 'openid_identifier' in request.GET:
|
if 'openid_identifier' in request.GET:
|
||||||
context['openid_identifier'] = \
|
context['openid_identifier'] = \
|
||||||
request.GET['openid_identifier'] # pragma: no cover
|
request.GET['openid_identifier'] # pragma: no cover
|
||||||
client_ip, is_routable = get_client_ip(request)
|
client_ip = get_client_ip(request)[0]
|
||||||
context['client_ip'] = client_ip
|
context['client_ip'] = client_ip
|
||||||
context['ivatar_version'] = IVATAR_VERSION
|
context['ivatar_version'] = IVATAR_VERSION
|
||||||
context['site_name'] = SITE_NAME
|
context['site_name'] = SITE_NAME
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
'''
|
||||||
|
Register models in admin
|
||||||
|
'''
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from . models import Photo, ConfirmedEmail, UnconfirmedEmail
|
from . models import Photo, ConfirmedEmail, UnconfirmedEmail
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
|
'''
|
||||||
|
Classes for our ivatar.ivataraccount.forms
|
||||||
|
'''
|
||||||
|
from urllib.parse import urlsplit, urlunsplit
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
|
|
||||||
from urllib.parse import urlsplit, urlunsplit
|
from ipware import get_client_ip
|
||||||
|
|
||||||
from ivatar import settings
|
from ivatar import settings
|
||||||
from . models import UnconfirmedEmail, ConfirmedEmail, Photo
|
|
||||||
from . models import UnconfirmedOpenId, ConfirmedOpenId
|
|
||||||
|
|
||||||
from ivatar.settings import MAX_LENGTH_EMAIL
|
from ivatar.settings import MAX_LENGTH_EMAIL
|
||||||
from ivatar.ivataraccount.models import MAX_LENGTH_URL
|
from ivatar.ivataraccount.models import MAX_LENGTH_URL
|
||||||
|
from . models import UnconfirmedEmail, ConfirmedEmail, Photo
|
||||||
from ipware import get_client_ip
|
from . models import UnconfirmedOpenId, ConfirmedOpenId
|
||||||
|
|
||||||
MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT = 5
|
MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT = 5
|
||||||
|
|
||||||
@@ -54,7 +56,7 @@ class AddEmailForm(forms.Form):
|
|||||||
|
|
||||||
# Check whether or not a confirmation email has been
|
# Check whether or not a confirmation email has been
|
||||||
# sent by this user already
|
# sent by this user already
|
||||||
if UnconfirmedEmail.objects.filter(
|
if UnconfirmedEmail.objects.filter( # pylint: disable=no-member
|
||||||
user=user, email=self.cleaned_data['email']).exists():
|
user=user, email=self.cleaned_data['email']).exists():
|
||||||
self.add_error('email', _('Address already added, currently unconfirmed'))
|
self.add_error('email', _('Address already added, currently unconfirmed'))
|
||||||
return False
|
return False
|
||||||
@@ -112,7 +114,8 @@ class UploadPhotoForm(forms.Form):
|
|||||||
distribute photos to third parties.')
|
distribute photos to third parties.')
|
||||||
})
|
})
|
||||||
|
|
||||||
def save(self, request, data):
|
@staticmethod
|
||||||
|
def save(request, data):
|
||||||
'''
|
'''
|
||||||
Save the model and assign it to the current user
|
Save the model and assign it to the current user
|
||||||
'''
|
'''
|
||||||
@@ -122,7 +125,7 @@ class UploadPhotoForm(forms.Form):
|
|||||||
photo.ip_address = get_client_ip(request)
|
photo.ip_address = get_client_ip(request)
|
||||||
photo.data = data.read()
|
photo.data = data.read()
|
||||||
photo.save()
|
photo.save()
|
||||||
if not photo.id:
|
if not photo.pk:
|
||||||
return None
|
return None
|
||||||
return photo
|
return photo
|
||||||
|
|
||||||
@@ -151,19 +154,18 @@ class AddOpenIDForm(forms.Form):
|
|||||||
url.query, url.fragment))
|
url.query, url.fragment))
|
||||||
|
|
||||||
# TODO: Domain restriction as in libravatar?
|
# TODO: Domain restriction as in libravatar?
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def save(self, user):
|
def save(self, user):
|
||||||
'''
|
'''
|
||||||
Save the model, ensuring some safety
|
Save the model, ensuring some safety
|
||||||
'''
|
'''
|
||||||
if ConfirmedOpenId.objects.filter(
|
if ConfirmedOpenId.objects.filter( # pylint: disable=no-member
|
||||||
openid=self.cleaned_data['openid']).exists():
|
openid=self.cleaned_data['openid']).exists():
|
||||||
self.add_error('openid', _('OpenID already added and confirmed!'))
|
self.add_error('openid', _('OpenID already added and confirmed!'))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if UnconfirmedOpenId.objects.filter(
|
if UnconfirmedOpenId.objects.filter( # pylint: disable=no-member
|
||||||
openid=self.cleaned_data['openid']).exists():
|
openid=self.cleaned_data['openid']).exists():
|
||||||
self.add_error('openid', _('OpenID already added, but not confirmed yet!'))
|
self.add_error('openid', _('OpenID already added, but not confirmed yet!'))
|
||||||
return False
|
return False
|
||||||
@@ -173,4 +175,4 @@ class AddOpenIDForm(forms.Form):
|
|||||||
unconfirmed.user = user
|
unconfirmed.user = user
|
||||||
unconfirmed.save()
|
unconfirmed.save()
|
||||||
|
|
||||||
return unconfirmed.id
|
return unconfirmed.pk
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
'''
|
||||||
|
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 urlopen, HTTPError, URLError
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -21,18 +24,18 @@ def get_photo(email):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
urlopen(image_url, timeout=URL_TIMEOUT)
|
urlopen(image_url, timeout=URL_TIMEOUT)
|
||||||
except HTTPError as e:
|
except HTTPError as e: # pylint: disable=invalid-name
|
||||||
if e.code != 404 and e.code != 503:
|
if e.code != 404 and e.code != 503:
|
||||||
print( # pragma: no cover
|
print( # pragma: no cover
|
||||||
'Gravatar fetch failed with an unexpected %s HTTP error' %
|
'Gravatar fetch failed with an unexpected %s HTTP error' %
|
||||||
e.code)
|
e.code)
|
||||||
return False
|
return False
|
||||||
except URLError as e: # pragma: no cover
|
except URLError as e: # pragma: no cover # pylint: disable=invalid-name
|
||||||
print(
|
print(
|
||||||
'Gravatar fetch failed with URL error: %s' %
|
'Gravatar fetch failed with URL error: %s' %
|
||||||
e.reason) # pragma: no cover
|
e.reason) # pragma: no cover
|
||||||
return False # pragma: no cover
|
return False # pragma: no cover
|
||||||
except SSLError as e: # pragma: no cover
|
except SSLError as e: # pragma: no cover # pylint: disable=invalid-name
|
||||||
print(
|
print(
|
||||||
'Gravatar fetch failed with SSL error: %s' %
|
'Gravatar fetch failed with SSL error: %s' %
|
||||||
e.reason) # pragma: no cover
|
e.reason) # pragma: no cover
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ from django.utils import timezone
|
|||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from openid.association import Association as OIDAssociation
|
from openid.association import Association as OIDAssociation
|
||||||
from openid.store import nonce as oidnonce
|
from openid.store import nonce as oidnonce
|
||||||
from openid.store.interface import OpenIDStore
|
from openid.store.interface import OpenIDStore
|
||||||
from ipware import get_client_ip
|
|
||||||
|
|
||||||
from ivatar.settings import MAX_LENGTH_EMAIL, logger
|
from ivatar.settings import MAX_LENGTH_EMAIL, logger
|
||||||
from ivatar.settings import MAX_PIXELS, AVATAR_MAX_SIZE, JPEG_QUALITY
|
from ivatar.settings import MAX_PIXELS, AVATAR_MAX_SIZE, JPEG_QUALITY
|
||||||
@@ -42,6 +42,7 @@ def file_format(image_type):
|
|||||||
return 'png'
|
return 'png'
|
||||||
elif image_type == 'GIF':
|
elif image_type == 'GIF':
|
||||||
return 'gif'
|
return 'gif'
|
||||||
|
return None
|
||||||
|
|
||||||
def pil_format(image_type):
|
def pil_format(image_type):
|
||||||
'''
|
'''
|
||||||
@@ -54,7 +55,7 @@ def pil_format(image_type):
|
|||||||
elif image_type == 'gif':
|
elif image_type == 'gif':
|
||||||
return 'GIF'
|
return 'GIF'
|
||||||
|
|
||||||
logger.info('Unsupported file format: %s' % image_type)
|
logger.info('Unsupported file format: %s', image_type)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ class BaseAccountModel(models.Model):
|
|||||||
ip_address = models.GenericIPAddressField(unpack_ipv4=True, null=True)
|
ip_address = models.GenericIPAddressField(unpack_ipv4=True, null=True)
|
||||||
add_date = models.DateTimeField(default=timezone.now)
|
add_date = models.DateTimeField(default=timezone.now)
|
||||||
|
|
||||||
class Meta:
|
class Meta: # pylint: disable=too-few-public-methods
|
||||||
'''
|
'''
|
||||||
Class attributes
|
Class attributes
|
||||||
'''
|
'''
|
||||||
@@ -84,7 +85,7 @@ class Photo(BaseAccountModel):
|
|||||||
data = models.BinaryField()
|
data = models.BinaryField()
|
||||||
format = models.CharField(max_length=3)
|
format = models.CharField(max_length=3)
|
||||||
|
|
||||||
class Meta:
|
class Meta: # pylint: disable=too-few-public-methods
|
||||||
'''
|
'''
|
||||||
Class attributes
|
Class attributes
|
||||||
'''
|
'''
|
||||||
@@ -92,6 +93,9 @@ class Photo(BaseAccountModel):
|
|||||||
verbose_name_plural = _('photos')
|
verbose_name_plural = _('photos')
|
||||||
|
|
||||||
def import_image(self, service_name, email_address):
|
def import_image(self, service_name, email_address):
|
||||||
|
'''
|
||||||
|
Allow to import image from other (eg. Gravatar) service
|
||||||
|
'''
|
||||||
image_url = False
|
image_url = False
|
||||||
|
|
||||||
if service_name == 'Gravatar':
|
if service_name == 'Gravatar':
|
||||||
@@ -104,12 +108,12 @@ class Photo(BaseAccountModel):
|
|||||||
try:
|
try:
|
||||||
image = urlopen(image_url)
|
image = urlopen(image_url)
|
||||||
# No idea how to test this
|
# No idea how to test this
|
||||||
except HTTPError as e: # pragma: no cover
|
except HTTPError as e: # pragma: no cover # pylint: disable=invalid-name
|
||||||
print('%s import failed with an HTTP error: %s' %
|
print('%s import failed with an HTTP error: %s' %
|
||||||
(service_name, e.code))
|
(service_name, e.code))
|
||||||
return False
|
return False
|
||||||
# No idea how to test this
|
# No idea how to test this
|
||||||
except URLError as e: # pragma: no cover
|
except URLError as e: # pragma: no cover # pylint: disable=invalid-name
|
||||||
print('%s import failed: %s' % (service_name, e.reason))
|
print('%s import failed: %s' % (service_name, e.reason))
|
||||||
return False
|
return False
|
||||||
data = image.read()
|
data = image.read()
|
||||||
@@ -128,7 +132,8 @@ class Photo(BaseAccountModel):
|
|||||||
super().save()
|
super().save()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
'''
|
'''
|
||||||
Override save from parent, taking care about the image
|
Override save from parent, taking care about the image
|
||||||
'''
|
'''
|
||||||
@@ -136,7 +141,7 @@ class Photo(BaseAccountModel):
|
|||||||
try:
|
try:
|
||||||
img = Image.open(BytesIO(self.data))
|
img = Image.open(BytesIO(self.data))
|
||||||
# Testing? Ideas anyone?
|
# Testing? Ideas anyone?
|
||||||
except Exception as e: # pylint: disable=unused-variable
|
except Exception as e: # pylint: disable=invalid-name,broad-except
|
||||||
# For debugging only
|
# For debugging only
|
||||||
print('Exception caught: %s' % e)
|
print('Exception caught: %s' % e)
|
||||||
return False
|
return False
|
||||||
@@ -144,18 +149,21 @@ class Photo(BaseAccountModel):
|
|||||||
if not self.format:
|
if not self.format:
|
||||||
print('Format not recognized')
|
print('Format not recognized')
|
||||||
return False
|
return False
|
||||||
return super().save(*args, **kwargs)
|
return super().save(force_insert, force_update, using, update_fields)
|
||||||
|
|
||||||
def perform_crop(self, request, dimensions, email, openid):
|
def perform_crop(self, request, dimensions, email, openid):
|
||||||
|
'''
|
||||||
|
Helper to crop the image
|
||||||
|
'''
|
||||||
if request.user.photo_set.count() == 1:
|
if request.user.photo_set.count() == 1:
|
||||||
# This is the first photo, assign to all confirmed addresses
|
# This is the first photo, assign to all confirmed addresses
|
||||||
for email in request.user.confirmedemail_set.all():
|
for addr in request.user.confirmedemail_set.all():
|
||||||
email.photo = self
|
addr.photo = self
|
||||||
email.save()
|
addr.save()
|
||||||
|
|
||||||
for openid in request.user.confirmedopenid_set.all():
|
for addr in request.user.confirmedopenid_set.all():
|
||||||
openid.photo = self
|
addr.photo = self
|
||||||
openid.save()
|
addr.save()
|
||||||
|
|
||||||
if email:
|
if email:
|
||||||
# Explicitely asked
|
# Explicitely asked
|
||||||
@@ -171,25 +179,30 @@ class Photo(BaseAccountModel):
|
|||||||
img = Image.open(BytesIO(self.data))
|
img = Image.open(BytesIO(self.data))
|
||||||
|
|
||||||
# This should be anyway checked during save...
|
# This should be anyway checked during save...
|
||||||
a, b = img.size
|
dimensions['a'], dimensions['b'] = img.size # pylint: disable=invalid-name
|
||||||
if a > MAX_PIXELS or b > MAX_PIXELS:
|
if dimensions['a'] > MAX_PIXELS or dimensions['b'] > MAX_PIXELS:
|
||||||
messages.error(request, _('Image dimensions are too big(max: %s x %s' % (MAX_PIXELS, MAX_PIXELS)))
|
messages.error(
|
||||||
|
request,
|
||||||
|
_('Image dimensions are too big(max: %s x %s' %
|
||||||
|
(MAX_PIXELS, MAX_PIXELS)))
|
||||||
return HttpResponseRedirect(reverse_lazy('profile'))
|
return HttpResponseRedirect(reverse_lazy('profile'))
|
||||||
|
|
||||||
w = dimensions['w']
|
if dimensions['w'] == 0 and dimensions['h'] == 0:
|
||||||
h = dimensions['h']
|
dimensions['w'], dimensions['h'] = dimensions['a'], dimensions['b']
|
||||||
x = dimensions['x']
|
min_from_w_h = min(dimensions['w'], dimensions['h'])
|
||||||
y = dimensions['y']
|
dimensions['w'], dimensions['h'] = min_from_w_h, min_from_w_h
|
||||||
|
elif dimensions['w'] < 0 or \
|
||||||
if w == 0 and h == 0:
|
(dimensions['x'] + dimensions['w']) > dimensions['a'] or \
|
||||||
w, h = a, b
|
dimensions['h'] < 0 or \
|
||||||
i = min(w, h)
|
(dimensions['y'] + dimensions['h']) > dimensions['b']:
|
||||||
w, h = i, i
|
|
||||||
elif w < 0 or (x + w) > a or h < 0 or (y + h) > b:
|
|
||||||
messages.error(request, _('Crop outside of original image bounding box'))
|
messages.error(request, _('Crop outside of original image bounding box'))
|
||||||
return HttpResponseRedirect(reverse_lazy('profile'))
|
return HttpResponseRedirect(reverse_lazy('profile'))
|
||||||
|
|
||||||
cropped = img.crop((x, y, x + w, y + h))
|
cropped = img.crop((
|
||||||
|
dimensions['x'],
|
||||||
|
dimensions['y'],
|
||||||
|
dimensions['x'] + dimensions['w'],
|
||||||
|
dimensions['y'] + dimensions['h']))
|
||||||
# cropped.load()
|
# cropped.load()
|
||||||
# Resize the image only if it's larger than the specified max width.
|
# Resize the image only if it's larger than the specified max width.
|
||||||
cropped_w, cropped_h = cropped.size
|
cropped_w, cropped_h = cropped.size
|
||||||
@@ -200,12 +213,6 @@ class Photo(BaseAccountModel):
|
|||||||
data = BytesIO()
|
data = BytesIO()
|
||||||
cropped.save(data, pil_format(self.format), quality=JPEG_QUALITY)
|
cropped.save(data, pil_format(self.format), quality=JPEG_QUALITY)
|
||||||
data.seek(0)
|
data.seek(0)
|
||||||
# Create new photo?
|
|
||||||
# photo = Photo()
|
|
||||||
# photo.user = request.user
|
|
||||||
# photo.ip_address = get_client_ip(request)
|
|
||||||
# photo.data = data.read()
|
|
||||||
# photo.save()
|
|
||||||
|
|
||||||
# Overwrite the existing image
|
# Overwrite the existing image
|
||||||
self.data = data.read()
|
self.data = data.read()
|
||||||
@@ -214,12 +221,13 @@ class Photo(BaseAccountModel):
|
|||||||
return HttpResponseRedirect(reverse_lazy('profile'))
|
return HttpResponseRedirect(reverse_lazy('profile'))
|
||||||
|
|
||||||
|
|
||||||
class ConfirmedEmailManager(models.Manager):
|
class ConfirmedEmailManager(models.Manager): # pylint: disable=too-few-public-methods
|
||||||
'''
|
'''
|
||||||
Manager for our confirmed email addresses model
|
Manager for our confirmed email addresses model
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def create_confirmed_email(self, user, email_address, is_logged_in):
|
@staticmethod
|
||||||
|
def create_confirmed_email(user, email_address, is_logged_in):
|
||||||
'''
|
'''
|
||||||
Helper method to create confirmed email address
|
Helper method to create confirmed email address
|
||||||
'''
|
'''
|
||||||
@@ -254,7 +262,7 @@ class ConfirmedEmail(BaseAccountModel):
|
|||||||
digest = models.CharField(max_length=64)
|
digest = models.CharField(max_length=64)
|
||||||
objects = ConfirmedEmailManager()
|
objects = ConfirmedEmailManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta: # pylint: disable=too-few-public-methods
|
||||||
'''
|
'''
|
||||||
Class attributes
|
Class attributes
|
||||||
'''
|
'''
|
||||||
@@ -268,14 +276,15 @@ class ConfirmedEmail(BaseAccountModel):
|
|||||||
self.photo = photo
|
self.photo = photo
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
'''
|
'''
|
||||||
Override save from parent, add digest
|
Override save from parent, add digest
|
||||||
'''
|
'''
|
||||||
self.digest = hashlib.md5(
|
self.digest = hashlib.md5(
|
||||||
self.email.strip().lower().encode('utf-8')
|
self.email.strip().lower().encode('utf-8')
|
||||||
).hexdigest()
|
).hexdigest()
|
||||||
return super().save(*args, **kwargs)
|
return super().save(force_insert, force_update, using, update_fields)
|
||||||
|
|
||||||
|
|
||||||
class UnconfirmedEmail(BaseAccountModel):
|
class UnconfirmedEmail(BaseAccountModel):
|
||||||
@@ -285,18 +294,19 @@ class UnconfirmedEmail(BaseAccountModel):
|
|||||||
email = models.EmailField(max_length=MAX_LENGTH_EMAIL)
|
email = models.EmailField(max_length=MAX_LENGTH_EMAIL)
|
||||||
verification_key = models.CharField(max_length=64)
|
verification_key = models.CharField(max_length=64)
|
||||||
|
|
||||||
class Meta:
|
class Meta: # pylint: disable=too-few-public-methods
|
||||||
'''
|
'''
|
||||||
Class attributes
|
Class attributes
|
||||||
'''
|
'''
|
||||||
verbose_name = _('unconfirmed_email')
|
verbose_name = _('unconfirmed_email')
|
||||||
verbose_name_plural = _('unconfirmed_emails')
|
verbose_name_plural = _('unconfirmed_emails')
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
hash_object = hashlib.new('sha256')
|
hash_object = hashlib.new('sha256')
|
||||||
hash_object.update(urandom(1024) + self.user.username.encode('utf-8'))
|
hash_object.update(urandom(1024) + self.user.username.encode('utf-8')) # pylint: disable=no-member
|
||||||
self.verification_key = hash_object.hexdigest()
|
self.verification_key = hash_object.hexdigest()
|
||||||
super(UnconfirmedEmail, self).save(*args, **kwargs)
|
super(UnconfirmedEmail, self).save(force_insert, force_update, using, update_fields)
|
||||||
|
|
||||||
|
|
||||||
class UnconfirmedOpenId(BaseAccountModel):
|
class UnconfirmedOpenId(BaseAccountModel):
|
||||||
@@ -305,7 +315,10 @@ class UnconfirmedOpenId(BaseAccountModel):
|
|||||||
'''
|
'''
|
||||||
openid = models.URLField(unique=False, max_length=MAX_LENGTH_URL)
|
openid = models.URLField(unique=False, max_length=MAX_LENGTH_URL)
|
||||||
|
|
||||||
class Meta:
|
class Meta: # pylint: disable=too-few-public-methods
|
||||||
|
'''
|
||||||
|
Meta class
|
||||||
|
'''
|
||||||
verbose_name = _('unconfirmed OpenID')
|
verbose_name = _('unconfirmed OpenID')
|
||||||
verbose_name_plural = ('unconfirmed_OpenIDs')
|
verbose_name_plural = ('unconfirmed_OpenIDs')
|
||||||
|
|
||||||
@@ -325,15 +338,22 @@ class ConfirmedOpenId(BaseAccountModel):
|
|||||||
)
|
)
|
||||||
digest = models.CharField(max_length=64)
|
digest = models.CharField(max_length=64)
|
||||||
|
|
||||||
class Meta:
|
class Meta: # pylint: disable=too-few-public-methods
|
||||||
|
'''
|
||||||
|
Meta class
|
||||||
|
'''
|
||||||
verbose_name = _('confirmed OpenID')
|
verbose_name = _('confirmed OpenID')
|
||||||
verbose_name_plural = _('confirmed OpenIDs')
|
verbose_name_plural = _('confirmed OpenIDs')
|
||||||
|
|
||||||
def set_photo(self, photo):
|
def set_photo(self, photo):
|
||||||
|
'''
|
||||||
|
Helper method to save photo
|
||||||
|
'''
|
||||||
self.photo = photo
|
self.photo = photo
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
url = urlsplit(self.openid)
|
url = urlsplit(self.openid)
|
||||||
if url.username: # pragma: no cover
|
if url.username: # pragma: no cover
|
||||||
password = url.password or ''
|
password = url.password or ''
|
||||||
@@ -344,7 +364,7 @@ class ConfirmedOpenId(BaseAccountModel):
|
|||||||
(url.scheme.lower(), netloc, url.path, url.query, url.fragment)
|
(url.scheme.lower(), netloc, url.path, url.query, url.fragment)
|
||||||
)
|
)
|
||||||
self.digest = hashlib.sha256(lowercase_url.encode('utf-8')).hexdigest()
|
self.digest = hashlib.sha256(lowercase_url.encode('utf-8')).hexdigest()
|
||||||
return super().save(*args, **kwargs)
|
return super().save(force_insert, force_update, using, update_fields)
|
||||||
|
|
||||||
|
|
||||||
class OpenIDNonce(models.Model):
|
class OpenIDNonce(models.Model):
|
||||||
@@ -383,7 +403,7 @@ class DjangoOpenIDStore(OpenIDStore):
|
|||||||
assoc = OpenIDAssociation(
|
assoc = OpenIDAssociation(
|
||||||
server_url=server_url,
|
server_url=server_url,
|
||||||
handle=association.handle,
|
handle=association.handle,
|
||||||
secret=base64.encodestring(association.secret),
|
secret=base64.encodebytes(association.secret),
|
||||||
issued=association.issued,
|
issued=association.issued,
|
||||||
lifetime=association.issued,
|
lifetime=association.issued,
|
||||||
assoc_type=association.assoc_type)
|
assoc_type=association.assoc_type)
|
||||||
@@ -395,25 +415,25 @@ class DjangoOpenIDStore(OpenIDStore):
|
|||||||
'''
|
'''
|
||||||
assocs = []
|
assocs = []
|
||||||
if handle is not None:
|
if handle is not None:
|
||||||
assocs = OpenIDAssociation.objects.filter(
|
assocs = OpenIDAssociation.objects.filter( # pylint: disable=no-member
|
||||||
server_url=server_url, handle=handle)
|
server_url=server_url, handle=handle)
|
||||||
else:
|
else:
|
||||||
assocs = OpenIDAssociation.objects.filter(server_url=server_url)
|
assocs = OpenIDAssociation.objects.filter(server_url=server_url) # pylint: disable=no-member
|
||||||
if not assocs:
|
if not assocs:
|
||||||
return None
|
return None
|
||||||
associations = []
|
associations = []
|
||||||
for assoc in assocs:
|
for assoc in assocs:
|
||||||
if type(assoc.secret) is str:
|
if isinstance(assoc.secret, str):
|
||||||
assoc.secret = assoc.secret.split("b'")[1].split("'")[0]
|
assoc.secret = assoc.secret.split("b'")[1].split("'")[0]
|
||||||
assoc.secret = bytes(assoc.secret, 'utf-8')
|
assoc.secret = bytes(assoc.secret, 'utf-8')
|
||||||
association = OIDAssociation(assoc.handle,
|
association = OIDAssociation(assoc.handle,
|
||||||
base64.decodestring(assoc.secret),
|
base64.decodebytes(assoc.secret),
|
||||||
assoc.issued, assoc.lifetime,
|
assoc.issued, assoc.lifetime,
|
||||||
assoc.assoc_type)
|
assoc.assoc_type)
|
||||||
expires = 0
|
expires = 0
|
||||||
try:
|
try:
|
||||||
expires = association.getExpiresIn()
|
expires = association.getExpiresIn() # pylint: disable=no-member
|
||||||
except Exception as e:
|
except Exception as e: # pylint: disable=invalid-name,broad-except,unused-variable
|
||||||
expires = association.expiresIn
|
expires = association.expiresIn
|
||||||
if expires == 0:
|
if expires == 0:
|
||||||
self.removeAssociation(server_url, assoc.handle)
|
self.removeAssociation(server_url, assoc.handle)
|
||||||
@@ -429,7 +449,7 @@ class DjangoOpenIDStore(OpenIDStore):
|
|||||||
TODO: Could be moved to classmethod
|
TODO: Could be moved to classmethod
|
||||||
'''
|
'''
|
||||||
assocs = list(
|
assocs = list(
|
||||||
OpenIDAssociation.objects.filter(
|
OpenIDAssociation.objects.filter( # pylint: disable=no-member
|
||||||
server_url=server_url, handle=handle))
|
server_url=server_url, handle=handle))
|
||||||
assocs_exist = len(assocs) > 0
|
assocs_exist = len(assocs) > 0
|
||||||
for assoc in assocs:
|
for assoc in assocs:
|
||||||
@@ -445,12 +465,12 @@ class DjangoOpenIDStore(OpenIDStore):
|
|||||||
if abs(timestamp - time.time()) > oidnonce.SKEW:
|
if abs(timestamp - time.time()) > oidnonce.SKEW:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
nonce = OpenIDNonce.objects.get(
|
nonce = OpenIDNonce.objects.get( # pylint: disable=no-member
|
||||||
server_url__exact=server_url,
|
server_url__exact=server_url,
|
||||||
timestamp__exact=timestamp,
|
timestamp__exact=timestamp,
|
||||||
salt__exact=salt)
|
salt__exact=salt)
|
||||||
except OpenIDNonce.DoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
nonce = OpenIDNonce.objects.create(
|
nonce = OpenIDNonce.objects.create( # pylint: disable=no-member
|
||||||
server_url=server_url, timestamp=timestamp, salt=salt)
|
server_url=server_url, timestamp=timestamp, salt=salt)
|
||||||
return True
|
return True
|
||||||
nonce.delete()
|
nonce.delete()
|
||||||
@@ -462,12 +482,12 @@ class DjangoOpenIDStore(OpenIDStore):
|
|||||||
TODO: Could be moved to classmethod
|
TODO: Could be moved to classmethod
|
||||||
'''
|
'''
|
||||||
timestamp = int(time.time()) - oidnonce.SKEW
|
timestamp = int(time.time()) - oidnonce.SKEW
|
||||||
OpenIDNonce.objects.filter(timestamp__lt=timestamp).delete()
|
OpenIDNonce.objects.filter(timestamp__lt=timestamp).delete() # pylint: disable=no-member
|
||||||
|
|
||||||
def cleanupAssociations(self): # pragma: no cover
|
def cleanupAssociations(self): # pragma: no cover
|
||||||
'''
|
'''
|
||||||
Helper method to cleanup associations
|
Helper method to cleanup associations
|
||||||
TODO: Could be moved to classmethod
|
TODO: Could be moved to classmethod
|
||||||
'''
|
'''
|
||||||
OpenIDAssociation.objects.extra(
|
OpenIDAssociation.objects.extra( # pylint: disable=no-member
|
||||||
where=['issued + lifetimeint < (%s)' % time.time()]).delete()
|
where=['issued + lifetimeint < (%s)' % time.time()]).delete()
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from . views import CropPhotoView
|
|||||||
# Define URL patterns, self documenting
|
# Define URL patterns, self documenting
|
||||||
# To see the fancy, colorful evaluation of these use:
|
# To see the fancy, colorful evaluation of these use:
|
||||||
# ./manager show_urls
|
# ./manager show_urls
|
||||||
urlpatterns = [
|
urlpatterns = [ # pylint: disable=invalid-name
|
||||||
path('new/', CreateView.as_view(), name='new_account'),
|
path('new/', CreateView.as_view(), name='new_account'),
|
||||||
path('login/', LoginView.as_view(template_name='login.html'),
|
path('login/', LoginView.as_view(template_name='login.html'),
|
||||||
name='login'),
|
name='login'),
|
||||||
@@ -38,39 +38,39 @@ urlpatterns = [
|
|||||||
path('upload_photo/', UploadPhotoView.as_view(), name='upload_photo'),
|
path('upload_photo/', UploadPhotoView.as_view(), name='upload_photo'),
|
||||||
path('password_set/', PasswordSetView.as_view(), name='password_set'),
|
path('password_set/', PasswordSetView.as_view(), name='password_set'),
|
||||||
url(
|
url(
|
||||||
'remove_unconfirmed_openid/(?P<openid_id>\d+)',
|
r'remove_unconfirmed_openid/(?P<openid_id>\d+)',
|
||||||
RemoveUnconfirmedOpenIDView.as_view(),
|
RemoveUnconfirmedOpenIDView.as_view(),
|
||||||
name='remove_unconfirmed_openid'),
|
name='remove_unconfirmed_openid'),
|
||||||
url(
|
url(
|
||||||
'remove_confirmed_openid/(?P<openid_id>\d+)',
|
r'remove_confirmed_openid/(?P<openid_id>\d+)',
|
||||||
RemoveConfirmedOpenIDView.as_view(), name='remove_confirmed_openid'),
|
RemoveConfirmedOpenIDView.as_view(), name='remove_confirmed_openid'),
|
||||||
url(
|
url(
|
||||||
'openid_redirection/(?P<openid_id>\d+)',
|
r'openid_redirection/(?P<openid_id>\d+)',
|
||||||
RedirectOpenIDView.as_view(), name='openid_redirection'),
|
RedirectOpenIDView.as_view(), name='openid_redirection'),
|
||||||
url(
|
url(
|
||||||
'confirm_openid/(?P<openid_id>\w+)',
|
r'confirm_openid/(?P<openid_id>\w+)',
|
||||||
ConfirmOpenIDView.as_view(), name='confirm_openid'),
|
ConfirmOpenIDView.as_view(), name='confirm_openid'),
|
||||||
url(
|
url(
|
||||||
'confirm_email/(?P<verification_key>\w+)',
|
r'confirm_email/(?P<verification_key>\w+)',
|
||||||
ConfirmEmailView.as_view(), name='confirm_email'),
|
ConfirmEmailView.as_view(), name='confirm_email'),
|
||||||
url(
|
url(
|
||||||
'remove_unconfirmed_email/(?P<email_id>\d+)',
|
r'remove_unconfirmed_email/(?P<email_id>\d+)',
|
||||||
RemoveUnconfirmedEmailView.as_view(), name='remove_unconfirmed_email'),
|
RemoveUnconfirmedEmailView.as_view(), name='remove_unconfirmed_email'),
|
||||||
url(
|
url(
|
||||||
'remove_confirmed_email/(?P<email_id>\d+)',
|
r'remove_confirmed_email/(?P<email_id>\d+)',
|
||||||
RemoveConfirmedEmailView.as_view(), name='remove_confirmed_email'),
|
RemoveConfirmedEmailView.as_view(), name='remove_confirmed_email'),
|
||||||
url(
|
url(
|
||||||
'assign_photo_email/(?P<email_id>\d+)',
|
r'assign_photo_email/(?P<email_id>\d+)',
|
||||||
AssignPhotoEmailView.as_view(), name='assign_photo_email'),
|
AssignPhotoEmailView.as_view(), name='assign_photo_email'),
|
||||||
url(
|
url(
|
||||||
'assign_photo_openid/(?P<openid_id>\d+)',
|
r'assign_photo_openid/(?P<openid_id>\d+)',
|
||||||
AssignPhotoOpenIDView.as_view(), name='assign_photo_openid'),
|
AssignPhotoOpenIDView.as_view(), name='assign_photo_openid'),
|
||||||
url(
|
url(
|
||||||
'import_photo/(?P<email_id>\d+)',
|
r'import_photo/(?P<email_id>\d+)',
|
||||||
ImportPhotoView.as_view(), name='import_photo'),
|
ImportPhotoView.as_view(), name='import_photo'),
|
||||||
url(
|
url(
|
||||||
'delete_photo/(?P<pk>\d+)',
|
r'delete_photo/(?P<pk>\d+)',
|
||||||
DeletePhotoView.as_view(), name='delete_photo'),
|
DeletePhotoView.as_view(), name='delete_photo'),
|
||||||
url('raw_image/(?P<pk>\d+)', RawImageView.as_view(), name='raw_image'),
|
url(r'raw_image/(?P<pk>\d+)', RawImageView.as_view(), name='raw_image'),
|
||||||
url('crop_photo/(?P<pk>\d+)', CropPhotoView.as_view(), name='crop_photo'),
|
url(r'crop_photo/(?P<pk>\d+)', CropPhotoView.as_view(), name='crop_photo'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,15 +5,14 @@ Django settings for ivatar project.
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log_level = logging.DEBUG
|
log_level = logging.DEBUG # pylint: disable=invalid-name
|
||||||
logger = logging.getLogger('ivatar')
|
logger = logging.getLogger('ivatar') # pylint: disable=invalid-name
|
||||||
logger.setLevel(log_level)
|
logger.setLevel(log_level)
|
||||||
|
|
||||||
PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__))
|
PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__))
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
# TODO: Changeme
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = '=v(+-^t#ahv^a&&e)uf36g8algj$d1@6ou^w(r0@%)#8mlc*zk'
|
SECRET_KEY = '=v(+-^t#ahv^a&&e)uf36g8algj$d1@6ou^w(r0@%)#8mlc*zk'
|
||||||
|
|
||||||
@@ -119,4 +118,4 @@ PROJECT_ROOT = os.path.abspath(
|
|||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||||
|
|
||||||
from config import * # noqa
|
from config import * # pylint: disable=wildcard-import,wrong-import-position,unused-wildcard-import
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
'''
|
||||||
|
Unit tests for WSGI
|
||||||
|
'''
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -7,7 +10,13 @@ django.setup()
|
|||||||
|
|
||||||
|
|
||||||
class TestCase(unittest.TestCase):
|
class TestCase(unittest.TestCase):
|
||||||
|
'''
|
||||||
|
Simple testcase to see if WSGI loads correctly
|
||||||
|
'''
|
||||||
def test_run_wsgi(self):
|
def test_run_wsgi(self):
|
||||||
|
'''
|
||||||
|
Run wsgi import
|
||||||
|
'''
|
||||||
import ivatar.wsgi
|
import ivatar.wsgi
|
||||||
self.assertEqual(ivatar.wsgi.application.__class__,
|
self.assertEqual(ivatar.wsgi.application.__class__,
|
||||||
django.core.handlers.wsgi.WSGIHandler)
|
django.core.handlers.wsgi.WSGIHandler)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
'''
|
'''
|
||||||
ivatar URL Configuration
|
ivatar URL configuration
|
||||||
'''
|
'''
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
@@ -9,15 +9,15 @@ from django.views.generic import TemplateView
|
|||||||
from ivatar import settings
|
from ivatar import settings
|
||||||
from . views import AvatarImageView
|
from . views import AvatarImageView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [ # pylint: disable=invalid-name
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
url('openid/', include('django_openid_auth.urls')),
|
url('openid/', include('django_openid_auth.urls')),
|
||||||
url('accounts/', include('ivatar.ivataraccount.urls')),
|
url('accounts/', include('ivatar.ivataraccount.urls')),
|
||||||
url(
|
url(
|
||||||
'avatar/(?P<digest>\w{64})',
|
r'avatar/(?P<digest>\w{64})',
|
||||||
AvatarImageView.as_view(), name='avatar_view'),
|
AvatarImageView.as_view(), name='avatar_view'),
|
||||||
url(
|
url(
|
||||||
'avatar/(?P<digest>\w{32})',
|
r'avatar/(?P<digest>\w{32})',
|
||||||
AvatarImageView.as_view(), name='avatar_view'),
|
AvatarImageView.as_view(), name='avatar_view'),
|
||||||
url('', TemplateView.as_view(template_name='home.html')),
|
url('', TemplateView.as_view(template_name='home.html')),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
'''
|
||||||
|
Simple module providing reusable random_string function
|
||||||
|
'''
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
|
||||||
def random_string(len=10):
|
def random_string(length=10):
|
||||||
'''
|
'''
|
||||||
Return some random string with default length 10
|
Return some random string with default length 10
|
||||||
'''
|
'''
|
||||||
return ''.join(random.SystemRandom().choice(
|
return ''.join(random.SystemRandom().choice(
|
||||||
string.ascii_lowercase + string.digits) for _ in range(10))
|
string.ascii_lowercase + string.digits) for _ in range(length))
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import io
|
|||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from . ivataraccount.models import ConfirmedEmail, ConfirmedOpenId
|
from . ivataraccount.models import ConfirmedEmail, ConfirmedOpenId
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
|
|
||||||
class AvatarImageView(TemplateView):
|
class AvatarImageView(TemplateView):
|
||||||
@@ -29,13 +30,14 @@ class AvatarImageView(TemplateView):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
obj = model.objects.get(digest=kwargs['digest'])
|
obj = model.objects.get(digest=kwargs['digest'])
|
||||||
except model.DoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
# TODO: Use default!?
|
# TODO: Use default!?
|
||||||
raise Exception('Mail/openid ("%s") does not exist"' %
|
raise Exception('Mail/openid ("%s") does not exist"' %
|
||||||
kwargs['digest'])
|
kwargs['digest'])
|
||||||
if not obj.photo:
|
if not obj.photo:
|
||||||
# That is hacky, but achieves what we want :-)
|
# That is hacky, but achieves what we want :-)
|
||||||
attr = getattr(obj, 'email', obj.openid)
|
attr = getattr(obj, 'email', obj.openid)
|
||||||
|
# TODO: Use default!?
|
||||||
raise Exception('No photo assigned to "%s"' % attr)
|
raise Exception('No photo assigned to "%s"' % attr)
|
||||||
|
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ from django.core.wsgi import get_wsgi_application
|
|||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ivatar.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ivatar.settings")
|
||||||
|
|
||||||
application = get_wsgi_application()
|
application = get_wsgi_application() # pylint: disable=invalid-name
|
||||||
|
|||||||
Reference in New Issue
Block a user