ModelActivityMixin   A
last analyzed

Complexity

Total Complexity 2

Size/Duplication

Total Lines 7
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 7
rs 10
wmc 2

2 Methods

Rating   Name   Duplication   Size   Complexity  
A get_object() 0 2 1
A get_stream() 0 2 1
1
import json
2
3
from django.shortcuts import get_object_or_404
4
from django.core.exceptions import ObjectDoesNotExist
5
from django.utils.feedgenerator import Atom1Feed, rfc3339_date
6
from django.contrib.contenttypes.models import ContentType
7
from django.contrib.syndication.views import Feed, add_domain
8
from django.contrib.sites.models import Site
9
from django.utils.encoding import force_text
10
from django.utils.six import text_type
11
from django.utils import datetime_safe
12
from django.views.generic import View
13
from django.http import HttpResponse, Http404
14
15
try:
16
    from django.core.urlresolvers import reverse
17
except ImportError:
18
    from django.urls import reverse
19
20
from actstream.models import Action, model_stream, user_stream, any_stream
21
22
23
class AbstractActivityStream(object):
24
    """
25
    Abstract base class for all stream rendering.
26
    Supports hooks for fetching streams and formatting actions.
27
    """
28
    def get_stream(self, *args, **kwargs):
29
        """
30
        Returns a stream method to use.
31
        """
32
        raise NotImplementedError
33
34
    def get_object(self, *args, **kwargs):
35
        """
36
        Returns the object (eg user or actor) that the stream is for.
37
        """
38
        raise NotImplementedError
39
40
    def items(self, *args, **kwargs):
41
        """
42
        Returns a queryset of Actions to use based on the stream method and object.
43
        """
44
        return self.get_stream()(self.get_object(*args, **kwargs))
45
46
    def get_uri(self, action, obj=None, date=None):
47
        """
48
        Returns an RFC3987 IRI ID for the given object, action and date.
49
        """
50
        if date is None:
51
            date = action.timestamp
52
        date = datetime_safe.new_datetime(date).strftime('%Y-%m-%d')
53
        return 'tag:%s,%s:%s' % (Site.objects.get_current().domain, date,
54
                                 self.get_url(action, obj, False))
55
56
    def get_url(self, action, obj=None, domain=True):
57
        """
58
        Returns an RFC3987 IRI for a HTML representation of the given object, action.
59
        If domain is true, the current site's domain will be added.
60
        """
61
        if not obj:
62
            url = reverse('actstream_detail', None, (action.pk,))
63
        elif hasattr(obj, 'get_absolute_url'):
64
            url = obj.get_absolute_url()
65
        else:
66
            ctype = ContentType.objects.get_for_model(obj)
67
            url = reverse('actstream_actor', None, (ctype.pk, obj.pk))
68
        if domain:
69
            return add_domain(Site.objects.get_current().domain, url)
70
        return url
71
72
    def format(self, action):
73
        """
74
        Returns a formatted dictionary for the given action.
75
        """
76
        item = {
77
            'id': self.get_uri(action),
78
            'url': self.get_url(action),
79
            'verb': action.verb,
80
            'published': rfc3339_date(action.timestamp),
81
            'actor': self.format_actor(action),
82
            'title': text_type(action),
83
        }
84
        if action.description:
85
            item['content'] = action.description
86
        if action.target:
87
            item['target'] = self.format_target(action)
88
        if action.action_object:
89
            item['object'] = self.format_action_object(action)
90
        return item
91
92
    def format_item(self, action, item_type='actor'):
93
        """
94
        Returns a formatted dictionary for an individual item based on the action and item_type.
95
        """
96
        obj = getattr(action, item_type)
97
        return {
98
            'id': self.get_uri(action, obj),
99
            'url': self.get_url(action, obj),
100
            'objectType': ContentType.objects.get_for_model(obj).name,
101
            'displayName': text_type(obj)
102
        }
103
104
    def format_actor(self, action):
105
        """
106
        Returns a formatted dictionary for the actor of the action.
107
        """
108
        return self.format_item(action)
109
110
    def format_target(self, action):
111
        """
112
        Returns a formatted dictionary for the target of the action.
113
        """
114
        return self.format_item(action, 'target')
