From e94b93d0c10b4e61d7750ca47e1b2f888f5873ed Mon Sep 17 00:00:00 2001 From: Fringg Date: Tue, 10 Feb 2026 20:35:42 +0300 Subject: [PATCH] fix: handle nullable traffic_limit_gb and end_date in subscription model Add None-safety guards to Subscription model properties (is_active, is_expired, should_be_expired, actual_status, days_left, traffic_used_percent) and pricing handler comparisons to prevent TypeError when nullable columns contain None values. --- app/database/models.py | 27 ++++++++++++++++++--------- app/handlers/subscription/pricing.py | 9 +++++---- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/app/database/models.py b/app/database/models.py index fc265fcf..8403ef37 100644 --- a/app/database/models.py +++ b/app/database/models.py @@ -1159,17 +1159,25 @@ class Subscription(Base): @property def is_active(self) -> bool: current_time = datetime.utcnow() - return self.status == SubscriptionStatus.ACTIVE.value and self.end_date > current_time + return ( + self.status == SubscriptionStatus.ACTIVE.value + and self.end_date is not None + and self.end_date > current_time + ) @property def is_expired(self) -> bool: """Проверяет, истёк ли срок подписки""" - return self.end_date <= datetime.utcnow() + return self.end_date is not None and self.end_date <= datetime.utcnow() @property def should_be_expired(self) -> bool: current_time = datetime.utcnow() - return self.status == SubscriptionStatus.ACTIVE.value and self.end_date <= current_time + return ( + self.status == SubscriptionStatus.ACTIVE.value + and self.end_date is not None + and self.end_date <= current_time + ) @property def actual_status(self) -> str: @@ -1182,12 +1190,12 @@ class Subscription(Base): return 'disabled' if self.status == SubscriptionStatus.ACTIVE.value: - if self.end_date <= current_time: + if self.end_date is None or self.end_date <= current_time: return 'expired' return 'active' if self.status == SubscriptionStatus.TRIAL.value: - if self.end_date <= current_time: + if self.end_date is None or self.end_date <= current_time: return 'expired' return 'trial' @@ -1230,6 +1238,8 @@ class Subscription(Base): @property def days_left(self) -> int: + if self.end_date is None: + return 0 current_time = datetime.utcnow() if self.end_date <= current_time: return 0 @@ -1255,11 +1265,10 @@ class Subscription(Base): @property def traffic_used_percent(self) -> float: - if self.traffic_limit_gb == 0: + if not self.traffic_limit_gb: return 0.0 - if self.traffic_limit_gb > 0: - return min((self.traffic_used_gb / self.traffic_limit_gb) * 100, 100.0) - return 0.0 + used = self.traffic_used_gb or 0.0 + return min((used / self.traffic_limit_gb) * 100, 100.0) def extend_subscription(self, days: int): if self.end_date > datetime.utcnow(): diff --git a/app/handlers/subscription/pricing.py b/app/handlers/subscription/pricing.py index 8213a0db..7d935dee 100644 --- a/app/handlers/subscription/pricing.py +++ b/app/handlers/subscription/pricing.py @@ -404,15 +404,16 @@ async def get_subscription_info_text(subscription, texts, db_user, db: AsyncSess status_text = '⌛ Истекла' type_text = 'Платная подписка' - if subscription.traffic_limit_gb == 0: + traffic_limit = subscription.traffic_limit_gb or 0 + if traffic_limit == 0: if settings.is_traffic_fixed(): traffic_text = '∞ Безлимитный' else: traffic_text = '∞ Безлимитный' elif settings.is_traffic_fixed(): - traffic_text = f'{subscription.traffic_limit_gb} ГБ' + traffic_text = f'{traffic_limit} ГБ' else: - traffic_text = f'{subscription.traffic_limit_gb} ГБ' + traffic_text = f'{traffic_limit} ГБ' subscription_cost = await get_subscription_cost(subscription, db) @@ -444,7 +445,7 @@ async def get_subscription_info_text(subscription, texts, db_user, db: AsyncSess info_text += f'\n💰 Стоимость подписки в месяц: {texts.format_price(subscription_cost)}' # Отображаем докупленный трафик - if subscription.traffic_limit_gb > 0: # Только для лимитированных тарифов + if (subscription.traffic_limit_gb or 0) > 0: # Только для лимитированных тарифов from datetime import datetime from sqlalchemy import select as sql_select