GitHub notifications alike app for Django providing comprehensive activity tracking and notification features.
—
Admin interface configuration for managing notifications with proper field displays, filtering options, and bulk actions for administrative oversight and debugging.
Django admin configuration classes for managing notifications in the Django admin interface.
class AbstractNotificationAdmin(admin.ModelAdmin):
"""Base admin class for notification models."""
raw_id_fields = ('recipient',)
list_display = ('recipient', 'actor', 'level', 'target', 'unread', 'public')
list_filter = ('level', 'unread', 'public', 'timestamp')
def get_queryset(self, request):
"""
Optimize queryset with prefetch_related for better performance.
Args:
request: Django admin request object
Returns:
QuerySet: Optimized queryset with prefetched actors
"""
class NotificationAdmin(AbstractNotificationAdmin):
"""Concrete admin class for Notification model."""
raw_id_fields = ('recipient',)
readonly_fields = ('action_object_url', 'actor_object_url', 'target_object_url')
list_display = ('recipient', 'actor', 'level', 'target', 'unread', 'public')
list_filter = ('level', 'unread', 'public', 'timestamp')
actions = [mark_unread]
def get_queryset(self, request):
"""
Get optimized queryset for admin display.
Args:
request: Django admin request object
Returns:
QuerySet: Notifications with prefetched related objects
"""Custom admin actions for bulk operations on notifications.
def mark_unread(modeladmin, request, queryset):
"""
Admin action to mark selected notifications as unread.
Args:
modeladmin: Admin model instance
request: Django admin request object
queryset: Selected notification objects
Returns:
None: Updates notifications in place
Description:
Bulk action that sets unread=True for selected notifications
"""# In your admin.py
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from notifications.models import Notification
from notifications.admin import NotificationAdmin as BaseNotificationAdmin
# Extend the base admin class
class CustomNotificationAdmin(BaseNotificationAdmin):
# Add more fields to list display
list_display = (
'recipient',
'actor',
'verb',
'level',
'target',
'unread',
'public',
'timestamp',
'get_description_preview'
)
# Add more filter options
list_filter = (
'level',
'unread',
'public',
'timestamp',
'deleted',
'emailed'
)
# Enable search
search_fields = (
'recipient__username',
'recipient__email',
'verb',
'description'
)
# Add readonly fields
readonly_fields = (
'action_object_url',
'actor_object_url',
'target_object_url',
'timestamp',
'slug'
)
# Organize fields in fieldsets
fieldsets = (
('Basic Information', {
'fields': ('recipient', 'level', 'unread', 'public')
}),
('Activity Details', {
'fields': ('actor_content_type', 'actor_object_id', 'verb', 'description')
}),
('Related Objects', {
'fields': ('target_content_type', 'target_object_id', 'action_object_content_type', 'action_object_object_id'),
'classes': ('collapse',)
}),
('Metadata', {
'fields': ('timestamp', 'deleted', 'emailed', 'data'),
'classes': ('collapse',)
}),
('Admin Links', {
'fields': ('actor_object_url', 'target_object_url', 'action_object_url'),
'classes': ('collapse',)
})
)
# Custom methods for display
def get_description_preview(self, obj):
"""Show truncated description in list view."""
if obj.description:
return obj.description[:50] + '...' if len(obj.description) > 50 else obj.description
return '-'
get_description_preview.short_description = 'Description'
def get_actor_type(self, obj):
"""Show actor content type."""
return obj.actor_content_type.model
get_actor_type.short_description = 'Actor Type'
# Add custom actions
actions = ['mark_unread', 'mark_read', 'soft_delete', 'mark_as_sent']
def mark_read(self, request, queryset):
"""Mark selected notifications as read."""
updated = queryset.update(unread=False)
self.message_user(request, f'{updated} notifications marked as read.')
mark_read.short_description = 'Mark selected notifications as read'
def soft_delete(self, request, queryset):
"""Soft delete selected notifications."""
updated = queryset.update(deleted=True)
self.message_user(request, f'{updated} notifications soft deleted.')
soft_delete.short_description = 'Soft delete selected notifications'
def mark_as_sent(self, request, queryset):
"""Mark selected notifications as emailed."""
updated = queryset.update(emailed=True)
self.message_user(request, f'{updated} notifications marked as sent.')
mark_as_sent.short_description = 'Mark selected notifications as emailed'
# Unregister the default admin and register custom one
admin.site.unregister(Notification)
admin.site.register(Notification, CustomNotificationAdmin)from django.contrib import admin
from notifications.models import Notification
class NotificationAnalyticsAdmin(admin.ModelAdmin):
"""Read-only admin for analytics and reporting."""
list_display = (
'id',
'recipient',
'actor',
'verb',
'level',
'timestamp',
'unread',
'get_time_since_created'
)
list_filter = (
'level',
'unread',
'public',
'timestamp',
('timestamp', admin.DateFieldListFilter),
)
search_fields = ('recipient__username', 'verb', 'description')
# Make it read-only
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
def get_time_since_created(self, obj):
"""Show time since notification was created."""
return obj.timesince()
get_time_since_created.short_description = 'Age'
# Add date hierarchy for easy browsing
date_hierarchy = 'timestamp'
# Show more items per page
list_per_page = 50
# Register as separate admin interface
admin.site.register(Notification, NotificationAnalyticsAdmin, name='notification_analytics')from django.contrib import admin
from notifications.models import Notification
class NotificationInline(admin.TabularInline):
"""Inline admin for showing notifications on related models."""
model = Notification
extra = 0
readonly_fields = ('timestamp', 'level', 'verb', 'unread')
fields = ('timestamp', 'level', 'verb', 'unread', 'description')
def has_add_permission(self, request, obj=None):
return False
# Add to User admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
class UserAdmin(BaseUserAdmin):
inlines = [NotificationInline]
admin.site.unregister(User)
admin.site.register(User, UserAdmin)from django.contrib import admin
from django.utils.translation import gettext_lazy as _
class UnreadFilter(admin.SimpleListFilter):
"""Custom filter for unread status."""
title = _('read status')
parameter_name = 'read_status'
def lookups(self, request, model_admin):
return (
('unread', _('Unread')),
('read', _('Read')),
('recent_unread', _('Unread (last 7 days)')),
)
def queryset(self, request, queryset):
if self.value() == 'unread':
return queryset.filter(unread=True)
elif self.value() == 'read':
return queryset.filter(unread=False)
elif self.value() == 'recent_unread':
from django.utils import timezone
from datetime import timedelta
week_ago = timezone.now() - timedelta(days=7)
return queryset.filter(unread=True, timestamp__gte=week_ago)
class ActorTypeFilter(admin.SimpleListFilter):
"""Filter by actor model type."""
title = _('actor type')
parameter_name = 'actor_type'
def lookups(self, request, model_admin):
# Get unique actor content types
from django.contrib.contenttypes.models import ContentType
actor_types = Notification.objects.values_list(
'actor_content_type', flat=True
).distinct()
return [
(ct_id, ContentType.objects.get(id=ct_id).model.title())
for ct_id in actor_types
]
def queryset(self, request, queryset):
if self.value():
return queryset.filter(actor_content_type=self.value())
class CustomNotificationAdmin(admin.ModelAdmin):
list_filter = (
UnreadFilter,
ActorTypeFilter,
'level',
'public',
'timestamp'
)from django.contrib import admin
from django.http import HttpResponse
import csv
class NotificationDataAdmin(admin.ModelAdmin):
"""Admin with data export and bulk management features."""
actions = [
'export_as_csv',
'bulk_mark_read',
'bulk_soft_delete',
'cleanup_old_notifications'
]
def export_as_csv(self, request, queryset):
"""Export selected notifications as CSV."""
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="notifications.csv"'
writer = csv.writer(response)
writer.writerow([
'ID', 'Recipient', 'Actor', 'Verb', 'Level',
'Timestamp', 'Unread', 'Description'
])
for notification in queryset:
writer.writerow([
notification.id,
notification.recipient.username,
str(notification.actor),
notification.verb,
notification.level,
notification.timestamp,
notification.unread,
notification.description or ''
])
return response
export_as_csv.short_description = 'Export selected notifications as CSV'
def bulk_mark_read(self, request, queryset):
"""Mark all selected notifications as read."""
count = queryset.filter(unread=True).update(unread=False)
self.message_user(request, f'{count} notifications marked as read.')
bulk_mark_read.short_description = 'Mark selected as read'
def bulk_soft_delete(self, request, queryset):
"""Soft delete selected notifications."""
count = queryset.update(deleted=True)
self.message_user(request, f'{count} notifications soft deleted.')
bulk_soft_delete.short_description = 'Soft delete selected'
def cleanup_old_notifications(self, request, queryset):
"""Delete notifications older than 30 days."""
from django.utils import timezone
from datetime import timedelta
cutoff_date = timezone.now() - timedelta(days=30)
old_notifications = queryset.filter(timestamp__lt=cutoff_date)
count = old_notifications.count()
old_notifications.delete()
self.message_user(request, f'{count} old notifications deleted.')
cleanup_old_notifications.short_description = 'Delete notifications older than 30 days'from django.contrib import admin
class PermissionBasedNotificationAdmin(admin.ModelAdmin):
"""Admin with permission-based access control."""
def get_queryset(self, request):
"""Filter notifications based on user permissions."""
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
elif request.user.has_perm('notifications.view_all_notifications'):
return qs
else:
# Only show notifications for users the admin can manage
managed_users = self.get_managed_users(request.user)
return qs.filter(recipient__in=managed_users)
def get_managed_users(self, admin_user):
"""Get users this admin can manage."""
# Example: staff can manage users in their department
if hasattr(admin_user, 'department'):
return admin_user.department.users.all()
return []
def has_change_permission(self, request, obj=None):
"""Check if user can change specific notification."""
if not super().has_change_permission(request, obj):
return False
if obj and not request.user.is_superuser:
# Check if admin can manage this notification's recipient
managed_users = self.get_managed_users(request.user)
return obj.recipient in managed_users
return Truefrom django.contrib import admin
from myapp.models import BlogPost
from notifications.models import Notification
class BlogPostAdmin(admin.ModelAdmin):
"""Blog post admin with notification management."""
def save_model(self, request, obj, form, change):
"""Send notification when blog post is published."""
super().save_model(request, obj, form, change)
if not change and obj.status == 'published':
# Send notification to followers
from notifications.signals import notify
followers = obj.author.followers.all()
notify.send(
sender=obj.author,
recipient=followers,
verb='published new post',
target=obj,
description=f'New blog post: {obj.title}'
)
self.message_user(
request,
f'Post published and {followers.count()} followers notified.'
)
def get_notification_count(self, obj):
"""Show notification count for this post."""
return Notification.objects.filter(
target_content_type__model='blogpost',
target_object_id=obj.id
).count()
get_notification_count.short_description = 'Notifications'
admin.site.register(BlogPost, BlogPostAdmin)Install with Tessl CLI
npx tessl i tessl/pypi-django-notifications-hq