Compare commits

...

8 Commits

9 changed files with 77 additions and 107 deletions

4
TODO
View File

@@ -1,5 +1,9 @@
###
#
# 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

View File

@@ -267,25 +267,6 @@ FOT_Schema = FaceOverrideTypeSchema(many=True)
path_Schema = PathSchema(many=True)
person_Schema = PersonSchema(many=True)
################################################################################
# util function to just update the current/first/last positions needed for
# viewing / using pa_user_state DB table
################################################################################
def UpdatePref( pref, OPT ):
last_used=datetime.now(pytz.utc)
if OPT.current>0:
pref.current=OPT.current
if OPT.first_eid>0:
pref.first_eid=OPT.first_eid
if OPT.last_eid>0:
pref.last_eid=OPT.last_eid
if OPT.num_entries>0:
pref.num_entries=OPT.num_entries
pref.last_used=last_used
db.session.add(pref)
db.session.commit()
return
################################################################################
# /get_entries_by_ids -> route where we supply list of entry ids (for next/prev
# page of data we want to show). Returns json of all matching entries
@@ -512,8 +493,6 @@ def files_rbp():
def search(search_term):
OPT=States( request )
OPT.search_term = search_term
OPT.folders = False
query_data=GetSearchQueryData( OPT )
return render_template("files.html", page_title='View Files', search_term=search_term, query_data=query_data, OPT=OPT )
@@ -668,29 +647,6 @@ def view():
data=db.session.execute(stmt).unique().scalars().all()
return jsonify(entries_schema.dump(data))
####
"""
# process any overrides
for face in e.file_details.faces:
# now get any relevant override and store it in objs...
fnmo = FaceNoMatchOverride.query.filter(FaceNoMatchOverride.face_id==face.id).first()
if fnmo:
face.no_match_override=fnmo
mo = FaceForceMatchOverride.query.filter(FaceForceMatchOverride.face_id==face.id).first()
if mo:
mo.type = FaceOverrideType.query.filter( FaceOverrideType.name== 'Manual match to existing person' ).first()
face.manual_override=mo
NMO_data = FaceOverrideType.query.all()
setting = Settings.query.first()
imp_path = setting.import_path
st_path = setting.storage_path
bin_path = setting.recycle_bin_path
# print( f"BUG-DEBUG: /view/id GET route - OPT={OPT}, eids={eids}, current={int(id)} ")
return render_template("viewer.html", current=int(id), eids=eids, objs=objs, OPT=OPT, NMO_data=NMO_data, imp_path=imp_path, st_path=st_path, bin_path=bin_path )
"""
# route called from front/end - if multiple images are being transformed, each transorm == a separate call
# to this route (and therefore a separate transorm job. Each reponse allows the f/e to check the
# specific transorm job is finished (/check_transform_job) which will be called (say) every 1 sec. from f/e

View File

@@ -614,9 +614,7 @@ function isLastPage(pageNumber)
function getPageNumberForId(id) {
const idx = entryList.indexOf(id);
// should be impossible but jic
if (idx === -1) {
return -1; // or null, if you prefer
}
if (idx === -1) { return -1 }
return Math.floor(idx / OPT.how_many) + 1;
}

View File

@@ -298,6 +298,23 @@ function OverrideForceMatch( person_id, key )
} )
}
// function that handles the POSTed data that comes back when we add a
// reference image to a new or existing person (right-click on a face)
// used in success callbacks from CreatePersonAndRefimg() and AddRefimgTo()
function handleAddRefimgData(key, data)
{
document.viewing.file_details.faces[item[key].which_face].refimg=data.refimg
document.viewing.file_details.faces[item[key].which_face].refimg_lnk={}
// if we used this img, for now set distance to 0 - it is an exact match!
document.viewing.file_details.faces[item[key].which_face].refimg_lnk.face_distance=0.0
$('#dbox').modal('hide')
$('#faces').prop('checked',true)
DrawImg()
CheckForJobs()
}
// when we right-click a face and make a new person, this code creates and
// associates the face
function CreatePersonAndRefimg( key )
{
d='&face_id='+item[key].id
@@ -306,29 +323,17 @@ function CreatePersonAndRefimg( key )
+'&surname='+$('#surname').val()
+'&refimg_data='+item[key].refimg_data
$.ajax({ type: 'POST', data: d, url: '/match_with_create_person',
success: function(data) {
document.viewing.file_details.faces[item[key].which_face].refimg.person.tag=data.who
document.viewing.file_details.faces[item[key].which_face].facefile_lnk.face_distance=data.distance
$('#dbox').modal('hide')
$('#faces').prop('checked',true)
DrawImg()
CheckForJobs()
}
success: function(data) { handleAddRefimgData(key, data ) },
})
}
// when we right-click a face and connect to an existing person, this connects
// the refimg and associates the face
function AddRefimgTo( person_id, key, search )
{
d='&face_id='+item[key].id+'&person_id='+person_id+'&refimg_data='+item[key].refimg_data+'&search='+search
$.ajax({ type: 'POST', data: d, url: '/add_refimg_to_person',
success: function(data) {
document.viewing.file_details.faces[item[key].which_face].refimg.person.tag=data.who
document.viewing.file_details.faces[item[key].which_face].facefile_lnk.face_distance=data.distance
$('#dbox').modal('hide')
$('#faces').prop('checked',true)
DrawImg()
CheckForJobs()
}
success: function(data) { handleAddRefimgData(key, data ) },
})
}

View File

@@ -3,8 +3,9 @@ from flask_wtf import FlaskForm
from flask import request, render_template, redirect, url_for, make_response, jsonify
from main import db, app, ma
from settings import Settings, AIModel
from sqlalchemy import Sequence, func
from sqlalchemy import Sequence, func, select
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import joinedload
from flask_login import login_required, current_user
from werkzeug.utils import secure_filename
from shared import GenFace, GenThumb, PA
@@ -114,7 +115,7 @@ def AddRefimgToPerson( filename, person ):
SetFELog( f"<b>Failed to add Refimg:</b>&nbsp;{e.orig}", "danger" )
except Exception as e:
SetFELog( f"<b>Failed to modify Refimg:</b>&nbsp;{e}", "danger" )
return
return refimg
################################################################################
# TempRefimgFile: helper function that takes data POST'd (from dialog box to
@@ -182,9 +183,12 @@ def match_with_create_person():
p = Person( tag=request.form["tag"], surname=request.form["surname"], firstname=request.form["firstname"] )
# add this fname (of temp refimg) to person
fname=TempRefimgFile( request.form['refimg_data'], p.tag )
AddRefimgToPerson( fname, p )
r=AddRefimgToPerson( fname, p )
SetFELog( f"Created person: {p.tag}" )
return make_response( jsonify( who=p.tag, distance='0.0' ) )
refimg_schema=RefimgSchema(many=False)
r_data=refimg_schema.dump(r)
return make_response( jsonify( refimg=r_data, who=p.tag, distance='0.0' ) )
################################################################################
# /person/<id> -> GET/POST(save or delete) -> shows/edits/delets a single person
@@ -267,7 +271,7 @@ def add_refimg():
except Exception as e:
SetFELog( f"<b>Failed to load reference image:</b>&nbsp;{e}", "danger" )
AddRefimgToPerson( fname, person )
r=AddRefimgToPerson( fname, person )
return redirect( url_for( 'person', id=person.id) )
################################################################################
@@ -289,6 +293,29 @@ def find_persons(who):
return make_response( resp )
class FaceRefimgLinkSchema(ma.SQLAlchemyAutoSchema):
class Meta: model = FaceRefimgLink
face_distance = ma.auto_field() # Explicitly include face_distance
load_instance = True
class PersonSchema(ma.SQLAlchemyAutoSchema):
class Meta: model=Person
load_instance = True
class RefimgSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Refimg
exclude = ('face',)
load_instance = True
person = ma.Nested(PersonSchema)
class FaceSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model=Face
exclude = ('face',)
load_instance = True
refimg = ma.Nested(RefimgSchema,allow_none=True)
refimg_lnk = ma.Nested(FaceRefimgLinkSchema,allow_none=True)
################################################################################
# /add_refimg_to_person/ -> POST
@@ -296,12 +323,19 @@ def find_persons(who):
@app.route("/add_refimg_to_person", methods=["POST"])
@login_required
def add_refimg_to_person():
f = Face.query.get( request.form['face_id'] )
p = Person.query.get( request.form['person_id'] )
stmt = select(Face).options( joinedload(Face.refimg_lnk) ).where(Face.id == request.form['face_id'])
f=db.session.execute(stmt).scalars().first()
stmt = select(Person).options( joinedload(Person.refimg) ).where(Person.id == request.form['person_id'])
p=db.session.execute(stmt).scalars().first()
# add this fname (of temp refimg) to person
fname=TempRefimgFile( request.form['refimg_data'], p.tag )
AddRefimgToPerson( fname, p )
r=AddRefimgToPerson( fname, p )
# connect the refimg to the face in the db, now we have added this refimg to the person
frl=FaceRefimgLink( face_id=f.id, refimg_id=r.id, face_distance=0 )
db.session.add(frl)
db.session.commit()
if request.form['search'] == "true":
jex=[]
@@ -316,7 +350,12 @@ def add_refimg_to_person():
jex.append( JobExtra( name=f"path_type", value=str(ptype.id) ) )
job=NewJob( name="run_ai_on_path", num_files=0, wait_for=None, jex=jex, desc="Look for face(s) in storage path(s)" )
return make_response( jsonify( who=p.tag, distance='0.0' ) )
refimg_schema=RefimgSchema(many=False)
r_data=refimg_schema.dump(r)
frl_schema=FaceRefimgLinkSchema(many=False)
frl_data=refimg_schema.dump(r)
return make_response( jsonify( refimg=r_data, frl=frl_data ) )
################################################################################
# /add_force_match_override -> POST

