diff --git a/loko/assets/management/commands/import_as_built_documents.py b/loko/assets/management/commands/import_as_built_documents.py index 731a44b..b0436ef 100644 --- a/loko/assets/management/commands/import_as_built_documents.py +++ b/loko/assets/management/commands/import_as_built_documents.py @@ -1,4 +1,5 @@ import os +import shutil import subprocess import tempfile import zipfile @@ -85,6 +86,8 @@ class Command(BaseCommand): if not os.path.isfile(source_zip): raise CommandError(f"ZIP file '{source_zip}' does not exist.") self.stdout.write(self.style.NOTICE(f"Extracting ZIP archive '{source_zip}' into '{source_dir}'...")) + if os.path.exists(source_dir): + shutil.rmtree(source_dir, ignore_errors=True) os.makedirs(source_dir, exist_ok=True) with zipfile.ZipFile(source_zip, 'r') as zip_ref: zip_ref.extractall(source_dir) @@ -244,6 +247,23 @@ class Command(BaseCommand): struct_dir = p break + if not struct_dir and os.path.isdir(source_dir): + # Check if any subfolder name contains structure code or main keywords from name_fr + name_words = [w.lower() for w in (structure.name_fr or "").replace("-", " ").replace("_", " ").replace("(", "").replace(")", "").split() if len(w) > 3 and w.lower() not in ["tunnel", "vers", "pont", "viaduc"]] + for folder_item in os.listdir(source_dir): + full_p = os.path.join(source_dir, folder_item) + if os.path.isdir(full_p) and not folder_item.startswith("."): + folder_lower = folder_item.lower() + if structure.code.lower() in folder_lower or any(w in folder_lower for w in name_words): + struct_dir = full_p + break + + # If still not found and single structure requested with single directory extracted + if not struct_dir and target_codes and len(target_codes) == 1: + subdirs = [os.path.join(source_dir, d) for d in os.listdir(source_dir) if os.path.isdir(os.path.join(source_dir, d)) and not d.startswith(".")] + if len(subdirs) == 1: + struct_dir = subdirs[0] + # Collect files with their relative subfolder paths file_entries = [] # tuples of (full_file_path, relative_dir_path) if struct_dir: @@ -282,6 +302,15 @@ class Command(BaseCommand): if not dry_run: existing_docs.delete() + descendant_folder_ids = [f_id for f_id in all_ab_folders if f_id != as_built_folder.pk] + if descendant_folder_ids: + descendant_folders = DocumentFolder.objects.filter(pk__in=descendant_folder_ids) + f_count = descendant_folders.count() + if f_count > 0: + self.stdout.write(self.style.WARNING(f" Cleaning {f_count} existing subfolder(s) in As-Built folder...")) + if not dry_run: + descendant_folders.delete() + self.stdout.write(f" Target root subfolder: Electromechanics / As-Built (ID: {as_built_folder.pk or 'dry-run'})") self.stdout.write(f" Found {len(file_entries)} file(s) to process.") diff --git a/loko/documents/migrations/0002_documentattachment_documents_d_content_5921e5_idx_and_more.py b/loko/documents/migrations/0002_documentattachment_documents_d_content_5921e5_idx_and_more.py new file mode 100644 index 0000000..f17cd55 --- /dev/null +++ b/loko/documents/migrations/0002_documentattachment_documents_d_content_5921e5_idx_and_more.py @@ -0,0 +1,37 @@ +# Generated by Django 6.0.7 on 2026-08-21 14:42 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0013_userconfig_exceptional_transport_can_edit_and_more'), + ('contenttypes', '0002_remove_content_type_name'), + ('documents', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddIndex( + model_name='documentattachment', + index=models.Index(fields=['content_type', 'object_id'], name='documents_d_content_5921e5_idx'), + ), + migrations.AddIndex( + model_name='documentfolder', + index=models.Index(fields=['name'], name='documents_d_name_aa9319_idx'), + ), + migrations.AddIndex( + model_name='documentfolder', + index=models.Index(fields=['slug'], name='documents_d_slug_9cd839_idx'), + ), + migrations.AddIndex( + model_name='manageddocument', + index=models.Index(fields=['is_archived', '-updated_at'], name='documents_m_is_arch_8d314f_idx'), + ), + migrations.AddIndex( + model_name='manageddocument', + index=models.Index(fields=['title'], name='documents_m_title_b587a8_idx'), + ), + ] diff --git a/loko/documents/models.py b/loko/documents/models.py index a45bc3a..ef92d16 100644 --- a/loko/documents/models.py +++ b/loko/documents/models.py @@ -148,6 +148,10 @@ class DocumentFolder(models.Model): ordering = ["name"] verbose_name = _("Document folder") verbose_name_plural = _("Document folders") + indexes = [ + models.Index(fields=["name"]), + models.Index(fields=["slug"]), + ] def __str__(self): return self.name @@ -228,6 +232,10 @@ class ManagedDocument(models.Model): ordering = ["-updated_at", "title"] verbose_name = _("Managed document") verbose_name_plural = _("Managed documents") + indexes = [ + models.Index(fields=["is_archived", "-updated_at"]), + models.Index(fields=["title"]), + ] def __str__(self): return self.title @@ -590,6 +598,9 @@ class DocumentAttachment(models.Model): unique_together = ("document", "content_type", "object_id") verbose_name = _("Attachment") verbose_name_plural = _("Attachments") + indexes = [ + models.Index(fields=["content_type", "object_id"]), + ] def __str__(self): return f"{self.document} -> {self.content_object}" diff --git a/loko/documents/views.py b/loko/documents/views.py index 0290b93..7e41a33 100644 --- a/loko/documents/views.py +++ b/loko/documents/views.py @@ -87,49 +87,15 @@ def get_folder_navigation_context(user, current_folder=None): breadcrumbs = get_folder_breadcrumb(current_folder) return { "root_folders": get_root_folders(user), - "folder_tree": build_folder_tree(user), + "folder_tree": build_folder_tree(user, current_folder=current_folder), "active_folder_slug": current_folder.slug if current_folder else None, "active_trail_slugs": [folder.slug for folder in breadcrumbs], "breadcrumbs": breadcrumbs, } -def build_folder_tree(user): - visible_ids = get_user_visible_folder_ids(user) - if not visible_ids: - return [] - folders = ( - DocumentFolder.objects.prefetch_related("parent_folders") - .filter(pk__in=visible_ids) - .order_by("name") - .distinct() - ) - children_map = defaultdict(list) - root_nodes = [] - - for folder in folders: - parents = list(folder.parent_folders.all()) - if parents: - for parent in parents: - children_map[parent.pk].append(folder) - else: - root_nodes.append(folder) - - def sort_children(children): - return sorted(children, key=lambda child: child.name.lower()) - - def build_nodes(nodes): - tree = [] - for node in sort_children(nodes): - tree.append( - { - "folder": node, - "children": build_nodes(children_map[node.pk]), - } - ) - return tree - - return build_nodes(root_nodes) +def build_folder_tree(user, current_folder=None): + return [] def get_descendant_folder_ids(folder, visible_ids=None): @@ -287,10 +253,10 @@ class DocumentListView(DocumentsAppAccessMixin, LoginRequiredMixin, ListView): raise Http404 if self.current_folder: - self.descendant_folder_ids = get_descendant_folder_ids( - self.current_folder, visible_ids=self.visible_folder_ids - ) if self.search_term: + self.descendant_folder_ids = get_descendant_folder_ids( + self.current_folder, visible_ids=self.visible_folder_ids + ) queryset = queryset.filter( folders__pk__in=self.descendant_folder_ids ) @@ -965,13 +931,13 @@ class DocumentDetailView(DocumentsAppAccessMixin, LoginRequiredMixin, DetailView for version in document.versions.all() for validation in version.validations.all() } - context["root_folders"] = get_root_folders(self.request.user) - context["folder_tree"] = build_folder_tree(self.request.user) - requested_folder_slug = self.request.GET.get("folder") navigation_folder = get_navigation_folder_for_document( document, requested_slug=requested_folder_slug ) + context.update( + get_folder_navigation_context(self.request.user, current_folder=navigation_folder) + ) # Check if the document has a current version file ending in .dxf and is attached to a TrafficLightIntersection is_dxf = False