From ee1c9b54946aeb15c978ce80f06ad9a0eb061c7e Mon Sep 17 00:00:00 2001 From: Damien De Paoli Date: Thu, 9 Oct 2025 23:56:27 +1100 Subject: [PATCH] all override add and remove now use new datastructures, close to be able to test / augment as per TODO --- TODO | 25 +++++++------- face.py | 4 +++ files.py | 69 +++++++++++++++++++++---------------- internal/js/view_support.js | 54 ++++++++++++++--------------- person.py | 14 ++++++-- templates/files.html | 4 +-- 6 files changed, 95 insertions(+), 75 deletions(-) diff --git a/TODO b/TODO index 9cb8e1b..3cce686 100644 --- a/TODO +++ b/TODO @@ -1,19 +1,20 @@ ### -# -# fix all face, right-click options: -# [DONE] add to new, add to existing -# [TODO] 4 x override* -# -#1 get override data into view -# also all the add ref img/add override, etc are non-functional - FIX the override* stuff first to get table/naming consistency as that is half the problem -# NMO data -> there is an NMO object (just NMO names/types - |json), then there is per face level data - this should be a reference from Face and Schema/marshmallow -# -#4 TEST everything (don't forget keybindings,e.g. delete) +# 4 TEST everything (don't forget keybindings,e.g. delete) # -- go into viewer code from a files_rbp - had red bin, bot green on viewer. # -#5 think I killed pa_job_manager without passing an eid to a transform job, shouldn't crash -# SHOULD JUST get AI to help clean-up and write defensive code here... +# consider this: + $('#viewer_bin use').attr('fill', 'var(--bs-success)'); $('#viewer_del').removeClass('btn-outline-danger').addClass('btn-outline-success') + $('#viewer_bin').hover( + function() { + $('use', this).attr('fill', 'white'); + }, + function() { + $('use', this).attr('fill', 'var(--bs-success)'); + } +); # +# 5 think I killed pa_job_manager without passing an eid to a transform job, shouldn't crash +# SHOULD JUST get AI to help clean-up and write defensive code here... ### ### major fix - go to everywhere I call GetEntries(), and redo the logic totally... diff --git a/face.py b/face.py index 4be5e50..349143c 100644 --- a/face.py +++ b/face.py @@ -31,6 +31,8 @@ class Face(PA,db.Model): refimg_lnk = db.relationship("FaceRefimgLink", uselist=False, viewonly=True ) facefile_lnk = db.relationship("FaceFileLink", uselist=False, viewonly=True ) refimg =db.relationship("Refimg", secondary="face_refimg_link", uselist=False) + fnmo = db.relationship("FaceNoMatchOverride", back_populates="face") + ffmo = db.relationship("FaceForceMatchOverride", back_populates="face") ################################################################################ @@ -104,6 +106,7 @@ class FaceNoMatchOverride(PA, db.Model): face_id = db.Column(db.Integer, db.ForeignKey("face.id"), primary_key=True ) type_id = db.Column(db.Integer, db.ForeignKey("face_override_type.id")) type = db.relationship("FaceOverrideType") + face = db.relationship("Face", back_populates="fnmo") ################################################################################ @@ -123,3 +126,4 @@ class FaceForceMatchOverride(PA, db.Model): face_id = db.Column(db.Integer, db.ForeignKey("face.id"), primary_key=True ) person_id = db.Column(db.Integer, db.ForeignKey("person.id"), primary_key=True ) person = db.relationship("Person") + face = db.relationship("Face", back_populates="ffmo") diff --git a/files.py b/files.py index 53293b3..dd602ca 100644 --- a/files.py +++ b/files.py @@ -180,74 +180,83 @@ class PathSchema(ma.SQLAlchemyAutoSchema): class FileTypeSchema(ma.SQLAlchemyAutoSchema): - class Meta: model = FileType - load_instance = True + class Meta: + model = FileType + load_instance = True class DirSchema(ma.SQLAlchemyAutoSchema): - class Meta: model = Dir - load_instance = True + class Meta: + model = Dir + load_instance = True eid = ma.auto_field() # Explicitly include eid in_path = ma.Nested(PathSchema) class FaceFileLinkSchema(ma.SQLAlchemyAutoSchema): - class Meta: model = FaceFileLink + class Meta: + model = FaceFileLink + load_instance = True model_used = ma.auto_field() - load_instance = True class PersonSchema(ma.SQLAlchemyAutoSchema): - class Meta: model=Person - load_instance = True + class Meta: + model=Person + load_instance = True class RefimgSchema(ma.SQLAlchemyAutoSchema): class Meta: model = Refimg exclude = ('face',) - load_instance = True + load_instance = True person = ma.Nested(PersonSchema) class FaceRefimgLinkSchema(ma.SQLAlchemyAutoSchema): - class Meta: model = FaceRefimgLink - load_instance = True + class Meta: + model = FaceRefimgLink + load_instance = True + +class FaceOverrideTypeSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = FaceOverrideType + load_instance = True class FaceNoMatchOverrideSchema(ma.SQLAlchemyAutoSchema): - class Meta: model = FaceOverrideType - load_instance = True - type = ma.Nested(FaceOverrideType) + class Meta: + model = FaceNoMatchOverride + load_instance = True + type = ma.Nested(FaceOverrideTypeSchema) class FaceForceMatchOverrideSchema(ma.SQLAlchemyAutoSchema): - class Meta: model = FaceOverrideType - load_instance = True - person = ma.Nested(Person) + class Meta: + model = FaceForceMatchOverride + load_instance = True + person = ma.Nested(PersonSchema) class FaceSchema(ma.SQLAlchemyAutoSchema): class Meta: model=Face exclude = ('face',) - load_instance = True + load_instance = True refimg = ma.Nested(RefimgSchema,allow_none=True) # faces have to come with a file connection facefile_lnk = ma.Nested(FaceFileLinkSchema) refimg_lnk = ma.Nested(FaceRefimgLinkSchema,allow_none=True) - fnmo = ma.Nested( FaceNoMatchOverride, allow_none=True ) - ffmo = ma.Nested( FaceForceMatchOverride, allow_none=True ) + fnmo = ma.Nested( FaceNoMatchOverrideSchema, allow_none=True, many=True ) + ffmo = ma.Nested( FaceForceMatchOverrideSchema, allow_none=True, many=True ) class FileSchema(ma.SQLAlchemyAutoSchema): - class Meta: model = File - load_instance = True + class Meta: + model = File + load_instance = True faces = ma.Nested(FaceSchema,many=True,allow_none=True) -# used just in NMO var -class FaceOverrideTypeSchema(ma.SQLAlchemyAutoSchema): - class Meta: model = FaceOverrideType - load_instance = True - ################################################################################ # Schema for Entry so we can json for data to the client ################################################################################ class EntrySchema(ma.SQLAlchemyAutoSchema): # gives id, name, type_id - class Meta: model = Entry - load_instance = True + class Meta: + model = Entry + load_instance = True type = ma.Nested(FileTypeSchema) file_details = ma.Nested(FileSchema,allow_none=True) @@ -284,6 +293,8 @@ def process_ids(): joinedload(Entry.file_details).joinedload(File.faces).joinedload(Face.refimg).joinedload(Refimg.person), joinedload(Entry.file_details).joinedload(File.faces).joinedload(Face.refimg_lnk), joinedload(Entry.file_details).joinedload(File.faces).joinedload(Face.facefile_lnk), + joinedload(Entry.file_details).joinedload(File.faces).joinedload(Face.fnmo).joinedload(FaceNoMatchOverride.type), + joinedload(Entry.file_details).joinedload(File.faces).joinedload(Face.ffmo).joinedload(FaceForceMatchOverride.person), ) .where(Entry.id.in_(ids)) ) diff --git a/internal/js/view_support.js b/internal/js/view_support.js index c0d5198..c2559e0 100644 --- a/internal/js/view_support.js +++ b/internal/js/view_support.js @@ -117,10 +117,13 @@ function DrawImg() context.lineWidth = 2 // this face has an override so diff colour - if( faces[i].override ) + if( faces[i].fnmo.length || faces[i].ffmo.length ) { context.strokeStyle = 'blue' - DrawLabelOnFace( faces[i].override.who ) + if( faces[i].ffmo.length ) + DrawLabelOnFace( faces[i].ffmo[0].person.tag ) + else + DrawLabelOnFace( faces[i].fnmo[0].type.name ) } else { @@ -234,10 +237,10 @@ $(document).ready( function() if( x >= fx && x <= fx+fw && y >= fy && y <= fy+fh ) { - if( faces[i].override ) + if( faces[i].ffmo.length || faces[i].fnmo.length ) { item_list['remove_force_match_override']={ 'name': 'Remove override for this face', 'which_face': i, 'id': faces[i].id } - } + } else if( faces[i].refimg ) { item_list['match']={ 'name': faces[i].refimg.person.tag, 'which_face': i, 'id': faces[i].id } @@ -250,7 +253,7 @@ $(document).ready( function() item_list['no_match_new_person']={ 'name': 'Add as reference image to NEW person', 'which_face': i, 'id': faces[i].id } item_list['no_match_new_refimg']={ 'name': 'Add as reference image to EXISTING person', 'which_face': i, 'id': faces[i].id } for( var el in NMO ) { - item_list['NMO_'+el]={'type_id': NMO[el].type_id, 'name': 'Override: ' + NMO[el].name, 'which_face': i, 'id': faces[i].id } + item_list['NMO_'+el]={'type_id': NMO[el].id, 'name': 'Override: ' + NMO[el].name, 'which_face': i, 'id': faces[i].id } } } delete item_list['not_a_face'] @@ -276,7 +279,7 @@ function OverrideForceMatch( person_id, key ) // we have type_id passed in, so dig the NMO out, and use that below (its really just for name, but in case we change that in the DB) for( el in NMO ) { - if( NMO[el].type_id == item[key].type_id ) + if( NMO[el].id == item[key].type_id ) { fm_idx=el break @@ -284,12 +287,9 @@ function OverrideForceMatch( person_id, key ) } ofm='&person_id='+person_id+'&face_id='+item[key].id $.ajax({ type: 'POST', data: ofm, url: '/add_force_match_override', success: function(data) { - document.viewing.file_details.faces[item[key].which_face].override={} - document.viewing.file_details.faces[item[key].which_face].override.who=data.person_tag - document.viewing.file_details.faces[item[key].which_face].override.distance='N/A' - document.viewing.file_details.faces[item[key].which_face].override.type_id=NMO[fm_idx].id - document.viewing.file_details.faces[item[key].which_face].override.type_name=NMO[fm_idx].name - + document.viewing.file_details.faces[item[key].which_face].ffmo=[] + document.viewing.file_details.faces[item[key].which_face].ffmo[0]={} + document.viewing.file_details.faces[item[key].which_face].ffmo[0].person=data.person $('#dbox').modal('hide') $('#faces').prop('checked',true) DrawImg() @@ -350,8 +350,7 @@ function SearchForPerson(content, key, face_id, face_pos, type_id) for( var el in data ) { content+='
' var person = data[el]; - // NMO_1 is a non-match-override type_id==1 (or force match to existing person) - if( key == "NMO_1" ) + if( item[key].name == "Override: Manual match to existing person" ) { func='OverrideForceMatch('+person.id+',\''+key+'\' )' content+= '
' + person.tag + ' (' + person.firstname+' '+person.surname+ ')
' @@ -376,15 +375,16 @@ function SearchForPerson(content, key, face_id, face_pos, type_id) function RemoveOverrideForceMatch(face_pos) { - if( document.viewing.file_details.faces[face_pos].override ) - who=document.viewing.file_details.faces[face_pos].override.who + if( document.viewing.file_details.faces[face_pos].ffmo.length ) + who=document.viewing.file_details.faces[face_pos].ffmo[0].person.tag else who=document.viewing.file_details.faces[face_pos].refimg.person.tag - d='&face_id='+document.viewing.file_details.faces[face_pos].id+'&person_tag='+document.viewing.file_details.faces[face_pos].refimg.person.tag+'&file_eid='+document.viewing.id + d='&face_id='+document.viewing.file_details.faces[face_pos].id+'&person_tag='+who+'&file_eid='+document.viewing.id $.ajax({ type: 'POST', data: d, url: '/remove_force_match_override', success: function(data) { - delete document.viewing.file_details.faces[face_pos].override + // force/delete the ffmo cleanly + document.viewing.file_details.faces[face_pos].ffmo.length=0 $('#dbox').modal('hide') DrawImg() CheckForJobs() @@ -399,7 +399,7 @@ function RemoveOverrideNoMatch(face_pos, type_id) d='&face_id='+document.viewing.file_details.faces[face_pos].id+'&type_id='+type_id $.ajax({ type: 'POST', data: d, url: '/remove_no_match_override', success: function(data) { - delete document.viewing.file_details.faces[face_pos].override + document.viewing.file_details.faces[face_pos].fnmo.length=0 $('#dbox').modal('hide') DrawImg() CheckForJobs() @@ -414,11 +414,7 @@ function AddNoMatchOverride(type_id, face_id, face_pos, type_id) d='&type_id='+type_id+'&face_id='+face_id $.ajax({ type: 'POST', data: d, url: '/add_no_match_override', success: function(data) { - document.viewing.file_details.faces[face_pos].override={} - document.viewing.file_details.faces[face_pos].override.who=NMO[type_id].name - document.viewing.file_details.faces[face_pos].override.distance='N/A' - document.viewing.file_details.faces[face_pos].override.type_id=type_id - document.viewing.file_details.faces[face_pos].override.type_name=NMO[type_id].name + document.viewing.file_details.faces[face_pos].fnmo[0]=data $('#dbox').modal('hide') $('#faces').prop('checked',true) DrawImg() @@ -466,17 +462,17 @@ function FaceDBox(key, item) div+='
' if ( key == 'remove_force_match_override' ) { - if( document.viewing.file_details.faces[face_pos].override.type_name == 'Manual match to existing person' ) - div+='
remove this override (force match to: ' + document.viewing.file_details.faces[face_pos].override.who + ')
' + if( document.viewing.file_details.faces[face_pos].ffmo.length ) + div+='
remove this override (force match to: ' + document.viewing.file_details.faces[face_pos].ffmo[0].person.tag + ')
' else - div+='
remove this override (no match)
' + div+='
remove this override (' + document.viewing.file_details.faces[face_pos].fnmo[0].type.name + ')
' div+='
' div+='' div+='' else - div+='onClick="RemoveOverrideNoMatch(' +face_pos+','+document.viewing.file_details.faces[face_pos].override.type_id+ ')">Remove' + div+='onClick="RemoveOverrideNoMatch(' +face_pos+','+document.viewing.file_details.faces[face_pos].fnmo[0].type.id+ ')">Remove' div+='
' } if ( key == 'no_match_new_person' ) diff --git a/person.py b/person.py index 2b6c664..9d86766 100644 --- a/person.py +++ b/person.py @@ -385,7 +385,9 @@ def add_force_match_override(): NewJob( "metadata", num_files=0, wait_for=None, jex=jex, desc="create metadata for adding forced match" ) # this will reply to the Ajax / POST, and cause the page to re-draw with new face override to person_tag - return make_response( jsonify( person_tag=p.tag ) ) + person_schema = PersonSchema(many=False) + p_data = person_schema.dump(p) + return make_response( jsonify( person=p_data ) ) ################################################################################ # /remove_force_match_override -> POST @@ -436,6 +438,11 @@ def remove_no_match_override(): return make_response( jsonify( face_id=face_id ) ) +class FaceOverrideTypeSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = FaceOverrideType + load_instance = True + ################################################################################ # /add_no_match_override -> POST ################################################################################ @@ -463,5 +470,6 @@ def add_no_match_override(): # dont do status update here, the F/E is in the middle of a dbox, just send metadata through to the B/E NewJob( "metadata", num_files=0, wait_for=None, jex=jex, desc="create metadata for adding forced non-match" ) - # this will reply to the Ajax / POST, and cause the page to re-draw with new face override to person_tag - return make_response( jsonify( type=t.name ) ) + fot_schema = FaceOverrideTypeSchema(many=False) + t_data=fot_schema.dump(t) + return make_response( jsonify( type_id=t.id, type=t_data ) ) diff --git a/templates/files.html b/templates/files.html index 932d7b8..617f648 100644 --- a/templates/files.html +++ b/templates/files.html @@ -209,9 +209,9 @@ -