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