Add support for global and object event notification. GitLab issue #262.

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
Roberto Rosario
2017-08-01 01:18:07 -04:00
parent 5083a2d261
commit c0407652c0
30 changed files with 1193 additions and 111 deletions

View File

@@ -5,6 +5,7 @@ import logging
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common import MayanAppConfig, menu_user from common import MayanAppConfig, menu_user
from navigation.classes import Separator, Text
from .links import link_logout, link_password_change from .links import link_logout, link_password_change
@@ -21,6 +22,6 @@ class AuthenticationApp(MayanAppConfig):
menu_user.bind_links( menu_user.bind_links(
links=( links=(
link_password_change, link_logout Separator(), link_password_change, link_logout
), position=99 ), position=99
) )

View File

@@ -12,9 +12,14 @@ from django.utils.translation import ugettext_lazy as _
from acls import ModelPermission from acls import ModelPermission
from common import MayanAppConfig, menu_facet, menu_main, menu_sidebar from common import MayanAppConfig, menu_facet, menu_main, menu_sidebar
from common.classes import DashboardWidget from common.classes import DashboardWidget
from events import ModelEventType
from mayan.celery import app from mayan.celery import app
from rest_api.classes import APIEndPoint from rest_api.classes import APIEndPoint
from .events import (
event_document_auto_check_in, event_document_check_in,
event_document_check_out, event_document_forceful_check_in
)
from .handlers import check_new_version_creation from .handlers import check_new_version_creation
from .links import ( from .links import (
link_checkin_document, link_checkout_document, link_checkout_info, link_checkin_document, link_checkout_document, link_checkout_info,
@@ -82,6 +87,13 @@ class CheckoutsApp(MayanAppConfig):
) )
) )
ModelEventType.register(
model=Document, event_types=(
event_document_auto_check_in, event_document_check_in,
event_document_check_out, event_document_forceful_check_in
)
)
ModelPermission.register( ModelPermission.register(
model=Document, permissions=( model=Document, permissions=(
permission_document_checkout, permission_document_checkout,

View File

@@ -2,19 +2,21 @@ from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from events.classes import Event from events import EventTypeNamespace
event_document_auto_check_in = Event( namespace = EventTypeNamespace(name='checkouts', label=_('Checkouts'))
name='checkouts_document_auto_check_in',
event_document_auto_check_in = namespace.add_event_type(
name='document_auto_check_in',
label=_('Document automatically checked in') label=_('Document automatically checked in')
) )
event_document_check_in = Event( event_document_check_in = namespace.add_event_type(
name='checkouts_document_check_in', label=_('Document checked in') name='document_check_in', label=_('Document checked in')
) )
event_document_check_out = Event( event_document_check_out = namespace.add_event_type(
name='checkouts_document_check_out', label=_('Document checked out') name='document_check_out', label=_('Document checked out')
) )
event_document_forceful_check_in = Event( event_document_forceful_check_in = namespace.add_event_type(
name='checkouts_document_forceful_check_in', name='document_forceful_check_in',
label=_('Document forcefully checked in') label=_('Document forcefully checked in')
) )

View File

@@ -63,8 +63,8 @@ class MayanAppConfig(apps.AppConfig):
except ImportError as exception: except ImportError as exception:
if force_text(exception) != 'No module named urls': if force_text(exception) != 'No module named urls':
logger.error( logger.error(
'Import time error when running AppConfig.ready(). Check ' 'Import time error when running AppConfig.ready() of app '
'apps.py, urls.py, views.py, etc.' '"%s".', self.name
) )
raise exception raise exception
@@ -123,7 +123,6 @@ class CommonApp(MayanAppConfig):
Text(text=CommonApp.get_user_label_text), Separator(), Text(text=CommonApp.get_user_label_text), Separator(),
link_current_user_details, link_current_user_edit, link_current_user_details, link_current_user_edit,
link_current_user_locale_profile_edit, link_current_user_locale_profile_edit,
Separator()
) )
) )

View File

