Use datetime objects instead of timestamps in object model.

Trying to make better use of objects in this object oriented language.
master
voussoir 2022-07-19 20:03:43 -07:00
parent cb43b5d9e0
commit 4001f6f371
No known key found for this signature in database
GPG Key ID: 5F7554F8C26DACCB
9 changed files with 54 additions and 35 deletions

View File

@ -304,13 +304,11 @@ def is_xor(*args) -> bool:
''' '''
return [bool(a) for a in args].count(True) == 1 return [bool(a) for a in args].count(True) == 1
def now(timestamp=True): def now():
''' '''
Return the current UTC timestamp or datetime object. Return the current UTC datetime object.
''' '''
n = datetime.datetime.now(datetime.timezone.utc) n = datetime.datetime.now(datetime.timezone.utc)
if timestamp:
return n.timestamp()
return n return n
def parse_unit_string(s) -> typing.Union[int, float, None]: def parse_unit_string(s) -> typing.Union[int, float, None]:
@ -435,6 +433,9 @@ def split_easybake_string(ebstring) -> tuple[str, str, str]:
tagname = tagname.strip('.') tagname = tagname.strip('.')
return (tagname, synonym, rename_to) return (tagname, synonym, rename_to)
def timestamp_to_datetime(unix):
return datetime.datetime.utcfromtimestamp(unix).replace(tzinfo=datetime.timezone.utc)
def zip_album(album, recursive=True) -> zipstream.ZipFile: def zip_album(album, recursive=True) -> zipstream.ZipFile:
''' '''
Given an album, return a zipstream zipfile that contains the album's Given an album, return a zipstream zipfile that contains the album's

View File

