photolib/photoapp/types.py

157 lines
4.9 KiB
Python

from sqlalchemy import Column, Integer, String, DateTime, Unicode, DECIMAL, ForeignKey, Boolean, Enum
from sqlalchemy.orm import relationship
from sqlalchemy.schema import UniqueConstraint
from photoapp.dbutils import Base
from datetime import datetime
import uuid
import enum
# file extensions we allow
known_extensions = ["jpg", "png", "cr2", "xmp", "mp4", "mov"]
# categorizaiton of media type based on extension
regular_images = ["jpg", "png"] # we can pull metadata out of these
files_raw = ["cr2", "xmp"] # treated as black boxes
files_video = ["mp4", "mov"]
# extensions with well-known aliases
mapped_extensions = {"jpg": {"jpeg", }} # target: aliases
# allowed file types (based on magic identification)
# TODO enforce this
known_mimes = {"image/png",
"image/jpeg",
"image/gif",
"application/octet-stream-xmp",
"image/x-canon-cr2",
"video/mp4",
"video/quicktime"}
def genuuid():
return str(uuid.uuid4())
def map_extension(ext):
for target, aliases in mapped_extensions.items():
if ext in aliases:
return target
return ext
class PhotoStatus(enum.Enum):
private = 0
public = 1
hidden = 2
class PhotoSet(Base):
__tablename__ = 'photos'
id = Column(Integer, primary_key=True)
uuid = Column(Unicode(length=36), unique=True, default=lambda: str(uuid.uuid4()))
date = Column(DateTime)
date_real = Column(DateTime)
date_offset = Column(Integer, default=0) # minutes
lat = Column(DECIMAL(precision=11))
lon = Column(DECIMAL(precision=11))
files = relationship("Photo", back_populates="set")
tags = relationship("TagItem", back_populates="set")
title = Column(String(length=128))
description = Column(String(length=1024))
slug = Column(String(length=128))
status = Column(Enum(PhotoStatus), default=PhotoStatus.private)
def to_json(self, files=True):
s = {attr: getattr(self, attr) for attr in {"uuid", "title", "description"}}
s["lat"] = str(self.lat) if self.lat else None
s["lon"] = str(self.lon) if self.lon else None
s["date"] = self.date.isoformat()
if files:
s["files"] = {i.uuid: i.to_json() for i in self.files}
s["tags"] = [t.tag.name for t in self.tags]
s["status"] = self.status.name if self.status else PhotoStatus.private.name # duplicate default definition
return s
class Photo(Base):
__tablename__ = 'files'
id = Column(Integer, primary_key=True)
set_id = Column(Integer, ForeignKey("photos.id"))
uuid = Column(Unicode(length=36), unique=True, default=genuuid)
set = relationship("PhotoSet", back_populates="files", foreign_keys=[set_id])
size = Column(Integer)
width = Column(Integer)
height = Column(Integer)
orientation = Column(Integer, default=0)
hash = Column(String(length=64), unique=True)
path = Column(Unicode(length=64))
format = Column(String(length=32)) # TODO how long can a mime string be
fname = Column(String(length=64)) # seems generous enough
def to_json(self):
j = {attr: getattr(self, attr) for attr in
{"uuid", "size", "width", "height", "orientation", "format", "hash", "fname"}}
j["set"] = self.set.uuid if self.set else None
return j
class Tag(Base):
__tablename__ = 'tags'
id = Column(Integer, primary_key=True)
uuid = Column(Unicode(length=36), unique=True, default=lambda: str(uuid.uuid4()))
created = Column(DateTime, default=lambda: datetime.now())
modified = Column(DateTime, default=lambda: datetime.now())
is_album = Column(Boolean, default=False)
# slug-like short name such as "iomtrip"
name = Column(String(length=32), unique=True)
# longer human-format title like "Isle of Man trip"
title = Column(String(length=32))
# url slug like "isle-of-man-trip"
slug = Column(String(length=32), unique=True)
# fulltext description
description = Column(String(length=256))
entries = relationship("TagItem", back_populates="tag")
class TagItem(Base):
__tablename__ = 'tag_items'
id = Column(Integer, primary_key=True)
tag_id = Column(Integer, ForeignKey("tags.id"))
set_id = Column(Integer, ForeignKey("photos.id"))
order = Column(Integer, default=0)
tag = relationship("Tag", back_populates="entries", foreign_keys=[tag_id])
set = relationship("PhotoSet", back_populates="tags", foreign_keys=[set_id])
UniqueConstraint(tag_id, set_id)
class UserStatus(enum.Enum):
banned = -1
guest = 0
normal = 1
admin = 2
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(length=64), unique=True)
password = Column(String(length=64)) # sha256
status = Column(Enum(UserStatus), default=UserStatus.normal)
def to_json(self):
return dict(name=self.name, status=self.status.name)