another large clean up of code, all POSTs are now using make_response() and returning json OR are for a form that flask handles with rendering direct html. Where there is a POST with json response, the jscript now calls CheckForJobs() to show it in the F/E. Removed several debugs. Fixed up undocumented BUG where import datetime was wrong, and prefix/suffix also to offer directories near the date of an image. Removed unused routes for clearing messages

This commit is contained in:
2023-01-12 16:47:43 +11:00
parent 8d9cf5279e
commit ef9f26189a
12 changed files with 45 additions and 58 deletions

6
TODO
View File

@@ -1,10 +1,4 @@
### GENERAL ### GENERAL
* should be using jsonify to return real json to my API calls, e.g:
use make_response( jsonify (... ) )
-- [TODO] all POSTs should follow new status behaviour (unless its a form POST, as that immediately renders the form in flask and will show the Status)
all GETs stay as is for now (as they expect a html reply, not data, and then html is base.html+ and it handles the status)
-- [TODO] find persons needs an array returned - test this, also viewlist
* delete files should behave like /move_files (stay on same page) as well as the status messages above * delete files should behave like /move_files (stay on same page) as well as the status messages above
* all routes should be consistent naming conventions (with or without _ ) * all routes should be consistent naming conventions (with or without _ )

4
ai.py
View File

@@ -1,6 +1,6 @@
from wtforms import SubmitField, StringField, HiddenField, validators, Form from wtforms import SubmitField, StringField, HiddenField, validators, Form
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from flask import request, render_template, redirect from flask import request, render_template, redirect, make_response
from main import db, app, ma from main import db, app, ma
from sqlalchemy import Sequence from sqlalchemy import Sequence
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
@@ -124,4 +124,4 @@ def get_face_from_image(face_id):
img_bytearray = img_bytearray.getvalue() img_bytearray = img_bytearray.getvalue()
face_img = base64.b64encode(img_bytearray) face_img = base64.b64encode(img_bytearray)
face_img = str(face_img)[2:-1] face_img = str(face_img)[2:-1]
return face_img return make_response( face_img )

View File