@@ -1,4 +1,4 @@
from __future__ import unicode_literals from __future__ import absolute_import, unicode_literals
from django.apps import apps from django.apps import apps
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@@ -6,9 +6,13 @@ from django.utils.translation import ugettext_lazy as _
from acls import ModelPermission from acls import ModelPermission
from common import MayanAppConfig, menu_facet, menu_object, menu_sidebar from common import MayanAppConfig, menu_facet, menu_object, menu_sidebar
from documents.search import document_page_search, document_search from documents.search import document_page_search, document_search
from events import ModelEventType
from navigation import SourceColumn from navigation import SourceColumn
from rest_api.classes import APIEndPoint from rest_api.classes import APIEndPoint
from .events import (
event_document_comment_create, event_document_comment_delete
)
from .links import ( from .links import (
link_comment_add, link_comment_delete, link_comments_for_document link_comment_add, link_comment_delete, link_comments_for_document
) )
@@ -36,6 +40,12 @@ class DocumentCommentsApp(MayanAppConfig):
Comment = self.get_model('Comment') Comment = self.get_model('Comment')
ModelEventType.register(
model=Document, event_types=(
event_document_comment_create, event_document_comment_delete
)
)
ModelPermission.register( ModelPermission.register(
model=Document, permissions=( model=Document, permissions=(
permission_comment_create, permission_comment_delete, permission_comment_create, permission_comment_delete,

View File

@@ -2,13 +2,15 @@ from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from events.classes import Event from events import EventTypeNamespace
event_document_comment_create = Event( namespace = EventTypeNamespace(
name='document_comment_create', name='document_comments', label=_('Document comments')
label=_('Document comment created')
) )
event_document_comment_delete = Event(
name='document_comment_delete', event_document_comment_create = namespace.add_event_type(
label=_('Document comment deleted') name='create', label=_('Document comment created')
)
event_document_comment_delete = namespace.add_event_type(
name='delete', label=_('Document comment deleted')
) )

View File

@@ -23,7 +23,11 @@ from converter.permissions import (
permission_transformation_delete, permission_transformation_edit, permission_transformation_delete, permission_transformation_edit,
permission_transformation_view, permission_transformation_view,
) )
from events.links import link_events_for_object from events import ModelEventType
from events.links import (
link_events_for_object, link_object_event_types_user_subcriptions_list,
link_object_event_types_user_subcriptions_list_with_icon
)
from events.permissions import permission_events_view from events.permissions import permission_events_view
from mayan.celery import app from mayan.celery import app
from navigation import SourceColumn from navigation import SourceColumn
@@ -31,6 +35,12 @@ from rest_api.classes import APIEndPoint, APIResource
from rest_api.fields import DynamicSerializerField from rest_api.fields import DynamicSerializerField
from statistics.classes import StatisticNamespace, CharJSLine from statistics.classes import StatisticNamespace, CharJSLine
from .events import (
event_document_create, event_document_download,
event_document_properties_edit, event_document_type_change,
event_document_new_version, event_document_version_revert,
event_document_view
)
from .handlers import ( from .handlers import (
create_default_document_type, handler_scan_duplicates_for create_default_document_type, handler_scan_duplicates_for
) )
@@ -171,6 +181,19 @@ class DocumentsApp(MayanAppConfig):
label=_('MIME type'), name='versions__mimetype', type_name='field' label=_('MIME type'), name='versions__mimetype', type_name='field'
) )
ModelEventType.register(
model=DocumentType, event_types=(
event_document_create,
)
)
ModelEventType.register(
model=Document, event_types=(
event_document_download, event_document_properties_edit,
event_document_type_change, event_document_new_version,
event_document_version_revert, event_document_view
)
)
ModelPermission.register( ModelPermission.register(
model=Document, permissions=( model=Document, permissions=(
permission_acl_edit, permission_acl_view, permission_acl_edit, permission_acl_view,
@@ -386,7 +409,8 @@ class DocumentsApp(MayanAppConfig):
menu_object.bind_links( menu_object.bind_links(
links=( links=(
link_document_type_edit, link_document_type_filename_list, link_document_type_edit, link_document_type_filename_list,
link_acl_list, link_document_type_delete link_acl_list, link_object_event_types_user_subcriptions_list,
link_document_type_delete
), sources=(DocumentType,) ), sources=(DocumentType,)
) )
menu_object.bind_links( menu_object.bind_links(
@@ -443,8 +467,11 @@ class DocumentsApp(MayanAppConfig):
links=(link_document_properties,), sources=(Document,), position=2 links=(link_document_properties,), sources=(Document,), position=2
) )
menu_facet.bind_links( menu_facet.bind_links(
links=(link_events_for_object, link_document_version_list,), links=(
sources=(Document,), position=2 link_events_for_object,
link_object_event_types_user_subcriptions_list_with_icon,
link_document_version_list,
), sources=(Document,), position=2
) )
menu_facet.bind_links(links=(link_document_pages,), sources=(Document,)) menu_facet.bind_links(links=(link_document_pages,), sources=(Document,))
@@ -548,3 +575,4 @@ class DocumentsApp(MayanAppConfig):
registry.register(DeletedDocument) registry.register(DeletedDocument)
registry.register(Document) registry.register(Document)
registry.register(DocumentType)

View File

@@ -2,29 +2,28 @@ from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from events.classes import Event from events import EventTypeNamespace
event_document_create = Event( namespace = EventTypeNamespace(name='documents', label=_('Documents'))
name='documents_document_create', label=_('Document created')
event_document_create = namespace.add_event_type(
name='document_create', label=_('Document created')
) )
event_document_download = Event( event_document_download = namespace.add_event_type(
name='documents_document_download', name='document_download', label=_('Document downloaded')
label=_('Document downloaded')
) )
event_document_properties_edit = Event( event_document_properties_edit = namespace.add_event_type(
name='documents_document_edit', label=_('Document properties edited') name='document_edit', label=_('Document properties edited')
) )
event_document_type_change = Event( event_document_type_change = namespace.add_event_type(
name='documents_document_type_change', label=_('Document type changed') name='document_type_change', label=_('Document type changed')
) )
event_document_new_version = Event( event_document_new_version = namespace.add_event_type(
name='documents_document_new_version', label=_('New version uploaded') name='document_new_version', label=_('New version uploaded')
) )
event_document_version_revert = Event( event_document_version_revert = namespace.add_event_type(
name='documents_document_version_revert', name='document_version_revert', label=_('Document version reverted')
label=_('Document version reverted')
) )
event_document_view = Event( event_document_view = namespace.add_event_type(
name='documents_document_view', name='document_view', label=_('Document viewed')
label=_('Document viewed')
) )

View File

@@ -219,9 +219,13 @@ class Document(models.Model):
if new_document: if new_document:
if user: if user:
self.add_as_recent_document_for_user(user) self.add_as_recent_document_for_user(user)
event_document_create.commit(actor=user, target=self) event_document_create.commit(
actor=user, target=self, action_object=self.document_type
)
else: else:
event_document_create.commit(target=self) event_document_create.commit(
target=self, action_object=self.document_type
)
else: else:
event_document_properties_edit.commit(actor=user, target=self) event_document_properties_edit.commit(actor=user, target=self)

View File

@@ -64,7 +64,7 @@ class DocumentEventsTestCase(GenericDocumentViewTestCase):
event = Action.objects.any(obj=self.document).first() event = Action.objects.any(obj=self.document).first()
self.assertEqual(event.verb, event_document_download.name) self.assertEqual(event.verb, event_document_download.id)
self.assertEqual(event.target, self.document) self.assertEqual(event.target, self.document)
self.assertEqual(event.actor, self.user) self.assertEqual(event.actor, self.user)
@@ -98,6 +98,6 @@ class DocumentEventsTestCase(GenericDocumentViewTestCase):
event = Action.objects.any(obj=self.document).first() event = Action.objects.any(obj=self.document).first()
self.assertEqual(event.verb, event_document_view.name) self.assertEqual(event.verb, event_document_view.id)
self.assertEqual(event.target, self.document) self.assertEqual(event.target, self.document)
self.assertEqual(event.actor, self.user) self.assertEqual(event.actor, self.user)

View File

@@ -1,5 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from .classes import Event # NOQA from .classes import EventTypeNamespace, ModelEventType # NOQA
default_app_config = 'events.apps.EventsApp' default_app_config = 'events.apps.EventsApp'

View File

@@ -2,9 +2,19 @@ from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from .models import EventType from .models import EventSubscription, Notification, StoredEventType
@admin.register(EventType) @admin.register(EventSubscription)
class EventTypeAdmin(admin.ModelAdmin): class EventSubscriptionAdmin(admin.ModelAdmin):
list_display = ('user', 'stored_event_type')
@admin.register(StoredEventType)
class StoredEventTypeAdmin(admin.ModelAdmin):
readonly_fields = ('name', '__str__') readonly_fields = ('name', '__str__')
@admin.register(Notification)
class NotificationAdmin(admin.ModelAdmin):
list_display = ('user', 'action', 'read')

View File

@@ -10,9 +10,13 @@ from rest_framework import generics
from acls.models import AccessControlList from acls.models import AccessControlList
from rest_api.permissions import MayanPermission from rest_api.permissions import MayanPermission
from .classes import Event from .classes import EventType, EventTypeNamespace
from .models import Notification
from .permissions import permission_events_view from .permissions import permission_events_view
from .serializers import EventSerializer, EventTypeSerializer from .serializers import (
EventSerializer, EventTypeSerializer, EventTypeNamespaceSerializer,
NotificationSerializer
)
class APIObjectEventListView(generics.ListAPIView): class APIObjectEventListView(generics.ListAPIView):
@@ -46,13 +50,72 @@ class APIObjectEventListView(generics.ListAPIView):
return any_stream(obj) return any_stream(obj)
class APIEventTypeNamespaceDetailView(generics.RetrieveAPIView):
"""
Returns the details of an event type namespace.
"""
serializer_class = EventTypeNamespaceSerializer
def get_object(self):
try:
return EventTypeNamespace.get(name=self.kwargs['name'])
except KeyError:
raise Http404
class APIEventTypeNamespaceListView(generics.ListAPIView):
"""
Returns a list of all the available event type namespaces.
"""
serializer_class = EventTypeNamespaceSerializer
queryset = EventTypeNamespace.all()
def get_serializer_context(self):
return {
'format': self.format_kwarg,
'request': self.request,
'view': self
}
class APIEventTypeNamespaceEventTypeListView(generics.ListAPIView):
"""
Returns a list of all the available event types from a namespaces.
"""
serializer_class = EventTypeSerializer
def get_queryset(self):
try:
return EventTypeNamespace.get(
name=self.kwargs['name']
).get_event_types()
except KeyError:
raise Http404
def get_serializer_context(self):
return {
'format': self.format_kwarg,
'request': self.request,
'view': self
}
class APIEventTypeListView(generics.ListAPIView): class APIEventTypeListView(generics.ListAPIView):
""" """
Returns a list of all the available event types. Returns a list of all the available event types.
""" """
serializer_class = EventTypeSerializer serializer_class = EventTypeSerializer
queryset = sorted(Event.all(), key=lambda event: event.name) queryset = EventType.all()
def get_serializer_context(self):
return {
'format': self.format_kwarg,
'request': self.request,
'view': self
}
class APIEventListView(generics.ListAPIView): class APIEventListView(generics.ListAPIView):
@@ -64,3 +127,20 @@ class APIEventListView(generics.ListAPIView):
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = Action.objects.all() queryset = Action.objects.all()
serializer_class = EventSerializer serializer_class = EventSerializer
def get_serializer_context(self):
return {
'format': self.format_kwarg,
'request': self.request,
'view': self
}
class APINotificationListView(generics.ListAPIView):
"""
Return a list of notifications for the current user.
"""
serializer_class = NotificationSerializer
def get_queryset(self):
return Notification.objects.filter(user=self.request.user)

View File

@@ -3,13 +3,21 @@ from __future__ import unicode_literals
from django.apps import apps from django.apps import apps
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common import MayanAppConfig, menu_tools from common import (
MayanAppConfig, menu_main, menu_object, menu_secondary, menu_tools,
menu_user
)
from common.widgets import two_state_template
from navigation import SourceColumn from navigation import SourceColumn
from rest_api.classes import APIEndPoint from rest_api.classes import APIEndPoint
from .links import link_events_list from .links import (
link_events_list, link_event_types_subscriptions_list,
link_notification_mark_read, link_notification_mark_read_all,
link_user_notifications_list,
)
from .licenses import * # NOQA from .licenses import * # NOQA
from .widgets import event_type_link from .widgets import event_object_link, event_type_link
class EventsApp(MayanAppConfig): class EventsApp(MayanAppConfig):
@@ -20,6 +28,8 @@ class EventsApp(MayanAppConfig):
def ready(self): def ready(self):
super(EventsApp, self).ready() super(EventsApp, self).ready()
Action = apps.get_model(app_label='actstream', model_name='Action') Action = apps.get_model(app_label='actstream', model_name='Action')
Notification = self.get_model(model_name='Notification')
StoredEventType = self.get_model(model_name='StoredEventType')
APIEndPoint(app=self, version_string='1') APIEndPoint(app=self, version_string='1')
@@ -28,8 +38,48 @@ class EventsApp(MayanAppConfig):
) )
SourceColumn(source=Action, label=_('Actor'), attribute='actor') SourceColumn(source=Action, label=_('Actor'), attribute='actor')
SourceColumn( SourceColumn(
source=Action, label=_('Verb'), source=Action, label=_('Event'),
func=lambda context: event_type_link(context['object']) func=lambda context: event_type_link(context['object'])
) )
SourceColumn(
source=StoredEventType, label=_('Namespace'), attribute='namespace'
)
SourceColumn(
source=StoredEventType, label=_('Label'), attribute='label'
)
SourceColumn(
source=Notification, label=_('Timestamp'),
attribute='action.timestamp'
)
SourceColumn(
source=Notification, label=_('Actor'), attribute='action.actor'
)
SourceColumn(
source=Notification, label=_('Event'),
func=lambda context: event_type_link(context['object'].action)
)
SourceColumn(
source=Notification, label=_('Target'),
func=lambda context: event_object_link(context['object'].action)
)
SourceColumn(
source=Notification, label=_('Seen'),
func=lambda context: two_state_template(
state=context['object'].read
)
)
menu_main.bind_links(
links=(link_user_notifications_list,), position=99
)
menu_object.bind_links(
links=(link_notification_mark_read,), sources=(Notification,)
)
menu_secondary.bind_links(
links=(link_notification_mark_read_all,),
sources=('events:user_notifications_list',)
)
menu_tools.bind_links(links=(link_events_list,)) menu_tools.bind_links(links=(link_events_list,))
menu_user.bind_links(links=(link_event_types_subscriptions_list,))

View File

@@ -1,13 +1,21 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import logging
from django.apps import apps from django.apps import apps
from django.utils.encoding import force_text from django.contrib.auth import get_user_model
from django.utils.translation import ugettext_lazy as _ from django.core.exceptions import PermissionDenied
from django.utils.encoding import force_text, python_2_unicode_compatible
from actstream import action from actstream import action
from .permissions import permission_events_view
class Event(object): logger = logging.getLogger(__name__)
@python_2_unicode_compatible
class EventTypeNamespace(object):
_registry = {} _registry = {}
@classmethod @classmethod
@@ -16,44 +24,209 @@ class Event(object):
@classmethod @classmethod
def get(cls, name): def get(cls, name):
try:
return cls._registry[name] return cls._registry[name]
except KeyError:
raise KeyError(
_('Unknown or obsolete event type: {0}'.format(name))
)
@classmethod
def get_label(cls, name):
try:
return cls.get(name=name).label
except KeyError as exception:
return force_text(exception)
def __init__(self, name, label): def __init__(self, name, label):
self.name = name self.name = name
self.label = label self.label = label
self.event_type = None self.event_types = []
self.__class__._registry[name] = self self.__class__._registry[name] = self
def get_type(self): def __str__(self):
if not self.event_type: return force_text(self.label)
EventType = apps.get_model('events', 'EventType')
self.event_type, created = EventType.objects.get_or_create( def add_event_type(self, name, label):
name=self.name event_type = EventType(namespace=self, name=name, label=label)
self.event_types.append(event_type)
return event_type
def get_event_types(self):
return EventType.sort(event_type_list=self.event_types)
@python_2_unicode_compatible
class EventType(object):
_registry = {}
@classmethod
def all(cls):
# Return sorted permisions by namespace.name
return EventType.sort(event_type_list=cls._registry.values())
@classmethod
def get(cls, name):
try:
return cls._registry[name]
except KeyError:
raise KeyError(
'Unknown or obsolete event type: {0}'.format(name)
) )
return self.event_type def __init__(self, namespace, name, label):
self.namespace = namespace
self.name = name
self.label = label
self.stored_event_type = None
self.__class__._registry[self.id] = self
def __str__(self):
return force_text('{}: {}'.format(self.namespace.label, self.label))
@property
def id(self):
return '%s.%s' % (self.namespace.name, self.name)
@classmethod
def refresh(cls):
for event_type in cls.all():
event_type.get_stored_event_type()
def get_stored_event_type(self):
if not self.stored_event_type:
StoredEventType = apps.get_model('events', 'StoredEventType')
self.stored_event_type, created = StoredEventType.objects.get_or_create(
name=self.id
)
return self.stored_event_type
@staticmethod
def sort(event_type_list):
return sorted(
event_type_list, key=lambda x: (x.namespace.label, x.label)
)
def commit(self, actor=None, action_object=None, target=None): def commit(self, actor=None, action_object=None, target=None):
if not self.event_type: AccessControlList = apps.get_model(
EventType = apps.get_model('events', 'EventType') app_label='acls', model_name='AccessControlList'
self.event_type, created = EventType.objects.get_or_create( )
name=self.name Action = apps.get_model(
app_label='actstream', model_name='Action'
)
ContentType = apps.get_model(
app_label='contenttypes', model_name='ContentType'
)
Notification = apps.get_model(
app_label='events', model_name='Notification'
) )
action.send( results = action.send(
actor or target, actor=actor, verb=self.name, actor or target, actor=actor, verb=self.id,
action_object=action_object, target=target action_object=action_object, target=target
) )
for handler, result in results:
if isinstance(result, Action):
for user in get_user_model().objects.all():
notification = None
if user.event_subscriptions.filter(stored_event_type__name=result.verb).exists():
if result.target:
try:
AccessControlList.objects.check_access(
permissions=permission_events_view,
user=user, obj=result.target
)
except PermissionDenied:
pass
else:
notification = Notification.objects.create(action=result, user=user)
else:
notification = Notification.objects.create(action=result, user=user)
if result.target:
content_type = ContentType.objects.get_for_model(model=result.target)
relationship = user.object_subscriptions.filter(
content_type=content_type,
object_id=result.target.pk,
stored_event_type__name=result.verb
)
if relationship.exists():
try:
AccessControlList.objects.check_access(
permissions=permission_events_view,
user=user, obj=result.target
)
except PermissionDenied:
pass
else:
notification = Notification.objects.create(action=result, user=user)
if not notification and result.action_object:
content_type = ContentType.objects.get_for_model(model=result.action_object)
relationship = user.object_subscriptions.filter(
content_type=content_type,
object_id=result.action_object.pk,
stored_event_type__name=result.verb
)
if relationship.exists():
try:
AccessControlList.objects.check_access(
permissions=permission_events_view,
user=user, obj=result.action_object
)
except PermissionDenied:
pass
else:
notification = Notification.objects.create(action=result, user=user)
class ModelEventType(object):
"""
Class to allow matching a model to a specific set of events.
"""
_registry = {}
_proxies = {}
_inheritances = {}
@classmethod
def register(cls, model, event_types):
cls._registry.setdefault(model, [])
for event_type in event_types:
cls._registry[model].append(event_type)
@classmethod
def get_for_class(cls, klass):
return cls._registry.get(klass, ())
@classmethod
def get_for_instance(cls, instance):
StoredEventType = apps.get_model(
app_label='events', model_name='StoredEventType'
)
events = []
class_events = cls._registry.get(type(instance))
if class_events:
events.extend(class_events)
proxy = cls._proxies.get(type(instance))
if proxy:
events.extend(cls._registry.get(proxy))
pks = [
event.id for event in set(events)
]
return EventType.sort(
event_type_list=StoredEventType.objects.filter(name__in=pks)
)
@classmethod
def get_inheritance(cls, model):
return cls._inheritances[model]
@classmethod
def register_proxy(cls, source, model):
cls._proxies[model] = source
@classmethod
def register_inheritance(cls, model, related):
cls._inheritances[model] = related

122
mayan/apps/events/forms.py Normal file
View File

@@ -0,0 +1,122 @@
from __future__ import unicode_literals
from django import forms
from django.forms.formsets import formset_factory
from django.utils.translation import ugettext_lazy as _
from .models import EventSubscription, ObjectEventSubscription
class EventTypeUserRelationshipForm(forms.Form):
namespace = forms.CharField(
label=_('Namespace'), required=False,
widget=forms.TextInput(attrs={'readonly': 'readonly'})
)
label = forms.CharField(
label=_('Label'), required=False,
widget=forms.TextInput(attrs={'readonly': 'readonly'})
)
subscription = forms.ChoiceField(
label=_('Subscription'),
widget=forms.RadioSelect(), choices=(
('none', _('No')),
('subscribed', _('Subscribed')),
)
)
def __init__(self, *args, **kwargs):
super(EventTypeUserRelationshipForm, self).__init__(
*args, **kwargs
)
self.fields['namespace'].initial = self.initial['stored_event_type'].namespace
self.fields['label'].initial = self.initial['stored_event_type'].label
subscription = EventSubscription.objects.get_for(
stored_event_type=self.initial['stored_event_type'],
user=self.initial['user'],
)
if subscription.exists():
self.fields['subscription'].initial = 'subscribed'
else:
self.fields['subscription'].initial = 'none'
def save(self):
subscription = EventSubscription.objects.get_for(
stored_event_type=self.initial['stored_event_type'],
user=self.initial['user'],
)
if self.cleaned_data['subscription'] == 'none':
subscription.delete()
elif self.cleaned_data['subscription'] == 'subscribed':
if not subscription.exists():
EventSubscription.objects.create_for(
stored_event_type=self.initial['stored_event_type'],
user=self.initial['user']
)
EventTypeUserRelationshipFormSet = formset_factory(
EventTypeUserRelationshipForm, extra=0
)
class ObjectEventTypeUserRelationshipForm(forms.Form):
namespace = forms.CharField(
label=_('Namespace'), required=False,
widget=forms.TextInput(attrs={'readonly': 'readonly'})
)
label = forms.CharField(
label=_('Label'), required=False,
widget=forms.TextInput(attrs={'readonly': 'readonly'})
)
subscription = forms.ChoiceField(
label=_('Subscription'),
widget=forms.RadioSelect(), choices=(
('none', _('No')),
('subscribed', _('Subscribed')),
)
)
def __init__(self, *args, **kwargs):
super(ObjectEventTypeUserRelationshipForm, self).__init__(
*args, **kwargs
)
self.fields['namespace'].initial = self.initial['stored_event_type'].namespace
self.fields['label'].initial = self.initial['stored_event_type'].label
subscription = ObjectEventSubscription.objects.get_for(
obj=self.initial['object'],
stored_event_type=self.initial['stored_event_type'],
user=self.initial['user'],
)
if subscription.exists():
self.fields['subscription'].initial = 'subscribed'
else:
self.fields['subscription'].initial = 'none'
def save(self):
subscription = ObjectEventSubscription.objects.get_for(
obj=self.initial['object'],
stored_event_type=self.initial['stored_event_type'],
user=self.initial['user'],
)
if self.cleaned_data['subscription'] == 'none':
subscription.delete()
elif self.cleaned_data['subscription'] == 'subscribed':
if not subscription.exists():
ObjectEventSubscription.objects.create_for(
obj=self.initial['object'],
stored_event_type=self.initial['stored_event_type'],
user=self.initial['user']
)
ObjectEventTypeUserRelationshipFormSet = formset_factory(
ObjectEventTypeUserRelationshipForm, extra=0
)

View File

@@ -26,12 +26,44 @@ def get_kwargs_factory(variable_name):
return get_kwargs return get_kwargs
def get_notification_count(context):
return context['request'].user.notifications.filter(read=False).count()
link_events_list = Link( link_events_list = Link(
icon='fa fa-list-ol', permissions=(permission_events_view,), icon='fa fa-list-ol', permissions=(permission_events_view,),
text=_('Events'), view='events:events_list' text=_('Events'), view='events:events_list'
) )
link_events_details = Link(
text=_('Events'), view='events:events_list'
)
link_events_for_object = Link( link_events_for_object = Link(
icon='fa fa-list-ol', permissions=(permission_events_view,), icon='fa fa-list-ol', permissions=(permission_events_view,),
text=_('Events'), view='events:events_for_object', text=_('Events'), view='events:events_for_object',
kwargs=get_kwargs_factory('resolved_object') kwargs=get_kwargs_factory('resolved_object')
) )
link_event_types_subscriptions_list = Link(
icon='fa fa-list-ol', text=_('Event subscriptions'),
view='events:event_types_user_subcriptions_list'
)
link_notification_mark_read = Link(
args='object.pk', text=_('Mark as seen'),
view='events:notification_mark_read'
)
link_notification_mark_read_all = Link(
text=_('Mark all as seen'), view='events:notification_mark_read_all'
)
link_object_event_types_user_subcriptions_list = Link(
kwargs=get_kwargs_factory('resolved_object'),
permissions=(permission_events_view,), text=_('Subscriptions'),
view='events:object_event_types_user_subcriptions_list',
)
link_object_event_types_user_subcriptions_list_with_icon = Link(
kwargs=get_kwargs_factory('resolved_object'), icon='fa fa-rss',
permissions=(permission_events_view,), text=_('Subscriptions'),
view='events:object_event_types_user_subcriptions_list',
)
link_user_notifications_list = Link(
icon='fa fa-bell', text=get_notification_count,
view='events:user_notifications_list'
)

View File

@@ -0,0 +1,34 @@
from __future__ import unicode_literals
from django.contrib.contenttypes.models import ContentType
from django.db import models
class EventSubscriptionManager(models.Manager):
def create_for(self, stored_event_type, user):
return self.create(
stored_event_type=stored_event_type, user=user
)
def get_for(self, stored_event_type, user):
return self.filter(
stored_event_type=stored_event_type, user=user
)
class ObjectEventSubscriptionManager(models.Manager):
def create_for(self, obj, stored_event_type, user):
content_type = ContentType.objects.get_for_model(model=obj)
return self.create(
content_type=content_type, object_id=obj.pk,
stored_event_type=stored_event_type, user=user
)
def get_for(self, obj, stored_event_type, user):
content_type = ContentType.objects.get_for_model(model=obj)
return self.filter(
content_type=content_type, object_id=obj.pk,
stored_event_type=stored_event_type, user=user
)

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-07-29 07:04
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('events', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='EventSubscription',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('event_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subscriptions', to='events.EventType', verbose_name='Event type')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'Event subscription',
'verbose_name_plural': 'Event subscriptions',
},
),
]

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-07-29 07:23
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('actstream', '0002_remove_action_data'),
('events', '0002_eventsubscription'),
]
operations = [
migrations.CreateModel(
name='Notification',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('read', models.BooleanField(default=False, verbose_name='Read')),
('action', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to='actstream.Action', verbose_name='Action')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'Notification',
'verbose_name_plural': 'Notifications',
},
),
]

