From c94e7b46266500e6b79c360a077598238c826d94 Mon Sep 17 00:00:00 2001 From: Paul Marillonnet Date: Tue, 4 Sep 2018 16:26:15 +0200 Subject: [PATCH] WIP support avatar picture in user profile (#26022) --- src/authentic2/attribute_kinds.py | 10 +++- src/authentic2/custom_user/apps.py | 20 +++++-- .../templates/authentic2/accounts_edit.html | 7 ++- src/authentic2/utils.py | 58 ++++++++++++++++++- 4 files changed, 86 insertions(+), 9 deletions(-) diff --git a/src/authentic2/attribute_kinds.py b/src/authentic2/attribute_kinds.py index 27ae00b4..960a7ae3 100644 --- a/src/authentic2/attribute_kinds.py +++ b/src/authentic2/attribute_kinds.py @@ -17,6 +17,7 @@ from .decorators import to_iter from .plugins import collect_from_plugins from . import app_settings from .forms import widgets +from .utils import store_image capfirst = allow_lazy(capfirst, unicode) @@ -151,9 +152,16 @@ DEFAULT_ATTRIBUTE_KINDS = [ 'field_class': PhoneNumberField, 'rest_framework_field_class': PhoneNumberDRFField, }, + { + 'label': _('image'), + 'name': 'image', + 'field_class': forms.ImageField, + 'serialize': store_image, + 'deserialize': lambda x: x, + 'rest_framework_field_class': serializers.ImageField, + }, ] - def get_attribute_kinds(): attribute_kinds = {} for attribute_kind in chain(DEFAULT_ATTRIBUTE_KINDS, app_settings.A2_ATTRIBUTE_KINDS): diff --git a/src/authentic2/custom_user/apps.py b/src/authentic2/custom_user/apps.py index d220422c..1f35fa11 100644 --- a/src/authentic2/custom_user/apps.py +++ b/src/authentic2/custom_user/apps.py @@ -10,10 +10,10 @@ class CustomUserConfig(AppConfig): from django.db.models.signals import post_migrate post_migrate.connect( - self.create_first_name_last_name_attributes, + self.create_custom_attributes, sender=self) - def create_first_name_last_name_attributes(self, app_config, verbosity=2, interactive=True, + def create_custom_attributes(self, app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs): from django.utils import translation from django.utils.translation import ugettext_lazy as _ @@ -50,16 +50,24 @@ class CustomUserConfig(AppConfig): 'asked_on_registration': True, 'user_editable': True, 'user_visible': True}) + attrs['avatar_picture'], created = Attribute.objects.get_or_create( + name='avatar_picture', + defaults={'kind': 'image', + 'label': _('Avatar picture'), + 'required': False, + 'asked_on_registration': False, + 'user_editable': True, + 'user_visible': True}) - serialize = get_kind('string').get('serialize') for user in User.objects.all(): - for attr_name in attrs: + for at in attrs: + serialize = get_kind(at.kind).get('serialize') av, created = AttributeValue.objects.get_or_create( content_type=content_type, object_id=user.id, - attribute=attrs[attr_name], + attribute=attrs[at], defaults={ 'multiple': False, 'verified': False, - 'content': serialize(getattr(user, attr_name, None)) + 'content': serialize(getattr(user, at, None)) }) diff --git a/src/authentic2/templates/authentic2/accounts_edit.html b/src/authentic2/templates/authentic2/accounts_edit.html index c7587b14..a642b1b7 100644 --- a/src/authentic2/templates/authentic2/accounts_edit.html +++ b/src/authentic2/templates/authentic2/accounts_edit.html @@ -12,7 +12,12 @@ {% endblock %} {% block content %} -
+ {% if form.is_multipart %} + + {% else %} + + {% endif %} + {% csrf_token %} {{ form.as_p }} {% if form.instance and form.instance.id %} diff --git a/src/authentic2/utils.py b/src/authentic2/utils.py index d32a5a67..fc602830 100644 --- a/src/authentic2/utils.py +++ b/src/authentic2/utils.py @@ -8,11 +8,15 @@ import urlparse import uuid import datetime import copy +import fcntl +import os from functools import wraps from itertools import islice, chain, count - +from PIL import Image from importlib import import_module +from hashlib import md5 +from base64 import b32encode from django.conf import settings from django.http import HttpResponseRedirect, HttpResponse @@ -30,6 +34,7 @@ from django.shortcuts import resolve_url from django.template.loader import render_to_string, TemplateDoesNotExist from django.core.mail import send_mail from django.core import signing +from django.core.files.storage import default_storage from django.core.urlresolvers import reverse, NoReverseMatch from django.utils.formats import localize from django.contrib import messages @@ -1073,3 +1078,54 @@ def get_user_flag(user, name, default=None): if ou_value is not None: return ou_value return default + + +def store_image(image, owner_uuid, *args, **kwargs): + logger = logging.getLogger(__name__) + + digest = md5(value.read()) + image_uuid = b32encode(digest)[0:63] + image_format = image.image.format + + filefolder = default_storage.path('{root}/avatars/{oid}'.format( + root=app_settings.MEDIA_ROOT, + oid=owner_uuid)) + + filepath = os.path.join( + filefolder, + "{uuid}.{ext}".format(uuid=image_uuid, ext=image_format)) + + try: + if not os.path.exists(local_folder): + os.makesdir(local_folder) + + with open(filepath, 'wb') as f: + try: + fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB) + with tempfile.NamedTemporaryFile(mode='w', dir=folder, delete=False) as temp: + try: + image.seek(0) + temp.write(image.read()) + temp.flush() + os.rename(temp.name, filepath) + pass + except: + logger.error("Could'nt store fingerprint for entity ID", entity_id) + os.unlink(temp.name) + finally: + fcntl.lockf(f, fcntl.LOCK_UN) + except: + logger.error("Couldn't hold exclusive lock for file {}".format(filepath)) + finally: + fctnl.lockf(f, fcntl.LOCK_UN) + except IOError: + return + + return filepath + + +def retrieve_image_url(image_filename): + media_url = app_settings.MEDIA_URL + # xxx retrieve everything after MEDIA_ROOT, concatenate craft the + # appropriate media url + pass -- 2.19.0.rc1