@@ -15,7 +15,7 @@ import numpy
import cv2 import cv2
import time import time
import re import re
from datetime import datetime from datetime import datetime, timedelta
import pytz import pytz
from flask_login import login_required, current_user from flask_login import login_required, current_user
from states import States, PA_UserState from states import States, PA_UserState
@@ -366,16 +366,6 @@ def GetEntries( OPT ):
UpdatePref( pref, OPT ) UpdatePref( pref, OPT )
return entries return entries
################################################################################
# /clear_jm_msg -> with a dismissable error (ie. anything not success, that is
# not showing duplicates (so rare errors) - allow them to be dismissed
################################################################################
@app.route("/clear_jm_msg/<id>", methods=["POST"])
@login_required
def clear_jm_msg(id):
ClearJM_Message(id)
return redirect( url_for("main_page") )
@app.route("/ChangeFileOpts", methods=["POST"]) @app.route("/ChangeFileOpts", methods=["POST"])
@login_required @login_required
def ChangeFileOpts(): def ChangeFileOpts():
@@ -500,7 +490,7 @@ def fix_dups():
rows = db.engine.execute( "select e1.id as id1, f1.hash, d1.rel_path as rel_path1, d1.eid as did1, e1.name as fname1, p1.id as path1, p1.type_id as path_type1, e2.id as id2, d2.rel_path as rel_path2, d2.eid as did2, e2.name as fname2, p2.id as path2, p2.type_id as path_type2 from entry e1, file f1, dir d1, entry_dir_link edl1, path_dir_link pdl1, path p1, entry e2, file f2, dir d2, entry_dir_link edl2, path_dir_link pdl2, path p2 where e1.id = f1.eid and e2.id = f2.eid and d1.eid = edl1.dir_eid and edl1.entry_id = e1.id and edl2.dir_eid = d2.eid and edl2.entry_id = e2.id and p1.type_id != (select id from path_type where name = 'Bin') and p1.id = pdl1.path_id and pdl1.dir_eid = d1.eid and p2.type_id != (select id from path_type where name = 'Bin') and p2.id = pdl2.path_id and pdl2.dir_eid = d2.eid and f1.hash = f2.hash and e1.id != e2.id and f1.size_mb = f2.size_mb order by path1, rel_path1, fname1"); rows = db.engine.execute( "select e1.id as id1, f1.hash, d1.rel_path as rel_path1, d1.eid as did1, e1.name as fname1, p1.id as path1, p1.type_id as path_type1, e2.id as id2, d2.rel_path as rel_path2, d2.eid as did2, e2.name as fname2, p2.id as path2, p2.type_id as path_type2 from entry e1, file f1, dir d1, entry_dir_link edl1, path_dir_link pdl1, path p1, entry e2, file f2, dir d2, entry_dir_link edl2, path_dir_link pdl2, path p2 where e1.id = f1.eid and e2.id = f2.eid and d1.eid = edl1.dir_eid and edl1.entry_id = e1.id and edl2.dir_eid = d2.eid and edl2.entry_id = e2.id and p1.type_id != (select id from path_type where name = 'Bin') and p1.id = pdl1.path_id and pdl1.dir_eid = d1.eid and p2.type_id != (select id from path_type where name = 'Bin') and p2.id = pdl2.path_id and pdl2.dir_eid = d2.eid and f1.hash = f2.hash and e1.id != e2.id and f1.size_mb = f2.size_mb order by path1, rel_path1, fname1");
if rows.returns_rows == False: if rows.returns_rows == False:
SetFELog(f"Err, no dups - should now clear the FE 'danger' message?", "warning") SetFELog(f"Err, No more duplicates? Old link followed, or something is wrong!", "warning")
return redirect("/") return redirect("/")
if 'pagesize' not in request.form: if 'pagesize' not in request.form:
@@ -653,7 +643,7 @@ def viewlist():
pref=PA_UserState.query.filter(PA_UserState.pa_user_dn==current_user.dn,PA_UserState.orig_ptype==OPT.orig_ptype,PA_UserState.view_eid==OPT.view_eid).first() pref=PA_UserState.query.filter(PA_UserState.pa_user_dn==current_user.dn,PA_UserState.orig_ptype==OPT.orig_ptype,PA_UserState.view_eid==OPT.view_eid).first()
UpdatePref( pref, OPT ) UpdatePref( pref, OPT )
return resp return make_response( resp )
################################################################################ ################################################################################
# /view/id -> grabs data from DB and views it (GET) # /view/id -> grabs data from DB and views it (GET)
@@ -785,7 +775,7 @@ def _jinja2_filter_parentpath(path):
############################################################################### ###############################################################################
# route to allow the Move Dialog Box to pass a date (YYYYMMDD) and returns a # route to allow the Move Dialog Box to pass a date (YYYYMMDD) and returns a
# json? list of existing dir names that could be near it in time. Starting # json list of existing dir names that could be near it in time. Starting
# simple, by using YYYYMM-1, YYYYMM, YYYYMM+1 dirs # simple, by using YYYYMM-1, YYYYMM, YYYYMM+1 dirs
############################################################################### ###############################################################################
@app.route("/getexistingpaths/<dt>", methods=["POST"]) @app.route("/getexistingpaths/<dt>", methods=["POST"])
@@ -795,13 +785,15 @@ def GetExistingPathsAsDiv(dt):
dirs_arr=[] dirs_arr=[]
for delta in range(-7, 8): for delta in range(-7, 8):
try: try:
new_dtime=datetime.datetime.strptime(dt, "%Y%m%d") + datetime.timedelta(days=delta) new_dtime=datetime.strptime(dt, "%Y%m%d") + timedelta(days=delta)
except: except:
# this is not a date, so we cant work out possible dirs, just # this is not a date, so we cant work out possible dirs, just
# return an empty set # return an empty set
return "[]" return make_response( '[]' )
new_dt=new_dtime.strftime('%Y%m%d') new_dt=new_dtime.strftime('%Y%m%d')
# find dirs named with this date
dirs_arr+=Dir.query.distinct(Dir.rel_path).filter(Dir.rel_path.ilike('%'+new_dt+'%')).all(); dirs_arr+=Dir.query.distinct(Dir.rel_path).filter(Dir.rel_path.ilike('%'+new_dt+'%')).all();
# find dirs with non-dirs (files) with this date
dirs_arr+=Dir.query.distinct(Dir.rel_path).join(EntryDirLink).join(Entry).filter(Entry.type_id!=dir_ft.id).filter(Entry.name.ilike('%'+new_dt+'%')).all() dirs_arr+=Dir.query.distinct(Dir.rel_path).join(EntryDirLink).join(Entry).filter(Entry.type_id!=dir_ft.id).filter(Entry.name.ilike('%'+new_dt+'%')).all()
# remove duplicates from array # remove duplicates from array
@@ -811,12 +803,17 @@ def GetExistingPathsAsDiv(dt):
ret='[ ' ret='[ '
first_dir=1 first_dir=1
for dir in dirs: for dir in dirs:
print(dir)
# this can occur if there is a file with this date name in the top-levle of the path, its legit, but only really happens in DEV
# regardless, it cant be used for a existpath button in the F/E, ignore it
if dir.rel_path == '':
continue
if not first_dir: if not first_dir:
ret +=", " ret +=", "
bits=dir.rel_path.split('-') bits=dir.rel_path.split('-')
ret+= '{ ' ret+= '{ '
ret+= '"prefix":"' + bits[0] + '", ' ret+= '"prefix":"' + bits[0] + '-", '
if len(bits)>1: if len(bits)>1:
ret+= '"suffix":"' + bits[1] + '"' ret+= '"suffix":"' + bits[1] + '"'
else: else:
@@ -824,4 +821,4 @@ def GetExistingPathsAsDiv(dt):
ret+= ' } ' ret+= ' } '
first_dir=0 first_dir=0
ret+= ' ]' ret+= ' ]'
return ret return make_response( ret )

