Projet

Général

Profil

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

Paul Marillonnet, 24 novembre 2022 10:04

Télécharger (11,6 ko)

Voir les différences:

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

 src/authentic2/csv_import.py | 22 +++++++++++++++++++---
 tests/test_csv_import.py     | 30 ++++++++++++++++--------------
 tests/test_user_manager.py   | 13 ++++++-------
 3 files changed, 41 insertions(+), 24 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
24 25
from django.contrib.auth.hashers import identify_hasher
......
27 28
from django.db import IntegrityError, models
28 29
from django.db.transaction import atomic
29 30
from django.utils.encoding import force_bytes, force_str
31
from django.utils.timezone import now
30 32
from django.utils.translation import gettext as _
31 33

  
32 34
from authentic2 import app_settings
......
35 37
from authentic2.custom_user.models import User
36 38
from authentic2.forms.profile import BaseUserForm, modelform_factory
37 39
from authentic2.models import Attribute, AttributeValue, PasswordReset, UserExternalId
38
from authentic2.utils.misc import send_password_reset_mail
40
from authentic2.utils.misc import parse_phone_number, send_password_reset_mail
39 41

  
40 42

  
41 43
# http://www.attrs.org/en/stable/changelog.html :
......
470 472
                    header.key = True
471 473
            elif header.name not in SPECIAL_COLUMNS:
472 474
                try:
473
                    if header.name in ['email', 'first_name', 'last_name', 'username']:
475
                    if header.name in ['email', 'phone', 'first_name', 'last_name', 'username']:
474 476
                        User._meta.get_field(header.name)
475 477
                        header.field = True
476 478
                        if header.name == 'email':
......
543 545

  
544 546
    def parse_row(self, form_class, row, line):
545 547
        data = {}
546

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

  
555
        if 'phone' in data:
556
            pn = parse_phone_number(data['phone'])
557
            if pn:
558
                # fill multi value field
559
                data['phone_0'] = str(pn.country_code)
560
                data['phone_1'] = phonenumbers.format_number(pn, phonenumbers.PhoneNumberFormat.NATIONAL)
561
                data.pop('phone')
562
            else:
563
                # E.164 compliant parsing failed, leave the number untouched, add an error
564
                errors.update({'phone': [_('Enter a valid phone number.')]})
565

  
553 566
        form = form_class(data=data)
567
        form.errors.update(errors)
554 568
        form.is_valid()
555 569

  
556 570
        def get_form_errors(form, name):
......
716 730
                    setattr(user, cell.header.name, cell.value)
717 731
                    if cell.header.name == 'email' and cell.header.verified:
718 732
                        user.set_email_verified(True)
733
                    if cell.header.name == 'phone' and cell.header.verified:
734
                        user.phone_verified_on = now()
719 735
                    cell.action = 'updated'
720 736
                    continue
721 737
            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
467 467
            'users.csv',
468 468
            '''email key verified,first_name,last_name,phone
469 469
tnoel@entrouvert.com,Thomas,Noël,1234
470
fpeters@entrouvert.com,Frédéric,Péters,5678
470
fpeters@entrouvert.com,Frédéric,Péters,+325678
471 471
john.doe@entrouvert.com,John,Doe,9101112
472 472
x,x,x,x'''.encode(
473 473
                encoding
......
529 529
    assert len(response.pyquery('tr.row-cells-errors')) == 1
530 530
    assert sum(bool(response.pyquery(td).text()) for td in response.pyquery('tr.row-cells-errors td li')) == 2
531 531
    assert 'Enter a valid email address' in response.pyquery('tr.row-cells-errors td.cell-email li').text()
532
    assert 'Phone number can start with' in response.pyquery('tr.row-cells-errors td.cell-phone li').text()
532
    assert 'Enter a valid phone number' in response.pyquery('tr.row-cells-errors td.cell-phone li').text()
533 533

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

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

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

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