From 1d99ef69d1cc8c9074e58c242d911b9e7dcc63d1 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Sat, 13 Feb 2016 05:25:57 +0000 Subject: [PATCH 1/2] Front-end notification model Context processor to count notifications for display in base template Logging handler to generate notifications for users with permission --- README.md | 3 ++- alliance_auth/settings.py.example | 2 ++ notifications/__init__.py | 0 notifications/admin.py | 3 +++ notifications/context_processors.py | 4 ++++ notifications/handlers.py | 14 ++++++++++++++ notifications/models.py | 28 ++++++++++++++++++++++++++++ notifications/tests.py | 3 +++ notifications/views.py | 26 ++++++++++++++++++++++++++ util/__init__.py | 1 + 10 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 notifications/__init__.py create mode 100644 notifications/admin.py create mode 100644 notifications/context_processors.py create mode 100644 notifications/handlers.py create mode 100644 notifications/models.py create mode 100644 notifications/tests.py create mode 100644 notifications/views.py diff --git a/README.md b/README.md index d38b8bd6..d23a57bf 100755 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Join us in-game in the channel allianceauth for help and feature requests. Special Thanks: - Thanking [Nikdoof](https://github.com/nikdoof), without his old auth + Thanking Nikdoof, without his old auth implementation this project wouldn't be as far as it is now. Thanks to Raynaldo for his original work on this system and getting it as far as it is today. @@ -74,6 +74,7 @@ Special Permissions In Admin: auth | user | sigtracker_view ( Allows for an individual view signitures) auth | user | optimer_management ( Allows for an individual to create and remove fleet operations) auth | user | optimer_view ( Allows for an individual view fleet operations) + auth | user | logging_notifications ( Generate notifications from logging) Active Developers diff --git a/alliance_auth/settings.py.example b/alliance_auth/settings.py.example index 56ea232b..ed91e333 100755 --- a/alliance_auth/settings.py.example +++ b/alliance_auth/settings.py.example @@ -61,6 +61,7 @@ INSTALLED_APPS = ( 'sigtracker', 'optimer', 'corputils', + 'notifications', ) MIDDLEWARE_CLASSES = ( @@ -126,6 +127,7 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'util.context_processors.domain_url', 'util.context_processors.member_api_mask', 'util.context_processors.blue_api_mask', + 'notifications.context_processors.user_notification_count', ) TEMPLATE_DIRS = ( diff --git a/notifications/__init__.py b/notifications/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/notifications/admin.py b/notifications/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/notifications/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/notifications/context_processors.py b/notifications/context_processors.py new file mode 100644 index 00000000..d5985eff --- /dev/null +++ b/notifications/context_processors.py @@ -0,0 +1,4 @@ +from .models import Notification + +def user_notification_count(request): + return len(Notification.objects.filter(user=request.user).filter(viewed=False)) diff --git a/notifications/handlers.py b/notifications/handlers.py new file mode 100644 index 00000000..a047e279 --- /dev/null +++ b/notifications/handlers.py @@ -0,0 +1,14 @@ +import logging +from django.contrib.auth.models import User +from .models import Notification + +def NotificationHandler(logging.Handler): + def emit(self, record): + for user in User.objects.all(): + if user.has_perm('auth.logging_notifications'): + notif = Notification() + notif.user = user + notif.title = "%s [%s:%s]" % (record.levelname, record.funcName, record.lineno) + notif.level = str([item[0] for item in Notification.LEVEL_CHOICES if item[1] == record.levelname]) + notif.message = record.getMessage() + notif.save() diff --git a/notifications/models.py b/notifications/models.py new file mode 100644 index 00000000..f92c6852 --- /dev/null +++ b/notifications/models.py @@ -0,0 +1,28 @@ +from django.db import models +from django.contrib.auth.models import User + +class Notification(models.Model): + + LEVEL_CHOICES = ( + ('danger', 'CRITICAL'), + ('danger', 'ERROR'), + ('warning', 'WARN'), + ('info', 'INFO'), + ('success', 'DEBUG'), + ) + + user = models.ForeignKey(User, on_delete=models.CASCADE) + level = models.CharField(choices=LEVEL_CHOICES, max_length=10) + title = models.CharField(max_length=254) + message = models.TextField() + created = models.DateTimeField(auto_now_add=True) + viewed = models.BooleanField() + + def view(self): + logger.info("Marking notification as viewed: %s" % self) + self.viewed = True + self.save() + + def __unicode__(self): + output = "%s: %s" % (self.user, self.title) + return output.encode('utf-8') diff --git a/notifications/tests.py b/notifications/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/notifications/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/notifications/views.py b/notifications/views.py new file mode 100644 index 00000000..63076dd6 --- /dev/null +++ b/notifications/views.py @@ -0,0 +1,26 @@ +from django.shortcuts import render, get_object_or_404 +from .models import Notification + +@login_required +def notification_list(request): + logger.debug("notification_list called by user %s" % request.user) + new_notifs = Notification.objects.filter(user=request.user).filter(read=False) + old_notifs = Notification.objects.filter(user=request.user).filter(read=True) + logger.debug("User %s has %s unread and %s read notifications" % (request.user, len(new_notifs), len(old_notifs))) + context = { + 'read': old_notifs, + 'unread': new_notifs, + } + return render(request, 'registered/notification_list.html', context) + +@login_required +def notification_view(request, notif_id): + logger.debug("notification_view called by user %s for notif_id %s" % (request.user, notif_id)) + notif = get_object_or_404(notification, pk=notif_id) + if notif.user == request.user: + logger.debug("Providing notification for user %s" % request.user) + context = {'notification': notif} + notif.view() + return render(request, 'registered/notification_view.html', context) + else: + logger.warn("User %s not authorized to view notif_id %s belonging to user %s" % (request.user, notif_id, notif.user)) diff --git a/util/__init__.py b/util/__init__.py index 6f90971a..339b36bb 100755 --- a/util/__init__.py +++ b/util/__init__.py @@ -27,6 +27,7 @@ def bootstrap_permissions(): Permission.objects.get_or_create(codename="signature_view", content_type=ct, name="signature_view") Permission.objects.get_or_create(codename="optimer_management", content_type=ct, name="optimer_management") Permission.objects.get_or_create(codename="optimer_view", content_type=ct, name="optimer_view") + Permission.objects.get_or_create(codename="logging_notifications", content_type=ct, name="logging_notifications") Group.objects.get_or_create(name=settings.DEFAULT_AUTH_GROUP) Group.objects.get_or_create(name=settings.DEFAULT_BLUE_GROUP) logger.info("Bootstrapped permissions for auth and created default groups.") From 5e2c828c3b744807b3ea337c5d4fd711ca6def41 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Sat, 13 Feb 2016 21:53:43 +0000 Subject: [PATCH 2/2] Front-end notification system Currently only creates notifications for logging events Addresses #75 --- alliance_auth/settings.py.example | 37 ++++++---- alliance_auth/urls.py | 3 + notifications/admin.py | 3 +- notifications/context_processors.py | 2 +- notifications/handlers.py | 4 +- notifications/models.py | 10 ++- notifications/views.py | 15 ++-- stock/templates/public/base.html | 9 +++ .../registered/notification_list.html | 74 +++++++++++++++++++ .../registered/notification_view.html | 22 ++++++ 10 files changed, 152 insertions(+), 27 deletions(-) create mode 100644 stock/templates/registered/notification_list.html create mode 100644 stock/templates/registered/notification_view.html diff --git a/alliance_auth/settings.py.example b/alliance_auth/settings.py.example index ed91e333..830ed9ad 100755 --- a/alliance_auth/settings.py.example +++ b/alliance_auth/settings.py.example @@ -410,67 +410,72 @@ LOGGING = { 'level': 'DEBUG', # edit this line to change logging level to console 'class': 'logging.StreamHandler', 'formatter': 'verbose', - } + }, + 'notifications': { # creates notifications for users with logging_notifications permission + 'level': 'ERROR', # edit this line to change logging level to notifications + 'class': 'notifications.handlers.NotificationHandler', + 'formatter': 'verbose', + }, }, 'loggers': { 'authentication': { - 'handlers': ['log_file', 'console'], + 'handlers': ['log_file', 'console', 'notifications'], 'level': 'DEBUG', }, 'celerytask': { - 'handlers': ['log_file', 'console'], + 'handlers': ['log_file', 'console', 'notifications'], 'level': 'DEBUG', }, 'eveonline': { - 'handlers': ['log_file', 'console'], + 'handlers': ['log_file', 'console', 'notifications'], 'level': 'DEBUG', }, 'groupmanagement': { - 'handlers': ['log_file', 'console'], + 'handlers': ['log_file', 'console', 'notifications'], 'level': 'DEBUG', }, 'hrapplications': { - 'handlers': ['log_file', 'console'], + 'handlers': ['log_file', 'console', 'notifications'], 'level': 'DEBUG', }, 'portal': { - 'handlers': ['log_file', 'console'], + 'handlers': ['log_file', 'console', 'notifications'], 'level': 'DEBUG', }, 'registration': { - 'handlers': ['log_file', 'console'], + 'handlers': ['log_file', 'console', 'notifications'], 'level': 'DEBUG', }, 'services': { - 'handlers': ['log_file', 'console'], + 'handlers': ['log_file', 'console', 'notifications'], 'level': 'DEBUG', }, 'srp': { - 'handlers': ['log_file', 'console'], + 'handlers': ['log_file', 'console', 'notifications'], 'level': 'DEBUG', }, 'timerboard': { - 'handlers': ['log_file', 'console'], + 'handlers': ['log_file', 'console', 'notifications'], 'level': 'DEBUG', }, 'sigtracker': { - 'handlers': ['log_file', 'console'], + 'handlers': ['log_file', 'console', 'notifications'], 'level': 'DEBUG', }, 'optimer': { - 'handlers': ['log_file', 'console'], + 'handlers': ['log_file', 'console', 'notifications'], 'level': 'DEBUG', }, 'corputils': { - 'handlers': ['log_file', 'console'], + 'handlers': ['log_file', 'console', 'notifications'], 'level': 'DEBUG', }, 'util': { - 'handlers': ['log_file', 'console'], + 'handlers': ['log_file', 'console', 'notifications'], 'level': 'DEBUG', }, 'django': { - 'handlers': ['log_file', 'console'], + 'handlers': ['log_file', 'console', 'notifications'], 'level': 'ERROR', }, } diff --git a/alliance_auth/urls.py b/alliance_auth/urls.py index df632863..5325ea52 100755 --- a/alliance_auth/urls.py +++ b/alliance_auth/urls.py @@ -186,4 +186,7 @@ urlpatterns = patterns('', url(r'^remove_optimer/(\w+)', 'optimer.views.remove_optimer', name='auth_remove_optimer'), url(r'^edit_optimer/(\w+)$', 'optimer.views.edit_optimer', name='auth_edit_optimer'), + # Notifications + url(r'^notifications/$', 'notifications.views.notification_list', name='auth_notification_list'), + url(r'^notifications/(\w+)/$', 'notifications.views.notification_view', name='auth_notification_view'), ) diff --git a/notifications/admin.py b/notifications/admin.py index 8c38f3f3..56893293 100644 --- a/notifications/admin.py +++ b/notifications/admin.py @@ -1,3 +1,4 @@ from django.contrib import admin +from.models import Notification -# Register your models here. +admin.site.register(Notification) diff --git a/notifications/context_processors.py b/notifications/context_processors.py index d5985eff..1c9a5385 100644 --- a/notifications/context_processors.py +++ b/notifications/context_processors.py @@ -1,4 +1,4 @@ from .models import Notification def user_notification_count(request): - return len(Notification.objects.filter(user=request.user).filter(viewed=False)) + return {'notifications':len(Notification.objects.filter(user__id=request.user.id).filter(viewed=False))} diff --git a/notifications/handlers.py b/notifications/handlers.py index a047e279..bdffeb33 100644 --- a/notifications/handlers.py +++ b/notifications/handlers.py @@ -2,13 +2,13 @@ import logging from django.contrib.auth.models import User from .models import Notification -def NotificationHandler(logging.Handler): +class NotificationHandler(logging.Handler): def emit(self, record): for user in User.objects.all(): if user.has_perm('auth.logging_notifications'): notif = Notification() notif.user = user notif.title = "%s [%s:%s]" % (record.levelname, record.funcName, record.lineno) - notif.level = str([item[0] for item in Notification.LEVEL_CHOICES if item[1] == record.levelname]) + notif.level = str([item[0] for item in Notification.LEVEL_CHOICES if item[1] == record.levelname][0]) notif.message = record.getMessage() notif.save() diff --git a/notifications/models.py b/notifications/models.py index f92c6852..cf0d688d 100644 --- a/notifications/models.py +++ b/notifications/models.py @@ -1,5 +1,8 @@ from django.db import models from django.contrib.auth.models import User +import logging + +logger = logging.getLogger(__name__) class Notification(models.Model): @@ -15,8 +18,8 @@ class Notification(models.Model): level = models.CharField(choices=LEVEL_CHOICES, max_length=10) title = models.CharField(max_length=254) message = models.TextField() - created = models.DateTimeField(auto_now_add=True) - viewed = models.BooleanField() + timestamp = models.DateTimeField(auto_now_add=True) + viewed = models.BooleanField(default=False) def view(self): logger.info("Marking notification as viewed: %s" % self) @@ -26,3 +29,6 @@ class Notification(models.Model): def __unicode__(self): output = "%s: %s" % (self.user, self.title) return output.encode('utf-8') + + def set_level(self, level): + self.level = [item[0] for item in self.LEVEL_CHOICES if item[1] == level][0] diff --git a/notifications/views.py b/notifications/views.py index 63076dd6..7fa53a48 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -1,11 +1,15 @@ -from django.shortcuts import render, get_object_or_404 +from django.shortcuts import render, get_object_or_404, redirect from .models import Notification +from django.contrib.auth.decorators import login_required +import logging + +logger = logging.getLogger(__name__) @login_required def notification_list(request): logger.debug("notification_list called by user %s" % request.user) - new_notifs = Notification.objects.filter(user=request.user).filter(read=False) - old_notifs = Notification.objects.filter(user=request.user).filter(read=True) + new_notifs = Notification.objects.filter(user=request.user).filter(viewed=False) + old_notifs = Notification.objects.filter(user=request.user).filter(viewed=True) logger.debug("User %s has %s unread and %s read notifications" % (request.user, len(new_notifs), len(old_notifs))) context = { 'read': old_notifs, @@ -16,11 +20,12 @@ def notification_list(request): @login_required def notification_view(request, notif_id): logger.debug("notification_view called by user %s for notif_id %s" % (request.user, notif_id)) - notif = get_object_or_404(notification, pk=notif_id) + notif = get_object_or_404(Notification, pk=notif_id) if notif.user == request.user: logger.debug("Providing notification for user %s" % request.user) - context = {'notification': notif} + context = {'notif': notif} notif.view() return render(request, 'registered/notification_view.html', context) else: logger.warn("User %s not authorized to view notif_id %s belonging to user %s" % (request.user, notif_id, notif.user)) + return redirect('auth_notification_list') diff --git a/stock/templates/public/base.html b/stock/templates/public/base.html index a1c89870..b9f81154 100755 --- a/stock/templates/public/base.html +++ b/stock/templates/public/base.html @@ -47,6 +47,15 @@