""" Exporte les configurations utilisateur (UserConfig + toutes les relations) dans un fichier JSON portable entre environnements. Usage : # Exporter tous les utilisateurs python manage.py export_user_configs --output exports/user_configs.json # Exporter un ou plusieurs utilisateurs précis python manage.py export_user_configs --user alice@example.com --user bob@example.com --output users.json # Afficher dans la console (stdout) python manage.py export_user_configs """ import json import sys from django.core.management.base import BaseCommand from django.contrib.auth import get_user_model from common.models import UserConfig, UserThematics, UserContractAccess, UserWarehouseAccess, UserWarehouseLocationAccess from common.models import UserThematicStatusPermission, UserContractStatusPermission from contracts.models import UserCompanyTeamAccess User = get_user_model() EXPORT_VERSION = 2 class Command(BaseCommand): help = 'Exporte les configurations utilisateur en JSON.' def add_arguments(self, parser): parser.add_argument( '--output', '-o', metavar='FICHIER', default=None, help='Chemin du fichier de sortie. Si absent, affiche sur stdout.', ) parser.add_argument( '--user', metavar='USERNAME_OR_EMAIL', action='append', default=[], dest='users', help='Limite l\'export à ces utilisateurs (répétable). Accepte username ou email.', ) def handle(self, *args, **options): qs = UserConfig.objects.select_related( 'user', 'default_thematic', 'company' ).prefetch_related( 'roles', 'accessible_views', 'linked_configs__user', 'userthematics__thematic', 'userthematics__status_permissions', 'usercontractaccess_set__contract', 'usercontractaccess_set__status_permissions', 'userwarehouses__warehouse', 'team_accesses__team__company', 'userlocations__warehouse_location', ) identifiers = options['users'] if identifiers: qs = qs.filter( user__username__in=identifiers ) | qs.filter( user__email__in=identifiers ) qs = qs.distinct() if not qs.exists(): self.stderr.write(self.style.ERROR( f'Aucun utilisateur trouvé pour : {", ".join(identifiers)}' )) return payload = { 'version': EXPORT_VERSION, 'exported_at': __import__('datetime').datetime.utcnow().isoformat() + 'Z', 'count': qs.count(), 'users': [self._serialize_config(cfg) for cfg in qs], } output_json = json.dumps(payload, indent=2, ensure_ascii=False) output_path = options['output'] if output_path: with open(output_path, 'w', encoding='utf-8') as fh: fh.write(output_json) self.stdout.write(self.style.SUCCESS( f'{payload["count"]} configuration(s) exportée(s) vers {output_path}' )) else: sys.stdout.write(output_json + '\n') # ------------------------------------------------------------------ def _serialize_config(self, cfg): return { 'username': cfg.user.username, 'email': cfg.user.email, 'first_name': cfg.user.first_name, 'last_name': cfg.user.last_name, 'is_intern': cfg.is_intern, 'generic_email': cfg.generic_email, 'default_thematic': cfg.default_thematic.code if cfg.default_thematic else None, 'company': cfg.company.name if cfg.company else None, 'limit_assets_to_contracts': cfg.limit_assets_to_contracts, 'limit_interventions_to_contracts': cfg.limit_interventions_to_contracts, 'limit_shortcuts_to_contracts': cfg.limit_shortcuts_to_contracts, 'hide_intervention_map_by_default': cfg.hide_intervention_map_by_default, 'auto_logout_hours': cfg.auto_logout_hours, 'can_add_unique_tag_documents': cfg.can_add_unique_tag_documents, 'roles': list(cfg.roles.values_list('name', flat=True)), 'accessible_views': list(cfg.accessible_views.values_list('code', flat=True)), 'linked_configs': list(cfg.linked_configs.values_list('user__username', flat=True)), 'thematics': [self._serialize_thematic(ut) for ut in cfg.userthematics.all()], 'contracts': [self._serialize_contract(uc) for uc in cfg.usercontractaccess_set.all()], 'warehouses': [self._serialize_warehouse(wa) for wa in cfg.userwarehouses.all()], 'locations': [self._serialize_location(la) for la in cfg.userlocations.all()], 'team_accesses': [self._serialize_team(ta) for ta in cfg.team_accesses.all()], } def _serialize_thematic(self, ut): return { 'thematic': ut.thematic.code, 'can_view_assets': ut.can_view_assets, 'can_edit_assets': ut.can_edit_assets, 'can_view_interventions': ut.can_view_interventions, 'can_edit_interventions': ut.can_edit_interventions, 'can_edit_locations': ut.can_edit_locations, 'can_process_observations': ut.can_process_observations, 'status_permissions': [ { 'status': sp.status, 'can_view': sp.can_view, 'can_edit': sp.can_edit, 'can_change_status_to': sp.can_change_status_to, } for sp in ut.status_permissions.all() ], } def _serialize_contract(self, uc): return { 'contract': uc.contract.contract_number, 'can_view_posts': uc.can_view_posts, 'can_view_prices': uc.can_view_prices, 'can_view_interventions': uc.can_view_interventions, 'can_view_assets': uc.can_view_assets, 'can_create_interventions': uc.can_create_interventions, 'can_check_interventions': uc.can_check_interventions, 'can_approve': uc.can_approve, 'status_permissions': [ { 'status': sp.status, 'can_view': sp.can_view, 'can_edit': sp.can_edit, 'can_change_status_to': sp.can_change_status_to, } for sp in uc.status_permissions.all() ], } def _serialize_warehouse(self, wa): return { 'warehouse': wa.warehouse.code, 'can_view': wa.can_view, 'can_manage': wa.can_manage, 'can_administer': wa.can_administer, 'can_manage_preparation_orders': wa.can_manage_preparation_orders, 'can_manage_purchase_orders': wa.can_manage_purchase_orders, 'can_validate_inventory': wa.can_validate_inventory, 'is_default': wa.is_default, } def _serialize_location(self, la): return { 'location_code': la.warehouse_location.code, 'can_perform_inventory': la.can_perform_inventory, 'can_validate_inventory': la.can_validate_inventory, } def _serialize_team(self, ta): return { 'team': ta.team.name, 'company': ta.team.company.name, 'can_view': ta.can_view, 'can_edit': ta.can_edit, }