Files
photoassistant/states.py

335 lines
18 KiB
Python

from flask import request, render_template, redirect, url_for
from settings import Settings, SettingsIPath, SettingsSPath, SettingsRBPath
from flask_login import login_required, current_user
from main import db, app, ma
from shared import PA
from user import PAUser
from datetime import datetime
from job import SetFELog
from shared import SymlinkName
import pytz
import re
################################################################################
# PA_UserState: preference data for a given user / path_type combo, so a given user
# and their prefs for say the import path(s) and storage path(s) etc, each
# path_type has different defaults, and keeping those works better
################################################################################
class PA_UserState(db.Model):
__tablename__ = "pa_user_state"
id = db.Column(db.Integer, db.Sequence('pa_user_state_id_seq'), primary_key=True )
pa_user_dn = db.Column(db.String, db.ForeignKey('pa_user.dn'), primary_key=True )
last_used = db.Column(db.DateTime(timezone=True))
path_type = db.Column(db.String, primary_key=True, unique=False, nullable=False )
noo = db.Column(db.String, unique=False, nullable=False )
grouping = db.Column(db.String, unique=False, nullable=False )
how_many = db.Column(db.Integer, unique=False, nullable=False )
st_offset = db.Column(db.Integer, unique=False, nullable=False )
size = db.Column(db.Integer, unique=False, nullable=False )
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 )
## for now being lazy and not doing a separate table until I settle on needed fields and when
# only used if ptype == View
view_eid = db.Column(db.Integer, unique=False, nullable=False )
orig_ptype = db.Column(db.String, unique=False, nullable=False )
# only used if view and orig_ptype was search
orig_search_term = db.Column(db.String, unique=False, nullable=False )
orig_url = db.Column(db.String, unique=False, nullable=False )
current = db.Column(db.Integer)
first_eid = db.Column(db.Integer)
last_eid = db.Column(db.Integer)
num_entries = db.Column(db.Integer)
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}, st_offset: {self.st_offset}, size: {self.size}, folders: {self.folders}, root: {self.root}, cwd: {self.cwd}, view_eid: {self.view_eid}, orig_ptype: {self.orig_ptype}, orig_search_term: {self.orig_search_term}, orig_url: {self.orig_url}, current={self.current}, first_eid={self.first_eid}, last_eid={self.last_eid}, num_entries={self.num_entries}>"
################################################################################
# States: class to store set of default values for viewing (order/size, etc.)
# and if a request object (from a POST) is passed in, it returns those instead
# it also handles the cwd appropriately, paths, search, etc.
################################################################################
class States(PA):
def __init__(self, request):
self.path_type=''
self.orig_search_term = ''
self.url = request.path
self.view_eid = None
self.current=0
self.first_eid=0
self.last_eid=0
self.num_entries=0
self.prefix=None
# this is any next/prev or noo, grouping, etc. change (so use referrer to work out what to do with this)
# because this can happen on a view, or files_up, etc. change this FIRST
if 'change_file_opts' in request.path:
base=request.base_url
base=base.replace("change_file_opts", "")
self.url = "/"+request.referrer.replace(base, "" )
# if view_list, then we really are a view, and view_eid should be in the form
if 'view_list' in request.path:
self.path_type = 'View'
self.view_eid = request.form['view_eid']
self.url = request.form['orig_url']
# this occurs ONLY when a POST to /view/<id> occurs (at this stage orig_url will be from an import, storage, bin or search)
elif 'view' in request.path:
self.path_type = 'View'
self.view_eid = self.url[6:]
# use orig url to define defaults/look up states for 'last' import/storage/bin/search
if request.method == "POST":
self.url = request.form['orig_url']
else:
# GET's occur on redirect, and we don't have a form, so get it from pref
st=self.url[8:]
if request.referrer and 'search' in request.referrer:
st=re.sub( '.+/search/', '', request.referrer )
else:
st=''
pref=PA_UserState.query.filter(PA_UserState.pa_user_dn==current_user.dn,PA_UserState.path_type==self.path_type,PA_UserState.view_eid==self.view_eid,PA_UserState.orig_search_term==st).first()
if not pref:
SetFELog( message=f"ERROR: pref not found - dn={current_user.dn}, st={st}, s={self}????" , level="danger", persistent=True, cant_close=False )
SetFELog( message=f"WARNING: I think this error occurred because you reloaded a page and the server had restarted between your original page load and this page reload, is that possible?" , level="warning", persistent=True, cant_close=False )
redirect("/")
else:
if not hasattr( pref, 'orig_url' ):
SetFELog( message=f"ERROR: orig_url not in pref - dn={current_user.dn}, st={st}, self={self}, pref={pref}????" , level="danger", persistent=True, cant_close=True )
redirect("/")
self.url = pref.orig_url
if 'files_ip' in self.url or 'file_list_ip' in self.url:
if self.path_type == "View":
self.orig_ptype = 'Import'
self.orig_url = self.url
else:
self.path_type = 'Import'
elif 'files_sp' in self.url:
if self.path_type == "View":
self.orig_ptype = 'Storage'
self.orig_url = self.url
else:
self.path_type = 'Storage'
elif 'files_rbp' in self.url:
if self.path_type == "View":
self.orig_ptype = 'Bin'
self.orig_url = self.url
else:
self.path_type = 'Bin'
elif 'search' in self.url:
# okay if we are a search, but came from a view then get last_search_state form prefs and use it
m=re.match( '.*search/(.+)$', self.url )
if m == None:
SetFELog( message=f"ERROR: DDP messed up, seems we are processing a search, but cant see the search term - is this even possible?" )
return
self.orig_search_term = m[1]
if self.path_type == "View":
self.orig_ptype = 'Search'
self.orig_url = self.url
else:
self.path_type = 'Search'
elif 'view' in self.url:
# use url to get eid of viewed entry
self.view_eid = self.url[6:]
# force this to be a search so rest of code won't totally die, but also not return anything
self.path_type="Search"
self.orig_url=self.url
elif 'change_file_opts' not in self.url:
SetFELog( message=f"ERROR: DDP messed up, failed to match URL {self.url} for settings this will fail, redirecting to home" , level="danger", persistent=True, cant_close=True )
SetFELog( message=f"referrer={request.referrer}" , level="danger", persistent=True, cant_close=True )
return
if self.path_type == 'View':
pref=PA_UserState.query.filter(PA_UserState.pa_user_dn==current_user.dn,PA_UserState.path_type==self.path_type,PA_UserState.view_eid==self.view_eid,PA_UserState.orig_search_term==self.orig_search_term).first()
if not hasattr( self, 'orig_ptype' ):
self.orig_ptype='View'
self.orig_url=''
SetFELog( message=f"ERROR: No orig ptype? s={self} - pref={pref}, redirecting to home" , level="danger", persistent=True, cant_close=True )
SetFELog( message=f"referrer={request.referrer}" , level="danger", persistent=True, cant_close=True )
redirect("/")
# should find original path or search for this view (if not a search, search_term='')
orig_pref=PA_UserState.query.filter(PA_UserState.pa_user_dn==current_user.dn,PA_UserState.path_type==self.orig_ptype,PA_UserState.orig_search_term==self.orig_search_term).first()
if not orig_pref:
SetFELog( message=f"ERROR: DDP messed up 2, failed to find orig_pref for a view pt={self.path_type} for search={self.orig_search_term}" , level="danger", persistent=True, cant_close=True )
SetFELog( message=f"referrer={request.referrer}" , level="danger", persistent=True, cant_close=True )
return
elif self.path_type == 'Search':
pref=PA_UserState.query.filter(PA_UserState.pa_user_dn==current_user.dn,PA_UserState.path_type==self.path_type,PA_UserState.orig_search_term==self.orig_search_term).first()
else:
pref=PA_UserState.query.filter(PA_UserState.pa_user_dn==current_user.dn,PA_UserState.path_type==self.path_type).first()
if pref:
self.grouping=pref.grouping
self.how_many=pref.how_many
self.offset=pref.st_offset
self.size=pref.size
self.cwd=pref.cwd
self.orig_ptype=pref.orig_ptype
self.orig_search_term=pref.orig_search_term
self.orig_url = pref.orig_url
self.view_eid = pref.view_eid
self.current = pref.current
if self.path_type == "View":
self.root='static/' + self.orig_ptype
self.first_eid=orig_pref.first_eid
self.last_eid=orig_pref.last_eid
self.num_entries=orig_pref.num_entries
self.noo=orig_pref.noo
self.folders=orig_pref.folders
self.orig_search_term=orig_pref.orig_search_term
else:
self.root=pref.root
self.first_eid = pref.first_eid
self.last_eid = pref.last_eid
self.num_entries = pref.num_entries
self.noo=pref.noo
self.folders=pref.folders
else:
# retreive defaults from 'PAUser' where defaults are stored
u=PAUser.query.filter(PAUser.dn==current_user.dn).one()
self.grouping=u.default_grouping
self.how_many=u.default_how_many
self.offset=0
self.size=u.default_size
if self.path_type == "View":
self.root='static/' + self.orig_ptype
self.first_eid=orig_pref.first_eid
self.last_eid=orig_pref.last_eid
self.num_entries=orig_pref.num_entries
self.noo=orig_pref.noo
self.folders=orig_pref.folders
self.orig_search_term=orig_pref.orig_search_term
else:
self.root='static/' + self.path_type
if self.path_type == 'Import':
self.noo = u.default_import_noo
self.folders = u.default_import_folders
elif self.path_type == 'Storage':
self.noo = u.default_storage_noo
self.folders = u.default_storage_folders
else:
# search so force folders to be false (rather see images, # than series of folders that dont match search themselves)
self.noo=u.default_search_noo
self.folders=False
self.cwd=self.root
if not hasattr(self, 'orig_ptype'):
self.orig_ptype=None
if not hasattr(self, 'orig_search_term'):
self.orig_search_term=None
self.orig_url = self.url
# the above are defaults, if we are here, then we have current values, use them instead if they are set -- AI: searches dont set them so then we use those in the DB first
if request.method=="POST":
if self.path_type != "View" and 'noo' in request.form:
# we are changing values based on a POST to the form, if we changed the noo option, we need to reset things
if 'change_file_opts' in request.path and self.noo != request.form['noo']:
self.noo=request.form['noo']
self.first_eid=0
self.last_eid=0
self.offset=0
if 'how_many' in request.form:
self.how_many=request.form['how_many']
if 'offset' in request.form:
self.offset=int(request.form['offset'])
if 'grouping' in request.form:
self.grouping=request.form['grouping']
# this can be null if we come from view by details
if 'size' in request.form:
self.size = request.form['size']
# seems html cant do boolean, but uses strings so convert
if self.path_type != "View" and 'folders' in request.form:
# we are changing values based on a POST to the form, if we are in folder view and we changed the folders option, we need to reset things
if 'change_file_opts' in request.path:
if self.folders and self.folders != request.form['folders']:
self.num_entries=0
self.first_eid=0
self.last_eid=0
if request.form['folders'] == "False":
self.folders=False
else:
self.folders=True
# have to force grouping to None if we flick to folders from a flat view with grouping (otherwise we print out
# group headings for child content that is not in the CWD)
self.grouping=None
if 'orig_url' in request.form:
self.orig_url = request.form['orig_url']
# possible to not be set for an AI: search
if 'cwd' in request.form:
self.cwd = request.form['cwd']
if 'prev' in request.form:
self.offset -= int(self.how_many)
# just in case we hit prev too fast, stop this...
if self.offset < 0:
self.offset=0
if 'next' in request.form:
if (self.offset + int(self.how_many)) < self.num_entries:
self.offset += int(self.how_many)
else:
# tripping this still
SetFELog( message=f"WARNING: next image requested, but would go past end of list? - ignore this" , level="warning", persistent=True, cant_close=False )
if 'current' in request.form:
self.current = int(request.form['current'])
last_used=datetime.now(pytz.utc)
# set the prefix based on path
path=None
if self.path_type == 'Storage':
path = SettingsSPath()
elif self.path_type == 'Import':
path = SettingsIPath()
elif self.path_type == 'Bin':
path = SettingsRBPath()
if path:
self.prefix = SymlinkName(self.path_type,path,path+'/')
# now save pref
if not pref:
# insert new pref for this combo (might be a new search or view, or first time for a path)
pref=PA_UserState( pa_user_dn=current_user.dn, last_used=last_used, path_type=self.path_type, view_eid=self.view_eid,
noo=self.noo, grouping=self.grouping, how_many=self.how_many, st_offset=self.offset, size=self.size,
folders=self.folders, root=self.root, cwd=self.cwd, orig_ptype=self.orig_ptype, orig_search_term=self.orig_search_term,
orig_url=self.orig_url, current=self.current, first_eid=self.first_eid, last_eid=self.last_eid, num_entries=self.num_entries )
else:
# update this pref with the values calculated above (most likely from POST to form)
pref.pa_user_dn=current_user.dn
pref.path_type=self.path_type
pref.view_eid=self.view_eid
pref.noo=self.noo
pref.grouping=self.grouping
pref.how_many=self.how_many
pref.st_offset=self.offset
pref.size=self.size
pref.folders=self.folders
pref.root = self.root
pref.cwd = self.cwd
pref.orig_ptype = self.orig_ptype
pref.orig_search_term = self.orig_search_term
pref.orig_url = self.orig_url
pref.last_used = last_used
pref.first_eid = self.first_eid
pref.last_eid = self.last_eid
pref.num_entries = self.num_entries
# only passed in (at the moment) in view_list
pref.current = self.current
db.session.add(pref)
db.session.commit()
return
################################################################################
# /states -> GET only -> prints out list of all prefs (simple for now)
################################################################################
@app.route("/states", methods=["GET"])
@login_required
def states():
user = PAUser.query.filter( PAUser.dn==current_user.dn ).one()
states = PA_UserState.query.filter( PA_UserState.pa_user_dn==current_user.dn ).all()
return render_template("states.html", user=user, states=states )