[API] fix Subscription API
[mygpo.git] / mygpo / subscriptions / __init__.py
blob47c8e3e09cfd8f97b841430ca4b559d9bb8bc087
1 from datetime import datetime
2 import collections
4 from django.db import transaction
6 from mygpo.users.models import Client
7 from mygpo.subscriptions.models import (Subscription, SubscribedPodcast,
8 PodcastConfig, )
9 from mygpo.subscriptions.signals import subscription_changed
10 from mygpo.history.models import HistoryEntry
11 from mygpo.utils import to_maxlength
13 import logging
14 logger = logging.getLogger(__name__)
17 SUBSCRIPTION_ACTIONS = (
18 HistoryEntry.SUBSCRIBE,
19 HistoryEntry.UNSUBSCRIBE,
22 @transaction.atomic
23 def subscribe(podcast, user, client, ref_url=None):
24 """ subscribes user to the current podcast on one client """
26 ref_url = ref_url or podcast.url
27 now = datetime.utcnow()
29 subscription, created = Subscription.objects.get_or_create(
30 user=user, client=client, podcast=podcast, defaults={
31 'ref_url': to_maxlength(Subscription, 'ref_url', ref_url),
32 'created': now,
33 'modified': now,
37 if not created:
38 return
40 logger.info('{user} subscribed to {podcast} on {client}'.format(
41 user=user, podcast=podcast, client=client))
43 HistoryEntry.objects.create(
44 timestamp=now,
45 podcast=podcast,
46 user=user,
47 client=client,
48 action=HistoryEntry.SUBSCRIBE,
51 subscription_changed.send(sender=podcast, user=user,
52 client=client, subscribed=True)
55 @transaction.atomic
56 def unsubscribe(podcast, user, client):
57 """ unsubscribes user from the current podcast on one client """
58 now = datetime.utcnow()
60 try:
61 subscription = Subscription.objects.get(
62 user=user,
63 client=client,
64 podcast=podcast,
66 except Subscription.DoesNotExist:
67 return
69 subscription.delete()
71 logger.info('{user} unsubscribed from {podcast} on {client}'.format(
72 user=user, podcast=podcast, client=client))
74 HistoryEntry.objects.create(
75 timestamp=now,
76 podcast=podcast,
77 user=user,
78 client=client,
79 action=HistoryEntry.UNSUBSCRIBE,
82 subscription_changed.send(sender=podcast, user=user,
83 client=client, subscribed=False)
86 @transaction.atomic
87 def subscribe_all(podcast, user, ref_url=None):
88 """ subscribes user to the current podcast on all clients """
89 clients = user.client_set.all()
90 for client in clients:
91 subscribe(podcast, user, client, ref_url)
94 @transaction.atomic
95 def unsubscribe_all(podcast, user):
96 """ unsubscribes user from the current podcast on all clients """
97 now = datetime.utcnow()
99 clients = user.client_set.filter(subscription__podcast=podcast)
100 for client in clients:
101 unsubscribe(podcast, user, client)
104 def get_subscribe_targets(podcast, user):
105 """ Clients / SyncGroup on which the podcast can be subscribed
107 This excludes all devices/syncgroups on which the podcast is already
108 subscribed """
110 clients = Client.objects.filter(user=user)\
111 .exclude(subscription__podcast=podcast)\
112 .select_related('sync_group')
114 targets = set()
115 for client in clients:
116 if client.sync_group:
117 targets.add(client.sync_group)
118 else:
119 targets.add(client)
121 return targets
124 def get_subscribed_podcasts(user, only_public=False):
125 """ Returns all subscribed podcasts for the user
127 The attribute "url" contains the URL that was used when subscribing to
128 the podcast """
130 subscriptions = Subscription.objects.filter(user=user)\
131 .order_by('podcast')\
132 .distinct('podcast')\
133 .select_related('podcast')
134 private = PodcastConfig.objects.get_private_podcasts(user)
136 podcasts = []
137 for subscription in subscriptions:
138 podcast = subscription.podcast
139 public = subscription.podcast not in private
141 # check if we want to include this podcast
142 if only_public and not public:
143 continue
145 subpodcast = SubscribedPodcast(podcast, public, subscription.ref_url)
146 podcasts.append(subpodcast)
148 return podcasts
151 def get_subscription_history(user, client=None, since=None, until=None,
152 public_only=False):
153 """ Returns chronologically ordered subscription history entries
155 Setting device_id restricts the actions to a certain device
158 logger.info('Subscription History for {user}'.format(user=user.username))
159 history = HistoryEntry.objects.filter(user=user)\
160 .filter(action__in=SUBSCRIPTION_ACTIONS)\
161 .order_by('timestamp')
163 if client:
164 logger.info('... client {client_uid}'.format(client_uid=client.uid))
165 history = history.filter(client=client)
167 if since:
168 logger.info('... since {since}'.format(since=since))
169 history = history.filter(timestamp__gt=since)
171 if until:
172 logger.info('... until {until}'.format(until=until))
173 history = history.filter(timestamp__lte=until)
175 if public_only:
176 logger.info('... only public')
177 private = PodcastConfig.objects.get_private_podcasts(user)
178 history = history.exclude(podcast__in=private)
180 return history
183 def get_subscription_change_history(history):
184 """ Actions that added/removed podcasts from the subscription list
186 Returns an iterator of all subscription actions that either
187 * added subscribed a podcast that hasn't been subscribed directly
188 before the action (but could have been subscribed) earlier
189 * removed a subscription of the podcast is not longer subscribed
190 after the action
192 This method assumes, that no subscriptions exist at the beginning of
193 ``history``.
196 subscriptions = collections.defaultdict(int)
198 for entry in history:
199 if entry.action == HistoryEntry.SUBSCRIBE:
200 subscriptions[entry.podcast] += 1
202 # a new subscription has been added
203 if subscriptions[entry.podcast] == 1:
204 yield entry
206 elif entry.action == HistoryEntry.UNSUBSCRIBE:
207 subscriptions[entry.podcast] -= 1
209 # the last subscription has been removed
210 if subscriptions[entry.podcast] == 0:
211 yield entry
214 def subscription_diff(history):
215 """ Calculates a diff of subscriptions based on a history (sub/unsub) """
217 subscriptions = collections.defaultdict(int)
219 for entry in history:
220 if entry.action == HistoryEntry.SUBSCRIBE:
221 subscriptions[entry.podcast] += 1
223 elif entry.action == HistoryEntry.UNSUBSCRIBE:
224 subscriptions[entry.podcast] -= 1
226 subscribe = [podcast for (podcast, value) in
227 subscriptions.items() if value > 0]
228 unsubscribe = [podcast for (podcast, value) in
229 subscriptions.items() if value < 0]
231 return subscribe, unsubscribe