daemon to use newer db access api

This commit is contained in:
dave 2019-07-04 17:46:26 -07:00
parent 022f4a90a7
commit 6589f71052
3 changed files with 55 additions and 77 deletions

View File

@ -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

View File

@ -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"<plaintext>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()

View File

@ -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,