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
# are not Django ORM models). Custom admin views are provided as regular
# app views in library/views.py, rendered within Themis's template structure.
#
# The embedding pipeline dashboard is at /library/embedding/
"""Admin registrations for the library app.
Most library content (Library, Collection, Item, Chunk, Concept, Image,
ImageEmbedding) lives in Neo4j via neomodel and cannot use Django's
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