loko/streetup/common/management/commands/export_user_configs.py
2026-07-22 14:48:40 +02:00

192 lines
8.1 KiB
Python

"""
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,
}