0003-migrations-set-existing-user-phone-fields-to-E.164-v.patch
src/authentic2/managers.py | ||
---|---|---|
109 | 109 | |
110 | 110 | |
111 | 111 |
class AttributeManager(managers.QueryManager.from_queryset(GetByNameQuerySet)): |
112 |
pass |
|
112 |
use_in_migrations = True |
|
113 | ||
114 | ||
115 |
class AttributeValueManager(managers.QueryManager.from_queryset(AttributeValueQuerySet)): |
|
116 |
use_in_migrations = True |
|
113 | 117 | |
114 | 118 | |
115 | 119 |
ServiceManager = BaseServiceManager.from_queryset(ServiceQuerySet) |
116 |
AttributeValueManager = managers.QueryManager.from_queryset(AttributeValueQuerySet) |
src/authentic2/migrations/0043_attribute_alter_model_managers.py | ||
---|---|---|
1 |
from django.db import migrations |
|
2 | ||
3 |
import authentic2.managers |
|
4 | ||
5 | ||
6 |
class Migration(migrations.Migration): |
|
7 | ||
8 |
dependencies = [ |
|
9 |
('authentic2', '0042_api_client'), |
|
10 |
] |
|
11 | ||
12 |
operations = [ |
|
13 |
migrations.AlterModelManagers( |
|
14 |
name='attribute', |
|
15 |
managers=[ |
|
16 |
('all_objects', authentic2.managers.AttributeManager()), |
|
17 |
('objects', authentic2.managers.AttributeManager(disabled=False)), |
|
18 |
], |
|
19 |
), |
|
20 |
migrations.AlterModelManagers( |
|
21 |
name='attributevalue', |
|
22 |
managers=[ |
|
23 |
('all_objects', authentic2.managers.AttributeValueManager()), |
|
24 |
('objects', authentic2.managers.AttributeValueManager(attribute__disabled=False)), |
|
25 |
], |
|
26 |
), |
|
27 |
] |
src/authentic2/migrations/0044_migrate_phone_field.py | ||
---|---|---|
1 |
import phonenumbers |
|
2 |
from django.conf import settings |
|
3 |
from django.db import migrations |
|
4 | ||
5 | ||
6 |
def migrate_phone_field(apps, schema_editor): |
|
7 |
Attribute = apps.get_model('authentic2', 'Attribute') |
|
8 |
AttributeValue = apps.get_model('authentic2', 'AttributeValue') |
|
9 |
User = apps.get_model(*settings.AUTH_USER_MODEL.split('.', maxsplit=1)) |
|
10 |
ContentType = apps.get_model('contenttypes', 'ContentType') |
|
11 | ||
12 |
user_ct = ContentType.objects.get_for_model(User) |
|
13 |
try: |
|
14 |
phone_attr = Attribute.objects.get(name='phone') |
|
15 |
except Attribute.DoesNotExist: |
|
16 |
return |
|
17 | ||
18 |
for atv in AttributeValue.objects.filter(attribute=phone_attr): |
|
19 |
pn = None |
|
20 |
try: |
|
21 |
pn = phonenumbers.parse(atv.content) |
|
22 |
except phonenumbers.NumberParseException: |
|
23 |
try: |
|
24 |
pn = phonenumbers.parse( |
|
25 |
atv.content, |
|
26 |
settings.PHONE_COUNTRY_CODES[settings.DEFAULT_COUNTRY_CODE], |
|
27 |
) |
|
28 |
except phonenumbers.NumberParseException: |
|
29 |
continue |
|
30 | ||
31 |
number_e164 = phonenumbers.format_number(pn, phonenumbers.PhoneNumberFormat.E164) |
|
32 |
atv.content = number_e164 |
|
33 |
atv.save() |
|
34 | ||
35 |
if not atv.content_type == user_ct: |
|
36 |
continue |
|
37 |
try: |
|
38 |
user = User.objects.get(id=atv.object_id) |
|
39 |
except User.DoesNotExist: |
|
40 |
# stale attribute value, should not happen as it is deleted on user deletion |
|
41 |
continue |
|
42 |
if hasattr(user, 'phone'): |
|
43 |
user.phone = number_e164 |
|
44 |
user.save() |
|
45 | ||
46 | ||
47 |
class Migration(migrations.Migration): |
|
48 | ||
49 |
dependencies = [ |
|
50 |
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
|
51 |
('custom_user', '0032_auto_20220919_1230'), |
|
52 |
('authentic2', '0043_attribute_alter_model_managers'), |
|
53 |
] |
|
54 | ||
55 |
operations = [ |
|
56 |
migrations.RunPython(migrate_phone_field, reverse_code=migrations.RunPython.noop), |
|
57 |
] |
tests/test_migrations.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 |
from django.conf import settings |
|
17 | 18 |
from django.contrib.auth.models import AbstractUser |
18 | 19 |
from django.utils.timezone import now |
19 | 20 | |
... | ... | |
58 | 59 |
User = new_apps.get_model('custom_user', 'User') |
59 | 60 |
user = User.objects.get() |
60 | 61 |
assert user.email_verified_date == user.date_joined |
62 | ||
63 | ||
64 |
def test_migration_authentic2_0044_migrate_phone_field(transactional_db, migration): |
|
65 |
old_apps = migration.before([('authentic2', '0043_attribute_alter_model_managers')]) |
|
66 | ||
67 |
Attribute = old_apps.get_model('authentic2', 'Attribute') |
|
68 |
AttributeValue = old_apps.get_model('authentic2', 'AttributeValue') |
|
69 |
User = old_apps.get_model(*settings.AUTH_USER_MODEL.split('.', maxsplit=1)) |
|
70 |
ContentType = old_apps.get_model('contenttypes', 'ContentType') |
|
71 | ||
72 |
at = Attribute.objects.create( |
|
73 |
name='phone', |
|
74 |
label='Phone number', |
|
75 |
kind='phone_number', |
|
76 |
) |
|
77 | ||
78 |
user = User.objects.create( |
|
79 |
email='john.doe@example.com', |
|
80 |
username='john.doe', |
|
81 |
phone='12345', |
|
82 |
) |
|
83 |
user2 = User.objects.create( |
|
84 |
email='billy.smith@example.com', |
|
85 |
username='billy.smith', |
|
86 |
phone='abcdef', # erroneous phone number |
|
87 |
) |
|
88 | ||
89 |
AttributeValue.objects.create( |
|
90 |
attribute=at, |
|
91 |
content_type=ContentType.objects.get_for_model(User), |
|
92 |
object_id=user.id, |
|
93 |
content='12345', |
|
94 |
) |
|
95 | ||
96 |
AttributeValue.objects.create( |
|
97 |
attribute=at, |
|
98 |
content_type=ContentType.objects.get_for_model(User), |
|
99 |
object_id=user2.id, |
|
100 |
content='abcdef', |
|
101 |
) |
|
102 | ||
103 |
new_apps = migration.apply([('authentic2', '0044_migrate_phone_field')]) |
|
104 |
User = new_apps.get_model('custom_user', 'User') |
|
105 | ||
106 |
assert User.objects.get(username='john.doe').phone == '+3312345' |
|
107 |
assert User.objects.get(username='billy.smith').phone == 'abcdef' |
|
108 | ||
109 |
Attribute = new_apps.get_model('authentic2', 'Attribute') |
|
110 |
AttributeValue = new_apps.get_model('authentic2', 'AttributeValue') |
|
111 |
ContentType = new_apps.get_model('contenttypes', 'ContentType') |
|
112 | ||
113 |
at = Attribute.objects.get(name='phone') |
|
114 |
atv = AttributeValue.objects.get( |
|
115 |
attribute=at, |
|
116 |
content_type=ContentType.objects.get_for_model(User), |
|
117 |
object_id=user.id, |
|
118 |
) |
|
119 |
assert atv.content == '+3312345' |
|
120 | ||
121 |
atv2 = AttributeValue.objects.get( |
|
122 |
attribute=at, |
|
123 |
content_type=ContentType.objects.get_for_model(User), |
|
124 |
object_id=user2.id, |
|
125 |
) |
|
126 |
assert atv2.content == 'abcdef' |
|
61 |
- |