Add support for showing the current user's events

Add support kwargs to the SourceColumn class. Improve the
event widgets, views and tests.

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
Roberto Rosario
2019-04-22 01:32:52 -04:00
parent 66e0d9f357
commit ce0a6368f2
14 changed files with 259 additions and 177 deletions

View File

@@ -104,6 +104,9 @@
* Add group created and group edited events. * Add group created and group edited events.
* Add support for SourceColumn widgets. * Add support for SourceColumn widgets.
* Improve styling of the template debug view. * Improve styling of the template debug view.
* Add support for showing the current user's events.
* Add support kwargs to the SourceColumn class.
* Improve the event widgets, views and tests.
3.1.11 (2019-04-XX) 3.1.11 (2019-04-XX)
=================== ===================

View File

@@ -136,6 +136,9 @@ Other changes
* Add group created and group edited events. * Add group created and group edited events.
* Add support for SourceColumn widgets. * Add support for SourceColumn widgets.
* Improve styling of the template debug view. * Improve styling of the template debug view.
* Add support for showing the current user's events.
* Add support kwargs to the SourceColumn class.
* Improve the event widgets, views and tests.
Removals Removals
-------- --------

View File

@@ -1,7 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.apps import apps from django.apps import apps
from django.contrib.auth import get_user_model
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from mayan.apps.common.apps import MayanAppConfig from mayan.apps.common.apps import MayanAppConfig
@@ -11,13 +10,15 @@ from mayan.apps.common.menus import (
from mayan.apps.common.widgets import TwoStateWidget from mayan.apps.common.widgets import TwoStateWidget
from mayan.apps.navigation import SourceColumn from mayan.apps.navigation import SourceColumn
from .html_widgets import (
ObjectLinkWidget, widget_event_actor_link, widget_event_type_link
)
from .links import ( from .links import (
link_events_list, link_event_types_subscriptions_list, link_current_user_events, link_event_types_subscriptions_list,
link_notification_mark_read, link_notification_mark_read_all, link_events_list, link_notification_mark_read,
link_user_events, link_user_notifications_list, link_notification_mark_read_all, link_user_notifications_list,
) )
from .licenses import * # NOQA from .licenses import * # NOQA
from .widgets import event_object_link, event_type_link, event_user_link
class EventsApp(MayanAppConfig): class EventsApp(MayanAppConfig):
@@ -33,24 +34,23 @@ class EventsApp(MayanAppConfig):
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') Notification = self.get_model(model_name='Notification')
StoredEventType = self.get_model(model_name='StoredEventType') StoredEventType = self.get_model(model_name='StoredEventType')
User = get_user_model()
SourceColumn( SourceColumn(
source=Action, label=_('Timestamp'), attribute='timestamp' source=Action, label=_('Timestamp'), attribute='timestamp'
) )
SourceColumn( SourceColumn(
source=Action, label=_('Actor'), func=widget_event_actor_link, label=_('Actor'), source=Action
func=lambda context: event_user_link(context['object'])
) )
SourceColumn( SourceColumn(
source=Action, label=_('Event'), func=widget_event_type_link, label=_('Event'), source=Action
func=lambda context: event_type_link(context['object'])
) )
SourceColumn( SourceColumn(
source=Action, label=_('Action object'), attribute='action_object', label=_('Action object'), source=Action,
func=lambda context: event_object_link( widget=ObjectLinkWidget
entry=context['object'], attribute='action_object' )
) SourceColumn(
attribute='target', label=_('Target'), source=Action,
widget=ObjectLinkWidget
) )
SourceColumn( SourceColumn(
@@ -64,17 +64,20 @@ class EventsApp(MayanAppConfig):
source=Notification, label=_('Timestamp'), source=Notification, label=_('Timestamp'),
attribute='action.timestamp' attribute='action.timestamp'
) )
SourceColumn( SourceColumn(
source=Notification, label=_('Actor'), attribute='action.actor' func=widget_event_actor_link, label=_('Actor'),
kwargs={'attribute': 'action'}, source=Notification
) )
SourceColumn( SourceColumn(
source=Notification, label=_('Event'), func=widget_event_type_link, label=_('Event'),
func=lambda context: event_type_link(context['object'].action) kwargs={'attribute': 'action'}, source=Notification
) )
SourceColumn( SourceColumn(
source=Notification, label=_('Target'), attribute='action.target', label=_('Target'), source=Notification,
func=lambda context: event_object_link(context['object'].action) widget=ObjectLinkWidget
) )
SourceColumn( SourceColumn(
source=Notification, label=_('Seen'), source=Notification, label=_('Seen'),
func=lambda context: TwoStateWidget( func=lambda context: TwoStateWidget(
@@ -88,14 +91,13 @@ class EventsApp(MayanAppConfig):
menu_object.bind_links( menu_object.bind_links(
links=(link_notification_mark_read,), sources=(Notification,) links=(link_notification_mark_read,), sources=(Notification,)
) )
menu_object.bind_links(
links=(link_user_events,), sources=(User,)
)
menu_secondary.bind_links( menu_secondary.bind_links(
links=(link_notification_mark_read_all,), links=(link_notification_mark_read_all,),
sources=('events:user_notifications_list',) 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( menu_user.bind_links(
links=(link_event_types_subscriptions_list,), position=50 links=(
link_event_types_subscriptions_list, link_current_user_events
), position=50
) )

View File

@@ -0,0 +1,82 @@
from __future__ import unicode_literals
from django.apps import apps
from django.template import Context, Template
from django.urls import reverse
from django.utils.encoding import force_text
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from .classes import EventType
class ObjectLinkWidget(object):
template_string = '<a href="{{ url }}">{{ object_type }}{{ label }}</a>'
def __init__(self):
self.template = Template(template_string=self.template_string)
def render(self, name=None, value=None):
label = ''
object_type = ''
url = None
if value:
label = force_text(value)
object_type = '{}: '.format(value._meta.verbose_name)
try:
url = value.get_absolute_url()
except AttributeError:
url = None
return self.template.render(
context=Context(
{'label': label, 'object_type': object_type, 'url': url or '#'}
)
)
def widget_event_actor_link(context, attribute=None):
entry = context['object']
ContentType = apps.get_model(
app_label='contenttypes', model_name='ContentType'
)
if attribute:
entry = getattr(entry, attribute)
if entry.actor == entry.target:
label = _('System')
url = None
else:
label = entry.actor
content_type = ContentType.objects.get_for_model(model=entry.actor)
url = reverse(
viewname='events:events_for_object', kwargs={
'app_label': content_type.app_label, 'model': content_type.model,
'object_id': entry.actor.pk
}
)
if url:
return Template(
template_string='<a href="{{ url }}">{{ label }}</a>'
).render(context=Context({'label': entry.actor, 'url': url}))
else:
return label
def widget_event_type_link(context, attribute=None):
entry = context['object']
if attribute:
entry = getattr(entry, attribute)
return mark_safe(
'<a href="%(url)s">%(label)s</a>' % {
'url': reverse(viewname='events:events_by_verb', kwargs={'verb': entry.verb}),
'label': EventType.get(name=entry.verb)
}
)

View File

@@ -5,9 +5,8 @@ from mayan.apps.appearance.classes import Icon
icon_event_types_subscriptions_list = Icon( icon_event_types_subscriptions_list = Icon(
driver_name='fontawesome', symbol='list-ol' driver_name='fontawesome', symbol='list-ol'
) )
icon_events_list = Icon(driver_name='fontawesome', symbol='list-ol')
icon_events_for_object = Icon(driver_name='fontawesome', symbol='list-ol') icon_events_for_object = Icon(driver_name='fontawesome', symbol='list-ol')
icon_events_user_list = Icon(driver_name='fontawesome', symbol='rss') icon_events_list = Icon(driver_name='fontawesome', symbol='list-ol')
icon_object_event_types_user_subcriptions_list = Icon( icon_object_event_types_user_subcriptions_list = Icon(
driver_name='fontawesome', symbol='rss' driver_name='fontawesome', symbol='rss'
) )

View File

@@ -41,9 +41,9 @@ def get_unread_notification_count(context):
).filter(read=False).count() ).filter(read=False).count()
link_events_list = Link( link_current_user_events = Link(
icon_class=icon_events_list, permissions=(permission_events_view,), icon_class=icon_events_list, text=_('My events'),
text=_('Events'), view='events:events_list' view='events:current_user_events'
) )
link_events_details = Link( link_events_details = Link(
text=_('Events'), view='events:events_list' text=_('Events'), view='events:events_list'
@@ -54,6 +54,10 @@ link_events_for_object = Link(
permissions=(permission_events_view,), text=_('Events'), permissions=(permission_events_view,), text=_('Events'),
view='events:events_for_object', view='events:events_for_object',
) )
link_events_list = Link(
icon_class=icon_events_list, permissions=(permission_events_view,),
text=_('Events'), view='events:events_list'
)
link_event_types_subscriptions_list = Link( link_event_types_subscriptions_list = Link(
icon_class=icon_event_types_subscriptions_list, icon_class=icon_event_types_subscriptions_list,
text=_('Event subscriptions'), text=_('Event subscriptions'),
@@ -72,10 +76,6 @@ link_object_event_types_user_subcriptions_list = Link(
permissions=(permission_events_view,), text=_('Subscriptions'), permissions=(permission_events_view,), text=_('Subscriptions'),
view='events:object_event_types_user_subcriptions_list', view='events:object_event_types_user_subcriptions_list',
) )
link_user_events = Link(
args='resolved_object.pk', text=_('User events'),
view='events:user_events'
)
link_user_notifications_list = Link( link_user_notifications_list = Link(
badge_text=get_unread_notification_count, badge_text=get_unread_notification_count,
icon_class=icon_user_notifications_list, text='', icon_class=icon_user_notifications_list, text='',

View File

@@ -0,0 +1,6 @@
from __future__ import unicode_literals
TEST_EVENT_TYPE_NAMESPACE_LABEL = 'test event type namespace label'
TEST_EVENT_TYPE_NAMESPACE_NAME = 'test_event_type_namespace_name'
TEST_EVENT_TYPE_LABEL = 'test event type label'
TEST_EVENT_TYPE_NAME = 'test_event_type_name'

View File

@@ -0,0 +1,20 @@
from __future__ import unicode_literals
from ..classes import EventTypeNamespace
from .literals import (
TEST_EVENT_TYPE_LABEL, TEST_EVENT_TYPE_NAME,
TEST_EVENT_TYPE_NAMESPACE_LABEL, TEST_EVENT_TYPE_NAMESPACE_NAME
)
class EventTypeTestMixin(object):
def _create_test_event_type(self):
self.test_event_type_namespace = EventTypeNamespace(
label=TEST_EVENT_TYPE_NAMESPACE_LABEL,
name=TEST_EVENT_TYPE_NAMESPACE_NAME
)
self.test_event_type = self.test_event_type_namespace.add_event_type(
label=TEST_EVENT_TYPE_LABEL,
name=TEST_EVENT_TYPE_NAME
)

View File

@@ -1,9 +1,69 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib.contenttypes.models import ContentType
from rest_framework import status
from mayan.apps.documents.tests import DocumentTestMixin
from mayan.apps.rest_api.tests import BaseAPITestCase from mayan.apps.rest_api.tests import BaseAPITestCase
from ..permissions import permission_events_view
from .mixins import EventTypeTestMixin
class EventTypeNamespaceAPITestCase(EventTypeTestMixin, BaseAPITestCase):
def setUp(self):
super(EventTypeNamespaceAPITestCase, self).setUp()
self._create_test_event_type()
class EventAPITestCase(BaseAPITestCase):
def test_evet_type_list_view(self): def test_evet_type_list_view(self):
response = self.get(viewname='rest_api:event-type-list') response = self.get(viewname='rest_api:event-type-list')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_event_type_namespace_list_view(self):
response = self.get(viewname='rest_api:event-type-namespace-list')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_event_type_namespace_event_type_list_view(self):
response = self.get(
viewname='rest_api:event-type-namespace-event-type-list',
kwargs={
'name': self.test_event_type_namespace.name
}
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
class ObjectEventAPITestCase(DocumentTestMixin, BaseAPITestCase):
auto_upload_document = False
def setUp(self):
super(ObjectEventAPITestCase, self).setUp()
self.test_object = self.test_document_type
content_type = ContentType.objects.get_for_model(model=self.test_object)
self.view_arguments = {
'app_label': content_type.app_label,
'model': content_type.model,
'object_id': self.test_object.pk
}
def _request_object_event_list_api_view(self):
return self.get(
viewname='rest_api:object-event-list',
kwargs=self.view_arguments
)
def test_object_event_list_view_no_permission(self):
response = self._request_object_event_list_api_view()
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_object_event_list_view_with_access(self):
self.grant_access(
obj=self.test_object, permission=permission_events_view
)
response = self._request_object_event_list_api_view()
self.assertEqual(response.status_code, status.HTTP_200_OK)

View File

@@ -8,15 +8,18 @@ from ..permissions import permission_events_view
class EventsViewTestCase(GenericDocumentViewTestCase): class EventsViewTestCase(GenericDocumentViewTestCase):
auto_upload_document = False
def setUp(self): def setUp(self):
super(EventsViewTestCase, self).setUp() super(EventsViewTestCase, self).setUp()
self.test_object = self.test_document_type
content_type = ContentType.objects.get_for_model(self.test_document) content_type = ContentType.objects.get_for_model(self.test_object)
self.view_arguments = { self.view_arguments = {
'app_label': content_type.app_label, 'app_label': content_type.app_label,
'model': content_type.model, 'model': content_type.model,
'object_id': self.document.pk 'object_id': self.test_object.pk
} }
def _request_events_for_object_view(self): def _request_events_for_object_view(self):
@@ -27,19 +30,15 @@ class EventsViewTestCase(GenericDocumentViewTestCase):
def test_events_for_object_view_no_permission(self): def test_events_for_object_view_no_permission(self):
response = self._request_events_for_object_view() response = self._request_events_for_object_view()
self.assertNotContains( self.assertNotContains(
response=response, text=self.test_document.label, status_code=403 response=response, text=self.test_object.label, status_code=404
)
self.assertNotContains(
response=response, text='otal:', status_code=403
) )
def test_events_for_object_view_with_permission(self): def test_events_for_object_view_with_permission(self):
self.grant_permission(permission=permission_events_view) self.grant_access(
obj=self.test_object, permission=permission_events_view
)
response = self._request_events_for_object_view() response = self._request_events_for_object_view()
self.assertContains( self.assertContains(
response=response, text=self.test_document.label, status_code=200 response=response, text=self.test_object.label, status_code=200
)
self.assertNotContains(
response=response, text='otal: 0', status_code=200
) )

View File

@@ -8,9 +8,10 @@ from .api_views import (
APINotificationListView, APIObjectEventListView APINotificationListView, APIObjectEventListView
) )
from .views import ( from .views import (
EventListView, EventTypeSubscriptionListView, NotificationListView, CurrentUserEventListView, EventListView, EventTypeSubscriptionListView,
NotificationMarkRead, NotificationMarkReadAll, ObjectEventListView, NotificationListView, NotificationMarkRead, NotificationMarkReadAll,
ObjectEventTypeSubscriptionListView, UserEventListView, VerbEventListView ObjectEventListView, ObjectEventTypeSubscriptionListView,
VerbEventListView
) )
urlpatterns = [ urlpatterns = [
@@ -31,15 +32,15 @@ urlpatterns = [
regex=r'^notifications/all/mark_read/$', regex=r'^notifications/all/mark_read/$',
view=NotificationMarkReadAll.as_view(), name='notification_mark_read_all' view=NotificationMarkReadAll.as_view(), name='notification_mark_read_all'
), ),
url(
regex=r'^user/events/$', name='current_user_events',
view=CurrentUserEventListView.as_view()
),
url( url(
regex=r'^user/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/subscriptions/$', regex=r'^user/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/subscriptions/$',
view=ObjectEventTypeSubscriptionListView.as_view(), view=ObjectEventTypeSubscriptionListView.as_view(),
name='object_event_types_user_subcriptions_list' name='object_event_types_user_subcriptions_list'
), ),
url(
regex=r'^user/(?P<pk>\d+)/events/$', view=UserEventListView.as_view(),
name='user_events'
),
url( url(
regex=r'^user/event_types/subscriptions/$', regex=r'^user/event_types/subscriptions/$',
view=EventTypeSubscriptionListView.as_view(), view=EventTypeSubscriptionListView.as_view(),

View File

@@ -1,7 +1,6 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
@@ -13,7 +12,6 @@ from actstream.models import Action, any_stream
from mayan.apps.acls.models import AccessControlList from mayan.apps.acls.models import AccessControlList
from mayan.apps.common.generics import FormView, SimpleView from mayan.apps.common.generics import FormView, SimpleView
from mayan.apps.common.utils import encapsulate
from mayan.apps.common.views import SingleObjectListView from mayan.apps.common.views import SingleObjectListView
from .classes import EventType, ModelEventType from .classes import EventType, ModelEventType
@@ -21,12 +19,11 @@ from .forms import (
EventTypeUserRelationshipFormSet, ObjectEventTypeUserRelationshipFormSet EventTypeUserRelationshipFormSet, ObjectEventTypeUserRelationshipFormSet
) )
from .icons import ( from .icons import (
icon_events_list, icon_events_user_list, icon_user_notifications_list icon_events_list, icon_user_notifications_list
) )
from .links import link_event_types_subscriptions_list from .links import link_event_types_subscriptions_list
from .models import StoredEventType from .models import StoredEventType
from .permissions import permission_events_view from .permissions import permission_events_view
from .widgets import event_object_link
class EventListView(SingleObjectListView): class EventListView(SingleObjectListView):
@@ -34,14 +31,6 @@ class EventListView(SingleObjectListView):
def get_extra_context(self): def get_extra_context(self):
return { return {
'extra_columns': (
{
'name': _('Target'),
'attribute': encapsulate(
lambda entry: event_object_link(entry)
)
},
),
'hide_object': True, 'hide_object': True,
'title': _('Events'), 'title': _('Events'),
} }
@@ -154,26 +143,10 @@ class NotificationMarkReadAll(SimpleView):
class ObjectEventListView(EventListView): class ObjectEventListView(EventListView):
view_permissions = None view_permission = None
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.object_content_type = get_object_or_404( self.object = self.get_object()
klass=ContentType, app_label=self.kwargs['app_label'],
model=self.kwargs['model']
)
try:
self.content_object = self.object_content_type.get_object_for_this_type(
pk=self.kwargs['object_id']
)
except self.object_content_type.model_class().DoesNotExist:
raise Http404
AccessControlList.objects.check_access(
permissions=permission_events_view, user=request.user,
obj=self.content_object
)
return super( return super(
ObjectEventListView, self ObjectEventListView, self
).dispatch(request, *args, **kwargs) ).dispatch(request, *args, **kwargs)
@@ -188,13 +161,28 @@ class ObjectEventListView(EventListView):
'or using this object.' 'or using this object.'
), ),
'no_results_title': _('There are no events for this object'), 'no_results_title': _('There are no events for this object'),
'object': self.content_object, 'object': self.object,
'title': _('Events for: %s') % self.content_object, 'title': _('Events for: %s') % self.object,
}) })
return context return context
def get_object(self):
content_type = get_object_or_404(
klass=ContentType, app_label=self.kwargs['app_label'],
model=self.kwargs['model']
)
queryset = AccessControlList.objects.filter_by_access(
permission=permission_events_view,
queryset=content_type.model_class().objects.all(),
user=self.request.user
)
return get_object_or_404(
klass=queryset, pk=self.kwargs['object_id']
)
def get_object_list(self): def get_object_list(self):
return any_stream(self.content_object) return any_stream(self.object)
class ObjectEventTypeSubscriptionListView(FormView): class ObjectEventTypeSubscriptionListView(FormView):
@@ -270,50 +258,14 @@ class ObjectEventTypeSubscriptionListView(FormView):
return ModelEventType.get_for_instance(instance=self.get_object()) return ModelEventType.get_for_instance(instance=self.get_object())
class UserEventListView(SingleObjectListView): class CurrentUserEventListView(ObjectEventListView):
view_permission = permission_events_view def get_object(self):
return self.request.user
def get_extra_context(self):
return {
'extra_columns': (
{
'name': _('Target'),
'attribute': encapsulate(
lambda entry: event_object_link(entry)
)
},
),
'hide_object': True,
'no_results_icon': icon_events_user_list,
'no_results_text': _(
'Events are actions that have been performed to this '
'user account or by this user account.'
),
'no_results_title': _('There are no events for this user'),
'object': self.get_user(),
'title': _(
'Events for user: %s'
) % self.get_user(),
}
def get_object_list(self):
return Action.objects.actor(obj=self.get_user())
def get_user(self):
return get_object_or_404(klass=get_user_model(), pk=self.kwargs['pk'])
class VerbEventListView(SingleObjectListView): class VerbEventListView(SingleObjectListView):
def get_extra_context(self): def get_extra_context(self):
return { return {
'extra_columns': (
{
'name': _('Target'),
'attribute': encapsulate(
lambda entry: event_object_link(entry)
)
},
),
'hide_object': True, 'hide_object': True,
'title': _( 'title': _(
'Events of type: %s' 'Events of type: %s'

View File

@@ -1,49 +0,0 @@
from __future__ import unicode_literals
from django.urls import reverse
from django.utils.encoding import force_text
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from .classes import EventType
def event_object_link(entry, attribute='target'):
label = ''
url = '#'
obj_type = ''
obj = getattr(entry, attribute)
if obj:
obj_type = '{}: '.format(obj._meta.verbose_name)
if hasattr(obj, 'get_absolute_url'):
url = obj.get_absolute_url()
label = force_text(obj)
return mark_safe(
'<a href="%(url)s">%(obj_type)s%(label)s</a>' % {
'url': url, 'label': label, 'obj_type': obj_type
}
)
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': EventType.get(name=entry.verb)
}
)
def event_user_link(entry):
if entry.actor == entry.target:
return _('System')
else:
return mark_safe(
'<a href="%(url)s">%(label)s</a>' % {
'url': reverse('events:user_events', kwargs={'pk': entry.actor.pk}),
'label': entry.actor
}
)

View File

@@ -14,7 +14,7 @@ from django.urls import resolve, reverse
from django.utils.encoding import force_str, force_text from django.utils.encoding import force_str, force_text
from mayan.apps.common.settings import setting_home_view from mayan.apps.common.settings import setting_home_view
from mayan.apps.common.utils import return_attrib from mayan.apps.common.utils import resolve_attribute, return_attrib
from mayan.apps.permissions import Permission from mayan.apps.permissions import Permission
from .utils import get_current_view_name from .utils import get_current_view_name
@@ -536,11 +536,12 @@ class SourceColumn(object):
# unhashable type: list # unhashable type: list
return () return ()
def __init__(self, source, label=None, attribute=None, func=None, order=None, widget=None): def __init__(self, source, label=None, attribute=None, func=None, kwargs=None, order=None, widget=None):
self.source = source self.source = source
self._label = label self._label = label
self.attribute = attribute self.attribute = attribute
self.func = func self.func = func
self.kwargs = kwargs or {}
self.order = order or 0 self.order = order or 0
self.__class__._registry.setdefault(source, []) self.__class__._registry.setdefault(source, [])
self.__class__._registry[source].append(self) self.__class__._registry[source].append(self)
@@ -561,9 +562,12 @@ class SourceColumn(object):
def resolve(self, context): def resolve(self, context):
if self.attribute: if self.attribute:
result = return_attrib(context['object'], self.attribute) result = resolve_attribute(
attribute=self.attribute, kwargs=self.kwargs,
obj=context['object']
)
elif self.func: elif self.func:
result = self.func(context=context) result = self.func(context=context, **self.kwargs)
if self.widget: if self.widget:
widget_instance = self.widget() widget_instance = self.widget()