From e0a13220383da9f5d65a6fb1dc3805ac8b1cc604 Mon Sep 17 00:00:00 2001 From: Xinacod Date: Thu, 12 Mar 2026 09:24:49 +0100 Subject: [PATCH 1/3] Fix invalid %f datefmt in logging config causing errors on Windows --- myapp/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/myapp/settings.py b/myapp/settings.py index efb2522..b758d71 100644 --- a/myapp/settings.py +++ b/myapp/settings.py @@ -341,7 +341,7 @@ "formatters": { "detailed": { "format": LOG_FORMAT, - "datefmt": "%Y-%m-%d %H:%M:%S,%f", + "datefmt": "%Y-%m-%d %H:%M:%S", }, }, "handlers": { @@ -377,7 +377,7 @@ "formatters": { "detailed": { "format": LOG_FORMAT, - "datefmt": "%Y-%m-%d %H:%M:%S,%f", + "datefmt": "%Y-%m-%d %H:%M:%S", }, }, "handlers": { From 4171d0777a975bcffa35c1b49f684466057ba9c3 Mon Sep 17 00:00:00 2001 From: Xinacod Date: Thu, 12 Mar 2026 09:41:01 +0100 Subject: [PATCH 2/3] Add async email notifications for community join request events --- communities/api_join.py | 16 +++++++ myapp/services/send_emails.py | 81 +++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/communities/api_join.py b/communities/api_join.py index 4f5b3a6..e29d878 100644 --- a/communities/api_join.py +++ b/communities/api_join.py @@ -7,6 +7,7 @@ from communities.models import Community, JoinRequest from communities.schemas import JoinRequestSchema, Message +from myapp.services.send_emails import send_join_decision_email, send_join_request_email from users.auth import JWTAuth from users.models import Notification @@ -131,6 +132,11 @@ def join_community(request, community_id: int): # Continue even if notification fails pass + try: + send_join_request_email(user, community) + except Exception as e: + logger.error(f"Error sending join request email: {e}") + return 200, {"message": "Your request to join the community has been sent."} except Exception as e: logger.error(f"Error processing join request: {e}") @@ -211,6 +217,11 @@ def manage_join_request( # Continue even if notification fails pass + try: + send_join_decision_email(join_request.user, community, "approve") + except Exception as e: + logger.error(f"Error sending join decision email: {e}") + return 200, { "message": f"Join request approved. \ {join_request.user.username} is now a member of the community." @@ -227,6 +238,11 @@ def manage_join_request( "message": "Error updating join request status. Please try again." } + try: + send_join_decision_email(join_request.user, community, "reject") + except Exception as e: + logger.error(f"Error sending join decision email: {e}") + return 200, { "message": "You have rejected the join request successfully." } diff --git a/myapp/services/send_emails.py b/myapp/services/send_emails.py index 7f2a32d..a09f821 100644 --- a/myapp/services/send_emails.py +++ b/myapp/services/send_emails.py @@ -135,6 +135,87 @@ def send_review_notification_email(article, review, community): logger.error(f"Error sending review notification email: {e}") +def send_join_request_email(user, community): + admin = community.admins.first() + if not admin or not admin.email: + logger.warning( + f"Cannot send join request email: community {community.id} has no admin with email" + ) + return + + if not is_email_notifications_enabled(admin.id): + logger.debug( + f"Email notifications disabled for admin {admin.id}, skipping join request email" + ) + return + + domain = get_frontend_domain() + link = f"{domain}/community/{quote(community.name, safe='')}/requests" + + context = { + "recipient_name": admin.first_name or admin.username, + "notification_type": "New Join Request", + "message_text": mark_safe( + f"{user.username} has requested to join the {community.name} community." + ), + "content_preview": None, + "article_link": link, + } + + send_email_task.delay( + subject=f"New Join Request for {community.name}", + html_template_name="review_comment_notification.html", + context=context, + recipient_list=[admin.email], + from_email=settings.DEFAULT_FROM_EMAIL, + ) + + +def send_join_decision_email(user, community, action): + if not user or not user.email: + logger.warning( + f"Cannot send join decision email: user has no email for community {community.id}" + ) + return + + if not is_email_notifications_enabled(user.id): + logger.debug( + f"Email notifications disabled for user {user.id}, skipping join decision email" + ) + return + + domain = get_frontend_domain() + + if action == "approve": + notification_type = "Join Request Approved" + message_text = mark_safe( + f"Your request to join {community.name} has been approved. Welcome!" + ) + link = f"{domain}/community/{quote(community.name, safe='')}" + else: + notification_type = "Join Request Rejected" + message_text = mark_safe( + f"Your request to join {community.name} has been rejected." + ) + link = f"{domain}/communities" + + context = { + "recipient_name": user.first_name or user.username, + "notification_type": notification_type, + "message_text": message_text, + "content_preview": None, + "article_link": link, + } + + send_email_task.delay( + subject=f"Community Join Request {action.capitalize()}d: {community.name}", + html_template_name="review_comment_notification.html", + context=context, + recipient_list=[user.email], + from_email=settings.DEFAULT_FROM_EMAIL, + ) + + def send_comment_notification_email(comment, review, article, community): """ Send email notification when a new comment/reply is added to a review. From bb0c532096aa11ccd88c4db38f7a983d7df17bbb Mon Sep 17 00:00:00 2001 From: Xinacod Date: Mon, 16 Mar 2026 10:24:17 +0100 Subject: [PATCH 3/3] Fix URL encoding for community names in notification links Community names containing special characters (e.g. '+', spaces) were being inserted raw into notification link paths, causing 404s when users clicked through. Apply urllib.parse.quote(..., safe='') to all in-app notification links that include the community name in the path. Fixes #119 --- articles/api.py | 4 ++-- communities/api_join.py | 5 +++-- communities/articles_api.py | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/articles/api.py b/articles/api.py index 21fe9c4..b7d3555 100644 --- a/articles/api.py +++ b/articles/api.py @@ -2,7 +2,7 @@ from collections import defaultdict from datetime import timedelta from typing import Counter, List, Optional -from urllib.parse import quote_plus, unquote +from urllib.parse import quote, quote_plus, unquote from django.contrib.contenttypes.models import ContentType from django.core.paginator import Paginator @@ -168,7 +168,7 @@ def create_article( f"New article submitted in {community.name}" f" by {request.auth.username}" ), - link=f"/community/{community.name}/submissions", + link=f"/community/{quote(community.name, safe='')}/submissions", content=article.title, ) except Exception: diff --git a/communities/api_join.py b/communities/api_join.py index e29d878..a78bd84 100644 --- a/communities/api_join.py +++ b/communities/api_join.py @@ -1,5 +1,6 @@ import logging from typing import List, Literal +from urllib.parse import quote from django.utils import timezone from ninja import Router @@ -125,7 +126,7 @@ def join_community(request, community_id: int): community=community, notification_type="join_request_received", message=f"New join request from {user.username}", - link=f"/community/{community.name}/requests", + link=f"/community/{quote(community.name, safe='')}/requests", ) except Exception as e: logger.error(f"Error creating notification: {e}") @@ -210,7 +211,7 @@ def manage_join_request( community=community, notification_type="join_request_approved", message=f"Your join request to {community.name} has been approved.", - link=f"/community/{community.name}", + link=f"/community/{quote(community.name, safe='')}", ) except Exception as e: logger.error(f"Error creating notification: {e}") diff --git a/communities/articles_api.py b/communities/articles_api.py index 17f60b8..c331b1b 100644 --- a/communities/articles_api.py +++ b/communities/articles_api.py @@ -1,5 +1,6 @@ import logging from typing import List, Literal, Optional +from urllib.parse import quote from django.core.paginator import Paginator from django.db import transaction @@ -119,7 +120,7 @@ def submit_article(request, community_name: str, article_slug: str): message=( f"New article submitted in {community.name} by {request.auth.username}" ), - link=f"/community/{community.name}/submissions", + link=f"/community/{quote(community.name, safe='')}/submissions", content=article.title, ) except Exception as e: