feat(library): register IngestJob admin and link Neo4j views
All checks were successful
CVE Scan & Docker Build / security-scan (push) Successful in 52s
CVE Scan & Docker Build / build-and-push (push) Successful in 2m24s

- Add read-only ModelAdmin for IngestJob with filters, search, and
  date hierarchy for operational visibility
- Inject proxy entries into the admin index for Neo4j-backed entities
  (Libraries, Concepts, Search, Embedding pipeline) that link to
  existing CRUD views in library/views.py
- Makes library content discoverable from /admin/ without pretending
  neomodel StructuredNodes are Django ORM models
This commit is contained in:
2026-05-22 23:54:10 -04:00
parent 409da7d109
commit 50dffe688b

View File

@@ -1,5 +1,123 @@
# Library app does not use standard Django admin (neomodel StructuredNodes """Admin registrations for the library app.
# are not Django ORM models). Custom admin views are provided as regular
# app views in library/views.py, rendered within Themis's template structure. Most library content (Library, Collection, Item, Chunk, Concept, Image,
# ImageEmbedding) lives in Neo4j via neomodel and cannot use Django's
# The embedding pipeline dashboard is at /library/embedding/ standard ModelAdmin. Those entities have full CRUD pages in
``library/views.py``; this module wires proxy rows into the admin index
so they are discoverable from ``/admin/``.
``IngestJob`` is a regular Django ORM model and gets a normal,
read-only ModelAdmin.
"""
from django.contrib import admin
from django.urls import NoReverseMatch, reverse
from .models import IngestJob
@admin.register(IngestJob)
class IngestJobAdmin(admin.ModelAdmin):
list_display = (
"id",
"status",
"library_uid",
"item_uid",
"source",
"source_ref",
"progress",
"retry_count",
"created_at",
"completed_at",
)
list_filter = ("status", "source", "created_at")
search_fields = (
"id",
"library_uid",
"item_uid",
"source_ref",
"content_hash",
"title",
"celery_task_id",
)
date_hierarchy = "created_at"
ordering = ("-created_at",)
readonly_fields = (
"id",
"item_uid",
"library_uid",
"celery_task_id",
"status",
"progress",
"error",
"retry_count",
"chunks_created",
"concepts_extracted",
"embedding_model",
"content_hash",
"source",
"source_ref",
"s3_key",
"title",
"file_type",
"file_size",
"collection_uid",
"created_at",
"started_at",
"completed_at",
)
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
# View-only; superusers can still open the detail page.
return request.method in ("GET", "HEAD")
# ---------------------------------------------------------------------------
# Neo4j-resident entities: proxy entries on the admin index that link to the
# existing CRUD views in library/views.py. No shim models — just an honest
# signpost so /admin/ doesn't pretend the library is empty.
# ---------------------------------------------------------------------------
_NEO4J_ADMIN_LINKS = [
("Libraries", "library:library-list"),
("Concepts", "library:concept-list"),
("Search", "library:search"),
("Embedding pipeline", "library:embedding-dashboard"),
]
_original_get_app_list = admin.site.get_app_list
def _get_app_list_with_library_links(request, app_label=None):
app_list = _original_get_app_list(request, app_label)
for app in app_list:
if app["app_label"] != "library":
continue
for name, url_name in _NEO4J_ADMIN_LINKS:
try:
url = reverse(url_name)
except NoReverseMatch:
continue
app["models"].append(
{
"name": name,
"object_name": name,
"perms": {
"add": False,
"change": True,
"delete": False,
"view": True,
},
"admin_url": url,
"add_url": None,
"view_only": True,
}
)
return app_list
admin.site.get_app_list = _get_app_list_with_library_links