Projet

Général

Profil

0002-csv_import-adapt-user-csv-logic-to-new-phone_number-.patch

Paul Marillonnet, 06 octobre 2022 14:12

Télécharger (11,8 ko)

Voir les différences:

Subject: [PATCH 2/3] csv_import adapt user csv logic to new phone_number kind
 (#69365)

 src/authentic2/csv_import.py | 34 ++++++++++++++++++++++++++++++++--
 tests/test_csv_import.py     | 30 ++++++++++++++++--------------
 tests/test_user_manager.py   | 13 ++++++-------
 3 files changed, 54 insertions(+), 23 deletions(-)
src/authentic2/csv_import.py
19 19
import io
20 20

  
21 21
import attr
22
import phonenumbers
22 23
from chardet.universaldetector import UniversalDetector
23 24
from django import forms
25
from django.conf import settings
24 26
from django.contrib.auth.hashers import identify_hasher
25 27
from django.core.exceptions import FieldDoesNotExist, ValidationError
26 28
from django.core.validators import RegexValidator
27 29
from django.db import IntegrityError, models
28 30
from django.db.transaction import atomic
29 31
from django.utils.encoding import force_bytes, force_text
32
from django.utils.timezone import now
30 33
from django.utils.translation import ugettext as _
31 34

  
32 35
from authentic2 import app_settings
......
467 470
                    header.key = True
468 471
            elif header.name not in SPECIAL_COLUMNS:
469 472
                try:
470
                    if header.name in ['email', 'first_name', 'last_name', 'username']:
473
                    if header.name in ['email', 'phone', 'first_name', 'last_name', 'username']:
471 474
                        User._meta.get_field(header.name)
472 475
                        header.field = True
473 476
                        if header.name == 'email':
......
540 543

  
541 544
    def parse_row(self, form_class, row, line):
542 545
        data = {}
543

  
546
        errors = {}
544 547
        for header in self.headers:
545 548
            try:
546 549
                data[header.name] = row[header.column - 1]
547 550
            except IndexError:
548 551
                pass
549 552

  
553
        if 'phone' in data:
554
            pn = None
555
            try:
556
                # ensure E.164 international representation of phone numbers
557
                pn = phonenumbers.parse(data['phone'])
558
            except phonenumbers.NumberParseException:
559
                try:
560
                    # fallback on default local dial
561
                    pn = phonenumbers.parse(
562
                        data['phone'],
563
                        settings.PHONE_COUNTRY_CODES[settings.DEFAULT_COUNTRY_CODE],
564
                    )
565
                except phonenumbers.NumberParseException:
566
                    pass
567

  
568
            if pn:
569
                # fill multi value field
570
                data['phone_0'] = str(pn.country_code)
571
                data['phone_1'] = '0' * (pn.number_of_leading_zeros or 0) + str(pn.national_number)
572
                data.pop('phone')
573
            else:
574
                # E.164 compliant parsing failed, leave the number untouched, add an error
575
                errors.update({'phone': [_('Enter a valid phone number.')]})
576

  
550 577
        form = form_class(data=data)
578
        form.errors.update(errors)
551 579
        form.is_valid()
552 580

  
553 581
        def get_form_errors(form, name):
......
713 741
                    setattr(user, cell.header.name, cell.value)
714 742
                    if cell.header.name == 'email' and cell.header.verified:
715 743
                        user.set_email_verified(True)
744
                    if cell.header.name == 'phone' and cell.header.verified:
745
                        user.phone_verified_on = now()
716 746
                    cell.action = 'updated'
717 747
                    continue
718 748
            cell.action = 'nothing'
tests/test_csv_import.py
201 201
        CsvHeader(1, 'email', field=True, key=True, verified=True),
202 202
        CsvHeader(2, 'first_name', field=True),
203 203
        CsvHeader(3, 'last_name', field=True),
204
        CsvHeader(4, 'phone', attribute=True),
204
        CsvHeader(4, 'phone', field=True),
205 205
    ]
206 206
    assert importer.has_errors
