From 6589f71052756db70dc7eba1fa9de488834dea7f Mon Sep 17 00:00:00 2001 From: dave Date: Thu, 4 Jul 2019 17:46:26 -0700 Subject: [PATCH] daemon to use newer db access api --- README.md | 1 + photoapp/api.py | 4 +- photoapp/daemon.py | 127 +++++++++++++++++++-------------------------- 3 files changed, 55 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 6528cc9..fa1d0f3 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ Roadmap - ~~Standardize on API ingest~~ done - Display and retrieval of images from the abstracted image store - Database support + - Get the web UI code (daemon.py) using the same db access method as the api - Support any connection URI sqlalchemy is happy with - Tune certain databases if their uri is detected (sqlite and threads lol) - ~~Cache~~ done diff --git a/photoapp/api.py b/photoapp/api.py index 135392b..f3554c7 100644 --- a/photoapp/api.py +++ b/photoapp/api.py @@ -79,7 +79,6 @@ class GfapiAdapter(StorageAdapter): pass # TODO gluster storage backend -# This is largely duplicated from library.py, but written with intent for later refactoring to support abstract storage. class LibraryManager(object): def __init__(self, storage): assert isinstance(storage, StorageAdapter) @@ -98,7 +97,8 @@ class PhotosApiV1(object): @cherrypy.expose def index(self): - yield f"hello, this is the api. my database is: {db}\n" + cherrypy.response.headers["Content-type"] = "text/plain" + return "Hello! This is the Photolib V1 API.\n" @cherrypy.expose @cherrypy.tools.json_out() diff --git a/photoapp/daemon.py b/photoapp/daemon.py index 103c4a8..e8d8f75 100644 --- a/photoapp/daemon.py +++ b/photoapp/daemon.py @@ -13,6 +13,7 @@ from photoapp.dbutils import SAEnginePlugin, SATool import math from urllib.parse import urlparse from photoapp.utils import mime2ext, auth, require_auth, photo_auth_filter, slugify +from photoapp.dbutils import db APPROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../")) @@ -45,14 +46,13 @@ class PhotosWeb(object): """ Return a dict containing variables expected to be on every page """ - s = self.session() # all tags / albums with photos visible under the current auth context - tagq = s.query(Tag).join(TagItem).join(PhotoSet) + tagq = db.query(Tag).join(TagItem).join(PhotoSet) if not auth(): tagq = tagq.filter(PhotoSet.status == PhotoStatus.public) tagq = tagq.filter(Tag.is_album == False).order_by(Tag.name).all() # pragma: manual auth - albumq = s.query(Tag).join(TagItem).join(PhotoSet) + albumq = db.query(Tag).join(TagItem).join(PhotoSet) if not auth(): albumq = albumq.filter(PhotoSet.status == PhotoStatus.public) albumq = albumq.filter(Tag.is_album == True).order_by(Tag.name).all() # pragma: manual auth @@ -64,15 +64,8 @@ class PhotosWeb(object): "auth": auth(), "PhotoStatus": PhotoStatus } - s.close() return ret - def session(self): - """ - Get a database session - """ - return self.library.session() - @cherrypy.expose def index(self): """ @@ -85,10 +78,9 @@ class PhotosWeb(object): """ /feed - main photo feed - show photos sorted by date, newest first """ - s = self.session() page, pgsize = int(page), int(pgsize) - total_sets = photo_auth_filter(s.query(func.count(PhotoSet.id))).first()[0] - images = photo_auth_filter(s.query(PhotoSet)).order_by(PhotoSet.date.desc()). \ + total_sets = photo_auth_filter(db.query(func.count(PhotoSet.id))).first()[0] + images = photo_auth_filter(db.query(PhotoSet)).order_by(PhotoSet.date.desc()). \ offset(pgsize * page).limit(pgsize).all() yield self.render("feed.html", images=[i for i in images], page=page, pgsize=int(pgsize), total_sets=total_sets) @@ -97,12 +89,11 @@ class PhotosWeb(object): """ /stats - show server statistics """ - s = self.session() - images = photo_auth_filter(s.query(func.count(PhotoSet.uuid), - func.strftime('%Y', PhotoSet.date).label('year'), - func.strftime('%m', PhotoSet.date).label('month'))). \ + images = photo_auth_filter(db.query(func.count(PhotoSet.uuid), + func.strftime('%Y', PhotoSet.date).label('year'), + func.strftime('%m', PhotoSet.date).label('month'))). \ group_by('year', 'month').order_by(desc('year'), desc('month')).all() - tsize = photo_auth_filter(s.query(func.sum(Photo.size)).join(PhotoSet)).scalar() # pragma: manual auth + tsize = photo_auth_filter(db.query(func.sum(Photo.size)).join(PhotoSet)).scalar() # pragma: manual auth yield self.render("monthly.html", images=images, tsize=tsize) @cherrypy.expose @@ -112,8 +103,7 @@ class PhotosWeb(object): the given tag. TODO using so many coordinates is slow in the browser. dedupe them somehow. """ - s = self.session() - query = photo_auth_filter(s.query(PhotoSet)).filter(PhotoSet.lat != 0, PhotoSet.lon != 0) + query = photo_auth_filter(db.query(PhotoSet)).filter(PhotoSet.lat != 0, PhotoSet.lon != 0) if a: query = query.join(TagItem).join(Tag).filter(Tag.uuid == a) if i: @@ -131,44 +121,43 @@ class PhotosWeb(object): :param remove: target photos will have the tag specified by this uuid removed :param newtag: new tag name to create """ - s = self.session() def get_photos(): if fromdate: dt = datetime.strptime(fromdate, "%Y-%m-%d") dt_end = dt + timedelta(days=1) - photos = s.query(PhotoSet).filter(and_(PhotoSet.date >= dt, - PhotoSet.date < dt_end)).order_by(PhotoSet.date) - num_photos = s.query(func.count(PhotoSet.id)). \ + photos = db.query(PhotoSet).filter(and_(PhotoSet.date >= dt, + PhotoSet.date < dt_end)).order_by(PhotoSet.date) + num_photos = db.query(func.count(PhotoSet.id)). \ filter(and_(PhotoSet.date >= dt, PhotoSet.date < dt_end)).order_by(PhotoSet.date).scalar() if uuid: - photos = s.query(PhotoSet).filter(PhotoSet.uuid == uuid) - num_photos = s.query(func.count(PhotoSet.id)).filter(PhotoSet.uuid == uuid).scalar() + photos = db.query(PhotoSet).filter(PhotoSet.uuid == uuid) + num_photos = db.query(func.count(PhotoSet.id)).filter(PhotoSet.uuid == uuid).scalar() return photos, num_photos if remove: - rmtag = s.query(Tag).filter(Tag.uuid == remove).first() + rmtag = db.query(Tag).filter(Tag.uuid == remove).first() photoq, _ = get_photos() for photo in photoq: - s.query(TagItem).filter(TagItem.tag_id == rmtag.id, TagItem.set_id == photo.id).delete() - s.commit() + db.query(TagItem).filter(TagItem.tag_id == rmtag.id, TagItem.set_id == photo.id).delete() + db.commit() if newtag: - s.add(Tag(title=newtag.capitalize(), name=newtag, slug=slugify(newtag))) - s.commit() + db.add(Tag(title=newtag.capitalize(), name=newtag, slug=slugify(newtag))) + db.commit() photos, num_photos = get_photos() if tag: # Create the tag on all the photos - tag = s.query(Tag).filter(Tag.uuid == tag).first() + tag = db.query(Tag).filter(Tag.uuid == tag).first() for photo in photos.all(): - if 0 == s.query(func.count(TagItem.id)).filter(TagItem.tag_id == tag.id, - TagItem.set_id == photo.id).scalar(): - s.add(TagItem(tag_id=tag.id, set_id=photo.id)) - s.commit() + if 0 == db.query(func.count(TagItem.id)).filter(TagItem.tag_id == tag.id, + TagItem.set_id == photo.id).scalar(): + db.add(TagItem(tag_id=tag.id, set_id=photo.id)) + db.commit() - alltags = s.query(Tag).order_by(Tag.name).all() + alltags = db.query(Tag).order_by(Tag.name).all() yield self.render("create_tags.html", images=photos, alltags=alltags, num_photos=num_photos, fromdate=fromdate, uuid=uuid) @@ -207,21 +196,20 @@ class DateView(object): @cherrypy.expose def index(self, date=None, page=0): - s = self.master.session() if date: page = int(page) pgsize = 100 dt = datetime.strptime(date, "%Y-%m-%d") dt_end = dt + timedelta(days=1) - total_sets = photo_auth_filter(s.query(func.count(PhotoSet.id))). \ + total_sets = photo_auth_filter(db.query(func.count(PhotoSet.id))). \ filter(and_(PhotoSet.date >= dt, PhotoSet.date < dt_end)).first()[0] - images = photo_auth_filter(s.query(PhotoSet)).filter(and_(PhotoSet.date >= dt, - PhotoSet.date < dt_end)).order_by(PhotoSet.date). \ + images = photo_auth_filter(db.query(PhotoSet)).filter(and_(PhotoSet.date >= dt, + PhotoSet.date < dt_end)).order_by(PhotoSet.date). \ offset(page * pgsize).limit(pgsize).all() yield self.master.render("date.html", page=page, pgsize=pgsize, total_sets=total_sets, images=[i for i in images], date=dt) return - images = photo_auth_filter(s.query(PhotoSet, func.strftime('%Y-%m-%d', + images = photo_auth_filter(db.query(PhotoSet, func.strftime('%Y-%m-%d', PhotoSet.date).label('gdate'), func.count('photos.id'), func.strftime('%Y', PhotoSet.date).label('year'), @@ -242,9 +230,7 @@ class ThumbnailView(object): @cherrypy.expose def index(self, item_type, thumb_size, uuid): uuid = uuid.split(".")[0] - s = self.master.session() - - query = photo_auth_filter(s.query(Photo).join(PhotoSet)) + query = photo_auth_filter(db.query(Photo).join(PhotoSet)) query = query.filter(Photo.set.has(uuid=uuid)) if item_type == "set" \ else query.filter(Photo.uuid == uuid) if item_type == "one" \ @@ -284,10 +270,8 @@ class DownloadView(object): @cherrypy.expose def index(self, item_type, uuid, preview=False): uuid = uuid.split(".")[0] - s = self.master.session() - query = None if item_type == "set" \ - else photo_auth_filter(s.query(Photo)).filter(Photo.uuid == uuid) if item_type == "one" \ + else photo_auth_filter(db.query(Photo)).filter(Photo.uuid == uuid) if item_type == "one" \ else None # TODO set download query item = query.first() @@ -311,8 +295,7 @@ class PhotoView(object): @cherrypy.expose def index(self, uuid): # uuid = uuid.split(".")[0] - s = self.master.session() - photo = photo_auth_filter(s.query(PhotoSet)).filter(or_(PhotoSet.uuid == uuid, PhotoSet.slug == uuid)).first() + photo = photo_auth_filter(db.query(PhotoSet)).filter(or_(PhotoSet.uuid == uuid, PhotoSet.slug == uuid)).first() if not photo: raise cherrypy.HTTPError(404) yield self.master.render("photo.html", image=photo) @@ -327,8 +310,7 @@ class PhotoView(object): * "Make private": * "Save": update the photo's title, description, and date_offset fields """ - s = self.master.session() - photo = s.query(PhotoSet).filter(PhotoSet.uuid == uuid).first() + photo = db.query(PhotoSet).filter(PhotoSet.uuid == uuid).first() if op == "Make public": photo.status = PhotoStatus.public elif op == "Make private": @@ -338,14 +320,13 @@ class PhotoView(object): photo.description = description photo.slug = slugify(title) or None photo.date_offset = int(offset) if offset else 0 - s.commit() + db.commit() raise cherrypy.HTTPRedirect('/photo/{}'.format(photo.slug or photo.uuid), 302) @cherrypy.expose @require_auth def edit(self, uuid): - s = self.master.session() - photo = photo_auth_filter(s.query(PhotoSet)).filter(PhotoSet.uuid == uuid).first() + photo = photo_auth_filter(db.query(PhotoSet)).filter(PhotoSet.uuid == uuid).first() yield self.master.render("photo_edit.html", image=photo) @@ -361,20 +342,18 @@ class TagView(object): def index(self, uuid, page=0): page = int(page) pgsize = 100 - s = self.master.session() - if uuid == "untagged": - numphotos = photo_auth_filter(s.query(func.count(PhotoSet.id))). \ - filter(~PhotoSet.id.in_(s.query(TagItem.set_id))).scalar() - photos = photo_auth_filter(s.query(PhotoSet)).filter(~PhotoSet.id.in_(s.query(TagItem.set_id))).\ + numphotos = photo_auth_filter(db.query(func.count(PhotoSet.id))). \ + filter(~PhotoSet.id.in_(db.query(TagItem.set_id))).scalar() + photos = photo_auth_filter(db.query(PhotoSet)).filter(~PhotoSet.id.in_(db.query(TagItem.set_id))).\ offset(page * pgsize). \ limit(pgsize).all() yield self.master.render("untagged.html", images=photos, total_items=numphotos, pgsize=pgsize, page=page) else: - tag = s.query(Tag).filter(or_(Tag.uuid == uuid, Tag.slug == uuid)).first() - numphotos = photo_auth_filter(s.query(func.count(Tag.id)).join(TagItem).join(PhotoSet)). \ + tag = db.query(Tag).filter(or_(Tag.uuid == uuid, Tag.slug == uuid)).first() + numphotos = photo_auth_filter(db.query(func.count(Tag.id)).join(TagItem).join(PhotoSet)). \ filter(Tag.id == tag.id).scalar() - photos = photo_auth_filter(s.query(PhotoSet)).join(TagItem).join(Tag). \ + photos = photo_auth_filter(db.query(PhotoSet)).join(TagItem).join(Tag). \ filter(Tag.id == tag.id). \ order_by(PhotoSet.date.desc()). \ offset(page * pgsize). \ @@ -393,24 +372,23 @@ class TagView(object): - Make all public: mark all photos under this tag as public - Make all private: mark all photos under this tag as private """ - s = self.master.session() - tag = s.query(Tag).filter(or_(Tag.uuid == uuid, Tag.slug == uuid)).first() + tag = db.query(Tag).filter(or_(Tag.uuid == uuid, Tag.slug == uuid)).first() if op == "Demote to tag": tag.is_album = 0 elif op == "Promote to album": tag.is_album = 1 elif op == "Delete tag": - s.query(TagItem).filter(TagItem.tag_id == tag.id).delete() - s.delete(tag) - s.commit() + db.query(TagItem).filter(TagItem.tag_id == tag.id).delete() + db.delete(tag) + db.commit() raise cherrypy.HTTPRedirect('/', 302) elif op == "Make all public": # TODO smarter query - for photo in s.query(PhotoSet).join(TagItem).join(Tag).filter(Tag.id == tag.id).all(): + for photo in db.query(PhotoSet).join(TagItem).join(Tag).filter(Tag.id == tag.id).all(): photo.status = PhotoStatus.public elif op == "Make all private": # TODO smarter query - for photo in s.query(PhotoSet).join(TagItem).join(Tag).filter(Tag.id == tag.id).all(): + for photo in db.query(PhotoSet).join(TagItem).join(Tag).filter(Tag.id == tag.id).all(): photo.status = PhotoStatus.private elif op == "Save": tag.title = title @@ -418,14 +396,13 @@ class TagView(object): tag.slug = slugify(title) else: raise Exception("Invalid op: '{}'".format(op)) - s.commit() + db.commit() raise cherrypy.HTTPRedirect('/tag/{}'.format(tag.slug or tag.uuid), 302) @cherrypy.expose @require_auth def edit(self, uuid): - s = self.master.session() - tag = s.query(Tag).filter(Tag.uuid == uuid).first() + tag = db.query(Tag).filter(Tag.uuid == uuid).first() yield self.master.render("tag_edit.html", tag=tag) @@ -453,12 +430,12 @@ def main(): web = PhotosWeb(library, tpl_dir) def validate_password(realm, username, password): - s = library.session() - if s.query(User).filter(User.name == username, User.password == pwhash(password)).first(): + if db.query(User).filter(User.name == username, User.password == pwhash(password)).first(): return True return False cherrypy.tree.mount(web, '/', {'/': {'tools.trailing_slash.on': False, + 'tools.db.on': True, 'error_page.403': web.error, 'error_page.404': web.error}, '/static': {"tools.staticdir.on": True,