Skip to content

Integrated swappable model support #534

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion actstream/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
default_app_config = 'actstream.apps.ActstreamConfig'


__version__ = '2.0.0'
__version__ = '2.0.1'
__author__ = 'Asif Saif Uddin, Justin Quick <[email protected]>'
31 changes: 26 additions & 5 deletions actstream/actions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.apps import apps
from django.utils.translation import gettext_lazy as _
from django.utils.timezone import now
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldDoesNotExist

from actstream import settings
from actstream.signals import action
Expand All @@ -17,6 +17,7 @@ def follow(user, obj, send_action=True, actor_only=True, flag='', **kwargs):

If ``send_action`` is ``True`` (the default) then a
``<user> started following <object>`` action signal is sent.
Kwargs that can be passed to the Follow model instance will be passed.
Extra keyword arguments are passed to the action.send call.

If ``actor_only`` is ``True`` (the default) then only actions where the
Expand All @@ -32,11 +33,23 @@ def follow(user, obj, send_action=True, actor_only=True, flag='', **kwargs):
follow(request.user, group, actor_only=False, flag='liking')
"""
check(obj)
instance, created = apps.get_model('actstream', 'follow').objects.get_or_create(
follow_model = settings.get_follow_model()
instance, created = follow_model.objects.get_or_create(
user=user, object_id=obj.pk, flag=flag,
content_type=ContentType.objects.get_for_model(obj),
actor_only=actor_only
)
follow_updated = False
for attr in list(kwargs):
try:
follow_model._meta.get_field(attr)
except FieldDoesNotExist:
pass
else:
follow_updated = True
setattr(instance, attr, kwargs.pop(attr))
if follow_updated:
instance.save()
if send_action and created:
if not flag:
action.send(user, verb=_('started following'), target=obj, **kwargs)
Expand All @@ -60,7 +73,7 @@ def unfollow(user, obj, send_action=False, flag=''):
unfollow(request.user, other_user, flag='watching')
"""
check(obj)
qs = apps.get_model('actstream', 'follow').objects.filter(
qs = settings.get_follow_model().objects.filter(
user=user, object_id=obj.pk,
content_type=ContentType.objects.get_for_model(obj)
)
Expand Down Expand Up @@ -91,7 +104,7 @@ def is_following(user, obj, flag=''):
"""
check(obj)

qs = apps.get_model('actstream', 'follow').objects.filter(
qs = settings.get_follow_model().objects.filter(
user=user, object_id=obj.pk,
content_type=ContentType.objects.get_for_model(obj)
)
Expand All @@ -105,6 +118,7 @@ def is_following(user, obj, flag=''):
def action_handler(verb, **kwargs):
"""
Handler function to create Action instance upon action signal call.
Extra kwargs will be passed to the Action instance
"""
kwargs.pop('signal', None)
actor = kwargs.pop('sender')
Expand All @@ -118,7 +132,7 @@ def action_handler(verb, **kwargs):
elif hasattr(verb, '_args'):
verb = verb._args[0]

newaction = apps.get_model('actstream', 'action')(
newaction = settings.get_action_model()(
actor_content_type=ContentType.objects.get_for_model(actor),
actor_object_id=actor.pk,
verb=str(verb),
Expand All @@ -134,6 +148,13 @@ def action_handler(verb, **kwargs):
setattr(newaction, '%s_object_id' % opt, obj.pk)
setattr(newaction, '%s_content_type' % opt,
ContentType.objects.get_for_model(obj))
for attr in list(kwargs):
try:
settings.get_action_model()._meta.get_field(attr)
except FieldDoesNotExist:
pass
else:
setattr(newaction, attr, kwargs.pop(attr))
if settings.USE_JSONFIELD and len(kwargs):
newaction.data = kwargs
newaction.save(force_insert=True)
Expand Down
6 changes: 3 additions & 3 deletions actstream/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.contrib import admin

from actstream import models
from actstream.settings import get_action_model, get_follow_model

# Use django-generic-admin widgets if available
try:
Expand All @@ -25,5 +25,5 @@ class FollowAdmin(ModelAdmin):
raw_id_fields = ('user', 'content_type')


admin.site.register(models.Action, ActionAdmin)
admin.site.register(models.Follow, FollowAdmin)
admin.site.register(get_action_model(), ActionAdmin)
admin.site.register(get_follow_model(), FollowAdmin)
7 changes: 1 addition & 6 deletions actstream/apps.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
from collections import OrderedDict

import django
from django.apps import apps
from django.apps import AppConfig
from django.conf import settings
from django.db.models.signals import pre_delete

from actstream import settings as actstream_settings
Expand All @@ -18,7 +13,7 @@ class ActstreamConfig(AppConfig):
def ready(self):
from actstream.actions import action_handler
action.connect(action_handler, dispatch_uid='actstream.models')
action_class = self.get_model('action')
action_class = actstream_settings.get_action_model()

if actstream_settings.USE_JSONFIELD:
if not hasattr(action_class, 'data'):
Expand Down
8 changes: 4 additions & 4 deletions actstream/drf/serializers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from rest_framework import serializers
from generic_relations.relations import GenericRelatedField

from actstream.models import Follow, Action
from actstream import settings as actstream_settings
from actstream.registry import registry, label
from actstream.settings import DRF_SETTINGS, import_obj

Expand Down Expand Up @@ -72,7 +72,7 @@ class ActionSerializer(DEFAULT_SERIALIZER):
action_object = get_grf()

class Meta:
model = Action
model = actstream_settings.get_action_model()
fields = 'id verb public description timestamp actor target action_object'.split()


Expand All @@ -97,7 +97,7 @@ class FollowSerializer(DEFAULT_SERIALIZER):
follow_object = get_grf()

class Meta:
model = Follow
model = actstream_settings.get_follow_model()
fields = 'id flag user follow_object started actor_only'.split()


Expand All @@ -108,5 +108,5 @@ class FollowingSerializer(DEFAULT_SERIALIZER):
follow_object = get_grf()

class Meta:
model = Follow
model = actstream_settings.get_follow_model()
fields = ['follow_object']
12 changes: 6 additions & 6 deletions actstream/drf/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from actstream.drf import serializers
from actstream import models
from actstream.registry import label
from actstream.settings import DRF_SETTINGS, import_obj
from actstream.settings import DRF_SETTINGS, import_obj, get_action_model, get_follow_model
from actstream.signals import action as action_signal
from actstream.actions import follow as follow_action

Expand Down Expand Up @@ -52,7 +52,7 @@ def get_permissions(self):


class ActionViewSet(DefaultModelViewSet):
queryset = models.Action.objects.public().order_by('-timestamp', '-id').prefetch_related()
queryset = get_action_model().objects.public().order_by('-timestamp', '-id').prefetch_related()
serializer_class = serializers.ActionSerializer

@action(detail=False, permission_classes=[permissions.IsAuthenticated], methods=['POST'], serializer_class=serializers.SendActionSerializer)
Expand Down Expand Up @@ -159,7 +159,7 @@ def any_stream(self, request, content_type_id, object_id):


class FollowViewSet(DefaultModelViewSet):
queryset = models.Follow.objects.order_by('-started', '-id').prefetch_related()
queryset = get_follow_model().objects.order_by('-started', '-id').prefetch_related()
serializer_class = serializers.FollowSerializer
permission_classes = [permissions.IsAuthenticated]

Expand All @@ -185,7 +185,7 @@ def is_following(self, request, content_type_id, object_id):
"""
ctype = get_object_or_404(ContentType, id=content_type_id)
instance = ctype.get_object_for_this_type(pk=object_id)
following = models.Follow.objects.is_following(request.user, instance)
following = get_follow_model().objects.is_following(request.user, instance)
data = {'is_following': following}
return Response(json.dumps(data))

Expand All @@ -195,7 +195,7 @@ def following(self, request):
"""
Returns a JSON response whether the current user is following the object from content_type_id/object_id pair
"""
qs = models.Follow.objects.following_qs(request.user)
qs = get_follow_model().objects.following_qs(request.user)
return Response(serializers.FollowingSerializer(qs, many=True).data)

@action(detail=False, permission_classes=[permissions.IsAuthenticated],
Expand All @@ -208,7 +208,7 @@ def followers(self, request):
if user_model not in serializers.registered_serializers:
raise ModelNotRegistered(f'Auth user "{user_model.__name__}" not registered with actstream')
serializer = serializers.registered_serializers[user_model]
followers = models.Follow.objects.followers(request.user)
followers = get_follow_model().objects.followers(request.user)
return Response(serializer(followers, many=True).data)


Expand Down
6 changes: 4 additions & 2 deletions actstream/feeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from django.http import HttpResponse, Http404
from django.urls import reverse

from actstream.models import Action, model_stream, user_stream, any_stream
from actstream.models import model_stream, user_stream, any_stream
from actstream.settings import get_action_model


class AbstractActivityStream:
Expand Down Expand Up @@ -254,6 +255,7 @@ def items(self, request, *args, **kwargs):
)



class UserActivityMixin:

def get_object(self, request):
Expand All @@ -277,7 +279,7 @@ def get_object(self):
return

def get_stream(self):
return getattr(Action.objects, self.name)
return getattr(get_action_model().objects, self.name)

def items(self, *args, **kwargs):
return self.get_stream()(*args[1:], **kwargs)
Expand Down
4 changes: 2 additions & 2 deletions actstream/follows.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.core.exceptions import ImproperlyConfigured

from actstream.models import Follow
from actstream.settings import get_follow_model


def delete_orphaned_follows(sender, instance=None, **kwargs):
Expand All @@ -11,6 +11,6 @@ def delete_orphaned_follows(sender, instance=None, **kwargs):
return

try:
Follow.objects.for_object(instance).delete()
get_follow_model().objects.for_object(instance).delete()
except ImproperlyConfigured: # raised by actstream for irrelevant models
pass
3 changes: 2 additions & 1 deletion actstream/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from actstream.gfk import GFKManager
from actstream.decorators import stream
from actstream.registry import check
from actstream.settings import get_follow_model


class ActionManager(GFKManager):
Expand Down Expand Up @@ -99,7 +100,7 @@ def user(self, obj: Model, with_user_activity=False, follow_flag=None, **kwargs)
actor_object_id=obj.pk
)

