101 lines
4.4 KiB
Python
101 lines
4.4 KiB
Python
"""Read-only diagnostics for the legacy mobile uniqueness migration."""
|
|
import json
|
|
from collections import defaultdict
|
|
from importlib import import_module
|
|
|
|
from django.core.management.base import BaseCommand, CommandError
|
|
from django.db import connections
|
|
|
|
from apps.cuser.cuser_model import CUser
|
|
from apps.customer.models import Customer
|
|
|
|
|
|
canonical_mobile = import_module(
|
|
'apps.customer.migrations.0008_customer_mobile_unique'
|
|
).canonical_mobile
|
|
|
|
|
|
def _duplicate_groups(model, alias, per_workspace=False):
|
|
"""Use the migration's exact comparison, including country-prefix collisions."""
|
|
groups = defaultdict(list)
|
|
fields = ['pk', 'mobile'] + (['sys_ws_id'] if per_workspace else [])
|
|
for row in model.objects.using(alias).order_by('pk').values(*fields).iterator():
|
|
mobile = canonical_mobile(row['mobile'])
|
|
if mobile is None:
|
|
continue
|
|
key = (row['sys_ws_id'], mobile) if per_workspace else (None, mobile)
|
|
groups[key].append(row['pk'])
|
|
return [
|
|
{'workspace_id': workspace, 'canonical_mobile': mobile, 'record_ids': ids}
|
|
for (workspace, mobile), ids in groups.items() if len(ids) > 1
|
|
]
|
|
|
|
|
|
def _related_counts(record, alias):
|
|
"""Count direct foreign-key dependents without reading their private contents."""
|
|
counts = {}
|
|
for relation in record._meta.related_objects:
|
|
if relation.one_to_many or relation.one_to_one:
|
|
label = f'{relation.related_model._meta.label}.{relation.field.name}'
|
|
counts[label] = relation.related_model._default_manager.using(alias).filter(
|
|
**{relation.field.attname: record.pk}
|
|
).count()
|
|
return counts
|
|
|
|
|
|
def _customer_details(ids, alias):
|
|
rows = Customer.objects.using(alias).filter(pk__in=ids).select_related(
|
|
'sys_ws', 'cuser_role__cuser', 'cuser_role__role_type'
|
|
).order_by('pk')
|
|
details = []
|
|
for row in rows:
|
|
role = row.cuser_role
|
|
details.append({
|
|
'id': row.pk, 'name_family': row.name_family,
|
|
'mobile': row.mobile, 'canonical_mobile': canonical_mobile(row.mobile),
|
|
'workspace_id': row.sys_ws_id, 'workspace_name': row.sys_ws.name,
|
|
'panel_role': None if role is None else {
|
|
'id': role.pk, 'user_id': role.cuser_id, 'workspace_id': role.sys_ws_id,
|
|
'role': role.role_type.role_name_en, 'mobile': role.mobile,
|
|
'user_mobile': role.cuser.mobile if role.cuser_id else None,
|
|
'is_active': role.is_active, 'customer_panel': role.customer_panel,
|
|
},
|
|
'related_counts': _related_counts(row, alias),
|
|
})
|
|
return details
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = 'Report legacy Customer/CUser mobile conflicts; never modify or delete records.'
|
|
# Must work before migrations, without checking unrelated model declarations.
|
|
requires_system_checks = []
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument('--database', default='default')
|
|
parser.add_argument('--customer-ids', nargs='+', type=int, default=[])
|
|
|
|
def handle(self, *args, **options):
|
|
alias = options['database']
|
|
if alias not in connections:
|
|
raise CommandError(f'Unknown database alias: {alias}')
|
|
customer_groups = _duplicate_groups(Customer, alias, per_workspace=True)
|
|
user_groups = _duplicate_groups(CUser, alias)
|
|
customer_ids = set(options['customer_ids'])
|
|
customer_ids.update(pk for group in customer_groups for pk in group['record_ids'])
|
|
user_ids = [pk for group in user_groups for pk in group['record_ids']]
|
|
report = {
|
|
'read_only': True, 'database': alias,
|
|
'duplicate_group_count': len(customer_groups) + len(user_groups),
|
|
'customer_duplicate_groups': customer_groups,
|
|
'user_duplicate_groups': user_groups,
|
|
'customers': _customer_details(customer_ids, alias),
|
|
'missing_requested_customer_ids': sorted(customer_ids - set(
|
|
Customer.objects.using(alias).filter(pk__in=customer_ids).values_list('pk', flat=True)
|
|
)),
|
|
'users': [dict(row, canonical_mobile=canonical_mobile(row['mobile'])) for row in
|
|
CUser.objects.using(alias).filter(pk__in=user_ids).order_by('pk').values(
|
|
'id', 'username', 'name_family', 'mobile', 'is_active'
|
|
)],
|
|
}
|
|
self.stdout.write(json.dumps(report, ensure_ascii=False, indent=2))
|