File indexing completed on 2026-04-27 07:41:43
0001 """
0002 PanDA Database browsing views.
0003
0004 This module provides views for browsing the external PanDA database
0005 following the same patterns as the existing SWF database browser.
0006 """
0007
0008 from django.shortcuts import render
0009 from django.contrib.auth.decorators import login_required
0010 from django.http import Http404, JsonResponse
0011 from django.db import connections
0012 from datetime import datetime, date
0013
0014
0015 @login_required
0016 def panda_database_tables_list(request):
0017 """
0018 PanDA database tables list view using server-side DataTables.
0019 Shows all tables in the PanDA database with counts and last insert times.
0020 """
0021 from django.urls import reverse
0022
0023
0024 columns = [
0025 {'name': 'name', 'title': 'Table Name', 'orderable': True},
0026 {'name': 'count', 'title': 'Row Count', 'orderable': True},
0027 {'name': 'last_insert', 'title': 'Last Insert', 'orderable': True},
0028 ]
0029
0030 context = {
0031 'table_title': 'PanDA Database Overview',
0032 'table_description': 'Server-side processing view of all tables in the PanDA database with row counts and last insert times.',
0033 'ajax_url': reverse('monitor_app:panda_database_tables_datatable_ajax'),
0034 'columns': columns,
0035 }
0036 return render(request, 'monitor_app/database_tables_server.html', context)
0037
0038
0039 @login_required
0040 def panda_database_tables_datatable_ajax(request):
0041 """
0042 AJAX endpoint for server-side DataTables processing of PanDA database tables.
0043 """
0044 from ..utils import DataTablesProcessor, format_datetime
0045
0046
0047 columns = ['name', 'count', 'last_insert']
0048 dt = DataTablesProcessor(request, columns, default_order_column=0, default_order_direction='asc')
0049
0050
0051 panda_connection = connections['panda']
0052
0053
0054 table_records = []
0055 try:
0056 with panda_connection.cursor() as cursor:
0057
0058
0059 cursor.execute("""
0060 SELECT table_name
0061 FROM information_schema.tables
0062 WHERE table_schema = 'doma_panda'
0063 AND table_type = 'BASE TABLE'
0064 ORDER BY table_name
0065 """)
0066 tables = [row[0] for row in cursor.fetchall()]
0067
0068 for table_name in tables:
0069 record = {
0070 'name': table_name,
0071 'count': 0,
0072 'last_insert': None
0073 }
0074
0075 try:
0076
0077 cursor.execute(f'SELECT COUNT(*) FROM "doma_panda"."{table_name}"')
0078 record['count'] = cursor.fetchone()[0]
0079
0080
0081 cursor.execute("""
0082 SELECT column_name
0083 FROM information_schema.columns
0084 WHERE table_schema = 'doma_panda'
0085 AND table_name = %s
0086 AND data_type IN ('timestamp', 'timestamp with time zone', 'timestamp without time zone')
0087 ORDER BY ordinal_position
0088 LIMIT 1
0089 """, [table_name])
0090
0091 timestamp_col = cursor.fetchone()
0092 if timestamp_col:
0093 cursor.execute(f'SELECT MAX("{timestamp_col[0]}") FROM "doma_panda"."{table_name}"')
0094 result = cursor.fetchone()
0095 if result and result[0]:
0096 record['last_insert'] = result[0]
0097
0098 except Exception:
0099 pass
0100
0101 table_records.append(record)
0102
0103 except Exception as e:
0104
0105 table_records = []
0106
0107
0108 records_total = len(table_records)
0109
0110
0111 if dt.search_value:
0112 search_term = dt.search_value.lower()
0113 table_records = [r for r in table_records if search_term in r['name'].lower()]
0114
0115 records_filtered = len(table_records)
0116
0117
0118 table_records.sort(key=lambda r: (r[dt.order_column] is None, r[dt.order_column]), reverse=(dt.order_direction == 'desc'))
0119
0120
0121 start = dt.start
0122 length = dt.length if dt.length > 0 else len(table_records)
0123 paginated_records = table_records[start:start + length]
0124
0125
0126 data = []
0127 for record in paginated_records:
0128 from django.urls import reverse
0129 table_url = reverse('monitor_app:panda_database_table_list', args=[record['name']])
0130 table_link = f'<a href="{table_url}">{record["name"]}</a>'
0131 count_str = str(record['count'])
0132 last_insert_str = format_datetime(record['last_insert'])
0133
0134 data.append([table_link, count_str, last_insert_str])
0135
0136 return dt.create_response(data, records_total, records_filtered)
0137
0138
0139 @login_required
0140 def panda_database_table_list(request, table_name):
0141 """
0142 PanDA database individual table view (excluding problematic TEXT columns).
0143 """
0144 from django.urls import reverse
0145
0146
0147 panda_connection = connections['panda']
0148
0149
0150 columns = []
0151 try:
0152 with panda_connection.cursor() as cursor:
0153
0154 cursor.execute("""
0155 SELECT column_name
0156 FROM information_schema.columns
0157 WHERE table_schema = 'doma_panda'
0158 AND table_name = %s
0159 ORDER BY ordinal_position
0160 LIMIT 20
0161 """, [table_name])
0162 columns = [row[0] for row in cursor.fetchall()]
0163 if not columns:
0164 raise Http404()
0165 except Exception:
0166 raise Http404()
0167
0168
0169 datatable_columns = [{'name': col, 'title': col.replace('_', ' ').title(), 'orderable': True} for col in columns]
0170
0171 context = {
0172 'table_title': f'PanDA Table: {table_name}',
0173 'table_description': f'PanDA database table contents for {table_name} (first 20 columns only). Search, sorting, and pagination available.',
0174 'ajax_url': reverse('monitor_app:panda_database_table_datatable_ajax', kwargs={'table_name': table_name}),
0175 'columns': datatable_columns,
0176 'table_name': table_name,
0177 }
0178 return render(request, 'monitor_app/database_table_list.html', context)
0179
0180
0181 @login_required
0182 def panda_database_table_datatable_ajax(request, table_name):
0183 """
0184 AJAX endpoint for server-side DataTables processing of individual PanDA database table.
0185 """
0186 from ..utils import DataTablesProcessor, format_datetime
0187
0188
0189 panda_connection = connections['panda']
0190
0191
0192 columns = []
0193 try:
0194 with panda_connection.cursor() as cursor:
0195
0196 cursor.execute("""
0197 SELECT column_name
0198 FROM information_schema.columns
0199 WHERE table_schema = 'doma_panda'
0200 AND table_name = %s
0201 ORDER BY ordinal_position
0202 LIMIT 20
0203 """, [table_name])
0204 columns = [row[0] for row in cursor.fetchall()]
0205 if not columns:
0206 raise Http404()
0207 except Exception:
0208 raise Http404()
0209
0210
0211 dt = DataTablesProcessor(request, columns, default_order_column=0, default_order_direction='asc')
0212
0213
0214 column_list = ', '.join([f'"{col}"' for col in columns])
0215 query = f'SELECT {column_list} FROM "doma_panda"."{table_name}"'
0216 count_query = f'SELECT COUNT(*) FROM "doma_panda"."{table_name}"'
0217 params = []
0218
0219 try:
0220 with panda_connection.cursor() as cursor:
0221
0222 cursor.execute(count_query)
0223 records_total = cursor.fetchone()[0]
0224
0225
0226 where_conditions = []
0227 if dt.search_value:
0228 search_conditions = []
0229 for column in columns:
0230 search_conditions.append(f'CAST("{column}" AS TEXT) ILIKE %s')
0231 params.append(f'%{dt.search_value}%')
0232 where_conditions.append(f"({' OR '.join(search_conditions)})")
0233
0234
0235 filtered_query = query
0236 filtered_count_query = count_query
0237 if where_conditions:
0238 where_clause = ' WHERE ' + ' AND '.join(where_conditions)
0239 filtered_query += where_clause
0240 filtered_count_query += where_clause
0241
0242
0243 cursor.execute(filtered_count_query, params)
0244 records_filtered = cursor.fetchone()[0]
0245
0246
0247 if dt.order_column:
0248 order_direction = 'ASC' if dt.order_direction == 'asc' else 'DESC'
0249 filtered_query += f' ORDER BY "{dt.order_column}" {order_direction}'
0250
0251
0252 filtered_query += f' LIMIT {dt.length} OFFSET {dt.start}'
0253
0254
0255 cursor.execute(filtered_query, params)
0256 rows = cursor.fetchall()
0257
0258
0259 data = []
0260 for row in rows:
0261 formatted_row = []
0262 for i, value in enumerate(row):
0263 if i == 0:
0264 from django.urls import reverse
0265 detail_url = reverse('monitor_app:panda_database_table_row_detail', args=[table_name, str(value)])
0266 formatted_row.append(f'<a href="{detail_url}">{str(value) if value is not None else ""}</a>')
0267 elif value is None:
0268 formatted_row.append('')
0269 elif isinstance(value, (datetime, date)):
0270 formatted_row.append(format_datetime(value))
0271 else:
0272 formatted_row.append(str(value))
0273 data.append(formatted_row)
0274
0275 except Exception as e:
0276
0277 data = []
0278 records_total = 0
0279 records_filtered = 0
0280
0281 return dt.create_response(data, records_total, records_filtered)
0282
0283
0284 @login_required
0285 def panda_database_table_row_detail(request, table_name, row_id):
0286 """
0287 PanDA database individual row detail view.
0288 Shows all column values for a specific row.
0289 """
0290
0291 panda_connection = connections['panda']
0292
0293
0294 columns = []
0295 try:
0296 with panda_connection.cursor() as cursor:
0297 cursor.execute("""
0298 SELECT column_name
0299 FROM information_schema.columns
0300 WHERE table_schema = 'doma_panda'
0301 AND table_name = %s
0302 ORDER BY ordinal_position
0303 """, [table_name])
0304 columns = [row[0] for row in cursor.fetchall()]
0305 if not columns:
0306 raise Http404("Table not found")
0307 except Exception:
0308 raise Http404("Table not found")
0309
0310
0311 row_data = {}
0312 try:
0313 with panda_connection.cursor() as cursor:
0314
0315 column_list = ', '.join([f'"{col}"' for col in columns])
0316 query = f'SELECT {column_list} FROM "doma_panda"."{table_name}" WHERE "{columns[0]}" = %s LIMIT 1'
0317
0318 cursor.execute(query, [row_id])
0319 row = cursor.fetchone()
0320
0321 if not row:
0322 raise Http404("Row not found")
0323
0324
0325 row_data = []
0326 for i, column in enumerate(columns):
0327 value = row[i]
0328 if value is None:
0329 formatted_value = None
0330 elif isinstance(value, (datetime, date)):
0331 from ..utils import format_datetime
0332 formatted_value = format_datetime(value)
0333 else:
0334 formatted_value = str(value)
0335 row_data.append((column, formatted_value))
0336
0337 except Exception:
0338 raise Http404("Row not found")
0339
0340 context = {
0341 'table_name': table_name,
0342 'row_id': row_id,
0343 'row_data': row_data,
0344 }
0345 return render(request, 'monitor_app/panda_table_row_detail.html', context)
0346
0347