From 2edaec693317589f4ded56b3a7faaeb489530de7 Mon Sep 17 00:00:00 2001 From: Viet Than Date: Thu, 20 Jul 2023 23:48:19 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=8F=A6=20Database=20Change:=20convert=20c?= =?UTF-8?q?ommentflag's=20created=5Futc=20to=20created=5Ftimestampz=20(#61?= =?UTF-8?q?5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- files/classes/base.py | 83 ++++++++++++++++++- files/classes/comment.py | 2 +- files/classes/flags.py | 6 +- ...d7_change_created_utc_to_datetimez_for_.py | 53 ++++++++++++ 4 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 migrations/versions/2023_07_09_21_52_50_7ae4658467d7_change_created_utc_to_datetimez_for_.py diff --git a/files/classes/base.py b/files/classes/base.py index f83d9f1ff..edf4a7375 100644 --- a/files/classes/base.py +++ b/files/classes/base.py @@ -1,9 +1,11 @@ import time from datetime import datetime, timedelta, timezone +from sqlalchemy import text from sqlalchemy.orm import declarative_base, declared_attr from sqlalchemy.schema import Column -from sqlalchemy.sql.sqltypes import Integer +from sqlalchemy.sql.functions import now +from sqlalchemy.sql.sqltypes import Integer, DateTime from files.helpers.time import format_age, format_datetime @@ -45,3 +47,82 @@ class CreatedBase(Base): @property def age_string(self) -> str: return format_age(self.created_utc) + + +class CreatedDateTimeBase(Base): + """ + An abstract class extending our default SQLAlchemy's `Base`. + + All classes inherit from this class automatically maps a `created_datetimez` column + for the corresponding SQL table. This column will automatically record the created + timestamp of rows. Retrieving `created_datetimez` will return a `datetime` object with + `tzinfo` of UTC. + + This class holds various convenience properties to get `created_datetimez` in different + formats. + """ + __abstract__ = True + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @declared_attr + def created_datetimez(self): + """ + Declare default column for classes/tables inheriting `CreatedDateTimeBase`. + + Retrieving `created_datetimez` will return a `datetime` object with `tzinfo` for UTC. + """ + return Column(DateTime(timezone=True), nullable=False, server_default=now()) + + @property + def created_utc(self): + """ + the created date in UTC seconds. Milliseconds are truncated/rounded down. + """ + return int(self.created_datetimez.timestamp()) + + @property + def created_date(self) -> str: + """ + the created date in string. + See `file.helpers.time.DATETIME_FORMAT` for the exact format + Note: should this be using `format_date` and not `format_datetime`? + """ + return self.created_datetime + + @property + def created_datetime(self) -> str: + """ + the created datetime in string. + See `file.helpers.time.DATETIME_FORMAT` for the exact format. + """ + return format_datetime(self.created_datetimez) + + @property + def created_datetime_py(self) -> datetime: + """ + the created datetime as a `datetime` object with `tzinfo` of UTC. + """ + return self.created_datetimez + + @property + def age_seconds(self) -> int: + """ + number of seconds since created. + """ + return time.time() - self.created_utc + + @property + def age_timedelta(self) -> timedelta: + """ + a `timedelta` object representing time since created. + """ + return datetime.now(tz=timezone.utc) - self.created_datetimez + + @property + def age_string(self) -> str: + """ + a string representing time since created. Example: "1h ago", "2d ago". + """ + return format_age(self.created_datetimez) diff --git a/files/classes/comment.py b/files/classes/comment.py index 8d0d7494d..3e6e88082 100644 --- a/files/classes/comment.py +++ b/files/classes/comment.py @@ -71,7 +71,7 @@ class Comment(CreatedBase): viewonly=True) reports = relationship("CommentFlag", primaryjoin="CommentFlag.comment_id == Comment.id", - order_by="CommentFlag.created_utc", + order_by="CommentFlag.created_datetimez", viewonly=True) notes = relationship("UserNote", back_populates="comment") diff --git a/files/classes/flags.py b/files/classes/flags.py index 8661fd0ec..3f74c720a 100644 --- a/files/classes/flags.py +++ b/files/classes/flags.py @@ -1,9 +1,11 @@ from sqlalchemy import * from sqlalchemy.orm import relationship -from files.classes.base import CreatedBase +from files.classes.base import CreatedBase, CreatedDateTimeBase, Base from files.helpers.lazy import lazy from files.helpers.config.const import * + + class Flag(CreatedBase): __tablename__ = "flags" @@ -24,7 +26,7 @@ class Flag(CreatedBase): return self.reason -class CommentFlag(CreatedBase): +class CommentFlag(CreatedDateTimeBase): __tablename__ = "commentflags" id = Column(Integer, primary_key=True) diff --git a/migrations/versions/2023_07_09_21_52_50_7ae4658467d7_change_created_utc_to_datetimez_for_.py b/migrations/versions/2023_07_09_21_52_50_7ae4658467d7_change_created_utc_to_datetimez_for_.py new file mode 100644 index 000000000..14c2c4bed --- /dev/null +++ b/migrations/versions/2023_07_09_21_52_50_7ae4658467d7_change_created_utc_to_datetimez_for_.py @@ -0,0 +1,53 @@ +"""Change created_utc to datetimez for commentflags + +Revision ID: 7ae4658467d7 +Revises: ea282d7c711c +Create Date: 2023-07-09 21:52:50.386177+00:00 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.sql.functions import now + + +# revision identifiers, used by Alembic. +revision = '7ae4658467d7' +down_revision = 'ea282d7c711c' +branch_labels = None +depends_on = None + +table_name = 'commentflags' +from_column = 'created_utc' +to_column = 'created_datetimez' + + +def upgrade(): + op.add_column(table_name, sa.Column(to_column, sa.DateTime(timezone=True), nullable=True, server_default=now())) + op.execute(f""" + UPDATE {table_name} + SET {to_column} = + CASE + WHEN {from_column} > 0 THEN + (timestamp 'epoch' + {from_column} * interval '1 second') at time zone 'utc' + ELSE NULL + END + """) + op.alter_column(table_name, to_column, nullable=False) + op.drop_column(table_name, from_column) + + +def downgrade(): + """ + Downgrade will truncate the milliseconds. + """ + op.add_column(table_name, sa.Column('created_utc', sa.Integer(), server_default=sa.text('0'), nullable=True)) + op.execute(f""" + UPDATE {table_name} + SET created_utc = + COALESCE( + EXTRACT(EPOCH FROM {to_column})::integer, + 0 + ) + """) + op.alter_column(table_name, from_column, nullable=False) + op.drop_column(table_name, to_column)