115
116
    def format_action_object(self, action):
117
        """
118
        Returns a formatted dictionary for the action object of the action.
119
        """
120
        return self.format_item(action, 'action_object')
121
122
123
class ActivityStreamsAtomFeed(Atom1Feed):
124
    """
125
    Feed rendering class for the v1.0 Atom Activity Stream Spec
126
    """
127
    def root_attributes(self):
128
        attrs = super(ActivityStreamsAtomFeed, self).root_attributes()
129
        attrs['xmlns:activity'] = 'http://activitystrea.ms/spec/1.0/'
130
        return attrs
131
132
    def add_root_elements(self, handler):
133
        super(ActivityStreamsAtomFeed, self).add_root_elements(handler)
134
135
    def quick_elem(self, handler, key, value):
136
        if key == 'link':
137
            handler.addQuickElement(key, None, {
138
                'href': value, 'type': 'text/html', 'rel': 'alternate'})
139
        else:
140
            handler.addQuickElement(key, value)
141
142
    def item_quick_handler(self, handler, name, item):
143
        handler.startElement(name, {})
144
        for key, value in item.items():
145
            self.quick_elem(handler, key, value)
146
        handler.endElement(name)
147
148
    def add_item_elements(self, handler, item):
149
        item.pop('unique_id')
150
        actor = item.pop('actor')
151
        target = item.pop('target', None)
152
        action_object = item.pop('action_object', None)
153
        content = item.pop('content', None)
154
155
        if content:
156
            handler.addQuickElement('content', content, {'type': 'html'})
157
158
        for key, value in item.items():
159
            if value:
160
                self.quick_elem(handler, key, value)
161
162
        self.item_quick_handler(handler, 'author', actor)
163
164
        if action_object:
165
            self.item_quick_handler(handler, 'activity:object', action_object)
166
167
        if target:
168
            self.item_quick_handler(handler, 'activity:target', target)
169
170
171
class ActivityStreamsBaseFeed(AbstractActivityStream, Feed):
172
173
    def feed_extra_kwargs(self, obj):
174
        """
175
        Returns an extra keyword arguments dictionary that is used when
176
        initializing the feed generator.
177
        """
178
        return {}
179
180
    def item_extra_kwargs(self, action):
181
        """
182
        Returns an extra keyword arguments dictionary that is used with
183
        the `add_item` call of the feed generator.
184
        Add the 'content' field of the 'Entry' item, to be used by the custom
185
        feed generator.
186
        """
187
        item = self.format(action)
188
        item.pop('title', None)
189
        item['uri'] = item.pop('url')
190
        item['activity:verb'] = item.pop('verb')
191
        return item
192
193
    def format_item(self, action, item_type='actor'):
194
        name = item_type == 'actor' and 'name' or 'title'
195
        item = super(ActivityStreamsBaseFeed, self).format_item(action, item_type)
196
        item[name] = item.pop('displayName')
197
        item['activity:object-type'] = item.pop('objectType')
198
        item.pop('url')
199
        return item
200
201
    def item_link(self, action):
202
        return self.get_url(action)
203
204
    def item_description(self, action):
205
        if action.description:
206
            return force_text(action.description)
207
208
    def items(self, obj):
209
        return self.get_stream()(obj)[:30]
210
211
212
class JSONActivityFeed(AbstractActivityStream, View):
213
    """
214
    Feed that generates feeds compatible with the v1.0 JSON Activity Stream spec
215
    """
216
    def dispatch(self, request, *args, **kwargs):
217
        return HttpResponse(self.serialize(request, *args, **kwargs),
218
                            content_type='application/json')
219
220
    def serialize(self, request, *args, **kwargs):
221
        items = self.items(request, *args, **kwargs)
222
        return json.dumps({
223
            'totalItems': len(items),
224
            'items': [self.format(action) for action in items]
225
        }, indent=4 if 'pretty' in request.GET or 'pretty' in request.POST else None)
226
227
228
class ModelActivityMixin(object):
229
230
    def get_object(self, request, content_type_id):
231
        return get_object_or_404(ContentType, pk=content_type_id).model_class()
232
233
    def get_stream(self):
234
        return model_stream