View File

@@ -21,14 +21,15 @@ function GetSelnAsData()
return to_del return to_del
} }
// use an ajax POST to force an AI scan on the selected images // use an ajax POST to force an AI scan on the selected image(s)
function RunAIOnSeln(person) function RunAIOnSeln(person)
{ {
post_data = GetSelnAsData() post_data = GetSelnAsData()
post_data += '&person='+person.replace('ai-','') post_data += '&person='+person.replace('ai-','')
$.ajax({ type: 'POST', data: post_data, url: '/run_ai_on', success: function(data){ window.location='/'; return false; } }) $.ajax({ type: 'POST', data: post_data, url: '/run_ai_on', success: function(data){ CheckForJobs() } })
} }
// code to change the associated image/icon when the move to selection box (import or storage paths) is changed
function change_rp_sel() function change_rp_sel()
{ {
icon_url = $('option:selected', '#rp_sel').attr('icon_url') icon_url = $('option:selected', '#rp_sel').attr('icon_url')
@@ -37,6 +38,8 @@ function change_rp_sel()
$('#move_path_type').val( $('option:selected', '#rp_sel').attr('path_type') ) $('#move_path_type').val( $('option:selected', '#rp_sel').attr('path_type') )
} }
// POST to see if there are any other existing directories named around this date
// (if so display them as options for a move)
function GetExistingDirsAsDiv( dt, divname ) function GetExistingDirsAsDiv( dt, divname )
{ {
$.ajax({ $.ajax({
@@ -46,7 +49,7 @@ function GetExistingDirsAsDiv( dt, divname )
dirs = JSON.parse(data) dirs = JSON.parse(data)
s='' s=''
dirs.forEach( function(item, index) { dirs.forEach( function(item, index) {
s+= '<button class="btn btn-outline-primary" type="button" onClick="$(\'#prefix\').val(\''+item.prefix+'\'); $(\'#suffix\').val(\''+item.suffix+'\');return false;">'+item.prefix+'/'+item.suffix+'</button>' s+= '<button class="btn btn-outline-primary" type="button" onClick="$(\'#prefix\').val(\''+item.prefix+'\'); $(\'#suffix\').val(\''+item.suffix+'\');return false;">'+item.prefix+item.suffix+'</button>'
} ) } )
if( s == '' ) if( s == '' )
$('#existing').html('') $('#existing').html('')
@@ -59,12 +62,12 @@ function GetExistingDirsAsDiv( dt, divname )
// used to remove the highlighted item(s) -- effectively removing them from view/selection so continued 'Moves' can occur // used to remove the highlighted item(s) -- effectively removing them from view/selection so continued 'Moves' can occur
function MoveSubmit() function MoveSubmit()
{ {
// remove the images being moved // remove the images being moved (so UI immediately 'sees' the move)
$("[name^=eid-]").each( function() { $('#'+$(this).attr('value')).remove() } ) $("[name^=eid-]").each( function() { $('#'+$(this).attr('value')).remove() } )
// reorder the images via ecnt again, so highlighting can work // reorder the images via ecnt again, so future highlighting can work
document.mf_id=0; $('.figure').each( function() { $(this).attr('ecnt', document.mf_id ); document.mf_id++ } ) document.mf_id=0; $('.figure').each( function() { $(this).attr('ecnt', document.mf_id ); document.mf_id++ } )
$('#dbox').modal('hide') $('#dbox').modal('hide')
$.ajax({ type: 'POST', data: $('#mv_fm').serialize(), url: '/move_files', success: function(data){ CheckForJobs(); return false; } }) $.ajax({ type: 'POST', data: $('#mv_fm').serialize(), url: '/move_files', success: function(data) { CheckForJobs() } })
} }
// show the DBox for a move file, includes all thumbnails of selected files to move // show the DBox for a move file, includes all thumbnails of selected files to move
@@ -134,13 +137,13 @@ function DelDBox(del_or_undel)
if( del_or_undel == "Delete" ) if( del_or_undel == "Delete" )
div+=` div+=`
'/delete_files', '/delete_files',
success: function(data){ window.location='/'; return false; } })" class="btn btn-outline-danger col-2">Ok</button> success: function(data){ window.location='/'; CheckForJobs() } })" class="btn btn-outline-danger col-2">Ok</button>
</div> </div>
` `
else else
div+=` div+=`
'/restore_files', '/restore_files',
success: function(data){ window.location='/'; return false; } })" class="btn btn-outline-success col-2">Ok</button> success: function(data){ window.location='/'; CheckForJobs() } })" class="btn btn-outline-success col-2">Ok</button>
</div> </div>
` `
$('#dbox-content').html(div) $('#dbox-content').html(div)

View File

@@ -287,6 +287,7 @@ function OverrideForceMatch( person_id, key )
$('#dbox').modal('hide') $('#dbox').modal('hide')
$('#faces').prop('checked',true) $('#faces').prop('checked',true)
DrawImg() DrawImg()
CheckForJobs()
} }
} ) } )
} }
@@ -320,6 +321,7 @@ function AddRefimgTo( person_id, key, search )
$('#dbox').modal('hide') $('#dbox').modal('hide')
$('#faces').prop('checked',true) $('#faces').prop('checked',true)
DrawImg() DrawImg()
CheckForJobs()
} }
}) })
} }
@@ -374,6 +376,7 @@ function RemoveOverrideForceMatch(face_pos)
delete objs[current].faces[face_pos].override delete objs[current].faces[face_pos].override
$('#dbox').modal('hide') $('#dbox').modal('hide')
DrawImg() DrawImg()
CheckForJobs()
return false return false
} }
} ) } )
@@ -388,6 +391,7 @@ function RemoveOverrideNoMatch(face_pos, type_id)
delete objs[current].faces[face_pos].override delete objs[current].faces[face_pos].override
$('#dbox').modal('hide') $('#dbox').modal('hide')
DrawImg() DrawImg()
CheckForJobs()
return false return false
} }
} ) } )
@@ -407,6 +411,7 @@ function AddNoMatchOverride(type_id, face_id, face_pos, type_id)
$('#dbox').modal('hide') $('#dbox').modal('hide')
$('#faces').prop('checked',true) $('#faces').prop('checked',true)
DrawImg() DrawImg()
CheckForJobs()
} }
} ) } )
} }

