CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-django-notifications-hq

GitHub notifications alike app for Django providing comprehensive activity tracking and notification features.

Pending
Overview
Eval results
Files

api-endpoints.mddocs/

API Endpoints

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.

Capabilities

Notification Count Endpoints

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}
    """

Notification List Endpoints

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
            ]
        }
    """

Usage Examples

Basic AJAX Requests

// 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: [] };
    }
}

Live Notification Badge

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);

Live Notification List

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);

Mark as Read with API

// 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);
    }
}

Real-time Dashboard Widget

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
});

Error Handling and Fallbacks

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

docs

admin-integration.md

api-endpoints.md

configuration.md

core-system.md

index.md

signals.md

template-integration.md

utilities.md

web-interface.md

tile.json