235
236
237
class ObjectActivityMixin(object):
238
239
    def get_object(self, request, content_type_id, object_id):
240
        ct = get_object_or_404(ContentType, pk=content_type_id)
241
        try:
242
            obj = ct.get_object_for_this_type(pk=object_id)
243
        except ObjectDoesNotExist:
244
            raise Http404('No %s matches the given query.' % ct.model_class()._meta.object_name)
245
        return obj
246
247
    def get_stream(self):
248
        return any_stream
249
250
251
class UserActivityMixin(object):
252
253
    def get_object(self, request):
254
        if request.user.is_authenticated:
255
            return request.user
256
257
    def get_stream(self):
258
        return user_stream
259
260
261
class CustomStreamMixin(object):
262
    name = None
263
264
    def get_object(self):
265
        return
266
267
    def get_stream(self):
268
        return getattr(Action.objects, self.name)
269
270
    def items(self, *args, **kwargs):
271
        return self.get_stream()(*args[1:], **kwargs)
272
273
274
class ModelActivityFeed(ModelActivityMixin, ActivityStreamsBaseFeed):
275
276
    def title(self, model):
277
        return 'Activity feed from %s' % model.__name__
278
279
    def link(self, model):
280
        ctype = ContentType.objects.get_for_model(model)
281
        return reverse('actstream_model', None, (ctype.pk,))
282
283
    def description(self, model):
284
        return 'Public activities of %s' % model.__name__
285
286
287
class ObjectActivityFeed(ObjectActivityMixin, ActivityStreamsBaseFeed):
288
    def title(self, obj):
289
        return 'Activity for %s' % obj
290
291
    def link(self, obj):
292
        return self.get_url(obj)
293
294
    def description(self, obj):
295
        return 'Activity for %s' % obj
296
297
298
class UserActivityFeed(UserActivityMixin, ActivityStreamsBaseFeed):
299
300
    def title(self, user):
301
        return 'Activity feed for your followed actors'
302
303
    def link(self, user):
304
        if not user:
305
            return reverse('actstream')
306
        if hasattr(user, 'get_absolute_url'):
307
            return user.get_absolute_url()
308
        ctype = ContentType.objects.get_for_model(user)
309
        return reverse('actstream_actor', None, (ctype.pk, user.pk))
310
311
    def description(self, user):
312
        return 'Public activities of actors you follow'
313
314
315
class AtomUserActivityFeed(UserActivityFeed):
316
    """
317
    Atom feed of Activity for a given user (where actions are those that the given user follows).
318
    """
319
    feed_type = ActivityStreamsAtomFeed
320
    subtitle = UserActivityFeed.description
321
322
323
class AtomModelActivityFeed(ModelActivityFeed):
324
    """
325
    Atom feed of Activity for a given model (where actions involve the given model as any of the entities).
326
    """
327
    feed_type = ActivityStreamsAtomFeed
328
    subtitle = ModelActivityFeed.description
329
330
331
class AtomObjectActivityFeed(ObjectActivityFeed):
332
    """
333
    Atom feed of Activity for a given object (where actions involve the given object as any of the entities).
334
    """
335
    feed_type = ActivityStreamsAtomFeed
336
    subtitle = ObjectActivityFeed.description
337
338
339
class UserJSONActivityFeed(UserActivityMixin, JSONActivityFeed):
340
    """
341
    JSON feed of Activity for a given user (where actions are those that the given user follows).
342
    """
343
    pass
344
345
346
class ModelJSONActivityFeed(ModelActivityMixin, JSONActivityFeed):
347
    """
348
    JSON feed of Activity for a given model (where actions involve the given model as any of the entities).
349
    """
350
    pass
351
352
353
class ObjectJSONActivityFeed(ObjectActivityMixin, JSONActivityFeed):
354
    """
355
    JSON feed of Activity for a given object (where actions involve the given object as any of the entities).
356
    """
357
    pass
358
359
360
class CustomJSONActivityFeed(CustomStreamMixin, JSONActivityFeed):
361
    """
362
    JSON feed of Activity for a custom stream. self.name should be the name of the custom stream as defined in the Manager
363
    and arguments may be passed either in the url or when calling as_view(...)
364
    """
365
    pass
366