refactor: optimize document navigation, add model database indexes, and improve as-built document import logic
This commit is contained in:
parent
5fd6ded07f
commit
ce48b99a45
4 changed files with 86 additions and 43 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
@ -85,6 +86,8 @@ class Command(BaseCommand):
|
||||||
if not os.path.isfile(source_zip):
|
if not os.path.isfile(source_zip):
|
||||||
raise CommandError(f"ZIP file '{source_zip}' does not exist.")
|
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}'..."))
|
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)
|
os.makedirs(source_dir, exist_ok=True)
|
||||||
with zipfile.ZipFile(source_zip, 'r') as zip_ref:
|
with zipfile.ZipFile(source_zip, 'r') as zip_ref:
|
||||||
zip_ref.extractall(source_dir)
|
zip_ref.extractall(source_dir)
|
||||||
|
|
@ -244,6 +247,23 @@ class Command(BaseCommand):
|
||||||
struct_dir = p
|
struct_dir = p
|
||||||
break
|
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
|
# Collect files with their relative subfolder paths
|
||||||
file_entries = [] # tuples of (full_file_path, relative_dir_path)
|
file_entries = [] # tuples of (full_file_path, relative_dir_path)
|
||||||
if struct_dir:
|
if struct_dir:
|
||||||
|
|
@ -282,6 +302,15 @@ class Command(BaseCommand):
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
existing_docs.delete()
|
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" 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.")
|
self.stdout.write(f" Found {len(file_entries)} file(s) to process.")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -148,6 +148,10 @@ class DocumentFolder(models.Model):
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
verbose_name = _("Document folder")
|
verbose_name = _("Document folder")
|
||||||
verbose_name_plural = _("Document folders")
|
verbose_name_plural = _("Document folders")
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["name"]),
|
||||||
|
models.Index(fields=["slug"]),
|
||||||
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
@ -228,6 +232,10 @@ class ManagedDocument(models.Model):
|
||||||
ordering = ["-updated_at", "title"]
|
ordering = ["-updated_at", "title"]
|
||||||
verbose_name = _("Managed document")
|
verbose_name = _("Managed document")
|
||||||
verbose_name_plural = _("Managed documents")
|
verbose_name_plural = _("Managed documents")
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["is_archived", "-updated_at"]),
|
||||||
|
models.Index(fields=["title"]),
|
||||||
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
@ -590,6 +598,9 @@ class DocumentAttachment(models.Model):
|
||||||
unique_together = ("document", "content_type", "object_id")
|
unique_together = ("document", "content_type", "object_id")
|
||||||
verbose_name = _("Attachment")
|
verbose_name = _("Attachment")
|
||||||
verbose_name_plural = _("Attachments")
|
verbose_name_plural = _("Attachments")
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["content_type", "object_id"]),
|
||||||
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.document} -> {self.content_object}"
|
return f"{self.document} -> {self.content_object}"
|
||||||
|
|
|
||||||
|
|
@ -87,49 +87,15 @@ def get_folder_navigation_context(user, current_folder=None):
|
||||||
breadcrumbs = get_folder_breadcrumb(current_folder)
|
breadcrumbs = get_folder_breadcrumb(current_folder)
|
||||||
return {
|
return {
|
||||||
"root_folders": get_root_folders(user),
|
"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_folder_slug": current_folder.slug if current_folder else None,
|
||||||
"active_trail_slugs": [folder.slug for folder in breadcrumbs],
|
"active_trail_slugs": [folder.slug for folder in breadcrumbs],
|
||||||
"breadcrumbs": breadcrumbs,
|
"breadcrumbs": breadcrumbs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def build_folder_tree(user):
|
def build_folder_tree(user, current_folder=None):
|
||||||
visible_ids = get_user_visible_folder_ids(user)
|
return []
|
||||||
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 get_descendant_folder_ids(folder, visible_ids=None):
|
def get_descendant_folder_ids(folder, visible_ids=None):
|
||||||
|
|
@ -287,10 +253,10 @@ class DocumentListView(DocumentsAppAccessMixin, LoginRequiredMixin, ListView):
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
if self.current_folder:
|
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:
|
if self.search_term:
|
||||||
|
self.descendant_folder_ids = get_descendant_folder_ids(
|
||||||
|
self.current_folder, visible_ids=self.visible_folder_ids
|
||||||
|
)
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
folders__pk__in=self.descendant_folder_ids
|
folders__pk__in=self.descendant_folder_ids
|
||||||
)
|
)
|
||||||
|
|
@ -965,13 +931,13 @@ class DocumentDetailView(DocumentsAppAccessMixin, LoginRequiredMixin, DetailView
|
||||||
for version in document.versions.all()
|
for version in document.versions.all()
|
||||||
for validation in version.validations.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")
|
requested_folder_slug = self.request.GET.get("folder")
|
||||||
navigation_folder = get_navigation_folder_for_document(
|
navigation_folder = get_navigation_folder_for_document(
|
||||||
document, requested_slug=requested_folder_slug
|
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
|
# Check if the document has a current version file ending in .dxf and is attached to a TrafficLightIntersection
|
||||||
is_dxf = False
|
is_dxf = False
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue