fix: replace deprecated datetime.utcnow with timezone-aware datetime.now in all modules and fix comment count query column name

This commit is contained in:
2026-05-14 02:12:19 +00:00
parent cb0fccb270
commit 34cae11630
25 changed files with 82 additions and 385 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
import logging
from datetime import datetime
from datetime import datetime, timezone
from pathlib import Path
from PIL import Image
from io import BytesIO
@@ -74,7 +74,7 @@ def store_attachment(file_bytes, original_filename, user_uid):
"directory": directory, "file_size": len(file_bytes), "mime_type": mime,
"image_width": img_w, "image_height": img_h,
"has_thumbnail": 1 if thumb else 0,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
return {"uid": uid, "original_filename": original_filename, "file_size": len(file_bytes), "mime_type": mime, "url": f"/static/uploads/attachments/{directory}/{stored_name}", "thumbnail_url": f"/static/uploads/attachments/{directory}/{thumb}" if thumb else None, "has_thumbnail": thumb is not None, "is_image": is_img}
+2 -2
View File
@@ -143,8 +143,8 @@ def get_comment_counts_by_post_uids(post_uids):
return {}
placeholders = ", ".join(f":p{i}" for i in range(len(post_uids)))
params = {f"p{i}": u for i, u in enumerate(post_uids)}
rows = db.query(f"SELECT post_uid, COUNT(*) as c FROM comments WHERE post_uid IN ({placeholders}) GROUP BY post_uid", **params)
return {r["post_uid"]: r["c"] for r in rows}
rows = db.query(f"SELECT target_uid, COUNT(*) as c FROM comments WHERE target_type='post' AND target_uid IN ({placeholders}) GROUP BY target_uid", **params)
return {r["target_uid"]: r["c"] for r in rows}
def get_vote_counts(target_uids):
+1
View File
@@ -37,6 +37,7 @@ async def not_found(request: Request, exc):
@app.exception_handler(500)
async def server_error(request: Request, exc):
logger.exception("500 error on %s %s", request.method, request.url.path)
seo_ctx = base_seo_context(request, title="Server Error — DevPlace", description="Something went wrong.", robots="noindex")
return templates.TemplateResponse(request, "error.html", {**seo_ctx, "request": request, "error_code": 500, "error_message": "Internal server error"}, status_code=500)
+14 -6
View File
@@ -1,7 +1,7 @@
import hashlib
import secrets
import logging
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from fastapi import APIRouter, Request
from fastapi.responses import RedirectResponse, HTMLResponse
from devplacepy.database import get_table
@@ -90,7 +90,7 @@ async def signup(request: Request):
"level": 1,
"xp": 0,
"stars": 0,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
badges = get_table("badges")
@@ -98,7 +98,7 @@ async def signup(request: Request):
"uid": generate_uid(),
"user_uid": uid,
"badge_name": "Member",
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
token = create_session(uid)
@@ -174,9 +174,9 @@ async def forgot_password(request: Request):
"uid": generate_uid(),
"user_uid": user["uid"],
"token": token_hash,
"expires_at": (datetime.utcnow() + timedelta(hours=1)).isoformat(),
"expires_at": (datetime.now(timezone.utc) + timedelta(hours=1)).isoformat(),
"used": False,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
logger.info(f"Password reset requested for {email}")
@@ -220,7 +220,15 @@ async def reset_password(request: Request, token: str):
token_hash = hashlib.sha256(token.encode()).hexdigest()
matched = resets.find_one(token=token_hash, used=False)
if not matched or datetime.fromisoformat(matched["expires_at"]) < datetime.utcnow():
if not matched:
errors.append("Invalid or expired reset token")
return templates.TemplateResponse(request, "reset_password.html", {
"request": request, "token": token, "errors": errors,
})
expires = datetime.fromisoformat(matched["expires_at"])
if expires.tzinfo is None:
expires = expires.replace(tzinfo=timezone.utc)
if expires < datetime.now(timezone.utc):
errors.append("Invalid or expired reset token")
return templates.TemplateResponse(request, "reset_password.html", {
"request": request, "token": token, "errors": errors,
+2 -2
View File
@@ -1,5 +1,5 @@
import logging
from datetime import datetime
from datetime import datetime, timezone
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from devplacepy.database import get_table, load_comments
@@ -69,7 +69,7 @@ async def create_bug(request: Request):
"title": title,
"description": description,
"status": "open",
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
attachment_uids = form.getlist("attachment_uids") if hasattr(form, "getlist") else []
+5 -5
View File
@@ -1,5 +1,5 @@
import logging
from datetime import datetime
from datetime import datetime, timezone
from fastapi import APIRouter, Request
from fastapi.responses import RedirectResponse
from devplacepy.database import get_table, resolve_by_slug
@@ -56,7 +56,7 @@ async def create_comment(request: Request):
"user_uid": user["uid"],
"content": content,
"parent_uid": parent_uid or None,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
}
if target_type == "post":
insert["post_uid"] = target_uid
@@ -73,7 +73,7 @@ async def create_comment(request: Request):
"uid": generate_uid(),
"user_uid": user["uid"],
"badge_name": "First Comment",
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
if target_type == "post":
@@ -89,7 +89,7 @@ async def create_comment(request: Request):
"message": f"{user['username']} replied to your comment",
"related_uid": user["uid"],
"read": False,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
else:
posts = get_table("posts")
@@ -105,7 +105,7 @@ async def create_comment(request: Request):
"message": f"{user['username']} commented on your post",
"related_uid": user["uid"],
"read": False,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
create_mention_notifications(content, user["uid"], redirect_url)
+2 -2
View File
@@ -1,5 +1,5 @@
import logging
from datetime import datetime
from datetime import datetime, timezone
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from devplacepy.database import get_table, get_daily_topic, get_users_by_uids, get_comment_counts_by_post_uids
@@ -72,7 +72,7 @@ async def feed_page(request: Request, tab: str = "all", topic: str = None, befor
users_table = get_table("users")
total_members = len(list(users_table.all()))
posts_table = get_table("posts")
today_start = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
today_start = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
posts_today = len(list(posts_table.find(created_at={">=": today_start})))
total_projects = len(list(get_table("projects").all()))
total_gists = len(list(get_table("gists").all()))
+3 -3
View File
@@ -1,5 +1,5 @@
import logging
from datetime import datetime
from datetime import datetime, timezone
from fastapi import APIRouter, Request
from fastapi.responses import RedirectResponse
from devplacepy.database import get_table
@@ -26,7 +26,7 @@ async def follow_user(request: Request, username: str):
"uid": generate_uid(),
"follower_uid": user["uid"],
"following_uid": target["uid"],
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
notifications = get_table("notifications")
@@ -37,7 +37,7 @@ async def follow_user(request: Request, username: str):
"message": f"{user['username']} started following you",
"related_uid": user["uid"],
"read": False,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
logger.info(f"{user['username']} followed {username}")
+2 -2
View File
@@ -1,5 +1,5 @@
import logging
from datetime import datetime
from datetime import datetime, timezone
from fastapi import APIRouter, Request, HTTPException
from fastapi.responses import HTMLResponse, RedirectResponse
from devplacepy.database import get_table, load_comments, get_vote_counts, resolve_by_slug, db
@@ -161,7 +161,7 @@ async def create_gist(request: Request):
"source_code": source_code,
"language": language,
"stars": 0,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
attachment_uids = form.getlist("attachment_uids") if hasattr(form, "getlist") else []
+3 -3
View File
@@ -1,5 +1,5 @@
import logging
from datetime import datetime
from datetime import datetime, timezone
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
from devplacepy.database import get_table, db
@@ -160,7 +160,7 @@ async def send_message(request: Request):
"receiver_uid": receiver_uid,
"content": content,
"read": False,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
attachment_uids = form.getlist("attachment_uids") if hasattr(form, "getlist") else []
@@ -177,7 +177,7 @@ async def send_message(request: Request):
"message": f"{user['username']} sent you a message",
"related_uid": user["uid"],
"read": False,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
create_mention_notifications(content, user["uid"], f"/messages?with_uid={receiver_uid}")
+2 -2
View File
@@ -1,5 +1,5 @@
import logging
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from devplacepy.database import get_table, db, load_comments, resolve_by_slug
@@ -16,7 +16,7 @@ NEWS_MAX_AGE_DAYS = 4
@router.get("", response_class=HTMLResponse)
async def news_page(request: Request):
user = get_current_user(request)
cutoff = (datetime.utcnow() - timedelta(days=NEWS_MAX_AGE_DAYS)).isoformat()
cutoff = (datetime.now(timezone.utc) - timedelta(days=NEWS_MAX_AGE_DAYS)).isoformat()
news_table = get_table("news")
articles = list(news_table.find(
+2 -2
View File
@@ -1,5 +1,5 @@
import logging
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from devplacepy.database import get_table
@@ -14,7 +14,7 @@ router = APIRouter()
def _group_label(created_at: str) -> str:
try:
dt = datetime.fromisoformat(created_at)
now = datetime.utcnow()
now = datetime.now(timezone.utc)
today = now.date()
date = dt.date()
if date == today:
+3 -3
View File
@@ -1,6 +1,6 @@
import logging
import aiofiles
from datetime import datetime
from datetime import datetime, timezone
from pathlib import Path
from fastapi import APIRouter, Request, HTTPException
from fastapi.responses import RedirectResponse, HTMLResponse
@@ -76,7 +76,7 @@ async def create_post(request: Request):
"project_uid": project_uid or None,
"image": image_filename,
"stars": 0,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
badges = get_table("badges")
@@ -86,7 +86,7 @@ async def create_post(request: Request):
"uid": generate_uid(),
"user_uid": user["uid"],
"badge_name": "First Post",
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
attachment_uids = form.getlist("attachment_uids")
+2 -2
View File
@@ -1,5 +1,5 @@
import logging
from datetime import datetime
from datetime import datetime, timezone
from fastapi import APIRouter, Request, HTTPException
from fastapi.responses import HTMLResponse, RedirectResponse
from devplacepy.database import get_table, get_vote_counts, load_comments, resolve_by_slug
@@ -176,7 +176,7 @@ async def create_project(request: Request):
"platforms": platforms,
"status": status,
"stars": 0,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
attachment_uids = form.getlist("attachment_uids") if hasattr(form, "getlist") else []
+3 -3
View File
@@ -1,5 +1,5 @@
import logging
from datetime import datetime
from datetime import datetime, timezone
from fastapi import APIRouter, Request
from fastapi.responses import RedirectResponse
from devplacepy.database import get_table
@@ -33,7 +33,7 @@ async def vote(request: Request, target_type: str, target_uid: str):
"target_uid": target_uid,
"target_type": target_type,
"value": value,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
up_count = len(list(votes.find(target_uid=target_uid, value=1)))
@@ -77,7 +77,7 @@ async def vote(request: Request, target_type: str, target_uid: str):
"message": f"{user['username']} ++'d your {label}",
"related_uid": user["uid"],
"read": False,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})
referer = request.headers.get("Referer", "/feed")
+6 -6
View File
@@ -2,7 +2,7 @@ import asyncio
import logging
from abc import ABC, abstractmethod
from collections import deque
from datetime import datetime
from datetime import datetime, timezone
logger = logging.getLogger(__name__)
@@ -34,11 +34,11 @@ class BaseService(ABC):
def uptime(self) -> str | None:
if self._started_at is None:
return None
delta = datetime.utcnow() - self._started_at
delta = datetime.now(timezone.utc) - self._started_at
return str(delta).split(".")[0]
def log(self, message: str) -> None:
stamp = datetime.utcnow().strftime("%H:%M:%S")
stamp = datetime.now(timezone.utc).strftime("%H:%M:%S")
entry = f"[{stamp}] {message}"
self.log_buffer.append(entry)
logger.info(f"[{self.name}] {message}")
@@ -49,11 +49,11 @@ class BaseService(ABC):
async def _run_loop(self) -> None:
self._running = True
self._started_at = datetime.utcnow()
self._started_at = datetime.now(timezone.utc)
self.log(f"Service started (interval={self.interval_seconds}s)")
while self._running:
try:
self._last_run = datetime.utcnow().isoformat()
self._last_run = datetime.now(timezone.utc).isoformat()
self._next_run = None
await self.run_once()
except asyncio.CancelledError:
@@ -63,7 +63,7 @@ class BaseService(ABC):
self.log(f"Error in run_once: {e}")
if not self._running:
break
self._next_run = datetime.utcnow().isoformat()
self._next_run = datetime.now(timezone.utc).isoformat()
self.log(f"Sleeping for {self.interval_seconds}s")
try:
await asyncio.sleep(self.interval_seconds)
+2 -2
View File
@@ -1,7 +1,7 @@
import logging
import os
import re
from datetime import datetime
from datetime import datetime, timezone
import httpx
@@ -119,7 +119,7 @@ class NewsService(BaseService):
continue
grade = await self._grade_article(article, ai_url, ai_model)
now = datetime.utcnow().isoformat()
now = datetime.now(timezone.utc).isoformat()
if grade is None:
grade_val = 0
+3 -1
View File
@@ -1,3 +1,5 @@
{% if _user %}
<a href="/profile/{{ _user['username'] }}" class="user-avatar-link">
<img src="{{ avatar_url('multiavatar', _user['username'], _size) }}" class="avatar-img avatar-{{ _size_class }}" alt="{{ _user['username'] }}" loading="lazy">
</a>
</a>
{% endif %}
+10 -6
View File
@@ -2,7 +2,7 @@ import re
import secrets
import time
import logging
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from passlib.hash import pbkdf2_sha256
from fastapi import Request, HTTPException, status
from devplacepy.database import get_table
@@ -21,12 +21,12 @@ def verify_password(password: str, hashed: str) -> bool:
def create_session(user_uid: str) -> str:
token = secrets.token_hex(32)
expires_at = datetime.utcnow() + timedelta(seconds=SESSION_MAX_AGE)
expires_at = datetime.now(timezone.utc) + timedelta(seconds=SESSION_MAX_AGE)
sessions = get_table("sessions")
sessions.insert({
"session_token": token,
"user_uid": user_uid,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
"expires_at": expires_at.isoformat(),
})
return token
@@ -53,7 +53,9 @@ def get_current_user(request: Request):
if not session:
return None
expires = datetime.fromisoformat(session["expires_at"])
if expires < datetime.utcnow():
if expires.tzinfo is None:
expires = expires.replace(tzinfo=timezone.utc)
if expires < datetime.now(timezone.utc):
sessions.delete(id=session["id"])
return None
users = get_table("users")
@@ -104,7 +106,9 @@ def make_combined_slug(text: str, uid: str) -> str:
def time_ago(dt_str: str) -> str:
dt = datetime.fromisoformat(dt_str)
now = datetime.utcnow()
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
now = datetime.now(timezone.utc)
diff = now - dt
days = diff.days
if days > 30:
@@ -147,7 +151,7 @@ def create_mention_notifications(content: str, actor_uid: str, target_url: str)
"related_uid": actor_uid,
"target_url": target_url,
"read": False,
"created_at": datetime.utcnow().isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
})