View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-07-31 04:23
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('events', '0003_notification'),
]
operations = [
migrations.RenameModel(
old_name='EventType',
new_name='StoredEventType',
),
migrations.AlterModelOptions(
name='storedeventtype',
options={'verbose_name': 'Stored event type', 'verbose_name_plural': 'Stored event types'},
),
migrations.RemoveField(
model_name='eventsubscription',
name='event_type',
),
migrations.AddField(
model_name='eventsubscription',
name='stored_event_type',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='event_subscriptions', to='events.StoredEventType', verbose_name='Event type'),
preserve_default=False,
),
migrations.AlterField(
model_name='eventsubscription',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='event_subscriptions', to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
]

View File

@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-07-31 04:52
from __future__ import unicode_literals
import re
from django.db import migrations
def update_event_types_names(apps, schema_editor):
Action = apps.get_model('actstream', 'Action')
StoredEventType = apps.get_model('events', 'StoredEventType')
known_namespaces = {
'documents_': 'documents.',
'checkouts_': 'checkouts.',
'document_comment_': 'document_comments.',
}
pattern = re.compile('|'.join(known_namespaces.keys()))
for event_type in StoredEventType.objects.all():
event_type.name = pattern.sub(
lambda x: known_namespaces[x.group()], event_type.name
)
event_type.save()
for action in Action.objects.all():
action.verb = pattern.sub(
lambda x: known_namespaces[x.group()], action.verb
)
action.save()
class Migration(migrations.Migration):
dependencies = [
('events', '0004_auto_20170731_0423'),
('actstream', '0001_initial'),
]
operations = [
migrations.RunPython(update_event_types_names),
]

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-07-31 06:40
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('events', '0005_auto_20170731_0452'),
]
operations = [
migrations.CreateModel(
name='ObjectEventSubscription',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('object_id', models.PositiveIntegerField()),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
('stored_event_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='object_subscriptions', to='events.StoredEventType', verbose_name='Event type')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='object_subscriptions', to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'Object event subscription',
'verbose_name_plural': 'Object event subscriptions',
},
),
]