15
job.py
View File

@@ -297,6 +297,7 @@ def joblog_search():
for ent in ent_cursor: for ent in ent_cursor:
jobs_cursor=db.engine.execute( f"select l.log, j.id, j.name, j.state, l.log_date from joblog l, job j where l.job_id = j.id and l.log ilike '%%{ent[0]}%%' order by l.log_date") jobs_cursor=db.engine.execute( f"select l.log, j.id, j.name, j.state, l.log_date from joblog l, job j where l.job_id = j.id and l.log ilike '%%{ent[0]}%%' order by l.log_date")
print("HERE")
# turn DB output into json and return it to the f/e # turn DB output into json and return it to the f/e
ret='[ ' ret='[ '
first_job=1 first_job=1
@@ -312,7 +313,7 @@ def joblog_search():
ret+= '}' ret+= '}'
first_job=0 first_job=0
ret+= ' ]' ret+= ' ]'
return ret return make_response( ret )
############################################################################### ###############################################################################
@@ -331,18 +332,6 @@ def CheckForJobs():
sts.append( { 'id': msg.id, 'message': u+msg.message, 'level': msg.level, 'job_id': msg.job_id, 'persistent': msg.persistent, 'cant_close': msg.cant_close } ) sts.append( { 'id': msg.id, 'message': u+msg.message, 'level': msg.level, 'job_id': msg.job_id, 'persistent': msg.persistent, 'cant_close': msg.cant_close } )
return make_response( jsonify( num_active_jobs=num, sts=sts ) ) return make_response( jsonify( num_active_jobs=num, sts=sts ) )
###############################################################################
# / -> POST -> looks for pa_job_manager status to F/E jobs and sends json of
# them back to F/E (called form internal/js/jobs.js:CheckForJobs()
################################################################################
@app.route("/clearmsgforjob/<id>", methods=["POST"])
@login_required
def ClearMessageForJob(id):
PA_JobManager_Message.query.filter(PA_JobManager_Message.job_id==id).delete()
db.session.commit()
# no real need for this response, as if it succeeded/failed the F/E ignores it
return make_response( jsonify( status="success" ) )
############################################################################### ###############################################################################
# / -> POST -> looks for pa_job_manager status to F/E jobs and sends json of # / -> POST -> looks for pa_job_manager status to F/E jobs and sends json of
# them back to F/E (called form internal/js/jobs.js:CheckForJobs() # them back to F/E (called form internal/js/jobs.js:CheckForJobs()

