Skip to content
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
114 changes: 81 additions & 33 deletions api/subscriptions/views.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.db.models import Value, When, Case, OuterRef, Subquery
from django.db.models.fields import CharField, IntegerField
from django.db.models.functions import Concat, Cast
from django.contrib.contenttypes.models import ContentType

from rest_framework import generics
from rest_framework import permissions as drf_permissions
from rest_framework.exceptions import NotFound
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from rest_framework.response import Response

from framework import sentry
from framework.auth.oauth_scopes import CoreScopes

from api.base.views import JSONAPIBaseView
from api.base.filters import ListFilterMixin
from api.base import permissions as base_permissions
Expand All @@ -18,13 +22,15 @@
RegistrationSubscriptionSerializer,
)
from api.subscriptions.permissions import IsSubscriptionOwner

from osf.models import (
CollectionProvider,
PreprintProvider,
RegistrationProvider,
AbstractProvider,
AbstractNode,
Guid,
OSFUser,
)
from osf.models.notification_type import NotificationType
from osf.models.notification_subscription import NotificationSubscription
Expand Down Expand Up @@ -156,46 +162,88 @@ class SubscriptionDetail(JSONAPIBaseView, generics.RetrieveUpdateAPIView):
def get_object(self):
subscription_id = self.kwargs['subscription_id']
user_guid = self.request.user._id

provider_ct = ContentType.objects.get(app_label='osf', model='abstractprovider')
node_ct = ContentType.objects.get(app_label='osf', model='abstractnode')
user_file_updated_nt = NotificationType.Type.USER_FILE_UPDATED.instance
reviews_submission_status_nt = NotificationType.Type.REVIEWS_SUBMISSION_STATUS.instance
node_file_updated_nt = NotificationType.Type.NODE_FILE_UPDATED.instance
user_ct = ContentType.objects.get_for_model(OSFUser)
node_ct = ContentType.objects.get_for_model(AbstractNode)
provider_ct = ContentType.objects.get_for_model(AbstractProvider)

node_subquery = AbstractNode.objects.filter(
id=Cast(OuterRef('object_id'), IntegerField()),
).values('guids___id')[:1]

try:
annotated_obj_qs = NotificationSubscription.objects.filter(user=self.request.user).annotate(
legacy_id=Case(
When(
notification_type__name=NotificationType.Type.NODE_FILE_UPDATED.value,
content_type=node_ct,
then=Concat(Subquery(node_subquery), Value('_files_updated')),
),
When(
notification_type__name=NotificationType.Type.USER_FILE_UPDATED.value,
then=Value(f'{user_guid}_global_file_updated'),
),
When(
notification_type__name=NotificationType.Type.PROVIDER_NEW_PENDING_SUBMISSIONS.value,
content_type=provider_ct,
then=Value(f'{user_guid}_global_reviews'),
),
default=Value(f'{user_guid}_global'),
output_field=CharField(),
node_guid = 'n/a'
missing_subscription_created = None
annotated_obj_qs = NotificationSubscription.objects.filter(user=self.request.user).annotate(
legacy_id=Case(
When(
notification_type__name=NotificationType.Type.NODE_FILE_UPDATED.value,
content_type=node_ct,
then=Concat(Subquery(node_subquery), Value('_files_updated')),
),
When(
notification_type__name=NotificationType.Type.USER_FILE_UPDATED.value,
then=Value(f'{user_guid}_global_file_updated'),
),
When(
notification_type__name=NotificationType.Type.PROVIDER_NEW_PENDING_SUBMISSIONS.value,
content_type=provider_ct,
then=Value(f'{user_guid}_global_reviews'),
),
default=Value(f'{user_guid}_global'),
output_field=CharField(),
),
)
existing_subscriptions = annotated_obj_qs.filter(legacy_id=subscription_id)
if not existing_subscriptions:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if not existing_subscriptions:
if not existing_subscriptions.exists():

# `global_file_updated` and `global_reviews` should exist by default for every user.
# If not found, create them with `none` frequency and `_is_digest=True` as default.
if subscription_id == f'{user_guid}_global_file_updated':
# TODO: should we verify user auth/permissions?
notification_type = user_file_updated_nt
content_type = user_ct
object_id = self.request.user.id
elif subscription_id == f'{user_guid}_global_reviews':
# TODO: should we verify user auth/permissions?
notification_type = reviews_submission_status_nt
content_type = user_ct
object_id = self.request.user.id
elif subscription_id.endswith('_files_updated'):
node_guid = subscription_id[:-len('_files_updated')]
node = AbstractNode.objects.filter(guid___id=node_guid).first()
if not node:
sentry.log_message(f'Invalid node in legacy subscription ID: [user={user_guid}, legacy_id={subscription_id}]')
raise NotFound
# TODO: should we verify node contributorship and user auth/permissions?
notification_type = node_file_updated_nt
content_type = node_ct
object_id = node.id
else:
sentry.log_message(f'Subscription not found: [user={user_guid}, legacy_id={subscription_id}]')
raise NotFound
sentry.log_message(f'Missing default subscription has been created: [user={user_guid}], node={node_guid} type={notification_type}, legacy_id={subscription_id}]')
missing_subscription_created = NotificationSubscription.objects.create(
notification_type=notification_type,
user=self.request.user,
content_type=content_type,
object_id=object_id,
defaults={
'_is_digest': True,
'message_frequency': 'none',
},
)
obj = annotated_obj_qs.filter(legacy_id=subscription_id)

except ObjectDoesNotExist:
raise NotFound

obj = obj.filter(user=self.request.user).first()
if not obj:
raise PermissionDenied
if missing_subscription_created:
subscription = missing_subscription_created
else:
# TODO: should we only have one item in `existing_subscriptions` (assume there is no duplicates)?
subscription = existing_subscriptions.filter(user=self.request.user).order_by('id').last()
if not subscription:
raise PermissionDenied

self.check_object_permissions(self.request, obj)
return obj
self.check_object_permissions(self.request, subscription)
return subscription

def update(self, request, *args, **kwargs):
"""
Expand Down
10 changes: 8 additions & 2 deletions website/mailchimp_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,19 @@ def subscribe_on_confirm(user):
notification_type=NotificationType.Type.REVIEWS_SUBMISSION_STATUS.instance,
content_type=ContentType.objects.get_for_model(user),
object_id=user.id,
defaults={'message_frequency': 'instantly'},
defaults={
'_is_digest': True,
'message_frequency': 'instantly',
},
)

NotificationSubscription.objects.get_or_create(
user=user,
notification_type=NotificationType.Type.USER_FILE_UPDATED.instance,
content_type=ContentType.objects.get_for_model(user),
object_id=user.id,
defaults={'message_frequency': 'instantly'},
defaults={
'_is_digest': True,
'message_frequency': 'instantly',
},
)
Loading