GitHub notifications alike app for Django providing comprehensive activity tracking and notification features.
—
JSON API endpoints for real-time notification features including unread counts, notification lists, and AJAX-powered live updates for single-page applications and mobile apps.
JSON endpoints that return notification counts for authenticated users with caching and performance optimization.
@never_cache
def live_unread_notification_count(request):
"""
Return JSON with unread notification count for authenticated user.
Args:
request: Django HTTP request object
Returns:
JsonResponse: {"unread_count": <number>}
Response for unauthenticated users:
{"unread_count": 0}
Example response:
{"unread_count": 5}
"""
@never_cache
def live_all_notification_count(request):
"""
Return JSON with total notification count for authenticated user.
Args:
request: Django HTTP request object
Returns:
JsonResponse: {"all_count": <number>}
Response for unauthenticated users:
{"all_count": 0}
Example response:
{"all_count": 25}
"""JSON endpoints that return formatted notification lists with configurable limits and optional mark-as-read functionality.
@never_cache
def live_unread_notification_list(request):
"""
Return JSON with unread notification list and count.
Args:
request: Django HTTP request object
Query Parameters:
max (int, optional): Maximum notifications to return (1-100, default from settings)
mark_as_read (str, optional): Mark returned notifications as read if present
Returns:
JsonResponse: {
"unread_count": <number>,
"unread_list": [<notification_objects>]
}
Response for unauthenticated users:
{"unread_count": 0, "unread_list": []}
Example response:
{
"unread_count": 3,
"unread_list": [
{
"id": 123,
"slug": 110909,
"actor": "John Doe",
"verb": "liked",
"target": "My Blog Post",
"action_object": null,
"description": "John liked your post",
"timestamp": "2023-01-15T10:30:00Z",
"unread": true,
"level": "info",
"data": {}
},
// ... more notifications
]
}
"""
@never_cache
def live_all_notification_list(request):
"""
Return JSON with all notification list and count.
Args:
request: Django HTTP request object
Query Parameters:
max (int, optional): Maximum notifications to return (1-100, default from settings)
mark_as_read (str, optional): Mark returned notifications as read if present
Returns:
JsonResponse: {
"all_count": <number>,
"all_list": [<notification_objects>]
}
Response for unauthenticated users:
{"all_count": 0, "all_list": []}
Example response:
{
"all_count": 25,
"all_list": [
{
"id": 123,
"slug": 110909,
"actor": "John Doe",
"verb": "liked",
"target": "My Blog Post",
"action_object": null,
"description": "John liked your post",
"timestamp": "2023-01-15T10:30:00Z",
"unread": false,
"level": "info",
"data": {}
},
// ... more notifications
]
}
"""// Get unread notification count
async function getUnreadCount() {
try {
const response = await fetch('/inbox/notifications/api/unread_count/');
const data = await response.json();
return data.unread_count;
} catch (error) {
console.error('Error fetching unread count:', error);
return 0;
}
}
// Get unread notification list
async function getUnreadNotifications(maxCount = 10) {
try {
const response = await fetch(`/inbox/notifications/api/unread_list/?max=${maxCount}`);
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching notifications:', error);
return { unread_count: 0, unread_list: [] };
}
}
// Get all notifications
async function getAllNotifications(maxCount = 20) {
try {
const response = await fetch(`/inbox/notifications/api/all_list/?max=${maxCount}`);
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching all notifications:', error);
return { all_count: 0, all_list: [] };
}
}class NotificationBadge {
constructor(badgeElement, apiUrl = '/inbox/notifications/api/unread_count/') {
this.badge = badgeElement;
this.apiUrl = apiUrl;
this.updateInterval = 30000; // 30 seconds
this.startPolling();
}
async updateBadge() {
try {
const response = await fetch(this.apiUrl);
const data = await response.json();
const count = data.unread_count;
this.badge.textContent = count;
this.badge.style.display = count > 0 ? 'inline' : 'none';
// Add visual indication for new notifications
if (count > this.lastCount) {
this.badge.classList.add('pulse');
setTimeout(() => this.badge.classList.remove('pulse'), 1000);
}
this.lastCount = count;
} catch (error) {
console.error('Failed to update notification badge:', error);
}
}
startPolling() {
this.updateBadge(); // Initial update
this.intervalId = setInterval(() => this.updateBadge(), this.updateInterval);
}
stopPolling() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
}
// Usage
const badge = document.querySelector('.notification-badge');
const notificationBadge = new NotificationBadge(badge);class NotificationList {
constructor(containerElement, apiUrl = '/inbox/notifications/api/unread_list/') {
this.container = containerElement;
this.apiUrl = apiUrl;
this.updateInterval = 15000; // 15 seconds
this.startPolling();
}
async updateList() {
try {
const response = await fetch(`${this.apiUrl}?max=10`);
const data = await response.json();
this.renderNotifications(data.unread_list);
this.updateCount(data.unread_count);
} catch (error) {
console.error('Failed to update notification list:', error);
}
}
renderNotifications(notifications) {
this.container.innerHTML = '';
if (notifications.length === 0) {
this.container.innerHTML = '<li class="no-notifications">No new notifications</li>';
return;
}
notifications.forEach(notification => {
const li = document.createElement('li');
li.className = 'notification-item';
li.innerHTML = `
<div class="notification-content">
<strong>${notification.actor}</strong> ${notification.verb}
${notification.target ? ` ${notification.target}` : ''}
<small>${this.formatTime(notification.timestamp)}</small>
</div>
<div class="notification-actions">
<button onclick="markAsRead(${notification.slug})">Mark as Read</button>
</div>
`;
this.container.appendChild(li);
});
}
formatTime(timestamp) {
const date = new Date(timestamp);
const now = new Date();
const diffMs = now - date;
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 1) return 'just now';
if (diffMins < 60) return `${diffMins}m ago`;
if (diffMins < 1440) return `${Math.floor(diffMins / 60)}h ago`;
return `${Math.floor(diffMins / 1440)}d ago`;
}
updateCount(count) {
const countElement = document.querySelector('.notification-count');
if (countElement) {
countElement.textContent = count;
}
}
startPolling() {
this.updateList(); // Initial load
this.intervalId = setInterval(() => this.updateList(), this.updateInterval);
}
stopPolling() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
}
// Usage
const listContainer = document.querySelector('.notification-list');
const notificationList = new NotificationList(listContainer);// Get notifications and mark them as read
async function getAndMarkNotifications() {
try {
const response = await fetch('/inbox/notifications/api/unread_list/?mark_as_read=true');
const data = await response.json();
// Process notifications that were automatically marked as read
console.log(`Retrieved and marked ${data.unread_list.length} notifications as read`);
return data;
} catch (error) {
console.error('Error:', error);
}
}class NotificationWidget {
constructor(options = {}) {
this.options = {
updateInterval: 30000,
maxNotifications: 5,
showBadge: true,
showList: true,
...options
};
this.init();
}
init() {
this.createWidget();
this.startUpdates();
}
createWidget() {
this.widget = document.createElement('div');
this.widget.className = 'notification-widget';
this.widget.innerHTML = `
<div class="widget-header">
<span>Notifications</span>
${this.options.showBadge ? '<span class="badge">0</span>' : ''}
</div>
${this.options.showList ? '<ul class="notification-list"></ul>' : ''}
`;
document.body.appendChild(this.widget);
}
async update() {
const [countData, listData] = await Promise.all([
fetch('/inbox/notifications/api/unread_count/').then(r => r.json()),
this.options.showList ?
fetch(`/inbox/notifications/api/unread_list/?max=${this.options.maxNotifications}`)
.then(r => r.json()) :
Promise.resolve(null)
]);
// Update badge
if (this.options.showBadge) {
const badge = this.widget.querySelector('.badge');
badge.textContent = countData.unread_count;
badge.style.display = countData.unread_count > 0 ? 'inline' : 'none';
}
// Update list
if (this.options.showList && listData) {
this.renderList(listData.unread_list);
}
}
renderList(notifications) {
const list = this.widget.querySelector('.notification-list');
list.innerHTML = notifications.map(n => `
<li class="notification-item" data-id="${n.id}">
<div class="notification-text">
${n.description || `${n.actor} ${n.verb}`}
</div>
<div class="notification-time">
${this.formatTime(n.timestamp)}
</div>
</li>
`).join('');
}
formatTime(timestamp) {
return new Date(timestamp).toLocaleDateString();
}
startUpdates() {
this.update(); // Initial update
this.intervalId = setInterval(() => this.update(), this.options.updateInterval);
}
destroy() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
if (this.widget) {
this.widget.remove();
}
}
}
// Usage
const widget = new NotificationWidget({
updateInterval: 20000, // Update every 20 seconds
maxNotifications: 8,
showBadge: true,
showList: true
});class RobustNotificationClient {
constructor() {
this.retryCount = 0;
this.maxRetries = 3;
this.retryDelay = 5000;
}
async fetchWithRetry(url, options = {}) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
this.retryCount = 0; // Reset on success
return await response.json();
} catch (error) {
if (this.retryCount < this.maxRetries) {
this.retryCount++;
console.warn(`Fetch failed, retrying in ${this.retryDelay}ms (attempt ${this.retryCount}/${this.maxRetries})`);
await new Promise(resolve => setTimeout(resolve, this.retryDelay));
return this.fetchWithRetry(url, options);
} else {
console.error('Max retries exceeded:', error);
throw error;
}
}
}
async getNotifications() {
try {
return await this.fetchWithRetry('/inbox/notifications/api/unread_list/');
} catch (error) {
// Return fallback data
return { unread_count: 0, unread_list: [] };
}
}
}Install with Tessl CLI
npx tessl i tessl/pypi-django-notifications-hq