View File

@@ -1,24 +1,111 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .classes import Event from actstream.models import Action
from .classes import EventType
from .managers import (
EventSubscriptionManager, ObjectEventSubscriptionManager
)
@python_2_unicode_compatible @python_2_unicode_compatible
class EventType(models.Model): class StoredEventType(models.Model):
name = models.CharField( name = models.CharField(
max_length=64, unique=True, verbose_name=_('Name') max_length=64, unique=True, verbose_name=_('Name')
) )
class Meta: class Meta:
verbose_name = _('Event type') verbose_name = _('Stored event type')
verbose_name_plural = _('Event types') verbose_name_plural = _('Stored event types')
def __str__(self): def __str__(self):
return self.get_class().label return force_text(self.get_class())
def get_class(self): def get_class(self):
return Event.get_label(self.name) return EventType.get(name=self.name)
@property
def label(self):
return self.get_class().label
@property
def namespace(self):
return self.get_class().namespace
@python_2_unicode_compatible
class EventSubscription(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE,
related_name='event_subscriptions', verbose_name=_('User')
)
stored_event_type = models.ForeignKey(
StoredEventType, on_delete=models.CASCADE,
related_name='event_subscriptions', verbose_name=_('Event type')
)
objects = EventSubscriptionManager()
class Meta:
verbose_name = _('Event subscription')
verbose_name_plural = _('Event subscriptions')
def __str__(self):
return force_text(self.stored_event_type)
@python_2_unicode_compatible
class Notification(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE,
related_name='notifications', verbose_name=_('User')
)
action = models.ForeignKey(
Action, on_delete=models.CASCADE, related_name='notifications',
verbose_name=_('Action')
)
read = models.BooleanField(default=False, verbose_name=_('Read'))
class Meta:
ordering = ('-action__timestamp',)
verbose_name = _('Notification')
verbose_name_plural = _('Notifications')
def __str__(self):
return force_text(self.action)
@python_2_unicode_compatible
class ObjectEventSubscription(models.Model):
content_type = models.ForeignKey(
ContentType, on_delete=models.CASCADE,
)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey(
ct_field='content_type',
fk_field='object_id',
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE,
related_name='object_subscriptions', verbose_name=_('User')
)
stored_event_type = models.ForeignKey(
StoredEventType, on_delete=models.CASCADE,
related_name='object_subscriptions', verbose_name=_('Event type')
)
objects = ObjectEventSubscriptionManager()
class Meta:
verbose_name = _('Object event subscription')
verbose_name_plural = _('Object event subscriptions')
def __str__(self):
return force_text(self.stored_event_type)

