refactor account transfer

This commit is contained in:
alex 2020-02-03 12:32:53 +03:00
parent 33942b70b5
commit 3c56b0c061
12 changed files with 80 additions and 234 deletions

View File

@ -2,8 +2,10 @@ from django.core.management.base import BaseCommand
from django.db import connections from django.db import connections
from django.db.models import Q, F, Value from django.db.models import Q, F, Value
from django.db.models.functions import ConcatPair from django.db.models.functions import ConcatPair
from establishment.management.commands.add_position import namedtuplefetchall from tqdm import tqdm
from account.models import User from account.models import User
from establishment.management.commands.add_position import namedtuplefetchall
class Command(BaseCommand): class Command(BaseCommand):
@ -12,7 +14,8 @@ class Command(BaseCommand):
def account_sql(self): def account_sql(self):
with connections['legacy'].cursor() as cursor: with connections['legacy'].cursor() as cursor:
cursor.execute(''' cursor.execute('''
select a.email, a.id as account_id, a.encrypted_password, select a.email, a.id as account_id, a.encrypted_password, a.locale, a.city,
a.confirmed_at as cfd,
case when a.confirmed_at is not null then true else false end as confirmed_at, case when a.confirmed_at is not null then true else false end as confirmed_at,
case when a.confirmed_at is null then true else false end as unconfirmed_email, case when a.confirmed_at is null then true else false end as unconfirmed_email,
nickname nickname
@ -21,20 +24,24 @@ class Command(BaseCommand):
''') ''')
return namedtuplefetchall(cursor) return namedtuplefetchall(cursor)
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
objects = [] objects = []
for a in self.account_sql(): for a in tqdm(self.account_sql(), desc='find users'):
users = User.objects.filter(Q(email=a.email) | Q(old_id=a.account_id)) users = User.objects.filter(Q(email=a.email) | Q(old_id=a.account_id))
if not users.exists(): if not users.exists():
objects.append(User(email=a.email, objects.append(User(
unconfirmed_email=a.unconfirmed_email, email=a.email,
email_confirmed=a.confirmed_at, unconfirmed_email=a.unconfirmed_email,
old_id=a.account_id, email_confirmed=a.confirmed_at,
password=a.encrypted_password, old_id=a.account_id,
username=a.nickname password=a.encrypted_password,
)) username=a.nickname,
locale=a.locale,
city=a.city,
confirmed_at=a.cfd,
))
User.objects.bulk_create(objects) User.objects.bulk_create(objects)
user = User.objects.filter(old_id__isnull=False) user = User.objects.filter(old_id__isnull=False)
user.update(password=ConcatPair(Value('bcrypt$'), F('password'))) user.update(password=ConcatPair(Value('bcrypt$'), F('password')))
self.stdout.write(self.style.WARNING(f'Created accounts objects.')) self.stdout.write(self.style.WARNING(f'Created {len(objects)} accounts objects.'))

View File

