Projet

Général

Profil

0001-WIP-support-avatar-picture-in-user-profile-26022.patch

Paul Marillonnet, 17 septembre 2018 19:04

Télécharger (30,5 ko)

Voir les différences:

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(-)
debian-jessie/control
28 28
    python-jwcrypto (>= 0.3.1),
29 29
    python-cryptography (>= 1.3.4),
30 30
    python-django-filters (>= 1),
31
    python-django-filters (<< 2)
31
    python-django-filters (<< 2),
32
    python-sorl-thumbnail
32 33
Provides: ${python:Provides}
33 34
Recommends: python-ldap
34 35
Suggests: python-raven
debian-wheezy/control
25 25
    python-markdown (>= 2.1),
26 26
    python-ldap (>= 2.4),
27 27
    python-six (>= 1.0),
28
    python-django-filters (>= 1)
28
    python-django-filters (>= 1),
29
    python-sorl-thumbnail
29 30
Provides: ${python:Provides}
30 31
Recommends: python-ldap
31 32
Suggests: python-raven
setup.py
131 131
          'XStatic-jQuery',
132 132
          'XStatic-jquery-ui<1.12',
133 133
          'xstatic-select2',
134
          'sorl-thumbnail',
134 135
      ],
135 136
      zip_safe=False,
136 137
      classifiers=[
src/authentic2/api_views.py
298 298
@cache_control(private=True, max_age=60)
299 299
@decorators.json
300 300
def user(request):
301
    if request.user.is_anonymous():
301
    u = request.user
302
    if u.is_anonymous():
302 303
        return {}
303
    return request.user.to_json()
304
    return u.to_json(request)
304 305

  
305 306

  
306 307
def attributes_hash(attributes):
src/authentic2/attribute_kinds.py
17 17
from .plugins import collect_from_plugins
18 18
from . import app_settings
19 19
from .forms import widgets
20
from .utils import image_serialize
20 21

  
21 22
capfirst = allow_lazy(capfirst, unicode)
22 23

  
......
91 92
    default_validators = [validate_fr_postcode]
92 93

  
93 94

  
95
class A2ImageDRFField(serializers.ImageField):
96
    def to_representation(self, value, *args, **kwargs):
97
        return self.context['request'].build_absolute_uri(value)
98

  
99

  
94 100
DEFAULT_ALLOW_BLANK = True
95 101
DEFAULT_MAX_LENGTH = 256
96 102

  
......
151 157
        'field_class': PhoneNumberField,
152 158
        'rest_framework_field_class': PhoneNumberDRFField,
153 159
    },
160
    {
161
        'label': _('image'),
162
        'name': 'image',
163
        'field_class': forms.ImageField,
164
        'kwargs': {
165
            'widget': widgets.AttributeImageInput,
166
        },
167
        'serialize': image_serialize,
168
        'serialize_eval_kwargs' : {
169
            'owner_uuid': 'owner.uuid',
170
            'owner_pk': 'owner.pk',
171
            'attr_label': 'self.label',
172
        },
173
        'deserialize': lambda x: x,
174
        'rest_framework_field_class': A2ImageDRFField,
175
        'rest_framework_field_kwargs': {
176
            'read_only': True,
177
            },
178
        'value_is_relative_uri': True,
179
        'thumbnail': {
180
            'crop': 'center',
181
            'height': '100',
182
            'width': '100',
183
        },
184
    },
154 185
]
155 186

  
156 187

  
src/authentic2/custom_user/apps.py
10 10
        from django.db.models.signals import post_migrate
11 11

  
12 12
        post_migrate.connect(
13
            self.create_first_name_last_name_attributes,
13
            self.create_custom_attributes,
14 14
            sender=self)
