Projet

Général

Profil

0001-api-allow-changing-profile-image-52949.patch

Valentin Deniaud, 21 juillet 2021 11:38

Télécharger (5,93 ko)

Voir les différences:

Subject: [PATCH] api: allow changing profile image (#52949)

 src/authentic2/attribute_kinds.py | 29 +++++++++++++++++--
 tests/test_attribute_kinds.py     | 48 +++++++++++++++++++++++++++++++
 2 files changed, 75 insertions(+), 2 deletions(-)
src/authentic2/attribute_kinds.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import base64
18
import binascii
17 19
import datetime
18 20
import hashlib
19 21
import os
20 22
import re
21 23
import string
24
import urllib
25
import uuid
22 26
from itertools import chain
23 27

  
24 28
from django import forms
25 29
from django.conf import settings
26 30
from django.core.exceptions import ValidationError
27 31
from django.core.files.storage import default_storage
32
from django.core.files.uploadedfile import SimpleUploadedFile
28 33
from django.core.validators import RegexValidator
29 34
from django.db.models import query
30 35
from django.urls import reverse, reverse_lazy
......
34 39
from django.utils.translation import ugettext_lazy as _
35 40
from gadjo.templatetags.gadjo import xstatic
36 41
from rest_framework import serializers
42
from rest_framework.exceptions import ValidationError
37 43
from rest_framework.fields import empty
38 44

  
39 45
from . import app_settings
......
241 247
    return None
242 248

  
243 249

  
250
class Base64ImageField(serializers.ImageField):
251
    def to_internal_value(self, data):
252
        if data == '':
253
            return None
254

  
255
        if isinstance(data, str):
256
            if ';base64,' in data:
257
                data = data.split(';base64,')[1]
258
            try:
259
                decoded_file = base64.b64decode(data, validate=True)
260
            except binascii.Error:
261
                raise ValidationError(_('Invalid base64 encoding.'))
262

  
263
            data = SimpleUploadedFile(name=str(uuid.uuid4()), content=decoded_file)
264

  
265
        return super().to_internal_value(data)
266

  
267

  
244 268
def date_free_text_search(term):
245 269
    for date_format in formats.get_format('DATE_INPUT_FORMATS'):
246 270
        try:
......
322 346
        'field_class': fields.ProfileImageField,
323 347
        'serialize': profile_image_serialize,
324 348
        'deserialize': profile_image_deserialize,
325
        'rest_framework_field_class': serializers.FileField,
349
        'rest_framework_field_class': Base64ImageField,
326 350
        'rest_framework_field_kwargs': {
327
            'read_only': True,
328 351
            'use_url': True,
352
            'allow_empty_file': True,
353
            '_DjangoImageField': fields.ProfileImageField,
329 354
        },
330 355
        'html_value': profile_image_html_value,
331 356
        'attributes_ng_serialize': profile_attributes_ng_serialize,
tests/test_attribute_kinds.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import base64
17 18
import datetime
18 19
import os
19 20

  
......
440 441
    form = response.form
441 442
    assert form['cityscape_image'].attrs['accept'] == 'image/*'
442 443

  
444
    # clear image via API, by putting empty JSON
445
    response = app.put_json('/api/users/%s/' % john().uuid, params={'cityscape_image': ''})
446
    assert john().attributes.cityscape_image == None
447

  
448
    # put back first image via API, by putting base64 encoded JSON
449
    with open('tests/200x200.jpg', 'rb') as f:
450
        image = f.read()
451
    base64_image = base64.b64encode(image).decode()
452
    response = app.put_json('/api/users/%s/' % john().uuid, params={'cityscape_image': base64_image})
453
    assert john().attributes.cityscape_image
454
    profile_filename = john().attributes.cityscape_image.name
455
    assert profile_filename.endswith('.jpeg')
456

  
457
    # verify 201x201 image is accepted and resized by API
458
    with open('tests/201x201.jpg', 'rb') as f:
459
        image = f.read()
460
    base64_image = base64.b64encode(image).decode()
461
    response = app.put_json('/api/users/%s/' % john().uuid, params={'cityscape_image': base64_image})
462
    with PIL.Image.open(os.path.join(settings.MEDIA_ROOT, john().attributes.cityscape_image.name)) as image:
463
        assert image.width == 200
464
        assert image.height == 200
465
    assert john().attributes.cityscape_image.name != profile_filename
466

  
467
    # put back first image via API, by putting data URI representing a base64 encoded image using JSON
468
    data_uri = 'data:%s;base64,%s' % ('image/jpeg', base64_image)
469
    response = app.put_json('/api/users/%s/' % john().uuid, params={'cityscape_image': data_uri})
470
    assert john().attributes.cityscape_image
471
    profile_filename = john().attributes.cityscape_image.name
472
    assert profile_filename.endswith('.jpeg')
473

  
474
    # bad request on invalid b64
475
    response = app.put_json(
476
        '/api/users/%s/' % john().uuid, params={'cityscape_image': 'invalid_64'}, status=400
477
    )
478

  
479
    # clear image via API, not using JSON
480
    response = app.put('/api/users/%s/' % john().uuid, params={'cityscape_image': ''})
481
    assert john().attributes.cityscape_image == None
482

  
483
    # put back first image via API, not using JSON
484
    response = app.put(
485
        '/api/users/%s/' % john().uuid, params={'cityscape_image': Upload('tests/200x200.jpg')}
486
    )
487
    assert john().attributes.cityscape_image
488
    profile_filename = john().attributes.cityscape_image.name
489
    assert profile_filename.endswith('.jpeg')
490

  
443 491

  
444 492
def test_multiple_attribute_setter(db, app, simple_user):
445 493
    nicks = Attribute.objects.create(
446
-