@ -1,18 +1,18 @@
from account.models import OldRole, Role, User, UserRole
from main.models import SiteSettings
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.db import connections, transaction from django.db import connections, transaction
from django.db.models import Prefetch
from establishment.management.commands.add_position import namedtuplefetchall
from tqdm import tqdm from tqdm import tqdm
from account.models import OldRole, Role, User, UserRole
from establishment.management.commands.add_position import namedtuplefetchall
from main.models import SiteSettings
class Command(BaseCommand): class Command(BaseCommand):
help = '''Add site affilations from old db to new db. help = '''Add site affilations from old db to new db.
Run after migrate account models!!!''' Run after migrate account models!!!'''
def map_role_sql(self): def map_role_sql(self):
with connections['legacy'].cursor() as cursor: with connections['legacy'].cursor() as cursor:
cursor.execute(''' cursor.execute('''
select distinct select distinct
case when role = 'news_editor' then 'CONTENT_PAGE_MANAGER' case when role = 'news_editor' then 'CONTENT_PAGE_MANAGER'
@ -76,14 +76,14 @@ class Command(BaseCommand):
if not role.exists(): if not role.exists():
objects.append( objects.append(
Role(**data) Role(**data)
) )
Role.objects.bulk_create(objects) Role.objects.bulk_create(objects)
self.stdout.write(self.style.WARNING(f'Added site roles.')) self.stdout.write(self.style.WARNING(f'Added site roles.'))
def update_site_role(self): def update_site_role(self):
roles = Role.objects.filter(country__isnull=True).select_related('site')\ roles = Role.objects.filter(country__isnull=True).select_related('site') \
.filter(site__id__isnull=False).select_for_update() .filter(site__id__isnull=False).select_for_update()
with transaction.atomic(): with transaction.atomic():
for role in tqdm(roles, desc='Update role country'): for role in tqdm(roles, desc='Update role country'):
role.country = role.site.country role.country = role.site.country
@ -150,4 +150,4 @@ class Command(BaseCommand):
self.add_site_role() self.add_site_role()
self.update_site_role() self.update_site_role()
self.add_role_user() self.add_role_user()
self.add_superuser() self.add_superuser()

View File

@ -1,26 +0,0 @@
from django.core.management.base import BaseCommand
from tqdm import tqdm
from account.models import User, UserRole, Role
from transfer.models import OwnershipAffs, Accounts
from establishment.models import Establishment
class Command(BaseCommand):
help = """Add confirmed date to User."""
def handle(self, *args, **kwarg):
update_users = []
old_users = Accounts.objects.filter(confirmed_at__isnull=False)
for old_user in tqdm(old_users, desc='find users for update confirmed_at field'):
try:
user = User.objects.get(email=old_user.email)
except User.DoesNotExist:
continue
else:
user.confirmed_at = old_user.confirmed_at
update_users.append(user)
User.objects.bulk_update(update_users, ['confirmed_at', ])
self.stdout.write(self.style.WARNING(f'Updated users: {len(update_users)}'))

View File

@ -1,8 +1,8 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.db import connections from django.db import connections
from django.db.models import Q
from establishment.management.commands.add_position import namedtuplefetchall
from account.models import User from account.models import User
from establishment.management.commands.add_position import namedtuplefetchall
class Command(BaseCommand): class Command(BaseCommand):
@ -24,8 +24,8 @@ class Command(BaseCommand):
''') ''')
return namedtuplefetchall(cursor) return namedtuplefetchall(cursor)
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
for a in self.account_sql(): for a in self.account_sql():
users = User.objects.filter(old_id=a.account_id) users = User.objects.filter(old_id=a.account_id)
users.update(image_url= a.image_url) users.update(image_url=a.image_url)
self.stdout.write(self.style.WARNING(f'Update accounts image url.')) self.stdout.write(self.style.WARNING(f'Update accounts image url.'))

View File

@ -2,8 +2,8 @@ from django.core.management.base import BaseCommand
from tqdm import tqdm from tqdm import tqdm
from account.models import User, UserRole, Role from account.models import User, UserRole, Role
from transfer.models import OwnershipAffs
from establishment.models import Establishment from establishment.models import Establishment
from transfer.models import OwnershipAffs
class Command(BaseCommand): class Command(BaseCommand):

View File

@ -1,8 +1,9 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.db import connections from django.db import connections
from social_django.models import UserSocialAuth from social_django.models import UserSocialAuth
from establishment.management.commands.add_position import namedtuplefetchall
from account.models import User from account.models import User
from establishment.management.commands.add_position import namedtuplefetchall
class Command(BaseCommand): class Command(BaseCommand):

View File

@ -1,53 +0,0 @@
"""
Структура fields:
key - поле в таблице postgres
value - поле или группа полей в таблице legacy
В случае передачи группы полей каждое поле представляет собой кортеж, где:
field[0] - название аргумента
field[1] - название поля в таблице legacy
Опционально: field[2] - тип данных для преобразования
Структура внешних ключей:
"legacy_table" - спикок кортежей для сопоставления полей
"legacy_table": [
(("legacy_key", "legacy_field"),
("psql_table", "psql_key", "psql_field", "psql_field_type"))
], где:
legacy_table - название модели legacy
legacy_key - ForeignKey в legacy
legacy_field - уникальное поле в модели legacy для сопоставления с postgresql
psql_table - название модели psql
psql_key - ForeignKey в postgresql
psql_field - уникальное поле в модели postgresql для сопоставления с legacy
psql_field_type - тип уникального поля в postgresql
"""
card = {
"User": {
"data_type": "objects",
"dependencies": None,
"fields": {
"Accounts": {
"username": "nickname",
"email": "email",
"email_confirmed": ("confirmed_at", "django.db.models.BooleanField")
},
"relations": {
"Profiles": {
"key": "account",
"fields": {
"first_name": "firstname",
"last_name": "lastname"
}
}
}
}
}
}
used_apps = None

View File