15 15

  
16
    def create_first_name_last_name_attributes(self, app_config, verbosity=2, interactive=True,
16
    def create_custom_attributes(self, app_config, verbosity=2, interactive=True,
17 17
                                               using=DEFAULT_DB_ALIAS, **kwargs):
18 18
        from django.utils import translation
19 19
        from django.utils.translation import ugettext_lazy as _
......
34 34
        content_type = ContentType.objects.get_for_model(User)
35 35

  
36 36
        attrs = {}
37
        attrs['avatar_picture'], created = Attribute.objects.get_or_create(
38
            name='avatar_picture',
39
            defaults={'kind': 'image',
40
                      'label': _('Avatar picture'),
41
                      'required': False,
42
                      'asked_on_registration': False,
43
                      'user_editable': True,
44
                      'user_visible': True})
37 45
        attrs['first_name'], created = Attribute.objects.get_or_create(
38 46
            name='first_name',
39 47
            defaults={'kind': 'string',
......
51 59
                      'user_editable': True,
52 60
                      'user_visible': True})
53 61

  
54
        serialize = get_kind('string').get('serialize')
55 62
        for user in User.objects.all():
56
            for attr_name in attrs:
63
            for at in attrs:
64
                serialize = get_kind(at.kind).get('serialize')
57 65
                av, created = AttributeValue.objects.get_or_create(
58 66
                    content_type=content_type,
59 67
                    object_id=user.id,
60
                    attribute=attrs[attr_name],
68
                    attribute=attrs[at],
61 69
                    defaults={
62 70
                        'multiple': False,
63 71
                        'verified': False,
64
                        'content': serialize(getattr(user, attr_name, None))
72
                        'content': serialize(getattr(user, at, None))
65 73
                    })
src/authentic2/custom_user/models.py
219 219
    def has_verified_attributes(self):
220 220
        return AttributeValue.objects.with_owner(self).filter(verified=True).exists()
221 221

  
222
    def to_json(self):
222
    def to_json(self, request):
223 223
        d = {}
224 224
        for av in AttributeValue.objects.with_owner(self):
225
            d[str(av.attribute.name)] = av.to_python()
225
            d[str(av.attribute.name)] = av.to_python(request)
226 226
        d.update({
227 227
            'uuid': self.uuid,
228 228
            'username': self.username,
src/authentic2/forms/widgets.py
11 11
import re
12 12
import uuid
13 13

  
14
from django.forms.widgets import DateTimeInput, DateInput, TimeInput
14
from django.forms.widgets import DateTimeInput, DateInput, TimeInput, \
15
        ClearableFileInput
15 16
from django.forms.widgets import PasswordInput as BasePasswordInput
16 17
from django.utils.formats import get_language, get_format
17 18
from django.utils.safestring import mark_safe
......
246 247
                    json.dumps(_id),
247 248
                )
248 249
        return output
250

  
251

  
252
class AttributeImageInput(ClearableFileInput):
253
    # template_name = "authentic2/accounts_image.html" # Django 1.11 only todo
254
    template_with_initial = (
255
        '%(initial_text)s: <br /> <img src="%(initial)s"/> <br />'
256
        '%(clear_template)s<br />%(input_text)s: %(input)s'
257
    )
258

  
259
    def is_initial(self, value):
260
        return bool(value)
261

  
262
    def get_template_substitution_values(self, value):
263
        subs_values = dict()
264
        subs_values.update({
265
            'initial': value,
266
        })
267
        return subs_values
src/authentic2/models.py
1
import logging
1 2
import time
2 3
import urlparse
3 4
import uuid
......
203 204
                return kind['default']
204 205

  
205 206
    def set_value(self, owner, value, verified=False):
207
        logger = logging.getLogger(__name__)
206 208
        serialize = self.get_kind()['serialize']
207 209
        # setting to None is to delete
208 210
        if value is None:
......
225 227
                    av.verified = verified
226 228
                    av.save()
227 229
        else:
228
            content = serialize(value)
230
            kwargs = dict()
231
            for key, flat_value in self.get_kind().get('serialize_eval_kwargs', {}).items():
232
                try:
233
                    evalue = eval(flat_value)
234
                except NameError:
235
                    logger.error("Couldn't evaluate {} for attribute <{}: {}>".format(
236
                            flat_value,
237
                            self.get_kind()['kind'],
238
                            self.label))
239
                    continue
240
                kwargs.update({key: evalue})
241
            if kwargs:
242
                content = serialize(value, **kwargs)
243
            else:
244
                content = serialize(value)
245

  
229 246
            av, created = AttributeValue.objects.get_or_create(
230 247
                    content_type=ContentType.objects.get_for_model(owner),
231 248
                    object_id=owner.pk,
......
275 292

  
276 293
    objects = managers.AttributeValueManager()
277 294

  
278
    def to_python(self):
279
        deserialize = self.attribute.get_kind()['deserialize']
280
        return deserialize(self.content)
295
    def to_python(self, request=None):
296
        kind = self.attribute.get_kind()
297
        deserialize = kind['deserialize']
298
        content = self.content
299
        if request and kind.get('value_is_relative_uri'):
300
            content = request.build_absolute_uri(content)
301
        return deserialize(content)
281 302

  
282 303
    def natural_key(self):
283 304
        if not hasattr(self.owner, 'natural_key'):
src/authentic2/settings.py
22 22
DEBUG = False
23 23
DEBUG_DB = False
24 24
MEDIA = 'media'
25
MEDIA_ROOT = 'media'
26
MEDIA_URL = '/media/'
25 27

  
26 28
# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
27 29
ALLOWED_HOSTS = []
......
132 134
    'xstatic.pkg.jquery',
133 135
    'xstatic.pkg.jquery_ui',
134 136
    'xstatic.pkg.select2',
137
    'sorl.thumbnail',
135 138
)
136 139

  
137 140
INSTALLED_APPS = tuple(plugins.register_plugins_installed_apps(INSTALLED_APPS))
src/authentic2/templates/authentic2/accounts.html
1 1
{% extends "authentic2/base-page.html" %}
2 2
{% load i18n %}
3
{% load thumbnail %}
3 4

  
4 5
{% block page-title %}
5 6
  {{ block.super }} - {{ view.title }}
......
18 19
          {% for attribute in attributes %}
19 20
            <dt>{{ attribute.attribute.label|capfirst }}&nbsp;:</dt>
20 21
            <dd>
21
              {% if attribute.values|length == 1 %}
22
                {{ attribute.values.0 }}
22
              {% if attribute.attribute.kind == 'image' %}
23
                {% with request.is_secure|yesno:"https://,http://"|add:request.get_host|add:attribute.values.0 as im_url %}
24
                  {% thumbnail im_url "100x100" crop="center" as thumb_im %}
25
                    <img class={{ attribute.attribute.name }} src="{{ thumb_im.url }}">
26
                  {% endthumbnail %}
27
                {% endwith %}
23 28
              {% else %}
24
                <ul>
25
                  {% for value in attribute.values %}
26
                    <li>{{ value }}</li>
27
                  {% endfor %}
28
                </ul>
29
                {% if attribute.values|length == 1 %}
30
                  {{ attribute.values.0 }}
31
                {% else %}
32
                  <ul>
33
                    {% for value in attribute.values %}
34
                      <li>{{ value }}</li>
35
                    {% endfor %}
36
                  </ul>
37
                {% endif %}
29 38
              {% endif %}
30 39
            </dd>
31 40
          {% endfor %}
src/authentic2/templates/authentic2/accounts_edit.html
12 12
{% endblock %}
13 13

  
14 14
{% block content %}
15
  <form method="post">
15
    {% if form.is_multipart %}
16
      <form enctype="multipart/form-data" method="post">
17
    {% else %}
18
      <form method="post">
19
    {% endif %}
20

  
16 21
    {% csrf_token %}
17 22
    {{ form.as_p }}
18 23
    {% if form.instance and form.instance.id %}
src/authentic2/templates/registration/registration_form.html
15 15

  
16 16
<h2>{{ view.title }}</h2>
17 17

  
18
<form method="post">
18
  {% if form.is_multipart %}
19
    <form enctype="multipart/form-data" method="post">
20
  {% else %}
21
    <form method="post">
22
  {% endif %}
23

  
19 24
  {% csrf_token %}
20 25
  {{ form.as_p }}
21 26
  <button class="submit-button">{% trans 'Submit' %}</button>
src/authentic2/urls.py
44 44
    urlpatterns += [
45 45
        url(r'^static/(?P<path>.*)$', serve)
46 46
    ]
47
    urlpatterns += [
48
        url(r'^media/(?P<path>.*)$', 'django.views.static.serve', {
49
        'document_root': settings.MEDIA_ROOT})
50
    ]
47 51

  
48 52
if settings.DEBUG and 'debug_toolbar' in settings.INSTALLED_APPS:
49 53
    import debug_toolbar
src/authentic2/utils.py
8 8
import uuid
9 9
import datetime
10 10
import copy
11
import fcntl
12
import os
13
import tempfile
11 14

  
12 15
from functools import wraps
13 16
from itertools import islice, chain, count
14 17

  
15 18
from importlib import import_module
19
from hashlib import md5
16 20

  
17 21
from django.conf import settings
18 22
from django.http import HttpResponseRedirect, HttpResponse
......
30 34
from django.template.loader import render_to_string, TemplateDoesNotExist
31 35
from django.core.mail import send_mail
32 36
from django.core import signing
37
from django.core.files.storage import default_storage
33 38
from django.core.urlresolvers import reverse, NoReverseMatch
34 39
from django.utils.formats import localize
35 40
from django.contrib import messages
......
1073 1078
        if ou_value is not None:
1074 1079
            return ou_value
1075 1080
    return default
1081

  
1082

  
1083
def _store_image(in_memory_image, owner_uuid, attr_label):
1084
    logger = logging.getLogger(__name__)
1085

  
1086
    digest = md5(in_memory_image.read())
1087

  
1088
    img_id = digest.hexdigest()
1089
    img_ext = in_memory_image.image.format
1090

  
1091
    img_media_dir = '{label}/{oid}/'.format(
1092
            oid=owner_uuid,
1093
            label=attr_label)
1094
    img_media_path = '{imdir}/{iid}.{ext}'.format(
1095
            imdir=img_media_dir, iid=img_id, ext=img_ext)
1096

  
1097
    img_abs_path = default_storage.path(img_media_path)
1098
    img_abs_dir = default_storage.path(img_media_dir)
1099

  
1100
    try:
1101
        if not os.path.exists(img_abs_dir):
1102
            os.makedirs(img_abs_dir)
1103

  
1104
        with open(img_abs_path, 'wb') as f:
1105
            try:
1106
                fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
1107
                with tempfile.NamedTemporaryFile(mode='wb', dir=img_abs_dir, delete=False) as temp:
1108
                    try:
1109
                        in_memory_image.seek(0)
1110
                        temp.write(in_memory_image.read())
1111
                        temp.flush()
1112
                        os.rename(temp.name, img_abs_path)
1113
                    except:
1114
                        logger.error("Could'nt store image to {}".format(img_abs_path))
1115
                        os.unlink(temp.name)
1116
            except:
1117
                logger.error("Couldn't hold exclusive lock for file {}".format(img_abs_path))
1118
            finally:
1119
                fcntl.lockf(f, fcntl.LOCK_UN)
1120
    except IOError:
1121
        return
1122

  
1123
    return img_media_path
1124

  
1125

  
1126
def _delete_images_from_user(owner_pk, attr_label):
1127
    from .models import Attribute, AttributeValue
1128

  
1129
    logger = logging.getLogger(__name__)
1130
    User = get_user_model()
1131

  
1132
    try:
1133
        owner = User.objects.get(pk=owner_pk)
1134
    except User.DoesNotExist:
1135
        logger.error("Primary key {} doesn't match with any user.".format(owner_pk))
1136
        return
1137

  
1138
    try:
1139
        attr = Attribute.objects.get(label=attr_label)
1140
        all_values =  AttributeValue.objects.with_owner(owner)
1141
        values = all_values.filter(attribute=attr)
1142
    except:
1143
        logger.error("Couldn't retrieve values for Attribute {}.".format(attr_label))
1144

  
1145
    for value in values:
1146
        # Direct URI <-> file location correspondence
1147
        local_file = value.content.split(default_storage.base_url)[-1]
1148
        if not local_file:
1149
            continue
1150
        media_file = default_storage.path(local_file)
1151

  
1152
        try:
1153
            os.remove(media_file)
1154
            value.delete()
1155
        except:
1156
            logger.error("Could'nt delete image {}".format(media_file))
1157

  
1158

  
1159
def image_serialize(image, owner_uuid, owner_pk, attr_label):
1160
    uri = ''
1161
    if isinstance(image, basestring):
1162
        uri = image
1163
    else:
1164
        # Discard previous user avatars
1165
        _delete_images_from_user(owner_pk, attr_label)
1166
        if image:
1167
            img_media_path = _store_image(image, owner_uuid, attr_label)
1168
            uri = os.path.join(settings.MEDIA_URL, img_media_path)
1169
    return uri
tests/test_api.py
229 229

  
230 230
    resp = app.post_json('/api/users/', params=payload, status=status)
231 231
    if api_user.is_superuser or api_user.roles.exists():
232
        assert set(['ou', 'id', 'uuid', 'is_staff', 'is_superuser', 'first_name', 'last_name',
232
        assert (set(['ou', 'id', 'uuid', 'is_staff', 'is_superuser', 'first_name', 'last_name',
233 233
                    'date_joined', 'last_login', 'username', 'password', 'email', 'is_active',
234
                    'title', 'modified', 'email_verified']) == set(resp.json.keys())
234
                    'title', 'modified', 'email_verified', 'avatar_picture']) ==
235
                    set(resp.json.keys()))
235 236
        assert resp.json['first_name'] == payload['first_name']
236 237
        assert resp.json['last_name'] == payload['last_name']
237 238
        assert resp.json['email'] == payload['email']
tests/test_attribute_kinds.py
279 279
    app.post_json('/api/users/', params=payload)
280 280
    assert qs.get().attributes.phone_number == ''
281 281
    qs.delete()
282

  
283

  
284
def test_image(db, app, admin, mailoutbox):
285
    from webtest import Upload
286
    from hashlib import md5
287

  
288
    Attribute.objects.create(name='cityscape_image', label='cityscape', kind='image',
289
                             asked_on_registration=True)
290
    qs = User.objects.filter(first_name='John')
291

  
292

  
293
    response = app.get('/accounts/register/')
294
    form = response.form
295
    form.set('email', 'john.doe@example.com')
296
    response = form.submit().follow()
297
    assert 'john.doe@example.com' in response
298
    url = get_link_from_mail(mailoutbox[0])
299
    response = app.get(url)
300

  
301
    form = response.form
302
    form.set('first_name', 'John')
303
    form.set('last_name', 'Doe')
304
    form.set('cityscape_image', Upload('/dev/null'))
305
    form.set('password1', '12345abcdA')
306
    form.set('password2', '12345abcdA')
307
    response = form.submit()
308
    assert response.pyquery.find('.form-field-error #id_cityscape_image')
309

  
310
    digest = md5(open('tests/cityscape.png').read())
311
    img_id = digest.hexdigest()
312

  
313
    form = response.form
314
    form.set('cityscape_image', Upload('tests/cityscape.png'))
315
    form.set('password1', '12345abcdA')
316
    form.set('password2', '12345abcdA')
317
    response = form.submit()
318
    assert img_id in qs.get().attributes.cityscape_image
319

  
320
    app.authorization = ('Basic', (admin.username, admin.username))
321

  
322
    resp = app.get('/api/users/?first_name=John&last_name=Doe')
323
    assert img_id in resp.json_body['results'][0]['cityscape_image']
324

  
325
    qs.delete()
326

  
327
    response = app.get(url)
328
    form = response.form
329
    form.set('first_name', 'John')
330
    form.set('last_name', 'Doe')
331
    form.set('cityscape_image', None)
332
    form.set('password1', '12345abcdA')
333
    form.set('password2', '12345abcdA')
334
    response = form.submit().follow()
335
    assert qs.get().attributes.cityscape_image == None
336
    qs.delete()
337

  
338
def test_images_delete_on_form_field_clearance(db, app, admin, mailoutbox):
339
    from django.core.files.storage import default_storage
340
    from hashlib import md5
341
    from webtest import Upload
342

  
343
    Attribute.objects.create(
344
            name='cityscape_image', label='cityscape', kind='image',
345
            asked_on_registration=False, required=False,
346
            user_visible=True, user_editable=True)
347
    Attribute.objects.create(
348
            name='garden_image', label='garden', kind='image',
349
            asked_on_registration=False, required=False,
350
            user_visible=True, user_editable=True)
351

  
352
    qs = User.objects.filter(first_name='John')
353

  
354
    response = app.get('/accounts/register/')
355
    form = response.form
356
    form.set('email', 'john.doe@example.com')
357
    response = form.submit().follow()
358
    assert 'john.doe@example.com' in response
359
    url = get_link_from_mail(mailoutbox[0])
360
    response = app.get(url)
361

  
362
    form = response.form
363
    form.set('first_name', 'John')
364
    form.set('last_name', 'Doe')
365
    form.set('password1', '12345abcdA')
366
    form.set('password2', '12345abcdA')
367
    response = form.submit()
368

  
369
    john = qs.get()
370
    assert john
371
    digest1 = md5(open('tests/cityscape.png').read())
372
    img_path1 = 'cityscape/{oid}/{iid}.PNG'.format(
373
            oid=john.uuid,
374
            iid=digest1.hexdigest())
375
    digest2 = md5(open('tests/garden.png').read())
376
    img_path2 = 'garden/{oid}/{iid}.PNG'.format(
377
            oid=john.uuid,
378
            iid=digest2.hexdigest())
379

  
380
    assert not default_storage.exists(img_path1)
381
    assert not default_storage.exists(img_path2)
382

  
383
    response = app.get('/accounts/edit/')
384
    form = response.form
385
    form.set('edit-profile-cityscape_image', Upload('tests/cityscape.png'))
386
    response = form.submit()
387

  
388
    assert default_storage.exists(img_path1)
389
    assert not default_storage.exists(img_path2)
390

  
391
    response = app.get('/accounts/edit/')
392
    form = response.form
393
    form.set('edit-profile-garden_image', Upload('tests/garden.png'))
394
    response = form.submit()
395

  
396
    assert default_storage.exists(img_path1)
397
    assert default_storage.exists(img_path2)
398

  
399
    response = app.get('/accounts/edit/')
400
    form = response.form
401
    form.set('edit-profile-cityscape_image-clear', True)
402
    response = form.submit()
403

  
404
    assert not default_storage.exists(img_path1)
405
    assert default_storage.exists(img_path2)
406

  
407
    response = app.get('/accounts/edit/')
408
    form = response.form
409
    form.set('edit-profile-garden_image-clear', True)
410
    response = form.submit()
411

  
412
    assert not default_storage.exists(img_path1)
413
    assert not default_storage.exists(img_path2)
414

  
415

  
416
def test_images_account_registration(db, app, admin, mailoutbox):
417
    from webtest import Upload
418

  
419
    Attribute.objects.create(name='cityscape_image', label='cityscape', kind='image',
420
                             asked_on_registration=True)
421
    Attribute.objects.create(name='garden_image', label='garden', kind='image',
422
                             asked_on_registration=True)
423
    qs = User.objects.filter(first_name='John')
424

  
425
    response = app.get('/accounts/register/')
426
    form = response.form
427
    form.set('email', 'john.doe@example.com')
428
    response = form.submit().follow()
429
    assert 'john.doe@example.com' in response
430
    url = get_link_from_mail(mailoutbox[0])
431
    response = app.get(url)
432

  
433
    form = response.form
434
    assert form.get('cityscape_image')
435
    assert form.get('garden_image')
436
    form.set('first_name', 'John')
437
    form.set('last_name', 'Doe')
438
    form.set('cityscape_image', Upload('tests/cityscape.png'))
439
    form.set('garden_image', Upload('tests/garden.png'))
440
    form.set('password1', '12345abcdA')
441
    form.set('password2', '12345abcdA')
442
    response = form.submit()
443

  
444
    john = qs.get()
445
    assert john.attributes.cityscape_image
446
    assert john.attributes.garden_image
447
    john.delete()
tests/test_manager.py
105 105
    resp = login(app, superuser,
106 106
                 reverse('a2-manager-user-detail', kwargs={'pk': simple_user.pk}))
107 107
    assert len(mail.outbox) == 0
108
    resp = resp.form.submit('password_reset')
109
    assert 'A mail was sent to' in resp
110
    assert len(mail.outbox) == 1
111
    url = get_link_from_mail(mail.outbox[0])
112
    relative_url = url.split('testserver')[1]
113
    resp = app.get('/logout/').maybe_follow()
114
    resp = app.get(relative_url, status=200)
115
    resp.form.set('new_password1', '1234==aA')
116
    resp.form.set('new_password2', '1234==aA')
117
    resp = resp.form.submit().follow()
118
    assert str(app.session['_auth_user_id']) == str(simple_user.pk)
108
    if resp.form.enctype == u'application/x-www-form-urlencoded':
109
        resp = resp.form.submit('password_reset')
110
        assert 'A mail was sent to' in resp
111
        assert len(mail.outbox) == 1
112
        url = get_link_from_mail(mail.outbox[0])
113
        relative_url = url.split('testserver')[1]
114
        resp = app.get('/logout/').maybe_follow()
115
        resp = app.get(relative_url, status=200)
116
        resp.form.set('new_password1', '1234==aA')
117
        resp.form.set('new_password2', '1234==aA')
118
        resp = resp.form.submit().follow()
119
        assert str(app.session['_auth_user_id']) == str(simple_user.pk)
119 120

  
120 121

  
121 122
def test_manager_user_detail_by_uuid(app, superuser, simple_user):
tests/test_profile.py
36 36
    assert attribute.get_value(simple_user) == '0123456789'
37 37

  
38 38
    resp = app.get(url, status=200)
39
    resp.form.set('edit-profile-phone', '9876543210')
40
    resp = resp.form.submit('cancel').follow()
41
    assert attribute.get_value(simple_user) == '0123456789'
39
    if resp.form.enctype == u'application/x-www-form-urlencoded':
40
        resp.form.set('edit-profile-phone', '9876543210')
41
        resp = resp.form.submit('cancel').follow()
42
        assert attribute.get_value(simple_user) == '0123456789'
42 43

  
43 44
    attribute.set_value(simple_user, '0123456789', verified=True)
44 45
    resp = app.get(url, status=200)
......
74 75
    assert attribute.get_value(simple_user) == '0123456789'
75 76

  
76 77
    resp = app.get(url + '?next=%s' % external_redirect_next_url, status=200)
77
    resp.form.set('edit-profile-phone', '1234')
78
    resp = resp.form.submit('cancel')
79
    assert_external_redirect(resp, reverse('account_management'))
80
    assert attribute.get_value(simple_user) == '0123456789'
78
    if resp.form.enctype == u'application/x-www-form-urlencoded':
79
        resp.form.set('edit-profile-phone', '1234')
80
        resp = resp.form.submit('cancel')
81
        assert_external_redirect(resp, reverse('account_management'))
82
        assert attribute.get_value(simple_user) == '0123456789'
81 83

  
82 84

  
83 85
def test_account_edit_scopes(app, simple_user):
......
102 104
        return set(key.split('edit-profile-')[1]
103 105
                   for key in resp.form.fields.keys() if key and key.startswith('edit-profile-'))
104 106
    resp = app.get(url, status=200)
105
    assert get_fields(resp) == set(['first_name', 'last_name', 'phone', 'mobile', 'city', 'zipcode', 'next_url'])
107
    assert get_fields(resp) == set(['first_name', 'last_name', 'phone', 'mobile', 'city', 'zipcode',
108
            'next_url', 'avatar_picture'])
106 109

  
107 110
    resp = app.get(url + '?scope=contact', status=200)
108 111
    assert get_fields(resp) == set(['phone', 'mobile', 'next_url'])
tox.ini
47 47
  httmock
48 48
  pytz
49 49
  pytest-freezegun
50
  pillow
51
  sorl-thumbnail
50 52
commands =
51 53
  ./getlasso.sh
52 54
  authentic: py.test {env:FAST:} {env:REUSEDB:} {env:COVERAGE:} {posargs:tests/ --random}
53
-