[Subscriptions] trim Subscription.ref_url to max length
[mygpo.git] / mygpo / subscriptions / __init__.py
blob90b1e51749006bec3118e168652efbb5868f92fe
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 history = HistoryEntry.objects.filter(user=user)\
159 .filter(action__in=SUBSCRIPTION_ACTIONS)\
160 .order_by('timestamp')
162 if client:
163 history = history.filter(client=client)
165 if since:
166 history = history.filter(timestamp__gt=since)
168 if until:
169 history = history.filter(timestamp__lte=since)
171 if public_only:
172 private = PodcastConfig.objects.get_private_podcasts(user)
173 history = history.exclude(podcast__in=private)
175 return history
178 def get_subscription_change_history(history):
179 """ Actions that added/removed podcasts from the subscription list
181 Returns an iterator of all subscription actions that either
182 * added subscribed a podcast that hasn't been subscribed directly
183 before the action (but could have been subscribed) earlier
184 * removed a subscription of the podcast is not longer subscribed
185 after the action
187 This method assumes, that no subscriptions exist at the beginning of
188 ``history``.
191 subscriptions = collections.defaultdict(int)
193 for entry in history:
194 if entry.action == HistoryEntry.SUBSCRIBE:
195 subscriptions[entry.podcast] += 1
197 # a new subscription has been added
198 if subscriptions[entry.podcast] == 1:
199 yield entry
201 elif entry.action == HistoryEntry.UNSUBSCRIBE:
202 subscriptions[entry.podcast] -= 1
204 # the last subscription has been removed
205 if subscriptions[entry.podcast] == 0:
206 yield entry
209 def subscription_diff(history):
210 """ Calculates a diff of subscriptions based on a history (sub/unsub) """
212 subscriptions = collections.defaultdict(int)
214 for entry in history:
215 if entry.action == HistoryEntry.SUBSCRIBE:
216 subscriptions[entry.podcast] += 1
218 elif entry.action == HistoryEntry.UNSUBSCRIBE:
219 subscriptions[entry.podcast] -= 1
221 subscribe = [podcast for (podcast, value) in subscriptions if value > 0]
222 unsubscribe = [podcast for (podcast, value) in subscriptions if value < 0]
224 return subscribe, unsubscribe