View File

@@ -249,11 +249,12 @@ def person(id):
@app.route("/add_refimg", methods=["POST"]) @app.route("/add_refimg", methods=["POST"])
@login_required @login_required
def add_refimg(): def add_refimg():
# now save into the DB
person = Person.query.get(request.form['person_id']);
if not person:
raise Exception("could not find person to add reference image too!")
try: try:
# now save into the DB
person = Person.query.get(request.form['person_id']);
if not person:
raise Exception("could not find person to add reference image too!")
# save the actual uploaded image to reference_images/ # save the actual uploaded image to reference_images/
f=request.files['refimg_file'] f=request.files['refimg_file']
fname=secure_filename(f.filename) fname=secure_filename(f.filename)
@@ -285,7 +286,7 @@ def find_persons(who):
resp[p.id]['firstname']=p.firstname resp[p.id]['firstname']=p.firstname
resp[p.id]['surname']=p.surname resp[p.id]['surname']=p.surname
return resp return make_response( resp )
################################################################################ ################################################################################

View File

@@ -100,7 +100,6 @@ def settings():
from job import SetFELog from job import SetFELog
s = Settings.query.one() s = Settings.query.one()
if 'submit' in request.form: if 'submit' in request.form:
SetFELog("Successfully Updated Settings" )
s.import_path = request.form['import_path'] s.import_path = request.form['import_path']
s.storage_path = request.form['storage_path'] s.storage_path = request.form['storage_path']
s.recycle_bin_path = request.form['recycle_bin_path'] s.recycle_bin_path = request.form['recycle_bin_path']
@@ -118,7 +117,8 @@ def settings():
s.scheduled_bin_cleanup = request.form['scheduled_bin_cleanup'] s.scheduled_bin_cleanup = request.form['scheduled_bin_cleanup']
s.bin_cleanup_file_age = request.form['bin_cleanup_file_age'] s.bin_cleanup_file_age = request.form['bin_cleanup_file_age']
s.job_archive_age = request.form['job_archive_age'] s.job_archive_age = request.form['job_archive_age']
db.session.commit() SetFELog("Successfully Updated Settings" )
db.session.commit()
return redirect( url_for( 'settings' ) ) return redirect( url_for( 'settings' ) )
except SQLAlchemyError as e: except SQLAlchemyError as e:
SetFELog( f"<b>Failed to modify Setting:</b>&nbsp;{e.orig}", "danger" ) SetFELog( f"<b>Failed to modify Setting:</b>&nbsp;{e.orig}", "danger" )

View File

@@ -31,7 +31,6 @@
function ResetPageSize() function ResetPageSize()
{ {
console.log( $("#pagesize").val() )
$("#psform").submit() $("#psform").submit()
return false; return false;
} }

View File

@@ -24,8 +24,6 @@
orig_face_{{f.id}}.orig_w = {{f.face_right}}*1.05 - {{f.face_left}}*.95 orig_face_{{f.id}}.orig_w = {{f.face_right}}*1.05 - {{f.face_left}}*.95
orig_face_{{f.id}}.orig_h = {{f.face_bottom}}*1.05 - {{f.face_top}}*.95 orig_face_{{f.id}}.orig_h = {{f.face_bottom}}*1.05 - {{f.face_top}}*.95
//console.log( orig_face_{{f.id}} )
// when the document is ready, then DrawRefimg // when the document is ready, then DrawRefimg
$(function() { DrawUnmatchedFace( fig_{{f.id}}, im_{{f.id}}, c_{{f.id}}, orig_face_{{f.id}} ) }); $(function() { DrawUnmatchedFace( fig_{{f.id}}, im_{{f.id}}, c_{{f.id}}, orig_face_{{f.id}} ) });
</script> </script>

View File

@@ -31,6 +31,7 @@
{% block script_content %} {% block script_content %}
<script> <script>
<!-- browsers can put the fakepath in for security, remove it -->
function DoMagic() { function DoMagic() {
str=$("#new_file_chooser").val() str=$("#new_file_chooser").val()
console.log(str) console.log(str)

View File

@@ -62,4 +62,4 @@ def changedefaults():
user.default_storage_folders = False user.default_storage_folders = False
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()
return "done" return make_response( status="success" )