diff --git a/ivatar/context_processors.py b/ivatar/context_processors.py index 4bd0575..3c68db3 100644 --- a/ivatar/context_processors.py +++ b/ivatar/context_processors.py @@ -6,6 +6,10 @@ from ivatar.settings import IVATAR_VERSION, SITE_NAME from ipware import get_client_ip def basepage(request): + ''' + Our contextprocessor adds additional context variables + in order to be used in the templates + ''' context = {} if 'openid_identifier' in request.GET: context['openid_identifier'] = request.GET['openid_identifier'] diff --git a/ivatar/ivataraccount/admin.py b/ivatar/ivataraccount/admin.py index 8c38f3f..ac980ec 100644 --- a/ivatar/ivataraccount/admin.py +++ b/ivatar/ivataraccount/admin.py @@ -1,3 +1,12 @@ from django.contrib import admin -# Register your models here. +from . models import Photo, ConfirmedEmail, UnconfirmedEmail +from . models import ConfirmedOpenId, OpenIDNonce, OpenIDAssociation + +# Register models in admin +admin.site.register(Photo) +admin.site.register(ConfirmedEmail) +admin.site.register(UnconfirmedEmail) +admin.site.register(ConfirmedOpenId) +admin.site.register(OpenIDNonce) +admin.site.register(OpenIDAssociation) diff --git a/ivatar/ivataraccount/forms.py b/ivatar/ivataraccount/forms.py index 5314ddf..7857fd7 100644 --- a/ivatar/ivataraccount/forms.py +++ b/ivatar/ivataraccount/forms.py @@ -12,6 +12,9 @@ from ivatar.settings import MAX_LENGTH_EMAIL from ipware import get_client_ip class AddEmailForm(forms.Form): + ''' + Form to handle adding email addresses + ''' email = forms.EmailField( label=_('Email'), max_length=MAX_LENGTH_EMAIL, @@ -26,6 +29,9 @@ class AddEmailForm(forms.Form): return self.cleaned_data['email'].lower() def save(self, user): + ''' + Save the model, ensuring some safety + ''' # Enforce the maximum number of unconfirmed emails a user can have num_unconfirmed = user.unconfirmedemail_set.count() @@ -63,6 +69,9 @@ class AddEmailForm(forms.Form): class UploadPhotoForm(forms.Form): + ''' + Form handling photo upload + ''' photo = forms.FileField( label=_('Photo'), error_messages={'required': _('You must choose an image to upload.')}) @@ -82,6 +91,9 @@ class UploadPhotoForm(forms.Form): }) def save(self, request, data): + ''' + Save the model and assign it to the current user + ''' # Link this file to the user's profile photo = Photo() photo.user = request.user diff --git a/ivatar/ivataraccount/gravatar.py b/ivatar/ivataraccount/gravatar.py index e6a84a8..e463be9 100644 --- a/ivatar/ivataraccount/gravatar.py +++ b/ivatar/ivataraccount/gravatar.py @@ -5,6 +5,9 @@ import hashlib URL_TIMEOUT = 5 # in seconds def get_photo(email): + ''' + Fetch photo from Gravatar, given an email address + ''' hash_object = hashlib.new('md5') hash_object.update(email.lower().encode('utf-8')) thumbnail_url = 'https://secure.gravatar.com/avatar/' + hash_object.hexdigest( diff --git a/ivatar/ivataraccount/models.py b/ivatar/ivataraccount/models.py index 7144d73..185bdd5 100644 --- a/ivatar/ivataraccount/models.py +++ b/ivatar/ivataraccount/models.py @@ -24,6 +24,9 @@ MAX_LENGTH_URL = 255 # MySQL can't handle more than that (LP: 1018682) def file_format(image_type): + ''' + Helper method returning a 3 character long image type + ''' if image_type == 'JPEG': return 'jpg' elif image_type == 'PNG': @@ -36,6 +39,9 @@ def file_format(image_type): class BaseAccountModel(models.Model): + ''' + Base, abstract model, holding fields we use in all cases + ''' user = models.ForeignKey( User, on_delete=models.deletion.CASCADE, @@ -48,6 +54,9 @@ class BaseAccountModel(models.Model): class Photo(BaseAccountModel): + ''' + Model holding the photos and information about them + ''' ip_address = models.GenericIPAddressField(unpack_ipv4=True) data = models.BinaryField() format = models.CharField(max_length=3) @@ -89,6 +98,9 @@ class Photo(BaseAccountModel): return True class ConfirmedEmailManager(models.Manager): + ''' + Manager for our confirmed email addresses model + ''' def create_confirmed_email(self, user, email_address, is_logged_in): confirmed = ConfirmedEmail() confirmed.user = user @@ -106,6 +118,10 @@ class ConfirmedEmailManager(models.Manager): class ConfirmedEmail(BaseAccountModel): + ''' + Model holding our confirmed email addresses, as well as the relation + to the assigned photo + ''' email = models.EmailField(unique=True, max_length=MAX_LENGTH_EMAIL) photo = models.ForeignKey( Photo, @@ -126,6 +142,9 @@ class ConfirmedEmail(BaseAccountModel): class UnconfirmedEmail(BaseAccountModel): + ''' + Model holding unconfirmed email addresses as well as the verification key + ''' email = models.EmailField(max_length=MAX_LENGTH_EMAIL) verification_key = models.CharField(max_length=64) @@ -141,6 +160,9 @@ class UnconfirmedEmail(BaseAccountModel): class UnconfirmedOpenId(BaseAccountModel): + ''' + Model holding unconfirmed OpenIDs + ''' openid = models.URLField(unique=False, max_length=MAX_LENGTH_URL) class Meta: @@ -149,6 +171,10 @@ class UnconfirmedOpenId(BaseAccountModel): class ConfirmedOpenId(BaseAccountModel): + ''' + Model holding confirmed OpenIDs, as well as the relation to + the assigned photo + ''' openid = models.URLField(unique=True, max_length=MAX_LENGTH_URL) photo = models.ForeignKey( Photo, @@ -163,15 +189,19 @@ class ConfirmedOpenId(BaseAccountModel): verbose_name_plural = _('confirmed OpenIDs') - - -# Classes related to the OpenID Store (from https://github.com/edx/django-openid-auth/) class OpenIDNonce(models.Model): + ''' + Model holding OpenID Nonces + See also: https://github.com/edx/django-openid-auth/ + ''' server_url = models.CharField(max_length=255) timestamp = models.IntegerField() salt = models.CharField(max_length=128) class OpenIDAssociation(models.Model): + ''' + Model holding the relation/association about OpenIDs + ''' server_url = models.TextField(max_length=2047) handle = models.CharField(max_length=255) secret = models.TextField(max_length=255) # stored base64 encoded @@ -187,7 +217,10 @@ class DjangoOpenIDStore(OpenIDStore): ''' def storeAssociation(self, server_url, association): - # pylint: disable=unexpected-keyword-arg + ''' + Helper method to store associations + TODO: Could be moved to classmethod + ''' assoc = OpenIDAssociation( server_url=server_url, handle=association.handle, @@ -198,6 +231,9 @@ class DjangoOpenIDStore(OpenIDStore): assoc.save() def getAssociation(self, server_url, handle=None): + ''' + Helper method to get associations + ''' assocs = [] if handle is not None: assocs = OpenIDAssociation.objects.filter( @@ -229,6 +265,10 @@ class DjangoOpenIDStore(OpenIDStore): return associations[-1][1] def removeAssociation(self, server_url, handle): + ''' + Helper method to remove associations + TODO: Could be moved to classmethod + ''' assocs = list( OpenIDAssociation.objects.filter( server_url=server_url, handle=handle)) @@ -238,6 +278,10 @@ class DjangoOpenIDStore(OpenIDStore): return assocs_exist def useNonce(self, server_url, timestamp, salt): + ''' + Helper method to 'use' nonces + TODO: Could be moved to classmethod + ''' # Has nonce expired? if abs(timestamp - time.time()) > oidnonce.SKEW: return False @@ -254,15 +298,25 @@ class DjangoOpenIDStore(OpenIDStore): return False def cleanupNonces(self): + ''' + Helper method to cleanup nonces + TODO: Could be moved to classmethod + ''' timestamp = int(time.time()) - oidnonce.SKEW OpenIDNonce.objects.filter(timestamp__lt=timestamp).delete() def cleanupAssociations(self): + ''' + Helper method to cleanup associations + TODO: Could be moved to classmethod + ''' OpenIDAssociation.objects.extra( where=['issued + lifetimeint < (%s)' % time.time()]).delete() - # pylint: disable=invalid-name def getAuthKey(self): + ''' + Helper method to get authentication key + ''' # Use first AUTH_KEY_LEN characters of md5 hash of SECRET_KEY hash_object = hashlib.new('md5') hash_object.update(settings.SECRET_KEY) diff --git a/ivatar/ivataraccount/urls.py b/ivatar/ivataraccount/urls.py index 6836931..155c506 100644 --- a/ivatar/ivataraccount/urls.py +++ b/ivatar/ivataraccount/urls.py @@ -9,6 +9,9 @@ from . views import ImportPhotoView, RawImageView, DeletePhotoView, UploadPhotoV from django.contrib.auth.views import login, logout, password_change, password_change_done from django.urls import reverse_lazy +# Define URL patterns, self documenting +# To see the fancy, colorful evaluation of these use: +# ./manager show_urls urlpatterns = [ path('new/', CreateView.as_view(), name='new_account'), path('login/', login, { 'template_name': 'login.html' }, name='login'), diff --git a/ivatar/ivataraccount/views.py b/ivatar/ivataraccount/views.py index b7ab319..51fd644 100644 --- a/ivatar/ivataraccount/views.py +++ b/ivatar/ivataraccount/views.py @@ -26,6 +26,9 @@ from ipware import get_client_ip from . gravatar import get_photo as get_gravatar_photo class CreateView(SuccessMessageMixin, FormView): + ''' + TODO: Docs + ''' template_name = 'new.html' form_class = UserCreationForm success_message = _('created successfully')