2018-09-15 16:48:47 -07:00
|
|
|
from sqlalchemy import Column, Integer, String, DateTime, Unicode, DECIMAL, ForeignKey, Boolean, Enum
|
2018-09-08 15:49:16 -07:00
|
|
|
from sqlalchemy.orm import relationship
|
2018-09-09 16:47:21 -07:00
|
|
|
from sqlalchemy.schema import UniqueConstraint
|
2019-06-17 22:43:57 -07:00
|
|
|
from photoapp.dbutils import Base
|
2018-09-09 16:47:21 -07:00
|
|
|
from datetime import datetime
|
2018-09-08 15:49:16 -07:00
|
|
|
import uuid
|
2018-09-15 16:48:47 -07:00
|
|
|
import enum
|
2018-09-08 15:49:16 -07:00
|
|
|
|
|
|
|
|
2019-07-04 13:10:52 -07:00
|
|
|
# file extensions we allow
|
2019-06-21 09:22:42 -07:00
|
|
|
known_extensions = ["jpg", "png", "cr2", "xmp", "mp4", "mov"]
|
2019-07-04 13:10:52 -07:00
|
|
|
|
|
|
|
# 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
|
2019-06-21 09:22:42 -07:00
|
|
|
files_video = ["mp4", "mov"]
|
2019-07-04 13:10:52 -07:00
|
|
|
|
|
|
|
# extensions with well-known aliases
|
2019-06-25 21:34:32 -07:00
|
|
|
mapped_extensions = {"jpg": {"jpeg", }} # target: aliases
|
2019-07-04 13:10:52 -07:00
|
|
|
|
|
|
|
# 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"}
|
|
|
|
|
|
|
|
|
2019-07-04 14:24:54 -07:00
|
|
|
def genuuid():
|
|
|
|
return str(uuid.uuid4())
|
2019-06-25 21:34:32 -07:00
|
|
|
|
|
|
|
|
|
|
|
def map_extension(ext):
|
|
|
|
for target, aliases in mapped_extensions.items():
|
|
|
|
if ext in aliases:
|
|
|
|
return target
|
|
|
|
return ext
|
2019-06-21 09:22:42 -07:00
|
|
|
|
|
|
|
|
2018-09-15 16:48:47 -07:00
|
|
|
class PhotoStatus(enum.Enum):
|
|
|
|
private = 0
|
|
|
|
public = 1
|
|
|
|
hidden = 2
|
|
|
|
|
|
|
|
|
2018-09-08 15:49:16 -07:00
|
|
|
class PhotoSet(Base):
|
|
|
|
__tablename__ = 'photos'
|
|
|
|
|
|
|
|
id = Column(Integer, primary_key=True)
|
2019-07-06 11:25:15 -07:00
|
|
|
uuid = Column(Unicode(length=36), unique=True, default=lambda: str(uuid.uuid4()))
|
2018-09-08 15:49:16 -07:00
|
|
|
date = Column(DateTime)
|
2018-09-15 16:48:47 -07:00
|
|
|
date_real = Column(DateTime)
|
|
|
|
date_offset = Column(Integer, default=0) # minutes
|
2018-09-08 15:49:16 -07:00
|
|
|
lat = Column(DECIMAL(precision=11))
|
|
|
|
lon = Column(DECIMAL(precision=11))
|
|
|
|
|
|
|
|
files = relationship("Photo", back_populates="set")
|
2018-09-09 16:47:21 -07:00
|
|
|
tags = relationship("TagItem", back_populates="set")
|
2018-09-08 15:49:16 -07:00
|
|
|
|
2019-07-06 11:25:15 -07:00
|
|
|
title = Column(String(length=128))
|
|
|
|
description = Column(String(length=1024))
|
|
|
|
slug = Column(String(length=128))
|
2018-09-09 14:10:42 -07:00
|
|
|
|
2018-09-15 16:48:47 -07:00
|
|
|
status = Column(Enum(PhotoStatus), default=PhotoStatus.private)
|
|
|
|
|
2019-07-04 16:57:45 -07:00
|
|
|
def to_json(self, files=True):
|
2019-06-18 18:38:01 -07:00
|
|
|
s = {attr: getattr(self, attr) for attr in {"uuid", "title", "description"}}
|
2019-06-25 21:34:32 -07:00
|
|
|
s["lat"] = str(self.lat) if self.lat else None
|
|
|
|
s["lon"] = str(self.lon) if self.lon else None
|
2019-06-18 18:38:01 -07:00
|
|
|
s["date"] = self.date.isoformat()
|
2019-07-04 16:57:45 -07:00
|
|
|
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
|
2019-06-18 18:38:01 -07:00
|
|
|
return s
|
|
|
|
|
2018-09-08 15:49:16 -07:00
|
|
|
|
|
|
|
class Photo(Base):
|
|
|
|
__tablename__ = 'files'
|
|
|
|
|
|
|
|
id = Column(Integer, primary_key=True)
|
|
|
|
set_id = Column(Integer, ForeignKey("photos.id"))
|
2019-07-06 11:25:15 -07:00
|
|
|
uuid = Column(Unicode(length=36), unique=True, default=genuuid)
|
2018-09-08 15:49:16 -07:00
|
|
|
|
|
|
|
set = relationship("PhotoSet", back_populates="files", foreign_keys=[set_id])
|
|
|
|
|
2018-09-09 12:05:13 -07:00
|
|
|
size = Column(Integer)
|
|
|
|
width = Column(Integer)
|
|
|
|
height = Column(Integer)
|
2018-09-09 13:45:26 -07:00
|
|
|
orientation = Column(Integer, default=0)
|
2018-09-08 15:49:16 -07:00
|
|
|
hash = Column(String(length=64), unique=True)
|
2019-07-06 11:25:15 -07:00
|
|
|
path = Column(Unicode(length=64))
|
|
|
|
format = Column(String(length=32)) # TODO how long can a mime string be
|
2019-07-04 14:25:19 -07:00
|
|
|
fname = Column(String(length=64)) # seems generous enough
|
2018-09-09 14:10:42 -07:00
|
|
|
|
2019-06-18 18:38:01 -07:00
|
|
|
def to_json(self):
|
|
|
|
j = {attr: getattr(self, attr) for attr in
|
2019-07-04 14:25:19 -07:00
|
|
|
{"uuid", "size", "width", "height", "orientation", "format", "hash", "fname"}}
|
2019-07-02 23:03:28 -07:00
|
|
|
j["set"] = self.set.uuid if self.set else None
|
2019-06-18 18:38:01 -07:00
|
|
|
return j
|
|
|
|
|
2018-09-09 14:10:42 -07:00
|
|
|
|
|
|
|
class Tag(Base):
|
|
|
|
__tablename__ = 'tags'
|
|
|
|
|
|
|
|
id = Column(Integer, primary_key=True)
|
2019-07-06 11:25:15 -07:00
|
|
|
uuid = Column(Unicode(length=36), unique=True, default=lambda: str(uuid.uuid4()))
|
2018-09-09 16:47:21 -07:00
|
|
|
created = Column(DateTime, default=lambda: datetime.now())
|
|
|
|
modified = Column(DateTime, default=lambda: datetime.now())
|
2018-09-23 13:04:50 -07:00
|
|
|
is_album = Column(Boolean, default=False)
|
2018-09-23 15:00:54 -07:00
|
|
|
# slug-like short name such as "iomtrip"
|
2019-07-06 11:25:15 -07:00
|
|
|
name = Column(String(length=32), unique=True)
|
2018-09-23 15:00:54 -07:00
|
|
|
# longer human-format title like "Isle of Man trip"
|
2019-07-06 11:25:15 -07:00
|
|
|
title = Column(String(length=32))
|
2018-09-23 15:00:54 -07:00
|
|
|
# url slug like "isle-of-man-trip"
|
2019-07-06 11:25:15 -07:00
|
|
|
slug = Column(String(length=32), unique=True)
|
2018-09-23 15:00:54 -07:00
|
|
|
# fulltext description
|
2019-07-06 11:25:15 -07:00
|
|
|
description = Column(String(length=256))
|
2018-09-09 14:10:42 -07:00
|
|
|
|
|
|
|
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"))
|
2018-09-09 16:47:21 -07:00
|
|
|
set_id = Column(Integer, ForeignKey("photos.id"))
|
|
|
|
order = Column(Integer, default=0)
|
2018-09-09 14:10:42 -07:00
|
|
|
|
|
|
|
tag = relationship("Tag", back_populates="entries", foreign_keys=[tag_id])
|
2018-09-09 16:47:21 -07:00
|
|
|
set = relationship("PhotoSet", back_populates="tags", foreign_keys=[set_id])
|
2018-09-09 14:10:42 -07:00
|
|
|
|
2018-09-09 16:47:21 -07:00
|
|
|
UniqueConstraint(tag_id, set_id)
|
2018-09-23 16:39:46 -07:00
|
|
|
|
|
|
|
|
|
|
|
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)
|
2019-06-22 16:36:06 -07:00
|
|
|
|
|
|
|
def to_json(self):
|
|
|
|
return dict(name=self.name, status=self.status.name)
|