From 28a2a9c58a70cbff5bdfed7bc60ab3fcf7e57c1c 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) --- debian-jessie/control | 3 +- debian-wheezy/control | 3 +- setup.py | 1 + src/authentic2/api_views.py | 5 +- src/authentic2/attribute_kinds.py | 31 ++++ src/authentic2/custom_user/apps.py | 20 ++- src/authentic2/custom_user/models.py | 4 +- src/authentic2/forms/widgets.py | 21 ++- src/authentic2/models.py | 29 ++- src/authentic2/settings.py | 3 + .../templates/authentic2/accounts.html | 23 ++- .../templates/authentic2/accounts_edit.html | 7 +- .../registration/registration_form.html | 7 +- src/authentic2/urls.py | 4 + src/authentic2/utils.py | 94 ++++++++++ tests/test_api.py | 5 +- tests/test_attribute_kinds.py | 166 ++++++++++++++++++ tests/test_manager.py | 23 +-- tests/test_profile.py | 19 +- tox.ini | 2 + 20 files changed, 423 insertions(+), 47 deletions(-) diff --git a/debian-jessie/control b/debian-jessie/control index 1a419007..e3db3264 100644 --- a/debian-jessie/control +++ b/debian-jessie/control @@ -28,7 +28,8 @@ Depends: ${misc:Depends}, ${python:Depends}, python-jwcrypto (>= 0.3.1), python-cryptography (>= 1.3.4), python-django-filters (>= 1), - python-django-filters (<< 2) + python-django-filters (<< 2), + python-sorl-thumbnail Provides: ${python:Provides} Recommends: python-ldap Suggests: python-raven diff --git a/debian-wheezy/control b/debian-wheezy/control index b8039228..3f027bfa 100644 --- a/debian-wheezy/control +++ b/debian-wheezy/control @@ -25,7 +25,8 @@ Depends: ${misc:Depends}, ${python:Depends}, python-markdown (>= 2.1), python-ldap (>= 2.4), python-six (>= 1.0), - python-django-filters (>= 1) + python-django-filters (>= 1), + python-sorl-thumbnail Provides: ${python:Provides} Recommends: python-ldap Suggests: python-raven diff --git a/setup.py b/setup.py index 241aa73d..41ce1949 100755 --- a/setup.py +++ b/setup.py @@ -131,6 +131,7 @@ setup(name="authentic2", 'XStatic-jQuery', 'XStatic-jquery-ui<1.12', 'xstatic-select2', + 'sorl-thumbnail', ], zip_safe=False, classifiers=[ diff --git a/src/authentic2/api_views.py b/src/authentic2/api_views.py index b6fa431a..7ece0d8c 100644 --- a/src/authentic2/api_views.py +++ b/src/authentic2/api_views.py @@ -298,9 +298,10 @@ password_change = PasswordChange.as_view() @cache_control(private=True, max_age=60) @decorators.json def user(request): - if request.user.is_anonymous(): + u = request.user + if u.is_anonymous(): return {} - return request.user.to_json() + return u.to_json(request) def attributes_hash(attributes): diff --git a/src/authentic2/attribute_kinds.py b/src/authentic2/attribute_kinds.py index 27ae00b4..744423a7 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 image_serialize capfirst = allow_lazy(capfirst, unicode) @@ -91,6 +92,11 @@ class FrPostcodeDRFField(serializers.CharField): default_validators = [validate_fr_postcode] +class A2ImageDRFField(serializers.ImageField): + def to_representation(self, value, *args, **kwargs): + return self.context['request'].build_absolute_uri(value) + + DEFAULT_ALLOW_BLANK = True DEFAULT_MAX_LENGTH = 256 @@ -151,6 +157,31 @@ DEFAULT_ATTRIBUTE_KINDS = [ 'field_class': PhoneNumberField, 'rest_framework_field_class': PhoneNumberDRFField, }, + { + 'label': _('image'), + 'name': 'image', + 'field_class': forms.ImageField, + 'kwargs': { + 'widget': widgets.AttributeImageInput, + }, + 'serialize': image_serialize, + 'serialize_eval_kwargs' : { + 'owner_uuid': 'owner.uuid', + 'owner_pk': 'owner.pk', + 'attr_label': 'self.label', + }, + 'deserialize': lambda x: x, + 'rest_framework_field_class': A2ImageDRFField, + 'rest_framework_field_kwargs': { + 'read_only': True, + }, + 'value_is_relative_uri': True, + 'thumbnail': { + 'crop': 'center', + 'height': '100', + 'width': '100', + }, + }, ] diff --git a/src/authentic2/custom_user/apps.py b/src/authentic2/custom_user/apps.py index d220422c..442cf919 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 _ @@ -34,6 +34,14 @@ class CustomUserConfig(AppConfig): content_type = ContentType.objects.get_for_model(User) attrs = {} + 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}) attrs['first_name'], created = Attribute.objects.get_or_create( name='first_name', defaults={'kind': 'string', @@ -51,15 +59,15 @@ class CustomUserConfig(AppConfig): '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/custom_user/models.py b/src/authentic2/custom_user/models.py index ea3f4fba..2be14220 100644 --- a/src/authentic2/custom_user/models.py +++ b/src/authentic2/custom_user/models.py @@ -219,10 +219,10 @@ class User(AbstractBaseUser, PermissionMixin): def has_verified_attributes(self): return AttributeValue.objects.with_owner(self).filter(verified=True).exists() - def to_json(self): + def to_json(self, request): d = {} for av in AttributeValue.objects.with_owner(self): - d[str(av.attribute.name)] = av.to_python() + d[str(av.attribute.name)] = av.to_python(request) d.update({ 'uuid': self.uuid, 'username': self.username, diff --git a/src/authentic2/forms/widgets.py b/src/authentic2/forms/widgets.py index c3d1dda2..db0226c9 100644 --- a/src/authentic2/forms/widgets.py +++ b/src/authentic2/forms/widgets.py @@ -11,7 +11,8 @@ import json import re import uuid -from django.forms.widgets import DateTimeInput, DateInput, TimeInput +from django.forms.widgets import DateTimeInput, DateInput, TimeInput, \ + ClearableFileInput from django.forms.widgets import PasswordInput as BasePasswordInput from django.utils.formats import get_language, get_format from django.utils.safestring import mark_safe @@ -246,3 +247,21 @@ class CheckPasswordInput(PasswordInput): json.dumps(_id), ) return output + + +class AttributeImageInput(ClearableFileInput): + # template_name = "authentic2/accounts_image.html" # Django 1.11 only todo + template_with_initial = ( + '%(initial_text)s:

' + '%(clear_template)s
%(input_text)s: %(input)s' + ) + + def is_initial(self, value): + return bool(value) + + def get_template_substitution_values(self, value): + subs_values = dict() + subs_values.update({ + 'initial': value, + }) + return subs_values diff --git a/src/authentic2/models.py b/src/authentic2/models.py index b14f3085..9de3d68f 100644 --- a/src/authentic2/models.py +++ b/src/authentic2/models.py @@ -1,3 +1,4 @@ +import logging import time import urlparse import uuid @@ -203,6 +204,7 @@ class Attribute(models.Model): return kind['default'] def set_value(self, owner, value, verified=False): + logger = logging.getLogger(__name__) serialize = self.get_kind()['serialize'] # setting to None is to delete if value is None: @@ -225,7 +227,22 @@ class Attribute(models.Model): av.verified = verified av.save() else: - content = serialize(value) + kwargs = dict() + for key, flat_value in self.get_kind().get('serialize_eval_kwargs', {}).items(): + try: + evalue = eval(flat_value) + except NameError: + logger.error("Couldn't evaluate {} for attribute <{}: {}>".format( + flat_value, + self.get_kind()['kind'], + self.label)) + continue + kwargs.update({key: evalue}) + if kwargs: + content = serialize(value, **kwargs) + else: + content = serialize(value) + av, created = AttributeValue.objects.get_or_create( content_type=ContentType.objects.get_for_model(owner), object_id=owner.pk, @@ -275,9 +292,13 @@ class AttributeValue(models.Model): objects = managers.AttributeValueManager() - def to_python(self): - deserialize = self.attribute.get_kind()['deserialize'] - return deserialize(self.content) + def to_python(self, request=None): + kind = self.attribute.get_kind() + deserialize = kind['deserialize'] + content = self.content + if request and kind.get('value_is_relative_uri'): + content = request.build_absolute_uri(content) + return deserialize(content) def natural_key(self): if not hasattr(self.owner, 'natural_key'): diff --git a/src/authentic2/settings.py b/src/authentic2/settings.py index c045ce2a..bf0378cb 100644 --- a/src/authentic2/settings.py +++ b/src/authentic2/settings.py @@ -22,6 +22,8 @@ SECRET_KEY = 'please-change-me-with-a-very-long-random-string' DEBUG = False DEBUG_DB = False MEDIA = 'media' +MEDIA_ROOT = 'media' +MEDIA_URL = '/media/' # See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts ALLOWED_HOSTS = [] @@ -132,6 +134,7 @@ INSTALLED_APPS = ( 'xstatic.pkg.jquery', 'xstatic.pkg.jquery_ui', 'xstatic.pkg.select2', + 'sorl.thumbnail', ) INSTALLED_APPS = tuple(plugins.register_plugins_installed_apps(INSTALLED_APPS)) diff --git a/src/authentic2/templates/authentic2/accounts.html b/src/authentic2/templates/authentic2/accounts.html index 4e9f979f..ca250c21 100644 --- a/src/authentic2/templates/authentic2/accounts.html +++ b/src/authentic2/templates/authentic2/accounts.html @@ -1,5 +1,6 @@ {% extends "authentic2/base-page.html" %} {% load i18n %} +{% load thumbnail %} {% block page-title %} {{ block.super }} - {{ view.title }} @@ -18,14 +19,22 @@ {% for attribute in attributes %}
{{ attribute.attribute.label|capfirst }} :
- {% if attribute.values|length == 1 %} - {{ attribute.values.0 }} + {% if attribute.attribute.kind == 'image' %} + {% with request.is_secure|yesno:"https://,http://"|add:request.get_host|add:attribute.values.0 as im_url %} + {% thumbnail im_url "100x100" crop="center" as thumb_im %} + + {% endthumbnail %} + {% endwith %} {% else %} - + {% if attribute.values|length == 1 %} + {{ attribute.values.0 }} + {% else %} + + {% endif %} {% endif %}
{% endfor %} 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/templates/registration/registration_form.html b/src/authentic2/templates/registration/registration_form.html index 292cf023..09941a64 100644 --- a/src/authentic2/templates/registration/registration_form.html +++ b/src/authentic2/templates/registration/registration_form.html @@ -15,7 +15,12 @@

{{ view.title }}

- + {% if form.is_multipart %} + + {% else %} + + {% endif %} + {% csrf_token %} {{ form.as_p }} diff --git a/src/authentic2/urls.py b/src/authentic2/urls.py index 35b13139..048a6396 100644 --- a/src/authentic2/urls.py +++ b/src/authentic2/urls.py @@ -44,6 +44,10 @@ if settings.DEBUG: urlpatterns += [ url(r'^static/(?P.*)$', serve) ] + urlpatterns += [ + url(r'^media/(?P.*)$', 'django.views.static.serve', { + 'document_root': settings.MEDIA_ROOT}) + ] if settings.DEBUG and 'debug_toolbar' in settings.INSTALLED_APPS: import debug_toolbar diff --git a/src/authentic2/utils.py b/src/authentic2/utils.py index d32a5a67..60b88fae 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 +import tempfile from functools import wraps from itertools import islice, chain, count from importlib import import_module +from hashlib import md5 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,92 @@ def get_user_flag(user, name, default=None): if ou_value is not None: return ou_value return default + + +def _store_image(in_memory_image, owner_uuid, attr_label): + logger = logging.getLogger(__name__) + + digest = md5(in_memory_image.read()) + + img_id = digest.hexdigest() + img_ext = in_memory_image.image.format + + img_media_dir = '{label}/{oid}/'.format( + oid=owner_uuid, + label=attr_label) + img_media_path = '{imdir}/{iid}.{ext}'.format( + imdir=img_media_dir, iid=img_id, ext=img_ext) + + img_abs_path = default_storage.path(img_media_path) + img_abs_dir = default_storage.path(img_media_dir) + + try: + if not os.path.exists(img_abs_dir): + os.makedirs(img_abs_dir) + + with open(img_abs_path, 'wb') as f: + try: + fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB) + with tempfile.NamedTemporaryFile(mode='wb', dir=img_abs_dir, delete=False) as temp: + try: + in_memory_image.seek(0) + temp.write(in_memory_image.read()) + temp.flush() + os.rename(temp.name, img_abs_path) + except: + logger.error("Could'nt store image to {}".format(img_abs_path)) + os.unlink(temp.name) + except: + logger.error("Couldn't hold exclusive lock for file {}".format(img_abs_path)) + finally: + fcntl.lockf(f, fcntl.LOCK_UN) + except IOError: + return + + return img_media_path + + +def _delete_images_from_user(owner_pk, attr_label): + from .models import Attribute, AttributeValue + + logger = logging.getLogger(__name__) + User = get_user_model() + + try: + owner = User.objects.get(pk=owner_pk) + except User.DoesNotExist: + logger.error("Primary key {} doesn't match with any user.".format(owner_pk)) + return + + try: + attr = Attribute.objects.get(label=attr_label) + all_values = AttributeValue.objects.with_owner(owner) + values = all_values.filter(attribute=attr) + except: + logger.error("Couldn't retrieve values for Attribute {}.".format(attr_label)) + + for value in values: + # Direct URI <-> file location correspondence + local_file = value.content.split(default_storage.base_url)[-1] + if not local_file: + continue + media_file = default_storage.path(local_file) + + try: + os.remove(media_file) + value.delete() + except: + logger.error("Could'nt delete image {}".format(media_file)) + + +def image_serialize(image, owner_uuid, owner_pk, attr_label): + uri = '' + if isinstance(image, basestring): + uri = image + else: + # Discard previous user avatars + _delete_images_from_user(owner_pk, attr_label) + if image: + img_media_path = _store_image(image, owner_uuid, attr_label) + uri = os.path.join(settings.MEDIA_URL, img_media_path) + return uri diff --git a/tests/test_api.py b/tests/test_api.py index 240b0382..f1f89f5b 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -229,9 +229,10 @@ def test_api_users_create(settings, app, api_user): resp = app.post_json('/api/users/', params=payload, status=status) if api_user.is_superuser or api_user.roles.exists(): - assert set(['ou', 'id', 'uuid', 'is_staff', 'is_superuser', 'first_name', 'last_name', + assert (set(['ou', 'id', 'uuid', 'is_staff', 'is_superuser', 'first_name', 'last_name', 'date_joined', 'last_login', 'username', 'password', 'email', 'is_active', - 'title', 'modified', 'email_verified']) == set(resp.json.keys()) + 'title', 'modified', 'email_verified', 'avatar_picture']) == + set(resp.json.keys())) assert resp.json['first_name'] == payload['first_name'] assert resp.json['last_name'] == payload['last_name'] assert resp.json['email'] == payload['email'] diff --git a/tests/test_attribute_kinds.py b/tests/test_attribute_kinds.py index b6ecf052..d98cff98 100644 --- a/tests/test_attribute_kinds.py +++ b/tests/test_attribute_kinds.py @@ -279,3 +279,169 @@ def test_phone_number(db, app, admin, mailoutbox): app.post_json('/api/users/', params=payload) assert qs.get().attributes.phone_number == '' qs.delete() + + +def test_image(db, app, admin, mailoutbox): + from webtest import Upload + from hashlib import md5 + + Attribute.objects.create(name='cityscape_image', label='cityscape', kind='image', + asked_on_registration=True) + qs = User.objects.filter(first_name='John') + + + response = app.get('/accounts/register/') + form = response.form + form.set('email', 'john.doe@example.com') + response = form.submit().follow() + assert 'john.doe@example.com' in response + url = get_link_from_mail(mailoutbox[0]) + response = app.get(url) + + form = response.form + form.set('first_name', 'John') + form.set('last_name', 'Doe') + form.set('cityscape_image', Upload('/dev/null')) + form.set('password1', '12345abcdA') + form.set('password2', '12345abcdA') + response = form.submit() + assert response.pyquery.find('.form-field-error #id_cityscape_image') + + digest = md5(open('tests/cityscape.png').read()) + img_id = digest.hexdigest() + + form = response.form + form.set('cityscape_image', Upload('tests/cityscape.png')) + form.set('password1', '12345abcdA') + form.set('password2', '12345abcdA') + response = form.submit() + assert img_id in qs.get().attributes.cityscape_image + + app.authorization = ('Basic', (admin.username, admin.username)) + + resp = app.get('/api/users/?first_name=John&last_name=Doe') + assert img_id in resp.json_body['results'][0]['cityscape_image'] + + qs.delete() + + response = app.get(url) + form = response.form + form.set('first_name', 'John') + form.set('last_name', 'Doe') + form.set('cityscape_image', None) + form.set('password1', '12345abcdA') + form.set('password2', '12345abcdA') + response = form.submit().follow() + assert qs.get().attributes.cityscape_image == None + qs.delete() + +def test_images_delete_on_form_field_clearance(db, app, admin, mailoutbox): + from django.core.files.storage import default_storage + from hashlib import md5 + from webtest import Upload + + Attribute.objects.create( + name='cityscape_image', label='cityscape', kind='image', + asked_on_registration=False, required=False, + user_visible=True, user_editable=True) + Attribute.objects.create( + name='garden_image', label='garden', kind='image', + asked_on_registration=False, required=False, + user_visible=True, user_editable=True) + + qs = User.objects.filter(first_name='John') + + response = app.get('/accounts/register/') + form = response.form + form.set('email', 'john.doe@example.com') + response = form.submit().follow() + assert 'john.doe@example.com' in response + url = get_link_from_mail(mailoutbox[0]) + response = app.get(url) + + form = response.form + form.set('first_name', 'John') + form.set('last_name', 'Doe') + form.set('password1', '12345abcdA') + form.set('password2', '12345abcdA') + response = form.submit() + + john = qs.get() + assert john + digest1 = md5(open('tests/cityscape.png').read()) + img_path1 = 'cityscape/{oid}/{iid}.PNG'.format( + oid=john.uuid, + iid=digest1.hexdigest()) + digest2 = md5(open('tests/garden.png').read()) + img_path2 = 'garden/{oid}/{iid}.PNG'.format( + oid=john.uuid, + iid=digest2.hexdigest()) + + assert not default_storage.exists(img_path1) + assert not default_storage.exists(img_path2) + + response = app.get('/accounts/edit/') + form = response.form + form.set('edit-profile-cityscape_image', Upload('tests/cityscape.png')) + response = form.submit() + + assert default_storage.exists(img_path1) + assert not default_storage.exists(img_path2) + + response = app.get('/accounts/edit/') + form = response.form + form.set('edit-profile-garden_image', Upload('tests/garden.png')) + response = form.submit() + + assert default_storage.exists(img_path1) + assert default_storage.exists(img_path2) + + response = app.get('/accounts/edit/') + form = response.form + form.set('edit-profile-cityscape_image-clear', True) + response = form.submit() + + assert not default_storage.exists(img_path1) + assert default_storage.exists(img_path2) + + response = app.get('/accounts/edit/') + form = response.form + form.set('edit-profile-garden_image-clear', True) + response = form.submit() + + assert not default_storage.exists(img_path1) + assert not default_storage.exists(img_path2) + + +def test_images_account_registration(db, app, admin, mailoutbox): + from webtest import Upload + + Attribute.objects.create(name='cityscape_image', label='cityscape', kind='image', + asked_on_registration=True) + Attribute.objects.create(name='garden_image', label='garden', kind='image', + asked_on_registration=True) + qs = User.objects.filter(first_name='John') + + response = app.get('/accounts/register/') + form = response.form + form.set('email', 'john.doe@example.com') + response = form.submit().follow() + assert 'john.doe@example.com' in response + url = get_link_from_mail(mailoutbox[0]) + response = app.get(url) + + form = response.form + assert form.get('cityscape_image') + assert form.get('garden_image') + form.set('first_name', 'John') + form.set('last_name', 'Doe') + form.set('cityscape_image', Upload('tests/cityscape.png')) + form.set('garden_image', Upload('tests/garden.png')) + form.set('password1', '12345abcdA') + form.set('password2', '12345abcdA') + response = form.submit() + + john = qs.get() + assert john.attributes.cityscape_image + assert john.attributes.garden_image + john.delete() diff --git a/tests/test_manager.py b/tests/test_manager.py index f9ef9471..0fc6a2b0 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -105,17 +105,18 @@ def test_manager_user_password_reset(app, superuser, simple_user): resp = login(app, superuser, reverse('a2-manager-user-detail', kwargs={'pk': simple_user.pk})) assert len(mail.outbox) == 0 - resp = resp.form.submit('password_reset') - assert 'A mail was sent to' in resp - assert len(mail.outbox) == 1 - url = get_link_from_mail(mail.outbox[0]) - relative_url = url.split('testserver')[1] - resp = app.get('/logout/').maybe_follow() - resp = app.get(relative_url, status=200) - resp.form.set('new_password1', '1234==aA') - resp.form.set('new_password2', '1234==aA') - resp = resp.form.submit().follow() - assert str(app.session['_auth_user_id']) == str(simple_user.pk) + if resp.form.enctype == u'application/x-www-form-urlencoded': + resp = resp.form.submit('password_reset') + assert 'A mail was sent to' in resp + assert len(mail.outbox) == 1 + url = get_link_from_mail(mail.outbox[0]) + relative_url = url.split('testserver')[1] + resp = app.get('/logout/').maybe_follow() + resp = app.get(relative_url, status=200) + resp.form.set('new_password1', '1234==aA') + resp.form.set('new_password2', '1234==aA') + resp = resp.form.submit().follow() + assert str(app.session['_auth_user_id']) == str(simple_user.pk) def test_manager_user_detail_by_uuid(app, superuser, simple_user): diff --git a/tests/test_profile.py b/tests/test_profile.py index 7c3497dd..f9e65384 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -36,9 +36,10 @@ def test_account_edit_view(app, simple_user): assert attribute.get_value(simple_user) == '0123456789' resp = app.get(url, status=200) - resp.form.set('edit-profile-phone', '9876543210') - resp = resp.form.submit('cancel').follow() - assert attribute.get_value(simple_user) == '0123456789' + if resp.form.enctype == u'application/x-www-form-urlencoded': + resp.form.set('edit-profile-phone', '9876543210') + resp = resp.form.submit('cancel').follow() + assert attribute.get_value(simple_user) == '0123456789' attribute.set_value(simple_user, '0123456789', verified=True) resp = app.get(url, status=200) @@ -74,10 +75,11 @@ def test_account_edit_next_url(app, simple_user, external_redirect_next_url, ass assert attribute.get_value(simple_user) == '0123456789' resp = app.get(url + '?next=%s' % external_redirect_next_url, status=200) - resp.form.set('edit-profile-phone', '1234') - resp = resp.form.submit('cancel') - assert_external_redirect(resp, reverse('account_management')) - assert attribute.get_value(simple_user) == '0123456789' + if resp.form.enctype == u'application/x-www-form-urlencoded': + resp.form.set('edit-profile-phone', '1234') + resp = resp.form.submit('cancel') + assert_external_redirect(resp, reverse('account_management')) + assert attribute.get_value(simple_user) == '0123456789' def test_account_edit_scopes(app, simple_user): @@ -102,7 +104,8 @@ def test_account_edit_scopes(app, simple_user): return set(key.split('edit-profile-')[1] for key in resp.form.fields.keys() if key and key.startswith('edit-profile-')) resp = app.get(url, status=200) - assert get_fields(resp) == set(['first_name', 'last_name', 'phone', 'mobile', 'city', 'zipcode', 'next_url']) + assert get_fields(resp) == set(['first_name', 'last_name', 'phone', 'mobile', 'city', 'zipcode', + 'next_url', 'avatar_picture']) resp = app.get(url + '?scope=contact', status=200) assert get_fields(resp) == set(['phone', 'mobile', 'next_url']) diff --git a/tox.ini b/tox.ini index 5fc36428..ec51509a 100644 --- a/tox.ini +++ b/tox.ini @@ -47,6 +47,8 @@ deps = httmock pytz pytest-freezegun + pillow + sorl-thumbnail commands = ./getlasso.sh authentic: py.test {env:FAST:} {env:REUSEDB:} {env:COVERAGE:} {posargs:tests/ --random} -- 2.19.0