From a7f3d652c51ecd653900a530b7d38feaf603ecf1 Mon Sep 17 00:00:00 2001 From: Fringg Date: Wed, 18 Feb 2026 11:11:58 +0300 Subject: [PATCH] fix: use AwareDateTime TypeDecorator for all datetime columns TypeDecorator with process_result_value guarantees naive datetimes from pre-TIMESTAMPTZ databases are converted to UTC-aware on every load. Replaces unreliable event listener approach. All 175 DateTime columns now use AwareDateTime. --- app/database/models.py | 388 ++++++++++++++++++++--------------------- 1 file changed, 191 insertions(+), 197 deletions(-) diff --git a/app/database/models.py b/app/database/models.py index f3d30188..730c902f 100644 --- a/app/database/models.py +++ b/app/database/models.py @@ -25,39 +25,33 @@ from sqlalchemy import ( Table, Text, Time, + TypeDecorator, UniqueConstraint, - event, - inspect as sa_inspect, ) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import Mapped, backref, mapped_column, relationship from sqlalchemy.sql import func -Base = declarative_base() - -# Cache datetime attribute names per class to avoid repeated mapper inspection -_datetime_attrs_cache: dict[type, list[str]] = {} - - -@event.listens_for(Base, 'load', propagate=True) -def _ensure_aware_datetimes(target, context): - """Auto-convert naive datetimes to UTC-aware on load from DB. +class AwareDateTime(TypeDecorator): + """DateTime that auto-converts naive values to UTC-aware on load from DB. Handles pre-TIMESTAMPTZ databases that return naive datetimes. - Modifies __dict__ directly to avoid triggering SA dirty tracking. """ - cls = type(target) - if cls not in _datetime_attrs_cache: - mapper = sa_inspect(cls) - _datetime_attrs_cache[cls] = [ - attr.key for attr in mapper.column_attrs if any(isinstance(col.type, DateTime) for col in attr.columns) - ] - for key in _datetime_attrs_cache[cls]: - val = target.__dict__.get(key) - if val is not None and isinstance(val, datetime) and val.tzinfo is None: - target.__dict__[key] = val.replace(tzinfo=UTC) + impl = DateTime + cache_ok = True + + def __init__(self): + super().__init__(timezone=True) + + def process_result_value(self, value, dialect): + if value is not None and isinstance(value, datetime) and value.tzinfo is None: + return value.replace(tzinfo=UTC) + return value + + +Base = declarative_base() server_squad_promo_groups = Table( @@ -210,10 +204,10 @@ class YooKassaPayment(Base): payment_method_type = Column(String(50), nullable=True) refundable = Column(Boolean, default=False) test_mode = Column(Boolean, default=False) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) - yookassa_created_at = Column(DateTime(timezone=True), nullable=True) - captured_at = Column(DateTime(timezone=True), nullable=True) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) + yookassa_created_at = Column(AwareDateTime(), nullable=True) + captured_at = Column(AwareDateTime(), nullable=True) user = relationship('User', backref='yookassa_payments') transaction = relationship('Transaction', backref='yookassa_payment') @@ -259,11 +253,11 @@ class CryptoBotPayment(Base): mini_app_invoice_url = Column(Text, nullable=True) web_app_invoice_url = Column(Text, nullable=True) - paid_at = Column(DateTime(timezone=True), nullable=True) + paid_at = Column(AwareDateTime(), nullable=True) transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) user = relationship('User', backref='cryptobot_payments') transaction = relationship('Transaction', backref='cryptobot_payment') @@ -311,12 +305,12 @@ class HeleketPayment(Base): payment_url = Column(Text, nullable=True) metadata_json = Column(JSON, nullable=True) - paid_at = Column(DateTime(timezone=True), nullable=True) - expires_at = Column(DateTime(timezone=True), nullable=True) + paid_at = Column(AwareDateTime(), nullable=True) + expires_at = Column(AwareDateTime(), nullable=True) transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) user = relationship('User', backref='heleket_payments') transaction = relationship('Transaction', backref='heleket_payment') @@ -364,7 +358,7 @@ class MulenPayPayment(Base): status = Column(String(50), nullable=False, default='created') is_paid = Column(Boolean, default=False) - paid_at = Column(DateTime(timezone=True), nullable=True) + paid_at = Column(AwareDateTime(), nullable=True) payment_url = Column(Text, nullable=True) metadata_json = Column(JSON, nullable=True) @@ -372,8 +366,8 @@ class MulenPayPayment(Base): transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) user = relationship('User', backref='mulenpay_payments') transaction = relationship('Transaction', backref='mulenpay_payment') @@ -402,9 +396,9 @@ class Pal24Payment(Base): status = Column(String(50), nullable=False, default='NEW') is_active = Column(Boolean, default=True) is_paid = Column(Boolean, default=False) - paid_at = Column(DateTime(timezone=True), nullable=True) + paid_at = Column(AwareDateTime(), nullable=True) last_status = Column(String(50), nullable=True) - last_status_checked_at = Column(DateTime(timezone=True), nullable=True) + last_status_checked_at = Column(AwareDateTime(), nullable=True) link_url = Column(Text, nullable=True) link_page_url = Column(Text, nullable=True) @@ -419,12 +413,12 @@ class Pal24Payment(Base): payer_account = Column(String(255), nullable=True) ttl = Column(Integer, nullable=True) - expires_at = Column(DateTime(timezone=True), nullable=True) + expires_at = Column(AwareDateTime(), nullable=True) transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) user = relationship('User', backref='pal24_payments') transaction = relationship('Transaction', backref='pal24_payment') @@ -458,7 +452,7 @@ class WataPayment(Base): status = Column(String(50), nullable=False, default='Opened') is_paid = Column(Boolean, default=False) - paid_at = Column(DateTime(timezone=True), nullable=True) + paid_at = Column(AwareDateTime(), nullable=True) last_status = Column(String(50), nullable=True) terminal_public_id = Column(String(64), nullable=True) @@ -468,12 +462,12 @@ class WataPayment(Base): metadata_json = Column(JSON, nullable=True) callback_payload = Column(JSON, nullable=True) - expires_at = Column(DateTime(timezone=True), nullable=True) + expires_at = Column(AwareDateTime(), nullable=True) transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) user = relationship('User', backref='wata_payments') transaction = relationship('Transaction', backref='wata_payment') @@ -501,7 +495,7 @@ class PlategaPayment(Base): payment_method_code = Column(Integer, nullable=False) status = Column(String(50), nullable=False, default='PENDING') is_paid = Column(Boolean, default=False) - paid_at = Column(DateTime(timezone=True), nullable=True) + paid_at = Column(AwareDateTime(), nullable=True) redirect_url = Column(Text, nullable=True) return_url = Column(Text, nullable=True) @@ -510,12 +504,12 @@ class PlategaPayment(Base): metadata_json = Column(JSON, nullable=True) callback_payload = Column(JSON, nullable=True) - expires_at = Column(DateTime(timezone=True), nullable=True) + expires_at = Column(AwareDateTime(), nullable=True) transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) user = relationship('User', backref='platega_payments') transaction = relationship('Transaction', backref='platega_payment') @@ -544,7 +538,7 @@ class CloudPaymentsPayment(Base): status = Column(String(50), nullable=False, default='pending') # pending, completed, failed, authorized is_paid = Column(Boolean, default=False) - paid_at = Column(DateTime(timezone=True), nullable=True) + paid_at = Column(AwareDateTime(), nullable=True) # Данные карты (маскированные) card_first_six = Column(String(6), nullable=True) @@ -571,8 +565,8 @@ class CloudPaymentsPayment(Base): # Связь с транзакцией в нашей системе transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) user = relationship('User', backref='cloudpayments_payments') transaction = relationship('Transaction', backref='cloudpayments_payment') @@ -625,10 +619,10 @@ class FreekassaPayment(Base): callback_payload = Column(JSON, nullable=True) # Временные метки - paid_at = Column(DateTime(timezone=True), nullable=True) - expires_at = Column(DateTime(timezone=True), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + paid_at = Column(AwareDateTime(), nullable=True) + expires_at = Column(AwareDateTime(), nullable=True) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) # Связь с транзакцией transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True) @@ -687,10 +681,10 @@ class KassaAiPayment(Base): callback_payload = Column(JSON, nullable=True) # Временные метки - paid_at = Column(DateTime(timezone=True), nullable=True) - expires_at = Column(DateTime(timezone=True), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + paid_at = Column(AwareDateTime(), nullable=True) + expires_at = Column(AwareDateTime(), nullable=True) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) # Связь с транзакцией transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True) @@ -732,8 +726,8 @@ class PromoGroup(Base): auto_assign_total_spent_kopeks = Column(Integer, nullable=True, default=None) apply_discounts_to_addons = Column(Boolean, nullable=False, default=True) is_default = Column(Boolean, nullable=False, default=False) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) users = relationship('User', back_populates='promo_group') user_promo_groups = relationship('UserPromoGroup', back_populates='promo_group', cascade='all, delete-orphan') @@ -811,7 +805,7 @@ class UserPromoGroup(Base): user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), primary_key=True) promo_group_id = Column(Integer, ForeignKey('promo_groups.id', ondelete='CASCADE'), primary_key=True) - assigned_at = Column(DateTime(timezone=True), default=func.now()) + assigned_at = Column(AwareDateTime(), default=func.now()) assigned_by = Column(String(50), default='system') user = relationship('User', back_populates='user_promo_groups') @@ -885,8 +879,8 @@ class Tariff(Base): # Режим сброса трафика: DAY, WEEK, MONTH, NO_RESET (по умолчанию берётся из конфига) traffic_reset_mode = Column(String(20), nullable=True, default=None) # None = использовать глобальную настройку - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) # M2M связь с промогруппами (какие промогруппы имеют доступ к тарифу) allowed_promo_groups = relationship( @@ -1019,25 +1013,25 @@ class User(Base): has_had_paid_subscription = Column(Boolean, default=False, nullable=False) referred_by_id = Column(Integer, ForeignKey('users.id'), nullable=True, index=True) referral_code = Column(String(20), unique=True, nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) - last_activity = Column(DateTime(timezone=True), default=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) + last_activity = Column(AwareDateTime(), default=func.now()) remnawave_uuid = Column(String(255), nullable=True, unique=True) # Cabinet authentication fields email = Column(String(255), unique=True, nullable=True, index=True) email_verified = Column(Boolean, default=False, nullable=False) - email_verified_at = Column(DateTime(timezone=True), nullable=True) + email_verified_at = Column(AwareDateTime(), nullable=True) password_hash = Column(String(255), nullable=True) email_verification_token = Column(String(255), nullable=True) - email_verification_expires = Column(DateTime(timezone=True), nullable=True) + email_verification_expires = Column(AwareDateTime(), nullable=True) password_reset_token = Column(String(255), nullable=True) - password_reset_expires = Column(DateTime(timezone=True), nullable=True) - cabinet_last_login = Column(DateTime(timezone=True), nullable=True) + password_reset_expires = Column(AwareDateTime(), nullable=True) + cabinet_last_login = Column(AwareDateTime(), nullable=True) # Email change fields email_change_new = Column(String(255), nullable=True) # New email pending verification email_change_code = Column(String(6), nullable=True) # 6-digit verification code - email_change_expires = Column(DateTime(timezone=True), nullable=True) # Code expiration + email_change_expires = Column(AwareDateTime(), nullable=True) # Code expiration # OAuth provider IDs google_id = Column(String(255), unique=True, nullable=True, index=True) yandex_id = Column(String(255), unique=True, nullable=True, index=True) @@ -1056,8 +1050,8 @@ class User(Base): referral_commission_percent = Column(Integer, nullable=True) promo_offer_discount_percent = Column(Integer, nullable=False, default=0) promo_offer_discount_source = Column(String(100), nullable=True) - promo_offer_discount_expires_at = Column(DateTime(timezone=True), nullable=True) - last_remnawave_sync = Column(DateTime(timezone=True), nullable=True) + promo_offer_discount_expires_at = Column(AwareDateTime(), nullable=True) + last_remnawave_sync = Column(AwareDateTime(), nullable=True) trojan_password = Column(String(255), nullable=True) vless_uuid = Column(String(255), nullable=True) ss_password = Column(String(255), nullable=True) @@ -1164,14 +1158,14 @@ class Subscription(Base): status = Column(String(20), default=SubscriptionStatus.TRIAL.value) is_trial = Column(Boolean, default=True) - start_date = Column(DateTime(timezone=True), default=func.now()) - end_date = Column(DateTime(timezone=True), nullable=False) + start_date = Column(AwareDateTime(), default=func.now()) + end_date = Column(AwareDateTime(), nullable=False) traffic_limit_gb = Column(Integer, default=0) traffic_used_gb = Column(Float, default=0.0) purchased_traffic_gb = Column(Integer, default=0) # Докупленный трафик traffic_reset_at = Column( - DateTime(timezone=True), nullable=True + AwareDateTime(), nullable=True ) # Дата сброса докупленного трафика (30 дней после первой докупки) subscription_url = Column(String, nullable=True) @@ -1185,10 +1179,10 @@ class Subscription(Base): autopay_enabled = Column(Boolean, default=False) autopay_days_before = Column(Integer, default=3) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) - last_webhook_update_at = Column(DateTime(timezone=True), nullable=True) + last_webhook_update_at = Column(AwareDateTime(), nullable=True) remnawave_short_uuid = Column(String(255), nullable=True) @@ -1199,7 +1193,7 @@ class Subscription(Base): is_daily_paused = Column( Boolean, default=False, nullable=False ) # Приостановлена ли суточная подписка пользователем - last_daily_charge_at = Column(DateTime(timezone=True), nullable=True) # Время последнего суточного списания + last_daily_charge_at = Column(AwareDateTime(), nullable=True) # Время последнего суточного списания user = relationship('User', back_populates='subscription') tariff = relationship('Tariff', back_populates='subscriptions') @@ -1372,9 +1366,9 @@ class TrafficPurchase(Base): subscription_id = Column(Integer, ForeignKey('subscriptions.id', ondelete='CASCADE'), nullable=False, index=True) traffic_gb = Column(Integer, nullable=False) # Количество ГБ в покупке - expires_at = Column(DateTime(timezone=True), nullable=False, index=True) # Дата истечения (покупка + 30 дней) + expires_at = Column(AwareDateTime(), nullable=False, index=True) # Дата истечения (покупка + 30 дней) - created_at = Column(DateTime(timezone=True), default=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) subscription = relationship('Subscription', back_populates='traffic_purchases') @@ -1401,10 +1395,10 @@ class Transaction(Base): # NaloGO чек receipt_uuid = Column(String(255), nullable=True, index=True) - receipt_created_at = Column(DateTime(timezone=True), nullable=True) + receipt_created_at = Column(AwareDateTime(), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - completed_at = Column(DateTime(timezone=True), nullable=True) + created_at = Column(AwareDateTime(), default=func.now()) + completed_at = Column(AwareDateTime(), nullable=True) user = relationship('User', back_populates='transactions') @@ -1419,7 +1413,7 @@ class SubscriptionConversion(Base): id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey('users.id'), nullable=False) - converted_at = Column(DateTime(timezone=True), default=func.now()) + converted_at = Column(AwareDateTime(), default=func.now()) trial_duration_days = Column(Integer, nullable=True) @@ -1429,7 +1423,7 @@ class SubscriptionConversion(Base): first_paid_period_days = Column(Integer, nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) user = relationship('User', backref='subscription_conversions') @@ -1455,8 +1449,8 @@ class PromoCode(Base): max_uses = Column(Integer, default=1) current_uses = Column(Integer, default=0) - valid_from = Column(DateTime(timezone=True), default=func.now()) - valid_until = Column(DateTime(timezone=True), nullable=True) + valid_from = Column(AwareDateTime(), default=func.now()) + valid_until = Column(AwareDateTime(), nullable=True) is_active = Column(Boolean, default=True) first_purchase_only = Column(Boolean, default=False) # Только для первой покупки @@ -1464,8 +1458,8 @@ class PromoCode(Base): created_by = Column(Integer, ForeignKey('users.id'), nullable=True) promo_group_id = Column(Integer, ForeignKey('promo_groups.id', ondelete='SET NULL'), nullable=True, index=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) uses = relationship('PromoCodeUse', back_populates='promocode') promo_group = relationship('PromoGroup') @@ -1492,7 +1486,7 @@ class PromoCodeUse(Base): promocode_id = Column(Integer, ForeignKey('promocodes.id'), nullable=False) user_id = Column(Integer, ForeignKey('users.id'), nullable=False) - used_at = Column(DateTime(timezone=True), default=func.now()) + used_at = Column(AwareDateTime(), default=func.now()) promocode = relationship('PromoCode', back_populates='uses') user = relationship('User') @@ -1513,7 +1507,7 @@ class ReferralEarning(Base): Integer, ForeignKey('advertising_campaigns.id', ondelete='SET NULL'), nullable=True, index=True ) - created_at = Column(DateTime(timezone=True), default=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) user = relationship('User', foreign_keys=[user_id], back_populates='referral_earnings') referral = relationship('User', foreign_keys=[referral_id]) @@ -1555,11 +1549,11 @@ class WithdrawalRequest(Base): # Обработка админом processed_by = Column(Integer, ForeignKey('users.id'), nullable=True) - processed_at = Column(DateTime(timezone=True), nullable=True) + processed_at = Column(AwareDateTime(), nullable=True) admin_comment = Column(Text, nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) user = relationship('User', foreign_keys=[user_id], backref='withdrawal_requests') admin = relationship('User', foreign_keys=[processed_by]) @@ -1589,10 +1583,10 @@ class PartnerApplication(Base): admin_comment = Column(Text, nullable=True) approved_commission_percent = Column(Integer, nullable=True) processed_by = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True) - processed_at = Column(DateTime(timezone=True), nullable=True) + processed_at = Column(AwareDateTime(), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) user = relationship('User', foreign_keys=[user_id], backref='partner_applications') admin = relationship('User', foreign_keys=[processed_by]) @@ -1606,18 +1600,18 @@ class ReferralContest(Base): description = Column(Text, nullable=True) prize_text = Column(Text, nullable=True) contest_type = Column(String(50), nullable=False, default='referral_paid') - start_at = Column(DateTime(timezone=True), nullable=False) - end_at = Column(DateTime(timezone=True), nullable=False) + start_at = Column(AwareDateTime(), nullable=False) + end_at = Column(AwareDateTime(), nullable=False) daily_summary_time = Column(Time, nullable=False, default=time(hour=12, minute=0)) daily_summary_times = Column(String(255), nullable=True) # CSV HH:MM timezone = Column(String(64), nullable=False, default='UTC') is_active = Column(Boolean, nullable=False, default=True) last_daily_summary_date = Column(Date, nullable=True) - last_daily_summary_at = Column(DateTime(timezone=True), nullable=True) + last_daily_summary_at = Column(AwareDateTime(), nullable=True) final_summary_sent = Column(Boolean, nullable=False, default=False) created_by = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) creator = relationship('User', backref='created_referral_contests') events = relationship( @@ -1647,7 +1641,7 @@ class ReferralContestEvent(Base): referral_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False) event_type = Column(String(50), nullable=False) amount_kopeks = Column(Integer, nullable=False, default=0) - occurred_at = Column(DateTime(timezone=True), nullable=False, default=func.now()) + occurred_at = Column(AwareDateTime(), nullable=False, default=func.now()) contest = relationship('ReferralContest', back_populates='events') referrer = relationship('User', foreign_keys=[referrer_id]) @@ -1667,7 +1661,7 @@ class ReferralContestVirtualParticipant(Base): display_name = Column(String(255), nullable=False) referral_count = Column(Integer, nullable=False, default=0) total_amount_kopeks = Column(Integer, nullable=False, default=0) - created_at = Column(DateTime(timezone=True), default=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) contest = relationship('ReferralContest') @@ -1693,8 +1687,8 @@ class ContestTemplate(Base): cooldown_hours = Column(Integer, nullable=False, default=24) payload = Column(JSON, nullable=True) is_enabled = Column(Boolean, nullable=False, default=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) rounds = relationship('ContestRound', back_populates='template') @@ -1708,8 +1702,8 @@ class ContestRound(Base): id = Column(Integer, primary_key=True, index=True) template_id = Column(Integer, ForeignKey('contest_templates.id', ondelete='CASCADE'), nullable=False) - starts_at = Column(DateTime(timezone=True), nullable=False) - ends_at = Column(DateTime(timezone=True), nullable=False) + starts_at = Column(AwareDateTime(), nullable=False) + ends_at = Column(AwareDateTime(), nullable=False) status = Column(String(20), nullable=False, default='active') # active, finished payload = Column(JSON, nullable=True) winners_count = Column(Integer, nullable=False, default=0) @@ -1717,8 +1711,8 @@ class ContestRound(Base): attempts_per_user = Column(Integer, nullable=False, default=1) message_id = Column(BigInteger, nullable=True) chat_id = Column(BigInteger, nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) template = relationship('ContestTemplate', back_populates='rounds') attempts = relationship('ContestAttempt', back_populates='round', cascade='all, delete-orphan') @@ -1736,7 +1730,7 @@ class ContestAttempt(Base): user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False) answer = Column(Text, nullable=True) is_winner = Column(Boolean, nullable=False, default=False) - created_at = Column(DateTime(timezone=True), default=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) round = relationship('ContestRound', back_populates='attempts') user = relationship('User') @@ -1756,8 +1750,8 @@ class Squad(Base): description = Column(Text, nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) @property def price_rubles(self) -> float: @@ -1778,8 +1772,8 @@ class ServiceRule(Base): language = Column(String(5), default='ru') - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) class PrivacyPolicy(Base): @@ -1789,8 +1783,8 @@ class PrivacyPolicy(Base): language = Column(String(10), nullable=False, unique=True) content = Column(Text, nullable=False) is_enabled = Column(Boolean, default=True, nullable=False) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) class PublicOffer(Base): @@ -1800,8 +1794,8 @@ class PublicOffer(Base): language = Column(String(10), nullable=False, unique=True) content = Column(Text, nullable=False) is_enabled = Column(Boolean, default=True, nullable=False) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) class FaqSetting(Base): @@ -1810,8 +1804,8 @@ class FaqSetting(Base): id = Column(Integer, primary_key=True, index=True) language = Column(String(10), nullable=False, unique=True) is_enabled = Column(Boolean, default=True, nullable=False) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) class FaqPage(Base): @@ -1823,8 +1817,8 @@ class FaqPage(Base): content = Column(Text, nullable=False) display_order = Column(Integer, default=0, nullable=False) is_active = Column(Boolean, default=True, nullable=False) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) class SystemSetting(Base): @@ -1835,8 +1829,8 @@ class SystemSetting(Base): value = Column(Text, nullable=True) description = Column(Text, nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) class MonitoringLog(Base): @@ -1851,7 +1845,7 @@ class MonitoringLog(Base): is_success = Column(Boolean, default=True) - created_at = Column(DateTime(timezone=True), default=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) class SentNotification(Base): @@ -1862,7 +1856,7 @@ class SentNotification(Base): subscription_id = Column(Integer, ForeignKey('subscriptions.id', ondelete='CASCADE'), nullable=False) notification_type = Column(String(50), nullable=False) days_before = Column(Integer, nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) user = relationship('User', backref='sent_notifications') subscription = relationship('Subscription', backref=backref('sent_notifications', passive_deletes=True)) @@ -1879,9 +1873,9 @@ class SubscriptionEvent(Base): amount_kopeks = Column(Integer, nullable=True) currency = Column(String(16), nullable=True) message = Column(Text, nullable=True) - occurred_at = Column(DateTime(timezone=True), nullable=False, default=func.now()) + occurred_at = Column(AwareDateTime(), nullable=False, default=func.now()) extra = Column(JSON, nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) user = relationship('User', backref='subscription_events') subscription = relationship('Subscription', backref='subscription_events') @@ -1898,13 +1892,13 @@ class DiscountOffer(Base): notification_type = Column(String(50), nullable=False) discount_percent = Column(Integer, nullable=False, default=0) bonus_amount_kopeks = Column(Integer, nullable=False, default=0) - expires_at = Column(DateTime(timezone=True), nullable=False) - claimed_at = Column(DateTime(timezone=True), nullable=True) + expires_at = Column(AwareDateTime(), nullable=False) + claimed_at = Column(AwareDateTime(), nullable=True) is_active = Column(Boolean, default=True, nullable=False) effect_type = Column(String(50), nullable=False, default='percent_discount') extra_data = Column(JSON, nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) user = relationship('User', back_populates='discount_offers') subscription = relationship('Subscription', back_populates='discount_offers') @@ -1928,8 +1922,8 @@ class PromoOfferTemplate(Base): test_squad_uuids = Column(JSON, default=list) is_active = Column(Boolean, default=True, nullable=False) created_by = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) creator = relationship('User') @@ -1941,9 +1935,9 @@ class SubscriptionTemporaryAccess(Base): subscription_id = Column(Integer, ForeignKey('subscriptions.id', ondelete='CASCADE'), nullable=False) offer_id = Column(Integer, ForeignKey('discount_offers.id', ondelete='CASCADE'), nullable=False) squad_uuid = Column(String(255), nullable=False) - expires_at = Column(DateTime(timezone=True), nullable=False) - created_at = Column(DateTime(timezone=True), default=func.now()) - deactivated_at = Column(DateTime(timezone=True), nullable=True) + expires_at = Column(AwareDateTime(), nullable=False) + created_at = Column(AwareDateTime(), default=func.now()) + deactivated_at = Column(AwareDateTime(), nullable=True) is_active = Column(Boolean, default=True, nullable=False) was_already_connected = Column(Boolean, default=False, nullable=False) @@ -1962,7 +1956,7 @@ class PromoOfferLog(Base): percent = Column(Integer, nullable=True) effect_type = Column(String(50), nullable=True) details = Column(JSON, nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) user = relationship('User', back_populates='promo_offer_logs') offer = relationship('DiscountOffer', back_populates='logs') @@ -1985,8 +1979,8 @@ class BroadcastHistory(Base): status = Column(String(50), default='in_progress') admin_id = Column(Integer, ForeignKey('users.id')) admin_name = Column(String(255)) - created_at = Column(DateTime(timezone=True), server_default=func.now()) - completed_at = Column(DateTime(timezone=True), nullable=True) + created_at = Column(AwareDateTime(), server_default=func.now()) + completed_at = Column(AwareDateTime(), nullable=True) # Email broadcast fields channel = Column(String(20), default='telegram', nullable=False) # telegram|email|both @@ -2005,8 +1999,8 @@ class Poll(Base): reward_enabled = Column(Boolean, nullable=False, default=False) reward_amount_kopeks = Column(Integer, nullable=False, default=0) created_by = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now(), nullable=False) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now(), nullable=False) + created_at = Column(AwareDateTime(), default=func.now(), nullable=False) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now(), nullable=False) creator = relationship('User', backref='created_polls', foreign_keys=[created_by]) questions = relationship( @@ -2058,9 +2052,9 @@ class PollResponse(Base): id = Column(Integer, primary_key=True, index=True) poll_id = Column(Integer, ForeignKey('polls.id', ondelete='CASCADE'), nullable=False, index=True) user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False, index=True) - sent_at = Column(DateTime(timezone=True), default=func.now(), nullable=False) - started_at = Column(DateTime(timezone=True), nullable=True) - completed_at = Column(DateTime(timezone=True), nullable=True) + sent_at = Column(AwareDateTime(), default=func.now(), nullable=False) + started_at = Column(AwareDateTime(), nullable=True) + completed_at = Column(AwareDateTime(), nullable=True) reward_given = Column(Boolean, nullable=False, default=False) reward_amount_kopeks = Column(Integer, nullable=False, default=0) @@ -2082,7 +2076,7 @@ class PollAnswer(Base): response_id = Column(Integer, ForeignKey('poll_responses.id', ondelete='CASCADE'), nullable=False, index=True) question_id = Column(Integer, ForeignKey('poll_questions.id', ondelete='CASCADE'), nullable=False, index=True) option_id = Column(Integer, ForeignKey('poll_options.id', ondelete='CASCADE'), nullable=False, index=True) - created_at = Column(DateTime(timezone=True), default=func.now(), nullable=False) + created_at = Column(AwareDateTime(), default=func.now(), nullable=False) response = relationship('PollResponse', back_populates='answers') question = relationship('PollQuestion', back_populates='answers') @@ -2116,8 +2110,8 @@ class ServerSquad(Base): max_users = Column(Integer, nullable=True) current_users = Column(Integer, default=0) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) allowed_promo_groups = relationship( 'PromoGroup', @@ -2152,7 +2146,7 @@ class SubscriptionServer(Base): subscription_id = Column(Integer, ForeignKey('subscriptions.id'), nullable=False) server_squad_id = Column(Integer, ForeignKey('server_squads.id'), nullable=False) - connected_at = Column(DateTime(timezone=True), default=func.now()) + connected_at = Column(AwareDateTime(), default=func.now()) paid_price_kopeks = Column(Integer, default=0) @@ -2171,7 +2165,7 @@ class SupportAuditLog(Base): ticket_id = Column(Integer, ForeignKey('tickets.id', ondelete='SET NULL'), nullable=True) target_user_id = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True) details = Column(JSON, nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) actor = relationship('User', foreign_keys=[actor_user_id]) ticket = relationship('Ticket', foreign_keys=[ticket_id]) @@ -2184,8 +2178,8 @@ class UserMessage(Base): is_active = Column(Boolean, default=True) sort_order = Column(Integer, default=0) created_by = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) creator = relationship('User', backref='created_messages') def __repr__(self): @@ -2200,8 +2194,8 @@ class WelcomeText(Base): is_active = Column(Boolean, default=True) is_enabled = Column(Boolean, default=True) created_by = Column(Integer, ForeignKey('users.id'), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) creator = relationship('User', backref='created_welcome_texts') @@ -2217,8 +2211,8 @@ class PinnedMessage(Base): send_on_every_start = Column(Boolean, nullable=False, server_default='1', default=True) is_active = Column(Boolean, default=True) created_by = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) creator = relationship('User', backref='pinned_messages') @@ -2248,8 +2242,8 @@ class AdvertisingCampaign(Base): partner_user_id = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True, index=True) created_by = Column(Integer, ForeignKey('users.id'), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) registrations = relationship('AdvertisingCampaignRegistration', back_populates='campaign') tariff = relationship('Tariff', foreign_keys=[tariff_id]) @@ -2290,7 +2284,7 @@ class AdvertisingCampaignRegistration(Base): tariff_id = Column(Integer, ForeignKey('tariffs.id', ondelete='SET NULL'), nullable=True) tariff_duration_days = Column(Integer, nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) campaign = relationship('AdvertisingCampaign', back_populates='registrations') user = relationship('User') @@ -2319,13 +2313,13 @@ class Ticket(Base): priority = Column(String(20), default='normal', nullable=False) # low, normal, high, urgent # Блокировка ответов пользователя в этом тикете user_reply_block_permanent = Column(Boolean, default=False, nullable=False) - user_reply_block_until = Column(DateTime(timezone=True), nullable=True) + user_reply_block_until = Column(AwareDateTime(), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) - closed_at = Column(DateTime(timezone=True), nullable=True) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) + closed_at = Column(AwareDateTime(), nullable=True) # SLA reminders - last_sla_reminder_at = Column(DateTime(timezone=True), nullable=True) + last_sla_reminder_at = Column(AwareDateTime(), nullable=True) # Связи user = relationship('User', backref='tickets') @@ -2390,7 +2384,7 @@ class TicketMessage(Base): media_file_id = Column(String(255), nullable=True) media_caption = Column(Text, nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) # Связи ticket = relationship('Ticket', back_populates='messages') @@ -2416,10 +2410,10 @@ class WebApiToken(Base): token_hash = Column(String(128), nullable=False, unique=True, index=True) token_prefix = Column(String(32), nullable=False, index=True) description = Column(Text, nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) - expires_at = Column(DateTime(timezone=True), nullable=True) - last_used_at = Column(DateTime(timezone=True), nullable=True) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) + expires_at = Column(AwareDateTime(), nullable=True) + last_used_at = Column(AwareDateTime(), nullable=True) last_used_ip = Column(String(64), nullable=True) is_active = Column(Boolean, default=True, nullable=False) created_by = Column(String(255), nullable=True) @@ -2439,8 +2433,8 @@ class MainMenuButton(Base): visibility = Column(String(20), nullable=False, default=MainMenuButtonVisibility.ALL.value) is_active = Column(Boolean, nullable=False, default=True) display_order = Column(Integer, nullable=False, default=0) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) __table_args__ = (Index('ix_main_menu_buttons_order', 'display_order', 'id'),) @@ -2475,7 +2469,7 @@ class MenuLayoutHistory(Base): action = Column(String(50), nullable=False) # update, reset, import changes_summary = Column(Text, nullable=True) # Краткое описание изменений user_info = Column(String(255), nullable=True) # Информация о пользователе/токене - created_at = Column(DateTime(timezone=True), default=func.now(), index=True) + created_at = Column(AwareDateTime(), default=func.now(), index=True) __table_args__ = (Index('ix_menu_layout_history_created', 'created_at'),) @@ -2492,7 +2486,7 @@ class ButtonClickLog(Base): button_id = Column(String(100), nullable=False, index=True) # ID кнопки user_id = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True, index=True) callback_data = Column(String(255), nullable=True) # callback_data кнопки - clicked_at = Column(DateTime(timezone=True), default=func.now(), index=True) + clicked_at = Column(AwareDateTime(), default=func.now(), index=True) # Дополнительная информация button_type = Column(String(20), nullable=True, index=True) # builtin, callback, url, mini_app @@ -2526,9 +2520,9 @@ class Webhook(Base): event_type = Column(String(50), nullable=False) # user.created, payment.completed, ticket.created, etc. is_active = Column(Boolean, default=True, nullable=False) description = Column(Text, nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) - last_triggered_at = Column(DateTime(timezone=True), nullable=True) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) + last_triggered_at = Column(AwareDateTime(), nullable=True) failure_count = Column(Integer, default=0, nullable=False) success_count = Column(Integer, default=0, nullable=False) @@ -2557,9 +2551,9 @@ class WebhookDelivery(Base): status = Column(String(20), nullable=False) # pending, success, failed error_message = Column(Text, nullable=True) attempt_number = Column(Integer, default=1, nullable=False) - created_at = Column(DateTime(timezone=True), default=func.now()) - delivered_at = Column(DateTime(timezone=True), nullable=True) - next_retry_at = Column(DateTime(timezone=True), nullable=True) + created_at = Column(AwareDateTime(), default=func.now()) + delivered_at = Column(AwareDateTime(), nullable=True) + next_retry_at = Column(AwareDateTime(), nullable=True) webhook = relationship('Webhook', back_populates='deliveries') @@ -2577,9 +2571,9 @@ class CabinetRefreshToken(Base): user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False) token_hash = Column(String(255), unique=True, nullable=False, index=True) device_info = Column(String(500), nullable=True) - expires_at = Column(DateTime(timezone=True), nullable=False) - created_at = Column(DateTime(timezone=True), default=func.now()) - revoked_at = Column(DateTime(timezone=True), nullable=True) + expires_at = Column(AwareDateTime(), nullable=False) + created_at = Column(AwareDateTime(), default=func.now()) + revoked_at = Column(AwareDateTime(), nullable=True) user = relationship('User', backref='cabinet_tokens') @@ -2631,8 +2625,8 @@ class WheelConfig(Base): promo_prefix = Column(String(20), default='WHEEL', nullable=False) promo_validity_days = Column(Integer, default=7, nullable=False) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) prizes = relationship('WheelPrize', back_populates='config', cascade='all, delete-orphan') @@ -2670,8 +2664,8 @@ class WheelPrize(Base): promo_subscription_days = Column(Integer, default=0) promo_traffic_gb = Column(Integer, default=0) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) config = relationship('WheelConfig', back_populates='prizes') spins = relationship('WheelSpin', back_populates='prize') @@ -2706,9 +2700,9 @@ class WheelSpin(Base): # Флаг успешного начисления is_applied = Column(Boolean, default=False, nullable=False) - applied_at = Column(DateTime(timezone=True), nullable=True) + applied_at = Column(AwareDateTime(), nullable=True) - created_at = Column(DateTime(timezone=True), default=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) user = relationship('User', backref='wheel_spins') prize = relationship('WheelPrize', back_populates='spins') @@ -2753,8 +2747,8 @@ class TicketNotification(Base): # Прочитано ли уведомление is_read = Column(Boolean, default=False, nullable=False) - created_at = Column(DateTime(timezone=True), default=func.now()) - read_at = Column(DateTime(timezone=True), nullable=True) + created_at = Column(AwareDateTime(), default=func.now()) + read_at = Column(AwareDateTime(), nullable=True) ticket = relationship('Ticket', backref='notifications') user = relationship('User', backref='ticket_notifications') @@ -2811,8 +2805,8 @@ class PaymentMethodConfig(Base): lazy='selectin', ) - created_at = Column(DateTime(timezone=True), default=func.now()) - updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + created_at = Column(AwareDateTime(), default=func.now()) + updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now()) def __repr__(self) -> str: return f""