diff --git a/.gitignore b/.gitignore index 38088b7f..1e3ebcba 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ wheels/ *.egg-info/ .installed.cfg *.egg +dumps/ # PyInstaller # Usually these files are written by a python script from a template diff --git a/Pipfile b/Pipfile index a8cb0a9b..d096d046 100644 --- a/Pipfile +++ b/Pipfile @@ -39,6 +39,7 @@ pytest-django = "*" pytest-freezegun = "*" pytest-mock = "*" pytest = "*" +django-test-migrations = "*" [requires] python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock index 195adbcf..76740a6d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "7178cf0140201cb4699c69031e948c0c850c8ebe9229af31e8f15f4e9d8528ee" + "sha256": "d3bd2bcde4da46c4ac91e40a7d76e18c443e8ec263eb2aac3c090a1b022c41c6" }, "pipfile-spec": 6, "requires": { @@ -52,18 +52,18 @@ }, "boto3": { "hashes": [ - "sha256:68e32e2d1c911b0e8408278c7603f0f46c31780b46c44d23346ccef71b3f10dc", - "sha256:967c7a5ac484fe627706e241dfc9294a6220c863ceb53a4f34e3fe9e11a71d7a" + "sha256:4d7fb8028bd6cc431b2381f7ddc019a2376bed742ae480339b1bc8b347b3e402", + "sha256:acd8c19af1f82cfa011baed100f1659c1dc22daaeeec8d6db0567a4a1bfa103f" ], "index": "pypi", - "version": "==1.12.2" + "version": "==1.12.9" }, "botocore": { "hashes": [ - "sha256:00bff61d899c4f12abe020527452e08cf49b3b60400c5d0d9f83c00b7d18c642", - "sha256:5ffdf30746dbfca59d31d2059789168255e96bd98a17a65f8edb3b6de0a96b3e" + "sha256:6a62f52fff679a889ba00191ef6b3de6b5b54e1af9e0056af50931bd2d1fdaa8", + "sha256:bce789daa7ee2af73c8f63f5713b5058d4698987575f236ca4df3c91c53bb34c" ], - "version": "==1.15.2" + "version": "==1.15.9" }, "certifi": { "hashes": [ @@ -81,19 +81,19 @@ }, "codecov": { "hashes": [ - "sha256:8ed8b7c6791010d359baed66f84f061bba5bd41174bf324c31311e8737602788", - "sha256:ae00d68e18d8a20e9c3288ba3875ae03db3a8e892115bf9b83ef20507732bed4" + "sha256:38b32934e759a29313382287f59986f25613708f60760c88d31e956399bbeffe", + "sha256:4cf93c30cc1ddb6d7414fce0a45816889499c3febc8bbbc24f1cd1936a804087" ], "index": "pypi", - "version": "==2.0.15" + "version": "==2.0.16" }, "collectfast": { "hashes": [ - "sha256:bc47426542f7db96083b0a48db68286987f82dd4aa9a191b510b2493e3410458", - "sha256:f74a658fc5ed51577e20efd9ed7ae8da4abaf81eafecd870f93cc30b46b52804" + "sha256:6b6cb63b6f644a89612b197f4aa90d1c39a89a57f40341b0064c444cb6e5474d", + "sha256:ac5ae3738e03bf78c4058452440564f60b80c2a414a09f69b5a14f53d628a43a" ], "index": "pypi", - "version": "==1.3.2" + "version": "==2.0.1" }, "coverage": { "hashes": [ @@ -217,11 +217,11 @@ }, "django-storages": { "hashes": [ - "sha256:0a9b7e620e969fb0797523695329ed223bf540bbfdf6cd163b061fc11dab2d1c", - "sha256:9322ab74ba6371e2e0fccc350c741686ade829e43085597b26b07ae8955a0a00" + "sha256:3103991c2ee8cef8a2ff096709973ffe7106183d211a79f22cf855f33533d924", + "sha256:a59e9923cbce7068792f75344ed7727021ee4ac20f227cf17297d0d03d141e91" ], "index": "pypi", - "version": "==1.8" + "version": "==1.9.1" }, "docutils": { "hashes": [ @@ -270,10 +270,10 @@ }, "jmespath": { "hashes": [ - "sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6", - "sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c" + "sha256:695cb76fa78a10663425d5b73ddc5714eb711157e52704d69be03b1a02ba4fec", + "sha256:cca55c8d153173e21baa59983015ad0daf603f9cb799904ff057bfb8ff8dc2d9" ], - "version": "==0.9.4" + "version": "==0.9.5" }, "lxml": { "hashes": [ @@ -309,10 +309,10 @@ }, "newrelic": { "hashes": [ - "sha256:8283dd54299b3fd2818a262f38f9193a2ee52b2a02fecca1a1d04764be533c92" + "sha256:9228556dc93bd02d9164e3755f971c2ffba45ad2d8a9b6620f630a6d475421fc" ], "index": "pypi", - "version": "==5.6.0.135" + "version": "==5.8.0.136" }, "pagarme-python": { "hashes": [ @@ -323,10 +323,10 @@ }, "parso": { "hashes": [ - "sha256:56b2105a80e9c4df49de85e125feb6be69f49920e121406f15e7acde6c9dfc57", - "sha256:951af01f61e6dccd04159042a0706a31ad437864ec6e25d0d7a96a9fbb9b0095" + "sha256:0c5659e0c6eba20636f99a04f469798dca8da279645ce5c387315b2c23912157", + "sha256:8515fc12cfca6ee3aa59138741fc5624d62340c97e401c74875769948d4f2995" ], - "version": "==0.6.1" + "version": "==0.6.2" }, "pexpect": { "hashes": [ @@ -461,11 +461,11 @@ }, "responses": { "hashes": [ - "sha256:515fd7c024097e5da76e9c4cf719083d181f1c3ddc09c2e0e49284ce863dd263", - "sha256:8ce8cb4e7e1ad89336f8865af152e0563d2e7f0e0b86d2cf75f015f819409243" + "sha256:33d8994990de31a9fbb8c889c3a378dac296b821f37330c845608ae565e7fca3", + "sha256:859a7f8495a069ce835620bb4bb111688f593a6f8873a3335a99c31a5aef9a9a" ], "index": "pypi", - "version": "==0.10.9" + "version": "==0.10.11" }, "s3transfer": { "hashes": [ @@ -476,11 +476,11 @@ }, "sentry-sdk": { "hashes": [ - "sha256:b06dd27391fd11fb32f84fe054e6a64736c469514a718a99fb5ce1dff95d6b28", - "sha256:e023da07cfbead3868e1e2ba994160517885a32dfd994fc455b118e37989479b" + "sha256:480eee754e60bcae983787a9a13bc8f155a111aef199afaa4f289d6a76aa622a", + "sha256:a920387dc3ee252a66679d0afecd34479fb6fc52c2bc20763793ed69e5b0dcc0" ], "index": "pypi", - "version": "==0.14.1" + "version": "==0.14.2" }, "six": { "hashes": [ @@ -491,10 +491,10 @@ }, "soupsieve": { "hashes": [ - "sha256:bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5", - "sha256:e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda" + "sha256:e914534802d7ffd233242b785229d5ba0766a7f487385e3f714446a07bf540ae", + "sha256:fcd71e08c0aee99aca1b73f45478549ee7e7fc006d51b37bec9e9def7dc22b69" ], - "version": "==1.9.5" + "version": "==2.0" }, "sqlparse": { "hashes": [ @@ -593,6 +593,14 @@ "index": "pypi", "version": "==3.0.3" }, + "django-test-migrations": { + "hashes": [ + "sha256:4ca2c1fadfbc2d078a3868f015c23968f03cfab7ca27339de7275365223a51cb", + "sha256:bb4c20f7dd7ec2c589f400042aafb0d4d5a8d4add5a0b54b9ab43a00b7a6b736" + ], + "index": "pypi", + "version": "==0.2.0" + }, "entrypoints": { "hashes": [ "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", diff --git a/pythonpro/core/migrations/0010_normalizing_email.py b/pythonpro/core/migrations/0010_normalizing_email.py new file mode 100644 index 00000000..63d45691 --- /dev/null +++ b/pythonpro/core/migrations/0010_normalizing_email.py @@ -0,0 +1,39 @@ +# Generated by Django 3.0.3 on 2020-02-28 11:30 + +from django.db import IntegrityError, migrations + +from django.db.transaction import atomic + + +def normalize_email(apps, schema_editor): + ''' + We can't import the Post model directly as it may be a newer + version than this migration expects. We use the historical version. + ''' + User = apps.get_model('core', 'User') + print() + for msg in normalize_all_users_email(User): + print(msg) + + +def normalize_all_users_email(User): + for user in User.objects.order_by('id').all(): + original_email = user.email + normalized_email = user.email.lower() + if normalized_email != original_email: + user.email = normalized_email + try: + with atomic(): + user.save() + except IntegrityError: + yield f'Normalization error user {user.id}, {original_email}' + + +class Migration(migrations.Migration): + dependencies = [ + ('core', '0009_auto_20191031_1324'), + ] + + operations = [ + migrations.RunPython(normalize_email, migrations.RunPython.noop) + ] diff --git a/pythonpro/core/tests/test_email_migration.py b/pythonpro/core/tests/test_email_migration.py new file mode 100644 index 00000000..73448784 --- /dev/null +++ b/pythonpro/core/tests/test_email_migration.py @@ -0,0 +1,32 @@ +from importlib import import_module + +from model_mommy import mommy + +# Workaround since module beginning with number can't be imported in regular way +migration_module = import_module('pythonpro.core.migrations.0010_normalizing_email') + + +def test_user_email_normalization_success(django_user_model): + """Ensures email can be normalized""" + + upper_email = 'UPPER@python.pro.br' + mommy.make(django_user_model, email=upper_email) + msg = next(migration_module.normalize_all_users_email(django_user_model), None) + assert msg is None + assert django_user_model.objects.first().email == upper_email.lower() + + +def test_no_msg_for_already_normalized_emails(django_user_model): + mommy.make(django_user_model, email='lower@python.pro.br') + msg = next(migration_module.normalize_all_users_email(django_user_model), None) + assert msg is None + + +def test_conflictiong_email_msg(django_user_model): + lower_email = 'normalized@python.pro.br' + upper_email = 'NORMALIZED@python.pro.br' + upper_email_user = mommy.make(django_user_model, email=upper_email) + mommy.make(django_user_model, email=lower_email) + msg = next(migration_module.normalize_all_users_email(django_user_model)) + upper_email_user = django_user_model.objects.get(id=upper_email_user.id) + assert msg == f'Normalization error user {upper_email_user.id}, {upper_email_user.email}'