implemented job archiving (via a Setting, and just viewing recent or all)
This commit is contained in:
2
TODO
2
TODO
@@ -6,7 +6,7 @@
|
|||||||
- [DONE] joblog page should show last X logs, <show all button>, newest X logs,
|
- [DONE] joblog page should show last X logs, <show all button>, newest X logs,
|
||||||
- [DONE/TEST?] need to use sm-txt class more as the space is too constrained
|
- [DONE/TEST?] need to use sm-txt class more as the space is too constrained
|
||||||
- make clickable sort toggles
|
- make clickable sort toggles
|
||||||
- need to archive jobs
|
- [DONE] need to archive jobs
|
||||||
|
|
||||||
* per file you could select an unknown face and add it as a ref img to an existing person, or make a new person and attach?
|
* per file you could select an unknown face and add it as a ref img to an existing person, or make a new person and attach?
|
||||||
|
|
||||||
|
|||||||
20
job.py
20
job.py
@@ -1,8 +1,9 @@
|
|||||||
from wtforms import SubmitField, StringField, FloatField, HiddenField, validators, Form
|
from wtforms import SubmitField, StringField, FloatField, HiddenField, validators, Form
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from flask import request, render_template
|
from flask import request, render_template
|
||||||
|
from settings import Settings
|
||||||
from main import db, app, ma
|
from main import db, app, ma
|
||||||
from sqlalchemy import Sequence
|
from sqlalchemy import Sequence, func
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from status import st, Status
|
from status import st, Status
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
@@ -11,6 +12,8 @@ import pytz
|
|||||||
import socket
|
import socket
|
||||||
from shared import PA_JOB_MANAGER_HOST, PA_JOB_MANAGER_PORT, NEWEST_LOG_LIMIT, OLDEST_LOG_LIMIT
|
from shared import PA_JOB_MANAGER_HOST, PA_JOB_MANAGER_PORT, NEWEST_LOG_LIMIT, OLDEST_LOG_LIMIT
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
|
from sqlalchemy.dialects.postgresql import INTERVAL
|
||||||
|
from sqlalchemy.sql.functions import concat
|
||||||
|
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
|
|
||||||
@@ -103,13 +106,20 @@ def NewJob(name, num_files="0", wait_for=None, jex=None ):
|
|||||||
return job
|
return job
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# /jobs -> show current settings
|
# /jobs -> show jobs (default to only showing 'non-archived' jobs -- age is in
|
||||||
|
# settings.job_archive_age
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/jobs", methods=["GET"])
|
@app.route("/jobs", methods=["GET", "POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def jobs():
|
def jobs():
|
||||||
page_title='Job list'
|
settings = Settings.query.first()
|
||||||
jobs = Job.query.order_by(Job.id.desc()).all()
|
print( request.method )
|
||||||
|
if request.method == 'POST':
|
||||||
|
page_title='Job list (all)'
|
||||||
|
jobs = Job.query.order_by(Job.id.desc()).all()
|
||||||
|
else:
|
||||||
|
page_title='Job list (recent)'
|
||||||
|
jobs = Job.query.filter( Job.last_update >= (func.now() - func.cast(concat(settings.job_archive_age, 'DAYS'), INTERVAL)) ).order_by(Job.id.desc()).all()
|
||||||
return render_template("jobs.html", jobs=jobs, page_title=page_title)
|
return render_template("jobs.html", jobs=jobs, page_title=page_title)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -37,9 +37,10 @@ class Settings(db.Model):
|
|||||||
scheduled_storage_scan = db.Column(db.Integer)
|
scheduled_storage_scan = db.Column(db.Integer)
|
||||||
scheduled_bin_cleanup = db.Column(db.Integer)
|
scheduled_bin_cleanup = db.Column(db.Integer)
|
||||||
bin_cleanup_file_age = db.Column(db.Integer)
|
bin_cleanup_file_age = db.Column(db.Integer)
|
||||||
|
job_archive_age = db.Column(db.Integer)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<id: {self.id}, import_path: {self.import_path}, storage_path: {self.storage_path}, recycle_bin_path: {self.recycle_bin_path}, default_refimg_model: {self.default_refimg_model}, default_scan_model: {self.default_scan_model}, default_threshold: {self.default_threshold}, scheduled_import_scan:{self.scheduled_import_scan}, scheduled_storage_scan: {self.scheduled_storage_scan}, scheduled_bin_cleanup: {Self.scheduled_bin_cleanup}, bin_cleanup_file_age: {self.bin_cleanup_file_age} >"
|
return f"<id: {self.id}, import_path: {self.import_path}, storage_path: {self.storage_path}, recycle_bin_path: {self.recycle_bin_path}, default_refimg_model: {self.default_refimg_model}, default_scan_model: {self.default_scan_model}, default_threshold: {self.default_threshold}, scheduled_import_scan:{self.scheduled_import_scan}, scheduled_storage_scan: {self.scheduled_storage_scan}, scheduled_bin_cleanup: {self.scheduled_bin_cleanup}, bin_cleanup_file_age: {self.bin_cleanup_file_age}, job_archive_age: {self.job_archive_age}>"
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Helper class that inherits a .dump() method to turn class Settings into json / useful in jinja2
|
# Helper class that inherits a .dump() method to turn class Settings into json / useful in jinja2
|
||||||
@@ -68,6 +69,7 @@ class SettingsForm(FlaskForm):
|
|||||||
scheduled_storage_scan = IntegerField('Days between forced scan of storage path', [validators.DataRequired()])
|
scheduled_storage_scan = IntegerField('Days between forced scan of storage path', [validators.DataRequired()])
|
||||||
scheduled_bin_cleanup = IntegerField('Days between checking to clean Recycle Bin:', [validators.DataRequired()])
|
scheduled_bin_cleanup = IntegerField('Days between checking to clean Recycle Bin:', [validators.DataRequired()])
|
||||||
bin_cleanup_file_age = IntegerField('Age of files to clean out of the Recycle Bin', [validators.DataRequired()])
|
bin_cleanup_file_age = IntegerField('Age of files to clean out of the Recycle Bin', [validators.DataRequired()])
|
||||||
|
job_archive_age = IntegerField('Age of jobs to archive', [validators.DataRequired()])
|
||||||
submit = SubmitField('Save' )
|
submit = SubmitField('Save' )
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
@@ -90,6 +92,7 @@ def settings():
|
|||||||
HELP['scheduled_storage_scan']="The # of days between forced scans of the storage path for any file system changes outside of Photo Assistant"
|
HELP['scheduled_storage_scan']="The # of days between forced scans of the storage path for any file system changes outside of Photo Assistant"
|
||||||
HELP['scheduled_bin_cleanup']="The # of days between running a job to delete old files from the Recycle Bin"
|
HELP['scheduled_bin_cleanup']="The # of days between running a job to delete old files from the Recycle Bin"
|
||||||
HELP['bin_cleanup_file_age']="The # of days a file has to exist in the Recycle Bin before it can be really deleted"
|
HELP['bin_cleanup_file_age']="The # of days a file has to exist in the Recycle Bin before it can be really deleted"
|
||||||
|
HELP['job_archive_age']="The # of days a job has to exist for to be archived"
|
||||||
|
|
||||||
if request.method == 'POST' and form.validate():
|
if request.method == 'POST' and form.validate():
|
||||||
try:
|
try:
|
||||||
@@ -110,6 +113,7 @@ def settings():
|
|||||||
s.scheduled_storage_scan = request.form['scheduled_storage_scan']
|
s.scheduled_storage_scan = request.form['scheduled_storage_scan']
|
||||||
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']
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect( url_for( 'settings' ) )
|
return redirect( url_for( 'settings' ) )
|
||||||
except SQLAlchemyError as e:
|
except SQLAlchemyError as e:
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ ICON["Storage"]="db"
|
|||||||
ICON["Bin"]="trash"
|
ICON["Bin"]="trash"
|
||||||
|
|
||||||
SECS_IN_A_DAY = 86400
|
SECS_IN_A_DAY = 86400
|
||||||
NEWEST_LOG_LIMIT = 5
|
NEWEST_LOG_LIMIT = 15
|
||||||
OLDEST_LOG_LIMIT = 5
|
OLDEST_LOG_LIMIT = 5
|
||||||
|
|
||||||
# check where we are running, if laptop, then run web server and db on localhost
|
# check where we are running, if laptop, then run web server and db on localhost
|
||||||
|
|||||||
10
tables.sql
10
tables.sql
@@ -8,7 +8,9 @@ insert into AI_MODEL values ( 2, 'cnn', 'more accurate / much slower' );
|
|||||||
create table SETTINGS(
|
create table SETTINGS(
|
||||||
ID integer, BASE_PATH varchar, IMPORT_PATH varchar, STORAGE_PATH varchar, RECYCLE_BIN_PATH varchar,
|
ID integer, BASE_PATH varchar, IMPORT_PATH varchar, STORAGE_PATH varchar, RECYCLE_BIN_PATH varchar,
|
||||||
DEFAULT_REFIMG_MODEL integer, DEFAULT_SCAN_MODEL integer, DEFAULT_THRESHOLD float,
|
DEFAULT_REFIMG_MODEL integer, DEFAULT_SCAN_MODEL integer, DEFAULT_THRESHOLD float,
|
||||||
SCHEDULED_IMPORT_SCAN integer, SCHEDULED_STORAGE_SCAN integer, SCHEDULED_BIN_CLEANUP integer, BIN_CLEANUP_FILE_AGE integer,
|
SCHEDULED_IMPORT_SCAN integer, SCHEDULED_STORAGE_SCAN integer,
|
||||||
|
SCHEDULED_BIN_CLEANUP integer, BIN_CLEANUP_FILE_AGE integer,
|
||||||
|
JOB_ARCHIVE_AGE integer,
|
||||||
constraint PK_SETTINGS_ID primary key(ID),
|
constraint PK_SETTINGS_ID primary key(ID),
|
||||||
constraint FK_DEFAULT_REFIMG_MODEL foreign key (DEFAULT_REFIMG_MODEL) references AI_MODEL(ID),
|
constraint FK_DEFAULT_REFIMG_MODEL foreign key (DEFAULT_REFIMG_MODEL) references AI_MODEL(ID),
|
||||||
constraint FK_DEFAULT_SCAN_MODEL foreign key (DEFAULT_SCAN_MODEL) references AI_MODEL(ID) );
|
constraint FK_DEFAULT_SCAN_MODEL foreign key (DEFAULT_SCAN_MODEL) references AI_MODEL(ID) );
|
||||||
@@ -126,8 +128,8 @@ insert into PERSON values ( (select nextval('PERSON_ID_SEQ')), 'mum', 'Mandy', '
|
|||||||
insert into PERSON values ( (select nextval('PERSON_ID_SEQ')), 'cam', 'Cameron', 'De Paoli' );
|
insert into PERSON values ( (select nextval('PERSON_ID_SEQ')), 'cam', 'Cameron', 'De Paoli' );
|
||||||
insert into PERSON values ( (select nextval('PERSON_ID_SEQ')), 'mich', 'Michelle', 'De Paoli' );
|
insert into PERSON values ( (select nextval('PERSON_ID_SEQ')), 'mich', 'Michelle', 'De Paoli' );
|
||||||
-- DEV(ddp):
|
-- DEV(ddp):
|
||||||
insert into SETTINGS ( id, base_path, import_path, storage_path, recycle_bin_path, default_refimg_model, default_scan_model, default_threshold, scheduled_import_scan, scheduled_storage_scan, scheduled_bin_cleanup, bin_cleanup_file_age ) values ( (select nextval('SETTINGS_ID_SEQ')), '/home/ddp/src/photoassistant/', 'images_to_process/#new_img_dir/', 'photos/', '.pa_bin/', 2, 1, '0.55', 1, 1, 7, 30 );
|
insert into SETTINGS ( id, base_path, import_path, storage_path, recycle_bin_path, default_refimg_model, default_scan_model, default_threshold, scheduled_import_scan, scheduled_storage_scan, scheduled_bin_cleanup, bin_cleanup_file_age, job_archive_age ) values ( (select nextval('SETTINGS_ID_SEQ')), '/home/ddp/src/photoassistant/', 'images_to_process/#new_img_dir/', 'photos/', '.pa_bin/', 2, 1, '0.55', 1, 1, 7, 30, 3 );
|
||||||
-- DEV(cam):
|
-- DEV(cam):
|
||||||
--insert into SETTINGS ( id, base_path, import_path, storage_path, recycle_bin_path, default_refimg_model, default_scan_model, default_threshold ) values ( (select nextval('SETTINGS_ID_SEQ')), 'c:/Users/cam/Desktop/code/python/photoassistant/', 'c:\images_to_process', 'photos/', '.pa_bin/', 2, 1, '0.55' );
|
--insert into SETTINGS ( id, base_path, import_path, storage_path, recycle_bin_path, default_refimg_model, default_scan_model, default_threshold, scheduled_import_scan, scheduled_storage_scan, scheduled_bin_cleanup, bin_cleanup_file_age, job_archive_age ) values ( (select nextval('SETTINGS_ID_SEQ')), 'c:/Users/cam/Desktop/code/python/photoassistant/', 'c:\images_to_process', 'photos/', '.pa_bin/', 2, 1, '0.55', 1, 1, 7, 30, 3 );
|
||||||
-- PROD:
|
-- PROD:
|
||||||
--insert into SETTINGS ( id, base_path, import_path, storage_path, recycle_bin_path, default_refimg_model, default_scan_model, default_threshold ) values ( (select nextval('SETTINGS_ID_SEQ')), '/export/docker/storage/', 'Camera_uploads/', 'photos/', '.pa_bin/', 2, 1, '0.55', 1, 1, 7, 30 );
|
--insert into SETTINGS ( id, base_path, import_path, storage_path, recycle_bin_path, default_refimg_model, default_scan_model, default_threshold, scheduled_import_scan, scheduled_storage_scan, scheduled_bin_cleanup, bin_cleanup_file_age, job_archive_age ) values ( (select nextval('SETTINGS_ID_SEQ')), '/export/docker/storage/', 'Camera_uploads/', 'photos/', '.pa_bin/', 2, 1, '0.55', 1, 1, 7, 30, 4 );
|
||||||
|
|||||||
@@ -86,5 +86,14 @@
|
|||||||
for(el in completed_rows)
|
for(el in completed_rows)
|
||||||
$('#job_tbl_body').append(completed_rows[el])
|
$('#job_tbl_body').append(completed_rows[el])
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if 'recent' in page_title %}
|
||||||
|
tr=`
|
||||||
|
<tr>
|
||||||
|
<td><button type="button" class="btn btn-outline-info my-0 py-1" onClick="document.body.innerHTML+='<form id=_fm method=POST action={{url_for('jobs')}}></form>';document.getElementById('_fm').submit();">Show all jobs</button>
|
||||||
|
</td>
|
||||||
|
</tr>`
|
||||||
|
$('#job_tbl_body').append( tr )
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock main_content %}
|
{% endblock main_content %}
|
||||||
|
|||||||
Reference in New Issue
Block a user