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

134 lines
No EOL
5.1 KiB
Python

"""
Management command: migrate_project_permissions
================================================
Migre les droits existants vers le nouveau modèle de permissions de projets.
Deux opérations :
1. UserThematics.can_view_interventions = True -> can_view_projects = True
2. Lecture du fichier JSON exporte par `export_project_members` :
- tout role -> ProjectUserAccess(can_view=True)
- role='owner' -> ProjectUserAccess(can_edit=True)
Flux recommande en production :
1. python manage.py export_project_members
2. python manage.py migrate projects 0020_projectteamaccess_projectuseraccess
3. python manage.py migrate common 0064_userthematics_project_permissions
4. python manage.py migrate_project_permissions [--input project_members_export.json]
5. python manage.py migrate projects 0021_remove_projectmember
Usage :
python manage.py migrate_project_permissions [--dry-run] [--input fichier.json]
"""
import json
from pathlib import Path
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
DEFAULT_INPUT = Path("project_members_export.json")
class Command(BaseCommand):
help = "Migre les droits existants vers le nouveau modele de permissions de projets."
def add_arguments(self, parser):
parser.add_argument(
'--dry-run',
action='store_true',
help="Simule la migration sans ecrire en base.",
)
parser.add_argument(
'--input', '-i',
type=Path,
default=DEFAULT_INPUT,
help=f"Fichier JSON exporte par export_project_members (defaut : {DEFAULT_INPUT})",
)
def handle(self, *args, **options):
dry_run = options['dry_run']
if dry_run:
self.stdout.write(self.style.WARNING("=== MODE DRY-RUN : aucune ecriture en base ===\n"))
self._migrate_thematics(dry_run)
self._import_members(options['input'], dry_run)
self.stdout.write(self.style.SUCCESS("\nMigration des permissions terminee."))
def _migrate_thematics(self, dry_run):
from common.models import UserThematics
qs = UserThematics.objects.filter(can_view_interventions=True, can_view_projects=False)
count = qs.count()
self.stdout.write(
f"[Thematiques] {count} enregistrement(s) a mettre a jour "
"(can_view_interventions=True -> can_view_projects=True)."
)
if count and not dry_run:
with transaction.atomic():
updated = qs.update(can_view_projects=True)
self.stdout.write(self.style.SUCCESS(f" -> {updated} mis a jour."))
def _import_members(self, input_path, dry_run):
from projects.models import ProjectUserAccess
if not input_path.exists():
raise CommandError(
f"Fichier introuvable : '{input_path}'. "
"Lancez d'abord : python manage.py export_project_members"
)
data = json.loads(input_path.read_text(encoding='utf-8'))
total = len(data)
self.stdout.write(f"[ProjectMember] {total} entree(s) a importer depuis '{input_path}'.")
created = updated = skipped = 0
with transaction.atomic():
for row in data:
project_id = row['project_id']
user_id = row['member_id']
can_edit = row['role'] == 'owner'
existing = ProjectUserAccess.objects.filter(
project_id=project_id, user_id=user_id
).first()
if existing:
changed = False
if not existing.can_view:
existing.can_view = True
changed = True
if can_edit and not existing.can_edit:
existing.can_edit = True
changed = True
if changed:
if not dry_run:
existing.save(update_fields=['can_view', 'can_edit'])
updated += 1
self.stdout.write(
f" MODIFIE user_id={user_id} -> project_id={project_id} "
f"(can_view=True, can_edit={existing.can_edit})"
)
else:
skipped += 1
else:
if not dry_run:
ProjectUserAccess.objects.create(
project_id=project_id,
user_id=user_id,
can_view=True,
can_edit=can_edit,
)
created += 1
self.stdout.write(
f" CREE user_id={user_id} -> project_id={project_id} "
f"(can_view=True, can_edit={can_edit})"
)
self.stdout.write(
self.style.SUCCESS(
f" -> {created} cree(s), {updated} mis a jour, {skipped} inchange(s)."
)
)