@ -4,6 +4,7 @@ but are returned by the PDB accesses.
''' '''
import abc import abc
import bcrypt import bcrypt
import datetime
import hashlib import hashlib
import os import os
import PIL.Image import PIL.Image
@ -40,6 +41,8 @@ class ObjectBase(worms.Object):
self._photodb = photodb self._photodb = photodb
# To be lazily retrieved by @property author. # To be lazily retrieved by @property author.
self._author = None self._author = None
# To be lazily retrieved by @property created.
self._created_dt = None
@staticmethod @staticmethod
def normalize_author_id(author_id) -> typing.Optional[int]: def normalize_author_id(author_id) -> typing.Optional[int]:
@ -73,6 +76,13 @@ class ObjectBase(worms.Object):
self._author = user self._author = user
return user return user
@property
def created(self) -> datetime.datetime:
if self._created_dt is not None:
return self._created_dt
self._created_dt = helpers.timestamp_to_datetime(self.created_unix)
return self._created_dt
class GroupableMixin(metaclass=abc.ABCMeta): class GroupableMixin(metaclass=abc.ABCMeta):
group_getter_many = None group_getter_many = None
group_table = None group_table = None
@ -262,7 +272,7 @@ class Album(ObjectBase, GroupableMixin):
self.id = db_row['id'] self.id = db_row['id']
self.title = self.normalize_title(db_row['title']) self.title = self.normalize_title(db_row['title'])
self.description = self.normalize_description(db_row['description']) self.description = self.normalize_description(db_row['description'])
self.created = db_row['created'] self.created_unix = db_row['created']
# To be lazily retrieved by @property thumbnail_photo. # To be lazily retrieved by @property thumbnail_photo.
self._thumbnail_photo = db_row['thumbnail_photo'] self._thumbnail_photo = db_row['thumbnail_photo']
self._author_id = self.normalize_author_id(db_row['author_id']) self._author_id = self.normalize_author_id(db_row['author_id'])
@ -529,7 +539,7 @@ class Album(ObjectBase, GroupableMixin):
'id': self.id, 'id': self.id,
'description': self.description, 'description': self.description,
'title': self.title, 'title': self.title,
'created': self.created, 'created': self.created_unix,
'display_name': self.display_name, 'display_name': self.display_name,
'thumbnail_photo': self.thumbnail_photo.id if self._thumbnail_photo else None, 'thumbnail_photo': self.thumbnail_photo.id if self._thumbnail_photo else None,
'author': self.author.jsonify() if self._author_id else None, 'author': self.author.jsonify() if self._author_id else None,
@ -698,7 +708,7 @@ class Bookmark(ObjectBase):
self.id = db_row['id'] self.id = db_row['id']
self.title = self.normalize_title(db_row['title']) self.title = self.normalize_title(db_row['title'])
self.url = self.normalize_url(db_row['url']) self.url = self.normalize_url(db_row['url'])
self.created = db_row['created'] self.created_unix = db_row['created']
self._author_id = self.normalize_author_id(db_row['author_id']) self._author_id = self.normalize_author_id(db_row['author_id'])
def __repr__(self): def __repr__(self):
@ -786,7 +796,7 @@ class Bookmark(ObjectBase):
'id': self.id, 'id': self.id,
'author': self.author.jsonify() if self._author_id else None, 'author': self.author.jsonify() if self._author_id else None,
'url': self.url, 'url': self.url,
'created': self.created, 'created': self.created_unix,
'title': self.title, 'title': self.title,
'display_name': self.display_name, 'display_name': self.display_name,
} }
@ -808,7 +818,7 @@ class Photo(ObjectBase):
self.real_path = pathclass.Path(self.real_path) self.real_path = pathclass.Path(self.real_path)
self.id = db_row['id'] self.id = db_row['id']
self.created = db_row['created'] self.created_unix = db_row['created']
self._author_id = self.normalize_author_id(db_row['author_id']) self._author_id = self.normalize_author_id(db_row['author_id'])
self.override_filename = db_row['override_filename'] self.override_filename = db_row['override_filename']
self.extension = self.real_path.extension.no_dot self.extension = self.real_path.extension.no_dot
@ -828,7 +838,8 @@ class Photo(ObjectBase):
self.ratio = db_row['ratio'] self.ratio = db_row['ratio']
self.thumbnail = self.normalize_thumbnail(db_row['thumbnail']) self.thumbnail = self.normalize_thumbnail(db_row['thumbnail'])
self.tagged_at = db_row['tagged_at'] self.tagged_at_unix = db_row['tagged_at']
self._tagged_at_dt = None
self.searchhidden = db_row['searchhidden'] self.searchhidden = db_row['searchhidden']
self._assign_mimetype() self._assign_mimetype()
@ -920,7 +931,7 @@ class Photo(ObjectBase):
self.photodb.insert(table='photo_tag_rel', data=data) self.photodb.insert(table='photo_tag_rel', data=data)
data = { data = {
'id': self.id, 'id': self.id,
'tagged_at': helpers.now(), 'tagged_at': helpers.now().timestamp(),
} }
self.photodb.update(table=Photo, pairs=data, where_key='id') self.photodb.update(table=Photo, pairs=data, where_key='id')
@ -1114,7 +1125,7 @@ class Photo(ObjectBase):
'duration': self.duration, 'duration': self.duration,
'bytes_string': self.bytes_string, 'bytes_string': self.bytes_string,
'has_thumbnail': bool(self.thumbnail), 'has_thumbnail': bool(self.thumbnail),
'created': self.created, 'created': self.created_unix,
'filename': self.basename, 'filename': self.basename,
'mimetype': self.mimetype, 'mimetype': self.mimetype,
'searchhidden': bool(self.searchhidden), 'searchhidden': bool(self.searchhidden),
@ -1245,7 +1256,7 @@ class Photo(ObjectBase):
} }
self.photodb.update(table=Photo, pairs=data, where_key='id') self.photodb.update(table=Photo, pairs=data, where_key='id')
self._uncache() # self._uncache()
@decorators.required_feature('photo.edit') @decorators.required_feature('photo.edit')
@worms.atomic @worms.atomic
@ -1296,7 +1307,7 @@ class Photo(ObjectBase):
data = { data = {
'id': self.id, 'id': self.id,
'tagged_at': helpers.now(), 'tagged_at': helpers.now().timestamp(),
} }
self.photodb.update(table=Photo, pairs=data, where_key='id') self.photodb.update(table=Photo, pairs=data, where_key='id')
@ -1315,7 +1326,7 @@ class Photo(ObjectBase):
data = { data = {
'id': self.id, 'id': self.id,
'tagged_at': helpers.now(), 'tagged_at': helpers.now().timestamp(),
} }
self.photodb.update(table=Photo, pairs=data, where_key='id') self.photodb.update(table=Photo, pairs=data, where_key='id')
@ -1430,6 +1441,13 @@ class Photo(ObjectBase):
self.photodb.update(table=Photo, pairs=data, where_key='id') self.photodb.update(table=Photo, pairs=data, where_key='id')
self.searchhidden = searchhidden self.searchhidden = searchhidden
@property
def tagged_at(self) -> datetime.datetime:
if self._tagged_at_dt is not None:
return self._tagged_at_dt
self._tagged_at_dt = helpers.timestamp_to_datetime(self.tagged_at_unix)
return self._tagged_at_dt
class Tag(ObjectBase, GroupableMixin): class Tag(ObjectBase, GroupableMixin):
''' '''
A Tag, which can be applied to Photos for organization. A Tag, which can be applied to Photos for organization.
@ -1446,7 +1464,7 @@ class Tag(ObjectBase, GroupableMixin):
# from previous character / length rules. # from previous character / length rules.
self.name = db_row['name'] self.name = db_row['name']
self.description = self.normalize_description(db_row['description']) self.description = self.normalize_description(db_row['description'])
self.created = db_row['created'] self.created_unix = db_row['created']
self._author_id = self.normalize_author_id(db_row['author_id']) self._author_id = self.normalize_author_id(db_row['author_id'])
self.group_getter_many = self.photodb.get_tags_by_id self.group_getter_many = self.photodb.get_tags_by_id
@ -1700,7 +1718,7 @@ class Tag(ObjectBase, GroupableMixin):
'type': 'tag', 'type': 'tag',
'id': self.id, 'id': self.id,
'name': self.name, 'name': self.name,
'created': self.created, 'created': self.created_unix,
} }
if not minimal: if not minimal:
j['author'] = self.author.jsonify() if self._author_id else None j['author'] = self.author.jsonify() if self._author_id else None
@ -1818,7 +1836,7 @@ class User(ObjectBase):
self.id = db_row['id'] self.id = db_row['id']
self.username = db_row['username'] self.username = db_row['username']
self.created = db_row['created'] self.created_unix = db_row['created']
self.password_hash = db_row['password'] self.password_hash = db_row['password']
# Do not enforce maxlen here, they may be grandfathered in. # Do not enforce maxlen here, they may be grandfathered in.
self._display_name = self.normalize_display_name(db_row['display_name']) self._display_name = self.normalize_display_name(db_row['display_name'])
@ -1968,7 +1986,7 @@ class User(ObjectBase):
'type': 'user', 'type': 'user',
'id': self.id, 'id': self.id,
'username': self.username, 'username': self.username,
'created': self.created, 'created': self.created_unix,
'display_name': self.display_name, 'display_name': self.display_name,
} }
return j return j

View File

@ -113,7 +113,7 @@ class PDBAlbumMixin:
'id': album_id, 'id': album_id,
'title': title, 'title': title,
'description': description, 'description': description,
'created': helpers.now(), 'created': helpers.now().timestamp(),
'thumbnail_photo': None, 'thumbnail_photo': None,
'author_id': author_id, 'author_id': author_id,
} }
@ -205,7 +205,7 @@ class PDBBookmarkMixin:
'id': bookmark_id, 'id': bookmark_id,
'title': title, 'title': title,
'url': url, 'url': url,
'created': helpers.now(), 'created': helpers.now().timestamp(),
'author_id': author_id, 'author_id': author_id,
} }
self.insert(table=objects.Bookmark, data=data) self.insert(table=objects.Bookmark, data=data)
@ -360,7 +360,7 @@ class PDBPhotoMixin:
'basename': filepath.basename, 'basename': filepath.basename,
'override_filename': None, 'override_filename': None,
'extension': filepath.extension.no_dot, 'extension': filepath.extension.no_dot,
'created': helpers.now(), 'created': helpers.now().timestamp(),
'tagged_at': None, 'tagged_at': None,
'author_id': author_id, 'author_id': author_id,
'searchhidden': searchhidden, 'searchhidden': searchhidden,
@ -980,7 +980,7 @@ class PDBTagMixin:
'id': tag_id, 'id': tag_id,
'name': tagname, 'name': tagname,
'description': description, 'description': description,
'created': helpers.now(), 'created': helpers.now().timestamp(),
'author_id': author_id, 'author_id': author_id,
} }
self.insert(table=objects.Tag, data=data) self.insert(table=objects.Tag, data=data)
@ -1150,7 +1150,7 @@ class PDBUserMixin:
'username': username, 'username': username,
'password': hashed_password, 'password': hashed_password,
'display_name': display_name, 'display_name': display_name,
'created': helpers.now(), 'created': helpers.now().timestamp(),
} }
self.insert(table=objects.User, data=data) self.insert(table=objects.User, data=data)

View File

@ -34,7 +34,7 @@ def get_dbdump():
with common.P.transaction: with common.P.transaction:
binary = common.P.database_filepath.read('rb') binary = common.P.database_filepath.read('rb')
now = etiquette.helpers.now(timestamp=False).strftime('%Y-%m-%d_%H-%M-%S') now = etiquette.helpers.now().strftime('%Y-%m-%d_%H-%M-%S')
download_as = f'etiquette {now}.db' download_as = f'etiquette {now}.db'
outgoing_headers = { outgoing_headers = {
'Content-Type': 'application/octet-stream', 'Content-Type': 'application/octet-stream',

View File

@ -58,16 +58,15 @@ def join_and_trail(l, s):
@filter_function @filter_function
def timestamp_to_8601(timestamp): def timestamp_to_8601(timestamp):
return datetime.datetime.utcfromtimestamp(timestamp).isoformat(' ') + ' UTC' return timestamp.isoformat(' ') + ' UTC'
@filter_function @filter_function
def timestamp_to_string(timestamp, format): def timestamp_strftime(timestamp, format):
date = datetime.datetime.utcfromtimestamp(timestamp) return timestamp.strftime(format)
return date.strftime(format)
@filter_function @filter_function
def timestamp_to_naturaldate(timestamp): def timestamp_to_naturaldate(timestamp):
return timestamp_to_string(timestamp, '%B %d, %Y') return timestamp_strftime(timestamp, '%B %d, %Y')
@filter_function @filter_function
def users_to_usernames(users): def users_to_usernames(users):

View File

@ -118,7 +118,7 @@ class Session:
def expired(self): def expired(self):
now = etiquette.helpers.now() now = etiquette.helpers.now()
age = now - self.last_activity age = now - self.last_activity
return age > SESSION_MAX_AGE return age.seconds > SESSION_MAX_AGE
def maintain(self): def maintain(self):
self.last_activity = etiquette.helpers.now() self.last_activity = etiquette.helpers.now()

View File

@ -247,7 +247,7 @@ const ALBUM_ID = undefined;
<p>Author: <a href="/userid/{{author.id}}">{{author.display_name}}</a></p> <p>Author: <a href="/userid/{{author.id}}">{{author.display_name}}</a></p>
{% endif %} {% endif %}
<p>Created on <span title="{{album.created|int|timestamp_to_8601}}">{{album.created|timestamp_to_naturaldate}}.</span></p> <p>Created on <span title="{{album.created|timestamp_to_8601}}">{{album.created|timestamp_to_naturaldate}}.</span></p>
<button class="green_button editor_toolbox_placeholder">Edit</button> <button class="green_button editor_toolbox_placeholder">Edit</button>
</div> <!-- #album_metadata --> </div> <!-- #album_metadata -->
<div id="album_thumbnail"> <div id="album_thumbnail">

View File

@ -190,6 +190,7 @@
<li>Duration: {{photo.duration_string}}</li> <li>Duration: {{photo.duration_string}}</li>
<li>Overall Bitrate: {{photo.bitrate|int}} kbps</li> <li>Overall Bitrate: {{photo.bitrate|int}} kbps</li>
{% endif %} {% endif %}
<li>Created {{photo.created|timestamp_to_naturaldate}}</li>
<li><button id="refresh_metadata_button" class="green_button button_with_spinner" onclick="return refresh_metadata_form();">Refresh metadata</button></li> <li><button id="refresh_metadata_button" class="green_button button_with_spinner" onclick="return refresh_metadata_form();">Refresh metadata</button></li>
{% if request.is_localhost %} {% if request.is_localhost %}
<li><button id="show_in_folder_button" onclick="return show_in_folder_form();">Show in folder</button></li> <li><button id="show_in_folder_button" onclick="return show_in_folder_form();">Show in folder</button></li>
@ -223,9 +224,9 @@
<!-- BEFORE & AFTER SEARCH LINKS --> <!-- BEFORE & AFTER SEARCH LINKS -->
<div id="before_after_links"> <div id="before_after_links">
<a href="/search?created=-{{photo.created}}">&larr;Before</a> <a href="/search?created=-{{photo.created_unix}}">&larr;Before</a>
<span> | </span> <span> | </span>
<a href="/search?created={{photo.created}}-&orderby=created-asc">After&rarr;</a> <a href="/search?created={{photo.created_unix}}-&orderby=created-asc">After&rarr;</a>
</div> </div>
</div> </div>

View File

@ -31,7 +31,7 @@
<div id="hierarchy_self" class="panel"> <div id="hierarchy_self" class="panel">
<h1 id="display_name">{{user.display_name}}</h1> <h1 id="display_name">{{user.display_name}}</h1>
<p>ID: <a href="/userid/{{user.id}}">{{user.id}}</a></p> <p>ID: <a href="/userid/{{user.id}}">{{user.id}}</a></p>
<p>User since <span title="{{user.created|int|timestamp_to_8601}}">{{user.created|timestamp_to_naturaldate}}.</span></p> <p>User since <span title="{{user.created|timestamp_to_8601}}">{{user.created|timestamp_to_naturaldate}}.</span></p>
</div> </div>
{% set photos = user.get_photos(direction='desc')|islice(0, 15)|list %} {% set photos = user.get_photos(direction='desc')|islice(0, 15)|list %}