View File

@@ -4,30 +4,59 @@ from django.utils.six import string_types
from actstream.models import Action from actstream.models import Action
from rest_framework import serializers from rest_framework import serializers
from rest_framework.reverse import reverse
from common.serializers import ContentTypeSerializer from common.serializers import ContentTypeSerializer
from rest_api.fields import DynamicSerializerField from rest_api.fields import DynamicSerializerField
from user_management.serializers import UserSerializer
from .classes import Event from .classes import EventType
from .models import EventType from .models import Notification, StoredEventType
class EventTypeNamespaceSerializer(serializers.Serializer):
label = serializers.CharField()
name = serializers.CharField()
url = serializers.SerializerMethodField()
event_types_url = serializers.HyperlinkedIdentityField(
lookup_field='name',
view_name='rest_api:event-type-namespace-event-type-list',
)
def get_url(self, instance):
return reverse(
'rest_api:event-type-namespace-detail', args=(
instance.name,
), request=self.context['request'], format=self.context['format']
)
class EventTypeSerializer(serializers.Serializer): class EventTypeSerializer(serializers.Serializer):
label = serializers.CharField() label = serializers.CharField()
name = serializers.CharField() name = serializers.CharField()
id = serializers.CharField()
event_type_namespace_url = serializers.SerializerMethodField()
def get_event_type_namespace_url(self, instance):
return reverse(
'rest_api:event-type-namespace-detail', args=(
instance.namespace.name,
), request=self.context['request'], format=self.context['format']
)
def to_representation(self, instance): def to_representation(self, instance):
if isinstance(instance, Event): if isinstance(instance, EventType):
return super(EventTypeSerializer, self).to_representation( return super(EventTypeSerializer, self).to_representation(
instance instance
) )
elif isinstance(instance, EventType): elif isinstance(instance, StoredEventType):
return super(EventTypeSerializer, self).to_representation( return super(EventTypeSerializer, self).to_representation(
instance.get_class() instance.get_class()
) )
elif isinstance(instance, string_types): elif isinstance(instance, string_types):
return super(EventTypeSerializer, self).to_representation( return super(EventTypeSerializer, self).to_representation(
Event.get(name=instance) EventType.get(name=instance)
) )
@@ -43,3 +72,12 @@ class EventSerializer(serializers.ModelSerializer):
'action_object_content_type', 'action_object_object_id' 'action_object_content_type', 'action_object_object_id'
) )
model = Action model = Action
class NotificationSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
action = EventSerializer(read_only=True)
class Meta:
fields = ('action', 'read', 'user')
model = Notification