@ -1,55 +0,0 @@
from pprint import pprint
from django.db.models import Q
from transfer.models import Accounts, Identities
from transfer.serializers.account import UserSerializer
from transfer.serializers.user_social_auth import UserSocialAuthSerializer
STOP_LIST = (
# 'cyril@tomatic.net',
# 'cyril2@tomatic.net',
# 'd.sadykova@id-east.ru',
# 'd.sadykova@octopod.ru',
# 'n.yurchenko@id-east.ru',
)
def transfer_user():
# queryset = Profiles.objects.all()
# queryset = queryset.annotate(nickname=F('account__nickname'))
# queryset = queryset.annotate(email=F('account__email'))
queryset = Accounts.objects.exclude(email__in=STOP_LIST)
serialized_data = UserSerializer(data=list(queryset.values()), many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f'News serializer errors: {serialized_data.errors}')
def transfer_identities():
queryset = Identities.objects.exclude(
Q(account_id__isnull=True) |
# Q(account__confirmed_at__isnull=True) |
Q(account__email__in=STOP_LIST)
).values_list(
'account_id',
'provider',
'uid',
)
serialized_data = UserSocialAuthSerializer(data=list(queryset.values()), many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f'UserSocialAuth serializer errors: {serialized_data.errors}')
data_types = {
'account': [transfer_user],
'identities': [transfer_identities],
}

View File

@ -12,7 +12,7 @@ class Command(BaseCommand):
SHORT_DATA_TYPES = [ SHORT_DATA_TYPES = [
'dictionaries', # №2 - перенос стран, регионов, городов, адресов 'dictionaries', # №2 - перенос стран, регионов, городов, адресов
'news', # перенос новостей (после №2) 'news', # перенос новостей (после №2)
'account', # №1 - перенос пользователей # 'account', # №1 - перенос пользователей - нет, см make_data_migrations.sh !!!
'subscriber', 'subscriber',
'recipe', # №2 - рецепты 'recipe', # №2 - рецепты
'partner', 'partner',

View File

@ -1,40 +0,0 @@
from rest_framework import serializers
from account.models import User
class UserSerializer(serializers.ModelSerializer):
nickname = serializers.CharField()
email = serializers.CharField()
confirmed_at = serializers.DateTimeField(allow_null=True)
id = serializers.CharField()
class Meta:
model = User
fields = (
"id",
"nickname",
"email",
"confirmed_at"
)
def validate(self, data):
data["old_id"] = data.pop("id")
data["username"] = self.get_username(data)
data["email_confirmed"] = self.get_email_confirmed(data)
data.pop("nickname")
data.pop("confirmed_at")
return data
def create(self, validated_data):
# использовать get_or_create
User.objects.create(**validated_data)
def get_email_confirmed(self, data):
if data.get("confirmed_at"):
return True
else:
return False
def get_username(self, obj):
return obj["email"]

View File

@ -1,30 +0,0 @@
from rest_framework import serializers
from social_django.models import UserSocialAuth
from account.models import User
class UserSocialAuthSerializer(serializers.Serializer):
account_id = serializers.IntegerField()
provider = serializers.CharField()
uid = serializers.CharField()
def validate(self, data):
data.update({
'user': self.get_account(data),
})
data.pop('account_id')
return data
def create(self, validated_data):
try:
return UserSocialAuth.objects.create(**validated_data)
except Exception as e:
raise ValueError(f"Error creating UserSocialAuth with {validated_data}: {e}")
@staticmethod
def get_account(data):
user = User.objects.filter(old_id=data['account_id']).first()
if not user:
raise ValueError(f"User account not found with old_id {data['account_id']}")
return user

View File

@ -1,5 +1,47 @@
#!/usr/bin/env bash #!/usr/bin/env bash
./manage.py transfer -a
# ПОЛЬЗОВАТЕЛИ
# Перенос пользователей из модели Accounts в User
# --------------------------
# id -> old_id
# email -> email
# unconfirmed_email -> unconfirmed_email
# confirmed_at (boolean) -> email_confirmed
# encrypted_password -> password
# nickname -> username
# locale -> locale
# city -> city
# confirmed_at -> confirmed_at
./manage.py add_account
# Добавление к уже перенесенным пользователям image_url по old_id
# --------------------------
# image_url -> image_url
./manage.py add_image
# Заполнение модели из identities в UserSocialAuth
# --------------------------
# пользователь -> user
# provider -> provider
# uid -> uid
./manage.py add_social
# Заполнение модели OldRole, UserRole (должны быть заполнены Role и SiteSettings) !!!
# --------------------------
# image_url -> image_url
#./manage.py add_affilations
# Заполнение модели из OwnershipAffs в UserRole (запускать после переноса заведений) !!!
# --------------------------
# user -> user,
# role -> role,
# establishment -> establishment,
# owner.state -> state,
# requester -> requester
#./manage.py add_ownership
./manage.py transfer --setup_clean_db ./manage.py transfer --setup_clean_db
./manage.py transfer -d ./manage.py transfer -d
./manage.py transfer -e ./manage.py transfer -e