View File

@@ -27,10 +27,9 @@ class PA_UserState(db.Model):
folders = db.Column(db.Boolean, unique=False, nullable=False )
root = db.Column(db.String, unique=False, nullable=False )
cwd = db.Column(db.String, unique=False, nullable=False )
search_term = db.Column(db.String, unique=False, nullable=False )
def __repr__(self):
return f"<pa_user_dn: {self.pa_user_dn}, path_type: {self.path_type}, noo: {self.noo}, grouping: {self.grouping}, how_many: {self.how_many}, size: {self.size}, folders: {self.folders}, root: {self.root}, cwd: {self.cwd}, search_term: {self.orig_search_term}>"
return f"<pa_user_dn: {self.pa_user_dn}, path_type: {self.path_type}, noo: {self.noo}, grouping: {self.grouping}, how_many: {self.how_many}, size: {self.size}, folders: {self.folders}, root: {self.root}, cwd: {self.cwd}>"
################################################################################

View File

@@ -50,11 +50,10 @@ CREATE TABLE pa_user(
default_storage_folders BOOLEAN,
CONSTRAINT pk_pa_user_id PRIMARY KEY(id) );
-- this is totally not 3rd normal form, but when I made it that, it was so complex, it was stupid
-- so for the little data here, I'm deliberately doing a redundant data structure
-- FIXME: NEED TO RETHINK THIS, not sure this even needs to be in the DB
CREATE TABLE pa_user_state ( id INTEGER, pa_user_dn VARCHAR(128), path_type VARCHAR(16),
noo VARCHAR(16), grouping VARCHAR(16), how_many INTEGER, size INTEGER, folders BOOLEAN,
root VARCHAR, cwd VARCHAR, search_term VARCHAR,
root VARCHAR, cwd VARCHAR,
CONSTRAINT fk_pa_user_dn FOREIGN KEY (pa_user_dn) REFERENCES pa_user(dn),
CONSTRAINT pk_pa_user_states_id PRIMARY KEY(id ) );

View File

@@ -252,10 +252,6 @@
var grayscale=0
var throbber=0
var imp_path="static/Import/{{imp_path}}"
var st_path="static/Storage/{{st_path}}"
var bin_path="static/Bin/{{bin_path}}"
function PrettyFname(fname)
{
s='<span class="alert alert-secondary py-2">'

View File

@@ -56,32 +56,6 @@
</div>
</div>
</div class="col-7">
<div class="row pt-5">
<alert class="alert alert-warning">The following values are based on the defaults above and subsequent changes as you navigate the application and are not set by hand. The following content is for checking/debugging only.</alert>
</div class="row">
<div class="row">
<table id="pa_user_state_tbl" class="table table-striped table-sm" data-toolbar="#toolbar" data-search="true">
<thead>
<tr class="table-primary"><th>Path</th><th>New or Oldest</th><th>How Many</th><th>Folders?</th><th>Group by</th><th>Thumb size</th><th>DB retrieve offset</th><th>Root</th><th>cwd</th></tr>
</thead>
<tbody>
{% for st in states %}
<tr>
<td>{{st.path_type}}</td>
<td>{{st.noo}}</td>
<td>{{st.how_many}}</td>
<td>{{st.folders}}</td>
<td>{{st.grouping}}</td>
<td>{{st.size}}</td>
<td>{{st.root}}</td>
<td>{{st.cwd}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div class="row">
</div class="container-fluid">
{% endblock main_content %}
{% block script_content %}