fix notification issue

This commit is contained in:
ismail 2025-09-17 15:44:47 +03:00
parent 536bdc4cb9
commit 10d48ca47d
5 changed files with 52 additions and 127 deletions

View File

@ -35,9 +35,9 @@ from pathlib import Path
# }
# )
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "car_inventory.settings")
django.setup()
# django.setup()
BASE_DIR = Path(__file__).resolve().parent.parent
# BASE_DIR = Path(__file__).resolve().parent.parent
app = get_asgi_application()
# app = WhiteNoise(app, root=str(BASE_DIR / 'staticfiles'))
@ -50,7 +50,7 @@ application = ProtocolTypeRouter(
path("sse/notifications/", NotificationSSEApp()),
re_path(
r"", app
), # All other routes go to Django
),
]
)
),
@ -58,5 +58,5 @@ application = ProtocolTypeRouter(
)
if django.conf.settings.DEBUG:
application = ASGIStaticFilesHandler(app)
# if django.conf.settings.DEBUG:
# application = ASGIStaticFilesHandler(app)

View File

@ -5,7 +5,7 @@ from django.urls import path, include
from schema_graph.views import Schema
from django.conf.urls.static import static
from django.conf.urls.i18n import i18n_patterns
from inventory.notifications.sse import NotificationSSEApp
# from inventory.notifications.sse import NotificationSSEApp
# import debug_toolbar
# from two_factor.urls import urlpatterns as tf_urls

View File

@ -1,82 +1,3 @@
# import json
# from django.contrib.auth.models import AnonymousUser
# from django.contrib.auth import get_user_model
# from django.db import close_old_connections
# from urllib.parse import parse_qs
# from channels.db import database_sync_to_async
# from inventory.models import Notification
# import asyncio
# @database_sync_to_async
# def get_notifications(user, last_id):
# return Notification.objects.filter(
# user=user, id__gt=last_id, is_read=False
# ).order_by("created")
# class NotificationSSEApp:
# async def __call__(self, scope, receive, send):
# if scope["type"] != "http":
# return
# query_string = parse_qs(scope["query_string"].decode())
# last_id = int(query_string.get("last_id", [0])[0])
# # Get user from scope if using AuthMiddlewareStack
# user = scope.get("user", AnonymousUser())
# if not user.is_authenticated:
# await send({
# "type": "http.response.start",
# "status": 403,
# "headers": [(b"content-type", b"text/plain")],
# })
# await send({
# "type": "http.response.body",
# "body": b"Unauthorized",
# })
# return
# await send({
# "type": "http.response.start",
# "status": 200,
# "headers": [
# (b"content-type", b"text/event-stream"),
# (b"cache-control", b"no-cache"),
# (b"x-accel-buffering", b"no"),
# ]
# })
# try:
# while True:
# close_old_connections()
# notifications = await get_notifications(user, last_id)
# for notification in notifications:
# data = {
# "id": notification.id,
# "message": notification.message,
# "created": notification.created.isoformat(),
# "is_read": notification.is_read,
# }
# event_str = (
# f"id: {notification.id}\n"
# f"event: notification\n"
# f"data: {json.dumps(data)}\n\n"
# )
# await send({
# "type": "http.response.body",
# "body": event_str.encode("utf-8"),
# "more_body": True
# })
# last_id = notification.id
# await asyncio.sleep(2)
# except asyncio.CancelledError:
# pass
import json
import time
from django.contrib.auth.models import AnonymousUser

View File

@ -5120,28 +5120,28 @@ class EstimatePrintView(EstimateDetailView):
uses a dedicated, stripped-down print template.
"""
template_name = "sales/estimates/estimate_preview.html"
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
# lang = request.GET.get('lang', 'ar')
template_path = "sales/estimates/estimate_preview.html"
html_string = render_to_string(template_path, context)
pdf_file = HTML(string=html_string).write_pdf()
response = HttpResponse(pdf_file, content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="estimate_{self.object.estimate_number}.pdf"'
return response
@ -10238,7 +10238,8 @@ def payment_callback(request, dealer_slug):
# return render(request, "payment_failed.html", {"message": message})
@login_required
async def sse_stream(request): # 👈 Mark as async!
async def sse_stream(request):
import asyncio
def event_generator():
last_id = int(request.GET.get("last_id", 0))
@ -11014,18 +11015,18 @@ class PurchaseOrderDetailView(LoginRequiredMixin, PermissionRequiredMixin, Detai
"item_model", "bill_model"
)
)
if self.object.po_status == 'fulfilled':
context['po_items_list']=po_items_qs
context['vendor']=po_items_qs.first().bill_model.vendor
context['dealer']=request.dealer
# Check if PDF format is requested
if request.GET.get('format') == 'pdf':
# Use a separate, print-friendly template for the PDF
if request.GET.get('lang')=='en':
html_string = render_to_string(
"purchase_orders/po_detail_en_pdf.html",
"purchase_orders/po_detail_en_pdf.html",
context
)
else:
@ -11033,12 +11034,12 @@ class PurchaseOrderDetailView(LoginRequiredMixin, PermissionRequiredMixin, Detai
"purchase_orders/po_detail_ar_pdf.html",
context
)
# Use WeasyPrint to generate the PDF
pdf = HTML(string=html_string).write_pdf()
response = HttpResponse(pdf, content_type="application/pdf")
response["Content-Disposition"] = f'attachment; filename="PO_{self.object.po_number}.pdf"'
return response

View File

@ -87,9 +87,13 @@
didOpen: (toast) => {
toast.onmouseenter = Swal.stopTimer;
toast.onmouseleave = Swal.resumeTimer;
} });
}
});
{% with last_notif=notifications_|last %}
let lastNotificationId = {{ last_notif.id|default:0 }};
{% endwith %}
let lastNotificationId = {{ notifications_.last.id|default:0 }};
let seenNotificationIds = new Set();
let counter = document.getElementById('notification-counter');
let notificationsContainer = document.getElementById('notifications-container');
@ -100,7 +104,6 @@
let initialUnreadCount = {{ notifications_.count|default:0 }};
updateCounter(initialUnreadCount);
fetchInitialNotifications();
function fetchInitialNotifications() {
@ -108,29 +111,22 @@
.then(response => response.json())
.then(data => {
if (data.notifications && data.notifications.length > 0) {
lastNotificationId = data.notifications[0].id;
seenNotificationIds = new Set();
let unreadCount = 0;
data.notifications.forEach(notification => {
seenNotificationIds.add(notification.id);
if (!notification.is_read) {
unreadCount++;
}
if (!notification.is_read) unreadCount++;
});
renderNotifications(data.notifications);
updateCounter(unreadCount);
setTimeout(() => {
connectSSE();
}, 5000);
}
// Always connect SSE after initial load
setTimeout(() => {
connectSSE();
}, 1000);
})
.catch(error => {
console.error('Error fetching initial notifications:', error);
@ -143,12 +139,12 @@
eventSource.close();
}
// ✅ FIXED URL HERE
eventSource = new EventSource("/sse/notifications/?last_id=" + lastNotificationId);
eventSource.addEventListener('notification', function(e) {
try {
const data = JSON.parse(e.data);
if (seenNotificationIds.has(data.id)) return;
seenNotificationIds.add(data.id);
@ -158,6 +154,11 @@
updateCounter('increment');
if (!notificationsContainer) {
console.warn("Notification container missing, can't render SSE event");
return;
}
const notificationElement = createNotificationElement(data);
notificationsContainer.insertAdjacentHTML('afterbegin', notificationElement);
@ -168,7 +169,7 @@
Toast.fire({
icon: 'info',
html:`${data.message}`
html: `${data.message}`
});
} catch (error) {
@ -220,7 +221,7 @@
</div>
</div>
</div>
`;
`;
}
function updateCounter(action) {
@ -231,12 +232,14 @@
if (notificationCountDiv) {
notificationCountDiv.innerHTML = `
<span class="badge bg-danger rounded-pill" id="notification-counter" style="position: absolute; top: 8px; right: 3px; font-size: 0.50rem;">0</span>
`;
`;
counter = document.getElementById('notification-counter');
}
}
}
if (!counter) return;
let currentCount = parseInt(counter.textContent) || 0;
if (action === 'increment') {
@ -294,11 +297,11 @@
notificationCard.closest('.notification-card').classList.add('fade-out');
setTimeout(() => {
notificationCard.closest('.notification-card').remove();
}, 200);
}, 1000);
}
}
});
}
});
});
</script>
</script>