diff --git a/ivatar/context_processors.py b/ivatar/context_processors.py index 519fca4..d9bcdb0 100644 --- a/ivatar/context_processors.py +++ b/ivatar/context_processors.py @@ -2,8 +2,8 @@ Default: useful variables for the base page templates. ''' -from ivatar.settings import IVATAR_VERSION, SITE_NAME from ipware import get_client_ip +from ivatar.settings import IVATAR_VERSION, SITE_NAME def basepage(request): @@ -15,7 +15,7 @@ def basepage(request): if 'openid_identifier' in request.GET: context['openid_identifier'] = \ 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['ivatar_version'] = IVATAR_VERSION context['site_name'] = SITE_NAME diff --git a/ivatar/ivataraccount/admin.py b/ivatar/ivataraccount/admin.py index ac980ec..498e52a 100644 --- a/ivatar/ivataraccount/admin.py +++ b/ivatar/ivataraccount/admin.py @@ -1,3 +1,6 @@ +''' +Register models in admin +''' from django.contrib import admin from . models import Photo, ConfirmedEmail, UnconfirmedEmail diff --git a/ivatar/ivataraccount/forms.py b/ivatar/ivataraccount/forms.py index 37a345b..168d884 100644 --- a/ivatar/ivataraccount/forms.py +++ b/ivatar/ivataraccount/forms.py @@ -1,19 +1,21 @@ +''' +Classes for our ivatar.ivataraccount.forms +''' +from urllib.parse import urlsplit, urlunsplit + from django import forms from django.utils.translation import ugettext_lazy as _ from django.urls import reverse from django.template.loader import render_to_string from django.core.mail import send_mail -from urllib.parse import urlsplit, urlunsplit +from ipware import get_client_ip from ivatar import settings -from . models import UnconfirmedEmail, ConfirmedEmail, Photo -from . models import UnconfirmedOpenId, ConfirmedOpenId - from ivatar.settings import MAX_LENGTH_EMAIL from ivatar.ivataraccount.models import MAX_LENGTH_URL - -from ipware import get_client_ip +from . models import UnconfirmedEmail, ConfirmedEmail, Photo +from . models import UnconfirmedOpenId, ConfirmedOpenId MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT = 5 @@ -54,7 +56,7 @@ class AddEmailForm(forms.Form): # Check whether or not a confirmation email has been # 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(): self.add_error('email', _('Address already added, currently unconfirmed')) return False @@ -112,7 +114,8 @@ class UploadPhotoForm(forms.Form): 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 ''' @@ -122,7 +125,7 @@ class UploadPhotoForm(forms.Form): photo.ip_address = get_client_ip(request) photo.data = data.read() photo.save() - if not photo.id: + if not photo.pk: return None return photo @@ -151,19 +154,18 @@ class AddOpenIDForm(forms.Form): url.query, url.fragment)) # TODO: Domain restriction as in libravatar? - return data def save(self, user): ''' Save the model, ensuring some safety ''' - if ConfirmedOpenId.objects.filter( + if ConfirmedOpenId.objects.filter( # pylint: disable=no-member openid=self.cleaned_data['openid']).exists(): self.add_error('openid', _('OpenID already added and confirmed!')) return False - if UnconfirmedOpenId.objects.filter( + if UnconfirmedOpenId.objects.filter( # pylint: disable=no-member openid=self.cleaned_data['openid']).exists(): self.add_error('openid', _('OpenID already added, but not confirmed yet!')) return False @@ -173,4 +175,4 @@ class AddOpenIDForm(forms.Form): unconfirmed.user = user unconfirmed.save() - return unconfirmed.id + return unconfirmed.pk diff --git a/ivatar/ivataraccount/gravatar.py b/ivatar/ivataraccount/gravatar.py index 911cd57..0de88dd 100644 --- a/ivatar/ivataraccount/gravatar.py +++ b/ivatar/ivataraccount/gravatar.py @@ -1,3 +1,6 @@ +''' +Helper method to fetch Gravatar image +''' from ssl import SSLError from urllib.request import urlopen, HTTPError, URLError import hashlib @@ -21,18 +24,18 @@ def get_photo(email): try: 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: print( # pragma: no cover 'Gravatar fetch failed with an unexpected %s HTTP error' % e.code) return False - except URLError as e: # pragma: no cover + except URLError as e: # pragma: no cover # pylint: disable=invalid-name print( 'Gravatar fetch failed with URL error: %s' % e.reason) # 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( 'Gravatar fetch failed with SSL error: %s' % e.reason) # pragma: no cover diff --git a/ivatar/ivataraccount/models.py b/ivatar/ivataraccount/models.py index 6536028..cc995df 100644 --- a/ivatar/ivataraccount/models.py +++ b/ivatar/ivataraccount/models.py @@ -19,10 +19,10 @@ from django.utils import timezone from django.http import HttpResponseRedirect from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ObjectDoesNotExist from openid.association import Association as OIDAssociation from openid.store import nonce as oidnonce 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_PIXELS, AVATAR_MAX_SIZE, JPEG_QUALITY @@ -42,6 +42,7 @@ def file_format(image_type): return 'png' elif image_type == 'GIF': return 'gif' + return None def pil_format(image_type): ''' @@ -54,7 +55,7 @@ def pil_format(image_type): elif image_type == 'gif': return 'GIF' - logger.info('Unsupported file format: %s' % image_type) + logger.info('Unsupported file format: %s', image_type) return None @@ -69,7 +70,7 @@ class BaseAccountModel(models.Model): ip_address = models.GenericIPAddressField(unpack_ipv4=True, null=True) add_date = models.DateTimeField(default=timezone.now) - class Meta: + class Meta: # pylint: disable=too-few-public-methods ''' Class attributes ''' @@ -84,7 +85,7 @@ class Photo(BaseAccountModel): data = models.BinaryField() format = models.CharField(max_length=3) - class Meta: + class Meta: # pylint: disable=too-few-public-methods ''' Class attributes ''' @@ -92,6 +93,9 @@ class Photo(BaseAccountModel): verbose_name_plural = _('photos') def import_image(self, service_name, email_address): + ''' + Allow to import image from other (eg. Gravatar) service + ''' image_url = False if service_name == 'Gravatar': @@ -104,12 +108,12 @@ class Photo(BaseAccountModel): try: image = urlopen(image_url) # 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' % (service_name, e.code)) return False # 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)) return False data = image.read() @@ -128,7 +132,8 @@ class Photo(BaseAccountModel): super().save() 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 ''' @@ -136,7 +141,7 @@ class Photo(BaseAccountModel): try: img = Image.open(BytesIO(self.data)) # Testing? Ideas anyone? - except Exception as e: # pylint: disable=unused-variable + except Exception as e: # pylint: disable=invalid-name,broad-except # For debugging only print('Exception caught: %s' % e) return False @@ -144,18 +149,21 @@ class Photo(BaseAccountModel): if not self.format: print('Format not recognized') 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): + ''' + Helper to crop the image + ''' if request.user.photo_set.count() == 1: # This is the first photo, assign to all confirmed addresses - for email in request.user.confirmedemail_set.all(): - email.photo = self - email.save() + for addr in request.user.confirmedemail_set.all(): + addr.photo = self + addr.save() - for openid in request.user.confirmedopenid_set.all(): - openid.photo = self - openid.save() + for addr in request.user.confirmedopenid_set.all(): + addr.photo = self + addr.save() if email: # Explicitely asked @@ -171,25 +179,30 @@ class Photo(BaseAccountModel): img = Image.open(BytesIO(self.data)) # This should be anyway checked during save... - a, b = img.size - if a > MAX_PIXELS or b > MAX_PIXELS: - messages.error(request, _('Image dimensions are too big(max: %s x %s' % (MAX_PIXELS, MAX_PIXELS))) + dimensions['a'], dimensions['b'] = img.size # pylint: disable=invalid-name + 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))) return HttpResponseRedirect(reverse_lazy('profile')) - w = dimensions['w'] - h = dimensions['h'] - x = dimensions['x'] - y = dimensions['y'] - - if w == 0 and h == 0: - w, h = a, b - i = min(w, h) - w, h = i, i - elif w < 0 or (x + w) > a or h < 0 or (y + h) > b: + if dimensions['w'] == 0 and dimensions['h'] == 0: + dimensions['w'], dimensions['h'] = dimensions['a'], dimensions['b'] + min_from_w_h = min(dimensions['w'], dimensions['h']) + dimensions['w'], dimensions['h'] = min_from_w_h, min_from_w_h + elif dimensions['w'] < 0 or \ + (dimensions['x'] + dimensions['w']) > dimensions['a'] or \ + dimensions['h'] < 0 or \ + (dimensions['y'] + dimensions['h']) > dimensions['b']: messages.error(request, _('Crop outside of original image bounding box')) 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() # Resize the image only if it's larger than the specified max width. cropped_w, cropped_h = cropped.size @@ -200,12 +213,6 @@ class Photo(BaseAccountModel): data = BytesIO() cropped.save(data, pil_format(self.format), quality=JPEG_QUALITY) 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 self.data = data.read() @@ -214,12 +221,13 @@ class Photo(BaseAccountModel): 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 ''' - 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 ''' @@ -254,7 +262,7 @@ class ConfirmedEmail(BaseAccountModel): digest = models.CharField(max_length=64) objects = ConfirmedEmailManager() - class Meta: + class Meta: # pylint: disable=too-few-public-methods ''' Class attributes ''' @@ -268,14 +276,15 @@ class ConfirmedEmail(BaseAccountModel): self.photo = photo 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 ''' self.digest = hashlib.md5( self.email.strip().lower().encode('utf-8') ).hexdigest() - return super().save(*args, **kwargs) + return super().save(force_insert, force_update, using, update_fields) class UnconfirmedEmail(BaseAccountModel): @@ -285,18 +294,19 @@ class UnconfirmedEmail(BaseAccountModel): email = models.EmailField(max_length=MAX_LENGTH_EMAIL) verification_key = models.CharField(max_length=64) - class Meta: + class Meta: # pylint: disable=too-few-public-methods ''' Class attributes ''' verbose_name = _('unconfirmed_email') 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.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() - super(UnconfirmedEmail, self).save(*args, **kwargs) + super(UnconfirmedEmail, self).save(force_insert, force_update, using, update_fields) class UnconfirmedOpenId(BaseAccountModel): @@ -305,7 +315,10 @@ class UnconfirmedOpenId(BaseAccountModel): ''' 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_plural = ('unconfirmed_OpenIDs') @@ -325,15 +338,22 @@ class ConfirmedOpenId(BaseAccountModel): ) digest = models.CharField(max_length=64) - class Meta: + class Meta: # pylint: disable=too-few-public-methods + ''' + Meta class + ''' verbose_name = _('confirmed OpenID') verbose_name_plural = _('confirmed OpenIDs') def set_photo(self, photo): + ''' + Helper method to save photo + ''' self.photo = photo 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) if url.username: # pragma: no cover password = url.password or '' @@ -344,7 +364,7 @@ class ConfirmedOpenId(BaseAccountModel): (url.scheme.lower(), netloc, url.path, url.query, url.fragment) ) 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): @@ -383,7 +403,7 @@ class DjangoOpenIDStore(OpenIDStore): assoc = OpenIDAssociation( server_url=server_url, handle=association.handle, - secret=base64.encodestring(association.secret), + secret=base64.encodebytes(association.secret), issued=association.issued, lifetime=association.issued, assoc_type=association.assoc_type) @@ -395,25 +415,25 @@ class DjangoOpenIDStore(OpenIDStore): ''' assocs = [] if handle is not None: - assocs = OpenIDAssociation.objects.filter( + assocs = OpenIDAssociation.objects.filter( # pylint: disable=no-member server_url=server_url, handle=handle) else: - assocs = OpenIDAssociation.objects.filter(server_url=server_url) + assocs = OpenIDAssociation.objects.filter(server_url=server_url) # pylint: disable=no-member if not assocs: return None associations = [] 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 = bytes(assoc.secret, 'utf-8') association = OIDAssociation(assoc.handle, - base64.decodestring(assoc.secret), + base64.decodebytes(assoc.secret), assoc.issued, assoc.lifetime, assoc.assoc_type) expires = 0 try: - expires = association.getExpiresIn() - except Exception as e: + expires = association.getExpiresIn() # pylint: disable=no-member + except Exception as e: # pylint: disable=invalid-name,broad-except,unused-variable expires = association.expiresIn if expires == 0: self.removeAssociation(server_url, assoc.handle) @@ -429,7 +449,7 @@ class DjangoOpenIDStore(OpenIDStore): TODO: Could be moved to classmethod ''' assocs = list( - OpenIDAssociation.objects.filter( + OpenIDAssociation.objects.filter( # pylint: disable=no-member server_url=server_url, handle=handle)) assocs_exist = len(assocs) > 0 for assoc in assocs: @@ -445,12 +465,12 @@ class DjangoOpenIDStore(OpenIDStore): if abs(timestamp - time.time()) > oidnonce.SKEW: return False try: - nonce = OpenIDNonce.objects.get( + nonce = OpenIDNonce.objects.get( # pylint: disable=no-member server_url__exact=server_url, timestamp__exact=timestamp, salt__exact=salt) - except OpenIDNonce.DoesNotExist: - nonce = OpenIDNonce.objects.create( + except ObjectDoesNotExist: + nonce = OpenIDNonce.objects.create( # pylint: disable=no-member server_url=server_url, timestamp=timestamp, salt=salt) return True nonce.delete() @@ -462,12 +482,12 @@ class DjangoOpenIDStore(OpenIDStore): TODO: Could be moved to classmethod ''' 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 ''' Helper method to cleanup associations TODO: Could be moved to classmethod ''' - OpenIDAssociation.objects.extra( + OpenIDAssociation.objects.extra( # pylint: disable=no-member where=['issued + lifetimeint < (%s)' % time.time()]).delete() diff --git a/ivatar/ivataraccount/test_views.py b/ivatar/ivataraccount/test_views.py index 9349dcb..39d2009 100644 --- a/ivatar/ivataraccount/test_views.py +++ b/ivatar/ivataraccount/test_views.py @@ -410,7 +410,7 @@ class Tester(TestCase): follow=True, ) # Create test addresses + 1 too much self.assertFormError(response, 'form', None, - 'Too many unconfirmed mail addresses!') + 'Too many unconfirmed mail addresses!') def test_add_mail_address_twice(self): ''' diff --git a/ivatar/ivataraccount/urls.py b/ivatar/ivataraccount/urls.py index afaefdf..bf6871b 100644 --- a/ivatar/ivataraccount/urls.py +++ b/ivatar/ivataraccount/urls.py @@ -17,7 +17,7 @@ from . views import CropPhotoView # Define URL patterns, self documenting # To see the fancy, colorful evaluation of these use: # ./manager show_urls -urlpatterns = [ +urlpatterns = [ # pylint: disable=invalid-name path('new/', CreateView.as_view(), name='new_account'), path('login/', LoginView.as_view(template_name='login.html'), name='login'), @@ -38,39 +38,39 @@ urlpatterns = [ path('upload_photo/', UploadPhotoView.as_view(), name='upload_photo'), path('password_set/', PasswordSetView.as_view(), name='password_set'), url( - 'remove_unconfirmed_openid/(?P\d+)', + r'remove_unconfirmed_openid/(?P\d+)', RemoveUnconfirmedOpenIDView.as_view(), name='remove_unconfirmed_openid'), url( - 'remove_confirmed_openid/(?P\d+)', + r'remove_confirmed_openid/(?P\d+)', RemoveConfirmedOpenIDView.as_view(), name='remove_confirmed_openid'), url( - 'openid_redirection/(?P\d+)', + r'openid_redirection/(?P\d+)', RedirectOpenIDView.as_view(), name='openid_redirection'), url( - 'confirm_openid/(?P\w+)', + r'confirm_openid/(?P\w+)', ConfirmOpenIDView.as_view(), name='confirm_openid'), url( - 'confirm_email/(?P\w+)', + r'confirm_email/(?P\w+)', ConfirmEmailView.as_view(), name='confirm_email'), url( - 'remove_unconfirmed_email/(?P\d+)', + r'remove_unconfirmed_email/(?P\d+)', RemoveUnconfirmedEmailView.as_view(), name='remove_unconfirmed_email'), url( - 'remove_confirmed_email/(?P\d+)', + r'remove_confirmed_email/(?P\d+)', RemoveConfirmedEmailView.as_view(), name='remove_confirmed_email'), url( - 'assign_photo_email/(?P\d+)', + r'assign_photo_email/(?P\d+)', AssignPhotoEmailView.as_view(), name='assign_photo_email'), url( - 'assign_photo_openid/(?P\d+)', + r'assign_photo_openid/(?P\d+)', AssignPhotoOpenIDView.as_view(), name='assign_photo_openid'), url( - 'import_photo/(?P\d+)', + r'import_photo/(?P\d+)', ImportPhotoView.as_view(), name='import_photo'), url( - 'delete_photo/(?P\d+)', + r'delete_photo/(?P\d+)', DeletePhotoView.as_view(), name='delete_photo'), - url('raw_image/(?P\d+)', RawImageView.as_view(), name='raw_image'), - url('crop_photo/(?P\d+)', CropPhotoView.as_view(), name='crop_photo'), + url(r'raw_image/(?P\d+)', RawImageView.as_view(), name='raw_image'), + url(r'crop_photo/(?P\d+)', CropPhotoView.as_view(), name='crop_photo'), ] diff --git a/ivatar/settings.py b/ivatar/settings.py index 9eec7e5..9362941 100644 --- a/ivatar/settings.py +++ b/ivatar/settings.py @@ -5,15 +5,14 @@ Django settings for ivatar project. import os import logging -log_level = logging.DEBUG -logger = logging.getLogger('ivatar') +log_level = logging.DEBUG # pylint: disable=invalid-name +logger = logging.getLogger('ivatar') # pylint: disable=invalid-name logger.setLevel(log_level) PACKAGE_ROOT = os.path.abspath(os.path.dirname(__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! 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_ROOT = os.path.join(BASE_DIR, 'static') -from config import * # noqa +from config import * # pylint: disable=wildcard-import,wrong-import-position,unused-wildcard-import diff --git a/ivatar/test_wsgi.py b/ivatar/test_wsgi.py index 1513b07..2e016ba 100644 --- a/ivatar/test_wsgi.py +++ b/ivatar/test_wsgi.py @@ -1,3 +1,6 @@ +''' +Unit tests for WSGI +''' import unittest import os @@ -7,7 +10,13 @@ django.setup() class TestCase(unittest.TestCase): + ''' + Simple testcase to see if WSGI loads correctly + ''' def test_run_wsgi(self): + ''' + Run wsgi import + ''' import ivatar.wsgi self.assertEqual(ivatar.wsgi.application.__class__, django.core.handlers.wsgi.WSGIHandler) diff --git a/ivatar/urls.py b/ivatar/urls.py index 533a9d2..e4be7b4 100644 --- a/ivatar/urls.py +++ b/ivatar/urls.py @@ -1,5 +1,5 @@ ''' -ivatar URL Configuration +ivatar URL configuration ''' from django.contrib import admin from django.urls import path, include @@ -9,15 +9,15 @@ from django.views.generic import TemplateView from ivatar import settings from . views import AvatarImageView -urlpatterns = [ +urlpatterns = [ # pylint: disable=invalid-name path('admin/', admin.site.urls), url('openid/', include('django_openid_auth.urls')), url('accounts/', include('ivatar.ivataraccount.urls')), url( - 'avatar/(?P\w{64})', + r'avatar/(?P\w{64})', AvatarImageView.as_view(), name='avatar_view'), url( - 'avatar/(?P\w{32})', + r'avatar/(?P\w{32})', AvatarImageView.as_view(), name='avatar_view'), url('', TemplateView.as_view(template_name='home.html')), ] diff --git a/ivatar/utils.py b/ivatar/utils.py index 445d2c2..eb391d1 100644 --- a/ivatar/utils.py +++ b/ivatar/utils.py @@ -1,10 +1,13 @@ +''' +Simple module providing reusable random_string function +''' import random import string -def random_string(len=10): +def random_string(length=10): ''' Return some random string with default length 10 ''' return ''.join(random.SystemRandom().choice( - string.ascii_lowercase + string.digits) for _ in range(10)) + string.ascii_lowercase + string.digits) for _ in range(length)) diff --git a/ivatar/views.py b/ivatar/views.py index 7ef9194..df7ab3f 100644 --- a/ivatar/views.py +++ b/ivatar/views.py @@ -5,6 +5,7 @@ import io from django.views.generic.base import TemplateView from django.http import HttpResponse from . ivataraccount.models import ConfirmedEmail, ConfirmedOpenId +from django.core.exceptions import ObjectDoesNotExist class AvatarImageView(TemplateView): @@ -29,13 +30,14 @@ class AvatarImageView(TemplateView): try: obj = model.objects.get(digest=kwargs['digest']) - except model.DoesNotExist: + except ObjectDoesNotExist: # TODO: Use default!? raise Exception('Mail/openid ("%s") does not exist"' % kwargs['digest']) if not obj.photo: # That is hacky, but achieves what we want :-) attr = getattr(obj, 'email', obj.openid) + # TODO: Use default!? raise Exception('No photo assigned to "%s"' % attr) return HttpResponse( diff --git a/ivatar/wsgi.py b/ivatar/wsgi.py index 3ce7ccf..18866fb 100644 --- a/ivatar/wsgi.py +++ b/ivatar/wsgi.py @@ -13,4 +13,4 @@ from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ivatar.settings") -application = get_wsgi_application() +application = get_wsgi_application() # pylint: disable=invalid-name