Backport 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 c4c0d4a007
commit f0baa16cde
38 changed files with 1274 additions and 159 deletions

View File

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

View File

@@ -2,13 +2,13 @@ from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext_lazy as _
from events.classes import Event
from events import EventTypeNamespace
event_cabinets_add_document = Event(
name='cabinets_add_document',
label=_('Document added to cabinet')
namespace = EventTypeNamespace(name='cabinets', label=_('Cabinets'))
event_cabinets_add_document = namespace.add_event_type(
label=_('Document added to cabinet'), name='add_document'
)
event_cabinets_remove_document = Event(
name='cabinets_remove_document',
label=_('Document removed from cabinet')
event_cabinets_remove_document = namespace.add_event_type(
label=_('Document removed from cabinet'), name='remove_document'
)

View File

@@ -10,11 +10,17 @@ from django.utils.translation import ugettext_lazy as _
from acls import ModelPermission
from common import MayanAppConfig, menu_facet, menu_main, menu_sidebar
from common.classes import DashboardWidget
from common.dashboards import dashboard_main
from events import ModelEventType
from mayan.celery import app
from rest_api.classes import APIEndPoint
from .dashboard_widgets import widget_checkouts
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 .links import (
link_checkin_document, link_checkout_document, link_checkout_info,
@@ -72,6 +78,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(
model=Document, permissions=(
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 events.classes import Event
from events import EventTypeNamespace
event_document_auto_check_in = Event(
name='checkouts_document_auto_check_in',
namespace = EventTypeNamespace(name='checkouts', label=_('Checkouts'))
event_document_auto_check_in = namespace.add_event_type(
name='document_auto_check_in',
label=_('Document automatically checked in')
)
event_document_check_in = Event(
name='checkouts_document_check_in', label=_('Document checked in')
event_document_check_in = namespace.add_event_type(
name='document_check_in', label=_('Document checked in')
)
event_document_check_out = Event(
name='checkouts_document_check_out', label=_('Document checked out')
event_document_check_out = namespace.add_event_type(
name='document_check_out', label=_('Document checked out')
)
event_document_forceful_check_in = Event(
name='checkouts_document_forceful_check_in',
event_document_forceful_check_in = namespace.add_event_type(
name='document_forceful_check_in',
label=_('Document forcefully checked in')
)

View File

@@ -67,8 +67,8 @@ class MayanAppConfig(apps.AppConfig):
except ImportError as exception:
if force_text(exception) not in ('No module named urls', 'No module named \'{}.urls\''.format(self.name)):
logger.error(
'Import time error when running AppConfig.ready(). Check '
'apps.py, urls.py, views.py, etc.'
'Import time error when running AppConfig.ready() of app '
'"%s".', self.name
)
raise exception
@@ -127,7 +127,6 @@ class CommonApp(MayanAppConfig):
Text(text=CommonApp.get_user_label_text), Separator(),
link_current_user_details, link_current_user_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.utils.translation import ugettext_lazy as _
@@ -6,9 +6,13 @@ from django.utils.translation import ugettext_lazy as _
from acls import ModelPermission
from common import MayanAppConfig, menu_facet, menu_object, menu_sidebar
from documents.search import document_page_search, document_search
from events import ModelEventType
from navigation import SourceColumn
from rest_api.classes import APIEndPoint
from .events import (
event_document_comment_create, event_document_comment_delete
)
from .links import (
link_comment_add, link_comment_delete, link_comments_for_document
)
@@ -36,6 +40,12 @@ class DocumentCommentsApp(MayanAppConfig):
Comment = self.get_model('Comment')
ModelEventType.register(
model=Document, event_types=(
event_document_comment_create, event_document_comment_delete
)
)
ModelPermission.register(
model=Document, permissions=(
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 events.classes import Event
from events import EventTypeNamespace
event_document_comment_create = Event(
name='document_comment_create',
label=_('Document comment created')
namespace = EventTypeNamespace(
name='document_comments', label=_('Document comments')
)
event_document_comment_delete = Event(
name='document_comment_delete',
label=_('Document comment deleted')
event_document_comment_create = namespace.add_event_type(
name='create', label=_('Document comment created')
)
event_document_comment_delete = namespace.add_event_type(
name='delete', label=_('Document comment deleted')
)

View File

@@ -2,13 +2,15 @@ from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext_lazy as _
from events.classes import Event
from events import EventTypeNamespace
event_parsing_document_version_submit = Event(
name='parsing_document_version_submit',
label=_('Document version submitted for parsing')
namespace = EventTypeNamespace(
name='document_parsing', label=_('Document parsing')
)
event_parsing_document_version_finish = Event(
name='parsing_document_version_finish',
label=_('Document version parsing finished')
event_parsing_document_version_submit = namespace.add_event_type(
label=_('Document version submitted for parsing'), name='version_submit'
)
event_parsing_document_version_finish = namespace.add_event_type(
label=_('Document version parsing finished'), name='version_finish'
)

View File

@@ -4,7 +4,7 @@ from django.apps import apps
from django.utils.translation import ugettext_lazy as _
from document_indexing.tasks import task_index_document
from events.classes import Event
from events.classes import EventType
def handler_index_document(sender, **kwargs):
@@ -42,7 +42,7 @@ def handler_trigger_transition(sender, **kwargs):
transition = list(set(trigger_transitions) & set(workflow_instance.get_transition_choices()))[0]
workflow_instance.do_transition(
comment=_('Event trigger: %s') % Event.get(name=action.verb).label,
comment=_('Event trigger: %s') % EventType.get(name=action.verb).label,
transition=transition
)

View File

@@ -9,7 +9,7 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('events', '0001_initial'),
('events', '0005_auto_20170731_0452'),
('document_states', '0004_workflow_internal_name'),
]

View File

@@ -17,7 +17,7 @@ from django.utils.translation import ugettext_lazy as _
from acls.models import AccessControlList
from common.validators import validate_internal_name
from documents.models import Document, DocumentType
from events.models import EventType
from events.models import StoredEventType
from permissions import Permission
from .error_logs import error_log_state_actions
@@ -306,7 +306,8 @@ class WorkflowTransitionTriggerEvent(models.Model):
related_name='trigger_events', verbose_name=_('Transition')
)
event_type = models.ForeignKey(
EventType, on_delete=models.CASCADE, verbose_name=_('Event type')
StoredEventType, on_delete=models.CASCADE,
verbose_name=_('Event type')
)
class Meta:

View File

@@ -17,8 +17,8 @@ from common.views import (
)
from documents.models import Document
from documents.views import DocumentListView
from events.classes import Event
from events.models import EventType
from events.classes import EventType
from events.models import StoredEventType
from .classes import WorkflowAction
from .forms import (
@@ -675,7 +675,7 @@ class WorkflowStateListView(SingleObjectListView):
class SetupWorkflowTransitionTriggerEventListView(FormView):
form_class = WorkflowTransitionTriggerEventRelationshipFormSet
submodel = EventType
submodel = StoredEventType
def dispatch(self, *args, **kwargs):
messages.warning(
@@ -689,7 +689,7 @@ class SetupWorkflowTransitionTriggerEventListView(FormView):
user=self.request.user, obj=self.get_object().workflow
)
Event.refresh()
EventType.refresh()
return super(
SetupWorkflowTransitionTriggerEventListView, self
).dispatch(*args, **kwargs)
@@ -735,8 +735,10 @@ class SetupWorkflowTransitionTriggerEventListView(FormView):
initial = []
# Return the queryset by name from the sorted list of the class
event_type_ids = [event_type.name for event_type in Event.all()]
event_type_queryset = EventType.objects.filter(name__in=event_type_ids)
event_type_ids = [event_type.id for event_type in Event.all()]
event_type_queryset = StoredEventType.objects.filter(
name__in=event_type_ids
)
for event_type in event_type_queryset:
initial.append({

View File

@@ -23,7 +23,11 @@ from converter.permissions import (
permission_transformation_delete, permission_transformation_edit,
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 mayan.celery import app
from mayan_statistics.classes import StatisticNamespace, CharJSLine
@@ -36,6 +40,12 @@ from .dashboard_widgets import (
widget_new_documents_this_month, widget_pages_per_month,
widget_total_documents
)
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 (
create_default_document_type, handler_scan_duplicates_for
)
@@ -142,6 +152,19 @@ class DocumentsApp(MayanAppConfig):
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(
model=Document, permissions=(
permission_acl_edit, permission_acl_view,
@@ -389,7 +412,8 @@ class DocumentsApp(MayanAppConfig):
menu_object.bind_links(
links=(
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,)
)
menu_object.bind_links(
@@ -446,8 +470,11 @@ class DocumentsApp(MayanAppConfig):
links=(link_document_properties,), sources=(Document,), position=2
)
menu_facet.bind_links(
links=(link_events_for_object, link_document_version_list,),
sources=(Document,), position=2
links=(
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,))

View File

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

View File

@@ -222,9 +222,13 @@ class Document(models.Model):
if new_document:
if 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:
event_document_create.commit(target=self)
event_document_create.commit(
target=self, action_object=self.document_type
)
else:
if _commit_events:
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()
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.actor, self.user)
@@ -98,6 +98,6 @@ class DocumentEventsTestCase(GenericDocumentViewTestCase):
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.actor, self.user)

View File

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

View File

@@ -2,9 +2,19 @@ from __future__ import unicode_literals
from django.contrib import admin
from .models import EventType
from .models import EventSubscription, Notification, StoredEventType
@admin.register(EventType)
class EventTypeAdmin(admin.ModelAdmin):
@admin.register(EventSubscription)
class EventSubscriptionAdmin(admin.ModelAdmin):
list_display = ('user', 'stored_event_type')
@admin.register(StoredEventType)
class StoredEventTypeAdmin(admin.ModelAdmin):
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 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 .serializers import EventSerializer, EventTypeSerializer
from .serializers import (
EventSerializer, EventTypeSerializer, EventTypeNamespaceSerializer,
NotificationSerializer
)
class APIObjectEventListView(generics.ListAPIView):
@@ -46,13 +50,72 @@ class APIObjectEventListView(generics.ListAPIView):
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):
"""
Returns a list of all the available event types.
"""
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):
@@ -64,3 +127,20 @@ class APIEventListView(generics.ListAPIView):
permission_classes = (MayanPermission,)
queryset = Action.objects.all()
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,11 +3,19 @@ from __future__ import unicode_literals
from django.apps import apps
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 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 .widgets import event_object_link, event_type_link
@@ -28,6 +36,8 @@ class EventsApp(MayanAppConfig):
def ready(self):
super(EventsApp, self).ready()
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')
@@ -39,7 +49,7 @@ class EventsApp(MayanAppConfig):
func=lambda context: event_actor(context['object'])
)
SourceColumn(
source=Action, label=_('Verb'),
source=Action, label=_('Event'),
func=lambda context: event_type_link(context['object'])
)
SourceColumn(
@@ -49,4 +59,44 @@ class EventsApp(MayanAppConfig):
)
)
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_user.bind_links(links=(link_event_types_subscriptions_list,))

View File

@@ -1,70 +1,231 @@
from __future__ import unicode_literals
import logging
from django.apps import apps
from django.utils.encoding import force_text
from django.contrib.auth import get_user_model
from django.core.exceptions import PermissionDenied
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
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 = {}
@classmethod
def all(cls):
return Event.sort(event_type_list=cls._registry.values())
return sorted(cls._registry.values())
@classmethod
def get(cls, name):
return cls._registry[name]
def __init__(self, name, label):
self.name = name
self.label = label
self.event_types = []
self.__class__._registry[name] = self
def __str__(self):
return force_text(self.label)
def add_event_type(self, name, label):
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 = {}
@staticmethod
def sort(event_type_list):
return sorted(
event_type_list, key=lambda x: (x.namespace.label, x.label)
)
@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))
)
@classmethod
def get_label(cls, name):
try:
return cls.get(name=name).label
except KeyError as exception:
return force_text(exception)
return _('Unknown or obsolete event type: %s') % name
@classmethod
def refresh(cls):
for event_type in cls.all():
event_type.get_type()
event_type.get_stored_event_type()
@staticmethod
def sort(event_type_list):
return sorted(
event_type_list, key=lambda x: x.label
)
def __init__(self, name, label):
def __init__(self, namespace, name, label):
self.namespace = namespace
self.name = name
self.label = label
self.event_type = None
self.__class__._registry[name] = self
self.stored_event_type = None
self.__class__._registry[self.id] = self
def get_type(self):
if not self.event_type:
EventType = apps.get_model('events', 'EventType')
self.event_type, created = EventType.objects.get_or_create(
name=self.name
)
return self.event_type
def __str__(self):
return force_text('{}: {}'.format(self.namespace.label, self.label))
def commit(self, actor=None, action_object=None, target=None):
if not self.event_type:
EventType = apps.get_model('events', 'EventType')
self.event_type, created = EventType.objects.get_or_create(
name=self.name
)
AccessControlList = apps.get_model(
app_label='acls', model_name='AccessControlList'
)
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(
actor or target, actor=actor, verb=self.name,
results = action.send(
actor or target, actor=actor, verb=self.id,
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)
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
@property
def id(self):
return '%s.%s' % (self.namespace.name, self.name)
class ModelEventType(object):
"""
Class to allow matching a model to a specific set of events.
"""
_inheritances = {}
_proxies = {}
_registry = {}
@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(cls, model, event_types):
cls._registry.setdefault(model, [])
for event_type in event_types:
cls._registry[model].append(event_type)
@classmethod
def register_inheritance(cls, model, related):
cls._inheritances[model] = related
@classmethod
def register_proxy(cls, source, model):
cls._proxies[model] = source

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
def get_notification_count(context):
return context['request'].user.notifications.filter(read=False).count()
link_events_list = Link(
icon='fa fa-list-ol', permissions=(permission_events_view,),
text=_('Events'), view='events:events_list'
)
link_events_details = Link(
text=_('Events'), view='events:events_list'
)
link_events_for_object = Link(
icon='fa fa-list-ol', permissions=(permission_events_view,),
text=_('Events'), view='events:events_for_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,86 @@
# -*- 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.',
'parsing_document_': 'document_parsing.',
'ocr_': 'ocr.',
'tag_': 'tags.',
}
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()
def revert_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_comments\.': 'document_comment_',
'document_parsing\.': 'parsing_document_',
'ocr\.': 'ocr_',
'tags\.': 'tag_',
}
pattern = re.compile('|'.join(known_namespaces.keys()))
for event_type in StoredEventType.objects.all():
old_name = event_type.name
new_name = pattern.sub(
lambda x: known_namespaces[x.group().replace('.', '\\.')],
event_type.name
)
event_type.name = new_name
if old_name == new_name:
event_type.delete()
else:
event_type.save()
for action in Action.objects.all():
new_name = pattern.sub(
lambda x: known_namespaces[x.group().replace('.', '\\.')],
action.verb
)
action.verb = new_name
action.save()
class Migration(migrations.Migration):
dependencies = [
('events', '0004_auto_20170731_0423'),
('actstream', '0001_initial'),
]
operations = [
migrations.RunPython(
code=update_event_types_names,
reverse_code=revert_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,28 +1,111 @@
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.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 .classes import Event
from actstream.models import Action
from .classes import EventType
from .managers import (
EventSubscriptionManager, ObjectEventSubscriptionManager
)
@python_2_unicode_compatible
class EventType(models.Model):
class StoredEventType(models.Model):
name = models.CharField(
max_length=64, unique=True, verbose_name=_('Name')
)
class Meta:
verbose_name = _('Event type')
verbose_name_plural = _('Event types')
verbose_name = _('Stored event type')
verbose_name_plural = _('Stored event types')
def __str__(self):
return self.get_class().label
return force_text(self.get_class())
def get_class(self):
return Event.get(name=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 rest_framework import serializers
from rest_framework.reverse import reverse
from common.serializers import ContentTypeSerializer
from rest_api.fields import DynamicSerializerField
from user_management.serializers import UserSerializer
from .classes import Event
from .models import EventType
from .classes 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):
label = 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):
if isinstance(instance, Event):
if isinstance(instance, EventType):
return super(EventTypeSerializer, self).to_representation(
instance
)
elif isinstance(instance, EventType):
elif isinstance(instance, StoredEventType):
return super(EventTypeSerializer, self).to_representation(
instance.get_class()
)
elif isinstance(instance, string_types):
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'
)
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 .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 = [
url(r'^all/$', EventListView.as_view(), name='events_list'),
@@ -14,16 +20,60 @@ urlpatterns = [
ObjectEventListView.as_view(), name='events_for_object'
),
url(
r'^by_verb/(?P<verb>[\w\-]+)/$', VerbEventListView.as_view(),
r'^by_verb/(?P<verb>[\w\-\.]+)/$', VerbEventListView.as_view(),
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 = [
url(r'^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'^object/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/events/$',
r'^notifications/$', APINotificationListView.as_view(),
name='notification-list'
),
url(
r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/events/$',
APIObjectEventListView.as_view(), name='object-event-list'
),
]

View File

@@ -1,17 +1,24 @@
from __future__ import absolute_import, unicode_literals
from django.contrib import messages
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.urls import reverse
from django.utils.translation import ugettext_lazy as _
from actstream.models import Action, any_stream
from acls.models import AccessControlList
from common.generics import FormView, SimpleView
from common.utils import encapsulate
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 .widgets import event_object_link
@@ -37,6 +44,96 @@ class EventListView(SingleObjectListView):
return Action.objects.all()
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_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)
def get_post_action_redirect(self):
return reverse('common:current_user_details')
class NotificationListView(SingleObjectListView):
def get_extra_context(self):
return {
'hide_object': True,
'object': self.request.user,
'title': _('Notifications'),
}
def get_object_list(self):
return self.request.user.notifications.all()
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):
view_permissions = None
@@ -73,6 +170,76 @@ class ObjectEventListView(EventListView):
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):
def get_extra_context(self):
return {
@@ -87,7 +254,7 @@ class VerbEventListView(SingleObjectListView):
'hide_object': True,
'title': _(
'Events of type: %s'
) % Event.get_label(self.kwargs['verb']),
) % EventType.get(name=self.kwargs['verb']),
}
def get_object_list(self):

View File

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

View File

@@ -2,13 +2,15 @@ from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext_lazy as _
from events.classes import Event
from events import EventTypeNamespace
event_ocr_document_version_submit = Event(
name='ocr_document_version_submit',
label=_('Document version submitted for OCR')
namespace = EventTypeNamespace(name='ocr', label=_('OCR'))
event_ocr_document_version_submit = namespace.add_event_type(
label=_('Document version submitted for OCR'),
name='document_version_submit'
)
event_ocr_document_version_finish = Event(
name='ocr_document_version_finish',
label=_('Document version OCR finished')
event_ocr_document_version_finish = namespace.add_event_type(
label=_('Document version OCR finished'),
name='document_version_finish'
)

View File

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

View File

@@ -124,7 +124,7 @@ class Source(models.Model):
logger.critical(
'Unexpected exception while trying to create version for '
'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)
raise

View File

@@ -2,13 +2,13 @@ from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext_lazy as _
from events.classes import Event
from events import EventTypeNamespace
event_tag_attach = Event(
name='tag_attach',
label=_('Tag attached to document')
namespace = EventTypeNamespace(name='tags', label=_('Tags'))
event_tag_attach = namespace.add_event_type(
label=_('Tag attached to document'), name='attach'
)
event_tag_remove = Event(
name='tag_remove',
label=_('Tag removed from document')
event_tag_remove = namespace.add_event_type(
label=_('Tag removed from document'), name='remove'
)