follows = apps.get_model('actstream', 'follow').objects.filter(user=obj)
follows = get_follow_model().objects.filter(user=obj)
if follow_flag:
follows = follows.filter(flag=follow_flag)

Expand Down
38 changes: 25 additions & 13 deletions actstream/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from actstream.managers import FollowManager


class Follow(models.Model):
class AbstractFollow(models.Model):
"""
Lets a user follow the activities of any specific actor
"""
Expand All @@ -36,13 +36,14 @@ class Follow(models.Model):
objects = FollowManager()

class Meta:
abstract = True
unique_together = ('user', 'content_type', 'object_id', 'flag')

def __str__(self):
return '{} -> {} : {}'.format(self.user, self.follow_object, self.flag)


class Action(models.Model):
class AbstractAction(models.Model):
"""
Action model describing the actor acting out a verb (on an optional
target).
Expand Down Expand Up @@ -72,7 +73,7 @@ class Action(models.Model):

"""
actor_content_type = models.ForeignKey(
ContentType, related_name='actor',
ContentType, related_name='%(app_label)s_actor',
on_delete=models.CASCADE, db_index=True
)
actor_object_id = models.CharField(max_length=255, db_index=True)
Expand All @@ -83,7 +84,7 @@ class Action(models.Model):

target_content_type = models.ForeignKey(
ContentType, blank=True, null=True,
related_name='target',
related_name='%(app_label)s_target',
on_delete=models.CASCADE, db_index=True
)
target_object_id = models.CharField(
Expand All @@ -96,7 +97,7 @@ class Action(models.Model):

action_object_content_type = models.ForeignKey(
ContentType, blank=True, null=True,
related_name='action_object',
related_name='%(app_label)s_action_object',
on_delete=models.CASCADE, db_index=True
)
action_object_object_id = models.CharField(
Expand All @@ -114,6 +115,7 @@ class Action(models.Model):
objects = actstream_settings.get_action_manager()

class Meta:
abstract = True
ordering = ('-timestamp',)

def __str__(self):
Expand Down Expand Up @@ -165,12 +167,22 @@ def get_absolute_url(self):
'actstream_detail', args=[self.pk])


class Follow(AbstractFollow):
class Meta(AbstractFollow.Meta):
swappable = 'ACTSTREAM_FOLLOW_MODEL'


class Action(AbstractAction):
class Meta(AbstractAction.Meta):
swappable = 'ACTSTREAM_ACTION_MODEL'


# convenient accessors
actor_stream = Action.objects.actor
action_object_stream = Action.objects.action_object
target_stream = Action.objects.target
user_stream = Action.objects.user
model_stream = Action.objects.model_actions
any_stream = Action.objects.any
followers = Follow.objects.followers
following = Follow.objects.following
actor_stream = actstream_settings.get_action_model().objects.actor
action_object_stream = actstream_settings.get_action_model().objects.action_object
target_stream = actstream_settings.get_action_model().objects.target
user_stream = actstream_settings.get_action_model().objects.user
model_stream = actstream_settings.get_action_model().objects.model_actions
any_stream = actstream_settings.get_action_model().objects.any
followers = actstream_settings.get_follow_model().objects.followers
following = actstream_settings.get_follow_model().objects.following
4 changes: 3 additions & 1 deletion actstream/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from django.db.models.base import ModelBase
from django.core.exceptions import ImproperlyConfigured

from actstream.settings import get_action_model


class RegistrationError(Exception):
pass
Expand All @@ -14,7 +16,7 @@ def setup_generic_relations(model_class):
"""
Set up GenericRelations for actionable models.
"""
Action = apps.get_model('actstream', 'action')
Action = get_action_model()

if Action is None:
raise RegistrationError(
Expand Down
Loading
Loading