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"" ################################################################################ # 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/ 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 if self.path_type == 'Storage': path = SettingsSPath() elif self.path_type == 'Import': path = SettingsIPath() elif self.path_type == 'Bin': path = SettingsRBPath() 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 )