diff --git a/core/management/commands/migrate_education.py b/core/management/commands/migrate_education.py index b48a469f..29fc092c 100644 --- a/core/management/commands/migrate_education.py +++ b/core/management/commands/migrate_education.py @@ -1,4 +1,7 @@ +import json + from django.db import transaction +from django.conf import settings from django.core.management.base import BaseCommand from django.contrib.auth import get_user_model @@ -32,13 +35,30 @@ def migrate_organization_to_education() -> int: """ Migrate old field `organization` to new model `Education`. Returns count migrated users. + Stored migrated info into `BASE_DIR / "core" / "log" / "migrated_users.json"` """ - users_with_irganization = CustomUser.objects.exclude(organization=None).exclude(organization="") + user_with_education_ids: list[int] = UserEducation.objects.values_list("user__id", flat=True) + users_with_organization_without_education = ( + CustomUser.objects + .exclude(organization=None) + .exclude(organization="") + .exclude(id__in=user_with_education_ids) + ) UserEducation.objects.bulk_create([ UserEducation( user=user, organization_name=user.organization, ) - for user in users_with_irganization + for user in users_with_organization_without_education ]) - return users_with_irganization.count() + + data = [ + {"user_id": user.id, "user_organization_field": user.organization} + for user in users_with_organization_without_education + ] + + file_dump = settings.BASE_DIR / "core" / "log" / "migrated_users.json" + with open(file_dump, "w", encoding="utf-8") as file: + json.dump(data, file, indent=4, ensure_ascii=False) + + return users_with_organization_without_education.count() diff --git a/core/management/commands/migrate_old_to_new_fields_trigram.py b/core/management/commands/migrate_old_to_new_fields_trigram.py deleted file mode 100644 index 0b109ff7..00000000 --- a/core/management/commands/migrate_old_to_new_fields_trigram.py +++ /dev/null @@ -1,91 +0,0 @@ -from time import time - -from django.db import transaction -from django.core.management.base import BaseCommand -from django.contrib.auth import get_user_model -from django.contrib.contenttypes.models import ContentType -from django.contrib.postgres.search import TrigramSimilarity - -from core.models import Specialization, Skill, SkillToObject - -CustomUser = get_user_model() - -# From 0.1 to 1 аdjust similarity threshold. -SIMULARITY: int = 0.1 - - -class Command(BaseCommand): - """ - Uses only with Postgres (TrigramSimilarity). - Needs extension: - $CREATE EXTENSION pg_trgm; - Migrate old fields to new. - """ - def handle(self, *args, **kwargs): - self.stdout.write("Starting manual migrations...") - try: - start = time() - migrate_old_to_new_fields_trigram() - end = time() - self.stdout.write(self.style.SUCCESS( - f"Manual trigram migrations completed successfully, timing: {end - start}") - ) - except Exception as e: - self.stderr.write(self.style.ERROR(f"Manual trigram migrations failed: {str(e)}")) - - -@transaction.atomic -def migrate_old_to_new_fields_trigram() -> None: - """ - Custom trigram migration `speciality` -> `v2_speciality` and `key_skills` -> `skills`. - Uses `TrigramSimilarity` and `SIMULARITY` for check similarity. - """ - for user in CustomUser.objects.all(): - - # Migration `speciality` -> `v2_speciality`. - # (Only users who have not filled `v2_speciality`, but have filled in `speciality`). - if user.speciality and not user.v2_speciality: - searched_speciality = [] - for speciality_name in user.speciality.split(","): - best_match: Specialization = ( # Searching best match. - Specialization.objects.annotate( - similarity=TrigramSimilarity("name", speciality_name), - ) - .filter(similarity__gt=SIMULARITY) - .order_by("-similarity") - .first() - ) - if best_match: - searched_speciality.append(best_match) - # Choose best match. - if searched_speciality: - searched_speciality.sort(key=lambda x: x.similarity, reverse=True) - user.v2_speciality = searched_speciality[0] - - # Migration `key_skills` -> `skills`. - # (Only users who have not filled `skills`, but have filled in `key_skills`). - if user.key_skills and not SkillToObject.objects.filter(object_id=user.id).exists(): - for skill_name in user.key_skills.lower().split(","): - if not skill_name: - continue # Skipping empty lines - best_match: Skill = ( # Searching best match. - Skill.objects.annotate( - similarity=TrigramSimilarity("name", skill_name), - ) - .filter(similarity__gt=SIMULARITY) - .order_by("-similarity") - .first() - ) - if best_match: - SkillToObject.objects.get_or_create( - skill=best_match, - content_type=ContentType.objects.get_for_model(CustomUser), - object_id=user.id, - ) - - # Boolean field is responsible for transferring data if both fields - # were already there or were transferred = `True` (default value = `False`). - if user.v2_speciality and SkillToObject.objects.filter(object_id=user.id).exists(): - user.dataset_migration_applied = True - - user.save() diff --git a/core/management/commands/reverse_old_to_new_fields.py b/core/management/commands/reverse_old_to_new_fields.py deleted file mode 100644 index c6a1ccc8..00000000 --- a/core/management/commands/reverse_old_to_new_fields.py +++ /dev/null @@ -1,53 +0,0 @@ -from django.db import transaction -from django.contrib.auth import get_user_model -from django.core.management.base import BaseCommand -from django.contrib.contenttypes.models import ContentType - -from core.models import SkillToObject - -CustomUser = get_user_model() - - -class Command(BaseCommand): - """ - DO NOT USE IN PROD, ONLY DEBUG. - """ - def add_arguments(self, parser): - parser.add_argument( - "--confirm", - action="store_true", - help="Confirm executing the manual migrations.", - ) - - def handle(self, *args, **options): - confirm = options["confirm"] - - if not confirm: - self.stdout.write(self.style.WARNING("DO NOT USE IN PROD, ONLY DEBUG.")) - self.stdout.write(self.style.WARNING("DO NOT USE IN PROD, ONLY DEBUG.")) - self.stdout.write(self.style.WARNING("DO NOT USE IN PROD, ONLY DEBUG.")) - answer = input( - "You are about to perform manual migrations. This command is for DEBUG use only. " - "Are you sure you want to proceed?" - "Type 'yes' to continue, or 'no' to cancel: " - ).lower() - if answer != "yes": - self.stdout.write(self.style.ERROR("Manual migrations canceled.")) - return - - self.stdout.write("Starting manual migrations...") - try: - reverse() - self.stdout.write(self.style.SUCCESS("Manual migrations completed successfully.")) - except Exception as e: - self.stderr.write(self.style.ERROR(f"Manual migrations failed: {str(e)}")) - - -@transaction.atomic -def reverse(): - """Clear CustomUser fields `v2_speciality`, `skills` and `dataset_migration_applied` -> False""" - for user in CustomUser.objects.all(): - user.dataset_migration_applied = False - user.v2_speciality = None - user.save() - SkillToObject.objects.filter(content_type=ContentType.objects.get_for_model(CustomUser)).delete()