View File

@@ -3,9 +3,15 @@ from __future__ import unicode_literals
from django.conf.urls import url from django.conf.urls import url
from .api_views import ( from .api_views import (
APIEventListView, APIEventTypeListView, APIObjectEventListView APIEventListView, APIEventTypeListView, APIEventTypeNamespaceDetailView,
APIEventTypeNamespaceEventTypeListView, APIEventTypeNamespaceListView,
APINotificationListView, APIObjectEventListView
)
from .views import (
EventListView, EventTypeSubscriptionListView, NotificationListView,
NotificationMarkRead, NotificationMarkReadAll, ObjectEventListView,
ObjectEventTypeSubscriptionListView, VerbEventListView
) )
from .views import EventListView, ObjectEventListView, VerbEventListView
urlpatterns = [ urlpatterns = [
url(r'^all/$', EventListView.as_view(), name='events_list'), url(r'^all/$', EventListView.as_view(), name='events_list'),
@@ -14,14 +20,58 @@ urlpatterns = [
ObjectEventListView.as_view(), name='events_for_object' ObjectEventListView.as_view(), name='events_for_object'
), ),
url( url(
r'^by_verb/(?P<verb>[\w\-]+)/$', VerbEventListView.as_view(), r'^by_verb/(?P<verb>[\w\-\.]+)/$', VerbEventListView.as_view(),
name='events_by_verb' name='events_by_verb'
), ),
url(
r'^notifications/(?P<pk>\d+)/mark_read/$',
NotificationMarkRead.as_view(), name='notification_mark_read'
),
url(
r'^notifications/all/mark_read/$',
NotificationMarkReadAll.as_view(), name='notification_mark_read_all'
),
url(
r'^user/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/subscriptions/$',
ObjectEventTypeSubscriptionListView.as_view(),
name='object_event_types_user_subcriptions_list'
),
url(
r'^user/event_types/subscriptions/$',
EventTypeSubscriptionListView.as_view(),
name='event_types_user_subcriptions_list'
),
url(
r'^user/notifications/$',
NotificationListView.as_view(),
name='user_notifications_list'
),
] ]
api_urls = [ api_urls = [
url(r'^event_types/$', APIEventTypeListView.as_view(), name='event-type-list'), url(
r'^event_type_namespaces/(?P<name>[-\w]+)/$',
APIEventTypeNamespaceDetailView.as_view(),
name='event-type-namespace-detail'
),
url(
r'^event_type_namespaces/(?P<name>[-\w]+)/event_types/$',
APIEventTypeNamespaceEventTypeListView.as_view(),
name='event-type-namespace-event-type-list'
),
url(
r'^event_type_namespaces/$', APIEventTypeNamespaceListView.as_view(),
name='event-type-namespace-list'
),
url(
r'^event_types/$', APIEventTypeListView.as_view(),
name='event-type-list'
),
url(r'^events/$', APIEventListView.as_view(), name='event-list'), url(r'^events/$', APIEventListView.as_view(), name='event-list'),
url(
r'^notifications/$', APINotificationListView.as_view(),
name='notification-list'
),
url( url(
r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/events/$', r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/events/$',
APIObjectEventListView.as_view(), name='object-event-list' APIObjectEventListView.as_view(), name='object-event-list'

View File

@@ -1,17 +1,24 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from django.contrib import messages
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.http import Http404 from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from actstream.models import Action, any_stream from actstream.models import Action, any_stream
from acls.models import AccessControlList from acls.models import AccessControlList
from common.generics import FormView, SimpleView
from common.utils import encapsulate from common.utils import encapsulate
from common.views import SingleObjectListView from common.views import SingleObjectListView
from .classes import Event from .classes import EventType, ModelEventType
from .forms import (
EventTypeUserRelationshipFormSet, ObjectEventTypeUserRelationshipFormSet
)
from .models import StoredEventType
from .permissions import permission_events_view from .permissions import permission_events_view
from .widgets import event_object_link from .widgets import event_object_link
@@ -37,6 +44,96 @@ class EventListView(SingleObjectListView):
} }
class EventTypeSubscriptionListView(FormView):
form_class = EventTypeUserRelationshipFormSet
main_model = 'user'
submodel = StoredEventType
def dispatch(self, *args, **kwargs):
EventType.refresh()
return super(EventTypeSubscriptionListView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
try:
for instance in form:
instance.save()
except Exception as exception:
messages.error(
self.request,
_('Error updating event subscription; %s') % exception
)
else:
messages.success(
self.request, _('Event subscriptions updated successfully')
)
return super(
EventTypeSubscriptionListView, self
).form_valid(form=form)
def get_object(self):
return self.request.user
def get_extra_context(self):
return {
'form_display_mode_table': True,
'object': self.get_object(),
'title': _(
'Event subscriptions'
) % self.get_object()
}
def get_initial(self):
obj = self.get_object()
initial = []
for element in self.get_queryset():
initial.append({
'user': obj,
'main_model': self.main_model,
'stored_event_type': element,
})
return initial
def get_post_action_redirect(self):
return reverse('common:current_user_details')
def get_queryset(self):
# Return the queryset by name from the sorted list of the class
event_type_ids = [event_type.id for event_type in EventType.all()]
return self.submodel.objects.filter(name__in=event_type_ids)
class NotificationListView(SingleObjectListView):
def get_queryset(self):
return self.request.user.notifications.all()
def get_extra_context(self):
return {
'hide_object': True,
'object': self.request.user,
'title': _('Notifications'),
}
class NotificationMarkRead(SimpleView):
def dispatch(self, *args, **kwargs):
self.get_queryset().filter(pk=self.kwargs['pk']).update(read=True)
return HttpResponseRedirect(reverse('events:user_notifications_list'))
def get_queryset(self):
return self.request.user.notifications.all()
class NotificationMarkReadAll(SimpleView):
def dispatch(self, *args, **kwargs):
self.get_queryset().update(read=True)
return HttpResponseRedirect(reverse('events:user_notifications_list'))
def get_queryset(self):
return self.request.user.notifications.all()
class ObjectEventListView(EventListView): class ObjectEventListView(EventListView):
view_permissions = None view_permissions = None
@@ -73,6 +170,76 @@ class ObjectEventListView(EventListView):
return any_stream(self.content_object) return any_stream(self.content_object)
class ObjectEventTypeSubscriptionListView(FormView):
form_class = ObjectEventTypeUserRelationshipFormSet
def dispatch(self, *args, **kwargs):
EventType.refresh()
return super(ObjectEventTypeSubscriptionListView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
try:
for instance in form:
instance.save()
except Exception as exception:
messages.error(
self.request,
_('Error updating object event subscription; %s') % exception
)
else:
messages.success(
self.request, _('Object event subscriptions updated successfully')
)
return super(
ObjectEventTypeSubscriptionListView, self
).form_valid(form=form)
def get_object(self):
object_content_type = get_object_or_404(
ContentType, app_label=self.kwargs['app_label'],
model=self.kwargs['model']
)
try:
content_object = object_content_type.get_object_for_this_type(
pk=self.kwargs['object_id']
)
except object_content_type.model_class().DoesNotExist:
raise Http404
AccessControlList.objects.check_access(
permissions=permission_events_view, user=self.request.user,
obj=content_object
)
return content_object
def get_extra_context(self):
return {
'form_display_mode_table': True,
'object': self.get_object(),
'title': _(
'Event subscriptions for: %s'
) % self.get_object()
}
def get_initial(self):
obj = self.get_object()
initial = []
for element in self.get_queryset():
initial.append({
'user': self.request.user,
'object': obj,
'stored_event_type': element,
})
return initial
def get_queryset(self):
return ModelEventType.get_for_instance(instance=self.get_object())
class VerbEventListView(SingleObjectListView): class VerbEventListView(SingleObjectListView):
def get_queryset(self): def get_queryset(self):
return Action.objects.filter(verb=self.kwargs['verb']) return Action.objects.filter(verb=self.kwargs['verb'])
@@ -90,5 +257,5 @@ class VerbEventListView(SingleObjectListView):
'hide_object': True, 'hide_object': True,
'title': _( 'title': _(
'Events of type: %s' 'Events of type: %s'
) % Event.get_label(self.kwargs['verb']), ) % EventType.get(name=self.kwargs['verb']),
} }

View File

@@ -3,7 +3,7 @@ from __future__ import unicode_literals
from django.urls import reverse from django.urls import reverse
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from .classes import Event from .classes import EventType
def event_object_link(entry): def event_object_link(entry):
@@ -16,5 +16,5 @@ def event_object_link(entry):
def event_type_link(entry): def event_type_link(entry):
return mark_safe('<a href="%(url)s">%(label)s</a>' % { return mark_safe('<a href="%(url)s">%(label)s</a>' % {
'url': reverse('events:events_by_verb', kwargs={'verb': entry.verb}), 'url': reverse('events:events_by_verb', kwargs={'verb': entry.verb}),
'label': Event.get_label(entry.verb)} 'label': EventType.get(name=entry.verb)}
) )

View File

@@ -24,7 +24,10 @@ class DynamicSerializerField(serializers.ReadOnlyField):
for klass, serializer_class in self.serializers.items(): for klass, serializer_class in self.serializers.items():
if isinstance(value, klass): if isinstance(value, klass):
return serializer_class( return serializer_class(
context={'request': self.context['request']} context={
'format': self.context['format'],
'request': self.context['request']
}
).to_representation(instance=value) ).to_representation(instance=value)
return _('Unable to find serializer class for: %s') % value return _('Unable to find serializer class for: %s') % value

View File

@@ -124,7 +124,7 @@ class Source(models.Model):
logger.critical( logger.critical(
'Unexpected exception while trying to create version for ' 'Unexpected exception while trying to create version for '
'new document "%s" from source "%s"; %s', 'new document "%s" from source "%s"; %s',
label or file_object.name, self, exception label or file_object.name, self, exception, exc_info=True
) )
document.delete(to_trash=False) document.delete(to_trash=False)
raise raise