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 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.")
|
||||
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
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}"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue