Use datetime objects instead of timestamps in object model.
Trying to make better use of objects in this object oriented language.
This commit is contained in:
parent
cb43b5d9e0
commit
4001f6f371
9 changed files with 54 additions and 35 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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}}">←Before</a>
|
<a href="/search?created=-{{photo.created_unix}}">←Before</a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a href="/search?created={{photo.created}}-&orderby=created-asc">After→</a>
|
<a href="/search?created={{photo.created_unix}}-&orderby=created-asc">After→</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
Loading…
Reference in a new issue