@@ -419,101 +419,119 @@ def query_sql():
419419@app .route ('/' )
420420def public_home ():
421421 return render_template ('landing.html' )
422-
422+ #########################################################
423423@app .route ('/materials' )
424424def materials_portal ():
425425 return render_template ('materials_portal.html' )
426-
426+ #########################################################
427427@app .route ('/materials/<property_name>/<tab>' , methods = ['GET' , 'POST' ])
428428def property_detail (property_name , tab ):
429+ # ---- titles / guards ----
429430 pretty_titles = {
430431 'bandgap' : 'Band Gap' ,
431432 'formation_energy' : 'Formation Energy' ,
432433 'melting_point' : 'Melting Point' ,
433- 'oxidation_state' : 'Oxidation State'
434+ 'oxidation_state' : 'Oxidation State' ,
434435 }
435- if property_name not in pretty_titles or tab not in [ 'dataset' , 'results' ] :
436+ if property_name not in pretty_titles or tab not in ( 'dataset' , 'results' ) :
436437 return "Not found." , 404
437438
438439 upload_message = ""
439440 edit_message = ""
440441 is_admin = session .get ('admin' , False )
441442
443+ # ---- admin POST handlers ----
442444 if is_admin and request .method == 'POST' :
443- # Inline edit form (from table row)
445+ # Inline row edit
444446 if 'edit_row' in request .form :
445447 row_filename = request .form .get ('row_filename' )
446- new_source = request .form .get ('row_source' , '' ).strip () if tab == 'dataset' else None
447- new_desc = request .form .get ('row_description' , '' ).strip ()
448- with sqlite3 .connect (DB_NAME ) as conn :
449- c = conn .cursor ()
450- if tab == 'dataset' :
448+ safe_row_filename = secure_filename (os .path .basename (row_filename or "" ))
449+
450+ new_desc = (request .form .get ('row_description' ) or '' ).strip ()
451+ if tab == 'dataset' :
452+ new_source = (request .form .get ('row_source' ) or '' ).strip ()
453+ with sqlite3 .connect (DB_NAME ) as conn :
454+ c = conn .cursor ()
451455 c .execute ("""
452456 UPDATE uploads_log
453- SET source=?, description=?
454- WHERE property=? AND tab=? AND filename=?
455- """ , (new_source , new_desc , property_name , tab , row_filename ))
456- else :
457+ SET source = ?, description = ?
458+ WHERE property = ? AND tab = ? AND filename = ?
459+ """ , (new_source , new_desc , property_name , tab , safe_row_filename ))
460+ conn .commit ()
461+ else :
462+ with sqlite3 .connect (DB_NAME ) as conn :
463+ c = conn .cursor ()
457464 c .execute ("""
458465 UPDATE uploads_log
459- SET description=?
460- WHERE property=? AND tab=? AND filename=?
461- """ , (new_desc , property_name , tab , row_filename ))
462- conn .commit ()
463- edit_message = f"Updated info for { row_filename } ."
464- # Upload form
466+ SET description = ?
467+ WHERE property = ? AND tab = ? AND filename = ?
468+ """ , (new_desc , property_name , tab , safe_row_filename ))
469+ conn .commit ()
470+ edit_message = f"Updated info for { safe_row_filename } ."
471+
472+ # New file upload
465473 elif 'file' in request .files :
466- if request .files ['file' ].filename == '' :
474+ f = request .files ['file' ]
475+ if not f or f .filename == '' :
467476 upload_message = "No file selected."
468477 else :
469- file = request .files ['file' ]
470- # Set allowed extensions logic
478+ # Validate extension by tab
471479 if tab == 'dataset' :
472- is_allowed = allowed_dataset_file (file .filename )
480+ is_allowed = allowed_dataset_file (f .filename )
473481 allowed_types = "CSV or NPY"
474482 elif tab == 'results' :
475- is_allowed = allowed_results_file (file .filename )
483+ is_allowed = allowed_results_file (f .filename )
476484 allowed_types = "JPG, PNG, GIF, PDF, or DOCX"
477485 else :
478486 is_allowed = False
479487 allowed_types = ""
480488
481- if file and is_allowed :
489+ if not is_allowed :
490+ upload_message = f"File type not allowed. Only { allowed_types } supported."
491+ else :
492+ # Save to disk (under /uploads/<property>/<tab>/)
482493 property_folder = os .path .join (app .config ['UPLOAD_FOLDER' ], property_name , tab )
483494 os .makedirs (property_folder , exist_ok = True )
484- filename = secure_filename (file .filename )
485- filepath = os .path .join (property_folder , filename )
486- file .save (filepath )
487- # LOG THE UPLOAD!
495+ safe_filename = secure_filename (os .path .basename (f .filename ))
496+ filepath = os .path .join (property_folder , safe_filename )
497+ f .save (filepath )
498+
499+ # Log to DB (idempotent)
488500 with sqlite3 .connect (DB_NAME ) as conn :
489501 c = conn .cursor ()
490- c .execute ("""
502+ c .execute (
503+ """
491504 INSERT INTO uploads_log (property, tab, filename, uploaded_at)
492505 VALUES (?, ?, ?, ?)
493506 ON CONFLICT(property, tab, filename)
494- DO UPDATE SET uploaded_at=excluded.uploaded_at
495- """ , (property_name , tab , filename , datetime .datetime .now ().isoformat ()))
507+ DO UPDATE SET uploaded_at = excluded.uploaded_at
508+ """ ,
509+ (property_name , tab , safe_filename , datetime .datetime .now ().isoformat ()),
510+ )
496511 conn .commit ()
497512
498- upload_message = f"File { filename } uploaded for { pretty_titles [property_name ]} { tab .title ()} !"
499- else :
500- upload_message = f"File type not allowed. Only { allowed_types } supported."
513+ upload_message = f"File { safe_filename } uploaded for { pretty_titles [property_name ]} { tab .title ()} !"
501514
502- # Always fetch current uploads after handling POSTs
503- uploads = []
515+ # ---- fetch current uploads (dedup to 1 row per filename; keep newest) ----
504516 with sqlite3 .connect (DB_NAME ) as conn :
505517 c = conn .cursor ()
506518 c .execute ("""
507- SELECT filename, source, description, uploaded_at
508- FROM uploads_log
509- WHERE property=? AND tab=?
510- ORDER BY uploaded_at DESC
511- """ , (property_name , tab ))
519+ SELECT u.filename,
520+ COALESCE(u.source, '') AS source,
521+ COALESCE(u.description, '') AS description,
522+ u.uploaded_at
523+ FROM uploads_log AS u
524+ JOIN (
525+ SELECT filename, MAX(uploaded_at) AS max_ts
526+ FROM uploads_log
527+ WHERE property = ? AND tab = ?
528+ GROUP BY filename
529+ ) AS m
530+ ON m.filename = u.filename AND u.uploaded_at = m.max_ts
531+ WHERE u.property = ? AND u.tab = ?
532+ ORDER BY u.uploaded_at DESC
533+ """ , (property_name , tab , property_name , tab ))
512534 uploads = c .fetchall ()
513- uploads = [
514- (fname , source , description , uploaded_at )
515- for (fname , source , description , uploaded_at ) in uploads
516- ]
517535
518536 return render_template (
519537 'property_detail.html' ,
@@ -523,10 +541,9 @@ def property_detail(property_name, tab):
523541 uploads = uploads ,
524542 upload_message = upload_message ,
525543 edit_message = edit_message ,
526- admin = is_admin
544+ admin = is_admin ,
527545 )
528-
529-
546+ #########################################################
530547@app .route ('/uploads/<path:filename>' )
531548def uploaded_file (filename ):
532549 full_path = os .path .join (app .config ['UPLOAD_FOLDER' ], filename )
@@ -535,7 +552,7 @@ def uploaded_file(filename):
535552 print ('File not found:' , full_path )
536553 abort (404 )
537554 return send_from_directory (app .config ['UPLOAD_FOLDER' ], filename )
538-
555+ #########################################################
539556@app .route ('/view_result/<property_name>/<tab>/<path:filename>' )
540557def view_result_file (property_name , tab , filename ):
541558 filepath = os .path .join (app .config ['UPLOAD_FOLDER' ], property_name , tab , filename )
@@ -554,7 +571,7 @@ def extract_drive_id(link):
554571 if match :
555572 return match .group (1 )
556573 raise ValueError ("Invalid Drive link" )
557-
574+ #########################################################
558575@app .route ('/clips' )
559576def public_clips ():
560577 import os
@@ -587,8 +604,7 @@ def public_clips():
587604 pass
588605
589606 return render_template ('clips.html' , clips = clips , admin = admin )
590-
591-
607+ #########################################################
592608@app .route ('/dataset/<table>' )
593609def public_view (table ):
594610 # Anyone can view any table
@@ -600,15 +616,15 @@ def public_view(table):
600616 filename = table ,
601617 imported_table = table ,
602618 admin = False )
603-
619+ #########################################################
604620@app .route ('/download/<table>' )
605621def download (table ):
606622 with sqlite3 .connect (DB_NAME ) as conn :
607623 df = pd .read_sql_query (f"SELECT * FROM { table } " , conn )
608624 csv_path = os .path .join (UPLOAD_FOLDER , f"{ table } .csv" )
609625 df .to_csv (csv_path , index = False )
610626 return send_from_directory (UPLOAD_FOLDER , f"{ table } .csv" , as_attachment = True )
611-
627+ #########################################################
612628@app .route ('/migrate_csv_to_db' )
613629def migrate_csv_to_db ():
614630 if not session .get ('admin' ):
@@ -651,7 +667,7 @@ def migrate_csv_to_db():
651667 return "✅ Table recreated and data loaded from CSV!"
652668 except Exception as e :
653669 return f"❌ Error: { e } "
654-
670+ #########################################################
655671# SEARCH ROUTE
656672@app .route ('/search' )
657673def search ():
@@ -686,7 +702,7 @@ def search():
686702 except Exception :
687703 clips = []
688704 return render_template ('search_results.html' , query = query , materials = materials , clips = clips )
689-
705+ #########################################################
690706# DELETE CLIP
691707@app .route ('/delete_clip/<int:clip_id>' , methods = ['POST' ])
692708def delete_clip (clip_id ):
@@ -705,7 +721,7 @@ def delete_clip(clip_id):
705721 c .execute ("DELETE FROM music_clips WHERE id = ?" , (clip_id ,))
706722 conn .commit ()
707723 return redirect (url_for ('public_clips' ))
708-
724+ #########################################################
709725# DELETE DATASET/RESULT FILE
710726from urllib .parse import unquote
711727
@@ -740,7 +756,7 @@ def delete_dataset_file(property_name, tab, filename):
740756 conn .commit ()
741757
742758 return redirect (url_for ('property_detail' , property_name = property_name , tab = tab ))
743-
759+ #########################################################
744760@app .route ('/add_drive_clip' , methods = ['GET' , 'POST' ])
745761def add_drive_clip ():
746762 if not session .get ('admin' ):
@@ -780,8 +796,7 @@ def extract_drive_id(link):
780796 message = "❌ Invalid link or missing title."
781797
782798 return render_template ('add_drive_clip.html' , message = message )
783-
784-
799+ #########################################################
785800
786801# ========== MAIN ==========
787802if __name__ == '__main__' :
0 commit comments