feat(assets): add create_structure_folders management command
This commit is contained in:
parent
9726e8b618
commit
ef40e48163
1 changed files with 210 additions and 0 deletions
210
loko/assets/management/commands/create_structure_folders.py
Normal file
210
loko/assets/management/commands/create_structure_folders.py
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
"""
|
||||
Management command: create_structure_folders
|
||||
===========================================
|
||||
Crée les répertoires par défaut de gestion documentaire pour les structures (ouvrages d'art)
|
||||
qui n'en possèdent pas encore (ex: stations et tunnels de métro récemment importés).
|
||||
|
||||
Pour chaque ouvrage d'art traité :
|
||||
1. Crée le dossier racine rattaché à l'ouvrage via DocumentFolderAttachment (is_default=True) :
|
||||
- Nom : "<code_ouvrage> - <nom_ouvrage>" (ex: "MS001 - Station Albert")
|
||||
- Slug : "<code_minuscule>" (ex: "ms001")
|
||||
- Dossier parent : "Structures" (slug: "assets-structures")
|
||||
- Thématique : "structures"
|
||||
- Visibilité : "internal"
|
||||
2. Crée l'arborescence standard de sous-dossiers :
|
||||
- Civil engineering
|
||||
├── Inspection reports
|
||||
└── Exceptional transport
|
||||
- Electromechanics
|
||||
└── Preventive maintenance
|
||||
|
||||
Usage :
|
||||
python manage.py create_structure_folders [--dry-run] [--type TYPE] [--code CODE]
|
||||
"""
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
from django.utils.text import slugify
|
||||
|
||||
from assets.models.structures import Structure
|
||||
from common.models import Thematic
|
||||
from documents.models import DocumentFolder, DocumentFolderAttachment, VisibilityScope
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
def ensure_structure_folders(structure, creator_user=None, dry_run=False):
|
||||
"""
|
||||
Crée ou s'assure de l'existence du dossier racine et des sous-dossiers
|
||||
standards pour un ouvrage d'art (Structure).
|
||||
Idempotent : ne recrée pas ce qui existe déjà.
|
||||
"""
|
||||
structure_ct = ContentType.objects.get_for_model(Structure)
|
||||
structures_thematic = Thematic.objects.filter(code='structures').first()
|
||||
structures_parent_folder = DocumentFolder.objects.filter(slug='assets-structures').first()
|
||||
|
||||
root_slug = slugify(structure.code)
|
||||
root_folder = DocumentFolder.objects.filter(slug=root_slug).first()
|
||||
|
||||
root_attachment = DocumentFolderAttachment.objects.filter(
|
||||
content_type=structure_ct,
|
||||
object_id=structure.pk,
|
||||
is_default=True
|
||||
).select_related('folder').first()
|
||||
|
||||
if not root_folder and root_attachment:
|
||||
root_folder = root_attachment.folder
|
||||
|
||||
root_name = f"{structure.code} - {structure.get_name()}"
|
||||
root_desc = f"Dossier pour la structure {structure.code} - {structure.get_name()}"
|
||||
|
||||
created_root = False
|
||||
if not root_folder:
|
||||
if not dry_run:
|
||||
root_folder = DocumentFolder.objects.create(
|
||||
name=root_name,
|
||||
slug=root_slug,
|
||||
description=root_desc,
|
||||
visibility=VisibilityScope.INTERNAL,
|
||||
is_predefined=True,
|
||||
application_label='structures',
|
||||
created_by=creator_user
|
||||
)
|
||||
if structures_thematic:
|
||||
root_folder.thematics.add(structures_thematic)
|
||||
if structures_parent_folder:
|
||||
root_folder.parent_folders.add(structures_parent_folder)
|
||||
created_root = True
|
||||
|
||||
if not dry_run and root_folder and not root_attachment:
|
||||
DocumentFolderAttachment.objects.get_or_create(
|
||||
content_type=structure_ct,
|
||||
object_id=structure.pk,
|
||||
folder=root_folder,
|
||||
defaults={'is_default': True}
|
||||
)
|
||||
|
||||
subfolders_created = 0
|
||||
|
||||
def get_or_create_subfolder(parent, name, sub_suffix):
|
||||
nonlocal subfolders_created
|
||||
base_slug = f"{root_slug}-{sub_suffix}"[:150]
|
||||
sub = DocumentFolder.objects.filter(slug=base_slug).first()
|
||||
if not sub and parent and getattr(parent, 'pk', None):
|
||||
sub = parent.child_folders.filter(name=name).first()
|
||||
|
||||
if not sub:
|
||||
if not dry_run:
|
||||
sub = DocumentFolder.objects.create(
|
||||
name=name,
|
||||
slug=base_slug,
|
||||
visibility=VisibilityScope.INTERNAL,
|
||||
is_predefined=False,
|
||||
application_label='structures',
|
||||
created_by=creator_user
|
||||
)
|
||||
if structures_thematic:
|
||||
sub.thematics.add(structures_thematic)
|
||||
if parent and getattr(parent, 'pk', None):
|
||||
sub.parent_folders.add(parent)
|
||||
subfolders_created += 1
|
||||
return sub, True
|
||||
return sub, False
|
||||
|
||||
# 1. Civil engineering & sous-dossiers
|
||||
civil_folder, _ = get_or_create_subfolder(root_folder, "Civil engineering", "civil-engineering")
|
||||
get_or_create_subfolder(civil_folder, "Inspection reports", "civil-engineering-inspection-reports")
|
||||
get_or_create_subfolder(civil_folder, "Exceptional transport", "civil-engineering-exceptional-transport")
|
||||
|
||||
# 2. Electromechanics & sous-dossiers
|
||||
em_folder, _ = get_or_create_subfolder(root_folder, "Electromechanics", "electromechanics")
|
||||
get_or_create_subfolder(em_folder, "Preventive maintenance", "electromechanics-preventive-maintenance")
|
||||
|
||||
return {
|
||||
'root_folder': root_folder,
|
||||
'created_root': created_root,
|
||||
'subfolders_created': subfolders_created
|
||||
}
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Crée les répertoires par défaut de gestion documentaire pour les structures."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help="Simule l'exécution sans effectuer d'écritures en base de données."
|
||||
)
|
||||
parser.add_argument(
|
||||
'--type',
|
||||
dest='structure_type',
|
||||
help="Filtre par type de structure (ex: metro_station,metro_tunnel)."
|
||||
)
|
||||
parser.add_argument(
|
||||
'--code',
|
||||
dest='structure_code',
|
||||
help="Cible un ouvrage spécifique via son code (ex: MS001)."
|
||||
)
|
||||
parser.add_argument(
|
||||
'--user',
|
||||
dest='username',
|
||||
help="Nom d'utilisateur créateur des dossiers (par défaut kdeterme ou superuser)."
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
dry_run = options['dry_run']
|
||||
structure_type = options.get('structure_type')
|
||||
structure_code = options.get('structure_code')
|
||||
username = options.get('username')
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING("=== Mode simulation (--dry-run) activé. Aucune modification en base. ==="))
|
||||
|
||||
creator_user = None
|
||||
if username:
|
||||
creator_user = User.objects.filter(username=username).first()
|
||||
if not creator_user:
|
||||
creator_user = User.objects.filter(username="kdeterme").first() or User.objects.filter(is_superuser=True).first()
|
||||
|
||||
qs = Structure.objects.all().order_by('code')
|
||||
|
||||
if structure_code:
|
||||
qs = qs.filter(code__iexact=structure_code)
|
||||
elif structure_type:
|
||||
types = [t.strip() for t in structure_type.split(',')]
|
||||
qs = qs.filter(structure_type__in=types)
|
||||
|
||||
structure_ct = ContentType.objects.get_for_model(Structure)
|
||||
existing_attached_ids = set(
|
||||
DocumentFolderAttachment.objects.filter(
|
||||
content_type=structure_ct,
|
||||
is_default=True
|
||||
).values_list('object_id', flat=True)
|
||||
)
|
||||
|
||||
missing_structures = [s for s in qs if s.pk not in existing_attached_ids]
|
||||
|
||||
self.stdout.write(f"Structures analysées : {qs.count()} | Sans répertoire par défaut : {len(missing_structures)}")
|
||||
|
||||
total_roots = 0
|
||||
total_subfolders = 0
|
||||
|
||||
target_structures = missing_structures if not structure_code else list(qs)
|
||||
|
||||
for structure in target_structures:
|
||||
res = ensure_structure_folders(structure, creator_user=creator_user, dry_run=dry_run)
|
||||
if res['created_root']:
|
||||
total_roots += 1
|
||||
total_subfolders += res['subfolders_created']
|
||||
self.stdout.write(f" ✓ [{structure.code}] {structure.get_name()} -> Dossier racine et arborescence configurés")
|
||||
|
||||
status_label = "[Simulation] Répertoires qui seraient créés" if dry_run else "Répertoires créés avec succès"
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f"\n=== {status_label} ===\n"
|
||||
f" - Dossiers racines créés : {total_roots}\n"
|
||||
f" - Sous-dossiers créés : {total_subfolders}\n"
|
||||
f" - Total dossiers : {total_roots + total_subfolders}"
|
||||
))
|
||||
Loading…
Reference in a new issue