207 207
    assert len(importer.rows) == 3
......
211 211
    assert all(error == Error('data-error') for error in importer.rows[2].cells[0].errors)
212 212
    assert not importer.rows[2].cells[1].errors
213 213
    assert not importer.rows[2].cells[2].errors
214
    assert not importer.rows[2].cells[3].errors
214
    assert importer.rows[2].cells[3].errors
215 215
    assert all(error == Error('data-error') for error in importer.rows[2].cells[3].errors)
216 216

  
217 217
    assert importer.updated == 0
......
225 225
    assert thomas.attributes.first_name == 'Thomas'
226 226
    assert thomas.last_name == 'Noël'
227 227
    assert thomas.attributes.last_name == 'Noël'
228
    assert thomas.attributes.phone == '1234'
228
    # phonenumbers' e.164 representation from a settings.DEFAULT_COUNTRY_CODE dial:
229
    assert thomas.attributes.phone == '+331234'
229 230
    assert thomas.password
230 231

  
231 232
    fpeters = User.objects.get(email='fpeters@entrouvert.com')
......
235 236
    assert fpeters.attributes.first_name == 'Frédéric'
236 237
    assert fpeters.last_name == 'Péters'
237 238
    assert fpeters.attributes.last_name == 'Péters'
238
    assert fpeters.attributes.phone == '5678'
239
    # phonenumbers' e.164 representation from a settings.DEFAULT_COUNTRY_CODE dial:
240
    assert fpeters.attributes.phone == '+335678'
239 241

  
240 242

  
241 243
def test_simulate(profile, user_csv_importer_factory):
......
251 253
        CsvHeader(1, 'email', field=True, key=True, verified=True),
252 254
        CsvHeader(2, 'first_name', field=True),
253 255
        CsvHeader(3, 'last_name', field=True),
254
        CsvHeader(4, 'phone', attribute=True),
256
        CsvHeader(4, 'phone', field=True),
255 257
    ]
256 258
    assert importer.has_errors
257 259
    assert len(importer.rows) == 3
......
277 279
    importer = user_csv_importer_factory(content)
278 280

  
279 281
    user = User.objects.create(ou=get_default_ou())
280
    user.attributes.phone = '1234'
282
    user.attributes.phone = '+331234'
281 283

  
282 284
    assert importer.run()
283 285

  
......
298 300
    importer = user_csv_importer_factory(content)
299 301

  
300 302
    user = User.objects.create()
301
    user.attributes.phone = '1234'
303
    user.attributes.phone = '+331234'
302 304

  
303 305
    assert importer.run()
304 306

  
......
318 320
    importer = user_csv_importer_factory(content)
319 321

  
320 322
    user = User.objects.create()
321
    user.attributes.phone = '1234'
323
    user.attributes.phone = '+331234'
322 324

  
323 325
    assert importer.run()
324 326

  
......
356 358
    importer = user_csv_importer_factory(content)
357 359

  
358 360
    user = User.objects.create(ou=get_default_ou())
359
    user.attributes.phone = '1234'
361
    user.attributes.phone = '+331234'
360 362

  
361 363
    user = User.objects.create(email='tnoel@entrouvert.com', ou=get_default_ou())
362 364

  
......
378 380
    importer = user_csv_importer_factory(content)
379 381

  
380 382
    user = User.objects.create()
381
    user.attributes.phone = '1234'
383
    user.attributes.phone = '+331234'
382 384

  
383 385
    User.objects.create(email='tnoel@entrouvert.com', ou=get_default_ou())
384 386

  
......
400 402
    importer = user_csv_importer_factory(content)
401 403

  
402 404
    user = User.objects.create()
403
    user.attributes.phone = '1234'
405
    user.attributes.phone = '+331234'
404 406

  
405 407
    thomas = User.objects.create(email='tnoel@entrouvert.com', ou=get_default_ou())
406 408

  
......
418 420
    thomas.refresh_from_db()
419 421
    assert not thomas.first_name
420 422
    assert not thomas.last_name
421
    assert thomas.attributes.phone == '1234'
