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