423
    assert thomas.attributes.phone == '+331234'
422 424

  
423 425

  
424 426
def test_external_id(profile, user_csv_importer_factory):
......
436 438
        CsvHeader(3, 'email', field=True, verified=True),
437 439
        CsvHeader(4, 'first_name', field=True),
438 440
        CsvHeader(5, 'last_name', field=True),
439
        CsvHeader(6, 'phone', attribute=True),
441
        CsvHeader(6, 'phone', field=True),
440 442
    ]
441 443
    assert not importer.has_errors
442 444
    assert len(importer.rows) == 2
......
449 451
        assert thomas.attributes.first_name == 'Thomas'
450 452
        assert thomas.last_name == 'Noël'
451 453
        assert thomas.attributes.last_name == 'Noël'
452
        assert thomas.attributes.phone == '1234'
454
        assert thomas.attributes.phone == '+331234'
453 455

  
454 456
    importer = user_csv_importer_factory(content)
455 457
    assert importer.run(), importer.errors
tests/test_user_manager.py
468 468
            'users.csv',
469 469
            '''email key verified,first_name,last_name,phone
470 470
tnoel@entrouvert.com,Thomas,Noël,1234
471
fpeters@entrouvert.com,Frédéric,Péters,5678
471
fpeters@entrouvert.com,Frédéric,Péters,+325678
472 472
john.doe@entrouvert.com,John,Doe,9101112
473 473
x,x,x,x'''.encode(
474 474
                encoding
......
530 530
    assert len(response.pyquery('tr.row-cells-errors')) == 1
531 531
    assert sum(bool(response.pyquery(td).text()) for td in response.pyquery('tr.row-cells-errors td li')) == 2
532 532
    assert 'Enter a valid email address' in response.pyquery('tr.row-cells-errors td.cell-email li').text()
533
    assert 'Phone number can start with' in response.pyquery('tr.row-cells-errors td.cell-phone li').text()
533
    assert 'Enter a valid phone number' in response.pyquery('tr.row-cells-errors td.cell-phone li').text()
534 534

  
535 535
    assert User.objects.count() == user_count
536 536

  
......
549 549
            email='tnoel@entrouvert.com',
550 550
            first_name='Thomas',
551 551
            last_name='Noël',
552
            attribute_values__content='1234',
552
            attribute_values__content='+331234',
553 553
        ).count()
554 554
        == 1
555 555
    )
......
558 558
            email='fpeters@entrouvert.com',
559 559
            first_name='Frédéric',
560 560
            last_name='Péters',
561
            attribute_values__content='5678',
561
            attribute_values__content='+325678',
562 562
        ).count()
563 563
        == 1
564 564
    )
......
567 567
            email='john.doe@entrouvert.com',
568 568
            first_name='John',
569 569
            last_name='Doe',
570
            attribute_values__content='9101112',
570
            attribute_values__content='+339101112',
571 571
        ).count()
572 572
        == 1
573 573
    )
......
697 697
    assert 'Enter a valid date.' in response.text
698 698
    assert 'birthdate must be in the past and greater or equal than 1900-01-01.' in response.text
699 699
    assert 'The value must be a valid french postcode' in response.text
700
    assert 'Phone number can start with a + and must contain only digits' in response.text
701 700

  
702 701
    assert User.objects.count() == user_count + 1
703 702
    elliot = User.objects.filter(email='elliot@universalpictures.com')[0]
......
707 706
    assert elliot.attributes.values['saintsday'].content == '2019-07-20'
708 707
    assert elliot.attributes.values['birthdate'].content == '1972-05-26'
709 708
    assert elliot.attributes.values['zip'].content == '75014'
710
    assert elliot.attributes.values['phone'].content == '1234'
709
    assert elliot.attributes.values['phone'].content == '+331234'
711 710

  
712 711
    csv_lines[2] = "et@universalpictures.com,ET,the Extra-Terrestrial,,,,,,42000,+888 5678"
713 712
    response = import_csv('\n'.join(csv_lines), app)
714
-