[Subscriptions] add Subscription, HistoryEntry
[mygpo.git] / mygpo / maintenance / migrate.py
bloba17e062b58bd16430364699a9a78900e568b6aac
1 from __future__ import unicode_literals
3 import json
4 from datetime import datetime
6 from django.contrib.contenttypes.models import ContentType
7 from django.contrib.auth.models import User
9 from mygpo.podcasts.models import Tag
10 from mygpo.users.models import UserProfile, Client, SyncGroup, PodcastUserState
11 from mygpo.subscriptions.models import Subscription, PodcastConfig
12 from mygpo.history.models import HistoryEntry
13 from mygpo.podcasts.models import Podcast
15 import logging
16 logger = logging.getLogger(__name__)
19 def to_maxlength(cls, field, val):
20 """ Cut val to the maximum length of cls's field """
21 max_length = cls._meta.get_field(field).max_length
22 orig_length = len(val)
23 if orig_length > max_length:
24 val = val[:max_length]
25 logger.warn('%s.%s length reduced from %d to %d',
26 cls.__name__, field, orig_length, max_length)
28 return val
31 def migrate_pstate(state):
32 """ migrate a podcast state """
34 try:
35 user = User.objects.get(profile__uuid=state.user)
36 except User.DoesNotExist:
37 logger.warn("User with ID '{id}' does not exist".format(
38 id=state.user))
39 return
41 try:
42 podcast = Podcast.objects.all().get_by_any_id(state.podcast)
43 except Podcast.DoesNotExist:
44 logger.warn("Podcast with ID '{id}' does not exist".format(
45 id=state.podcast))
46 return
48 logger.info('Updating podcast state for user {user} and podcast {podcast}'
49 .format(user=user, podcast=podcast))
51 # move all tags
52 for tag in state.tags:
53 ctype = ContentType.objects.get_for_model(podcast)
54 tag, created = Tag.objects.get_or_create(tag=tag,
55 source=Tag.USER,
56 user=user,
57 content_type=ctype,
58 object_id=podcast.id
60 if created:
61 logger.info("Created tag '{}' for user {} and podcast {}",
62 tag, user, podcast)
64 # create all history entries
65 history = HistoryEntry.objects.filter(user=user, podcast=podcast)
66 for action in state.actions:
67 timestamp = action.timestamp
68 client = user.client_set.get(id=action.device)
69 action = action.action
70 he_data = {
71 'timestamp': timestamp,
72 'podcast': podcast,
73 'user': user,
74 'client': client,
75 'action': action,
77 he, created = HistoryEntry.objects.get_or_create(**he_data)
79 if created:
80 logger.info('History Entry created: {user} {action} {podcast} '
81 'on {client} @ {timestamp}'.format(**he_data))
83 # check which clients are currently subscribed
84 subscribed_devices = get_subscribed_devices(state)
85 subscribed_ids = subscribed_devices.keys()
86 subscribed_clients = user.client_set.filter(id__in=subscribed_ids)
87 unsubscribed_clients = user.client_set.exclude(id__in=subscribed_ids)
89 # create subscriptions for subscribed clients
90 for client in subscribed_clients:
91 ts = subscribed_devices[client.id.hex]
92 sub_data = {
93 'user': user,
94 'client': client,
95 'podcast': podcast,
96 'ref_url': state.ref_url,
97 'created': ts,
98 'modified': ts,
99 'deleted': client.id.hex in state.disabled_devices,
101 subscription, created = Subscription.objects.get_or_create(**sub_data)
103 if created:
104 logger.info('Subscription created: {user} subscribed to {podcast} '
105 'on {client} @ {created}'.format(**sub_data))
107 # delete all other subscriptions
108 Subscription.objects.filter(user=user, podcast=podcast,
109 client__in=unsubscribed_clients).delete()
111 # only create the PodcastConfig obj if there are any settings
112 if state.settings:
113 logger.info('Updating {num} settings'.format(num=len(state.settings)))
114 PodcastConfig.objects.update_or_create(user=user, podcast=podcast,
115 defaults = {
116 'settings': json.dumps(state.settings),
121 def get_subscribed_devices(state):
122 """ device Ids on which the user subscribed to the podcast """
123 devices = {}
125 for action in state.actions:
126 if action.action == "subscribe":
127 if not action.device in state.disabled_devices:
128 devices[action.device] = action.timestamp
129 else:
130 if action.device in devices:
131 devices.pop(action.device)
133 return devices
137 from couchdbkit import Database
138 db = Database('http://127.0.0.1:5984/mygpo_userdata_copy')
139 from couchdbkit.changes import ChangesStream, fold, foreach
142 MIGRATIONS = {
143 'PodcastUserState': (PodcastUserState, migrate_pstate),
144 'User': (None, None),
145 'Suggestions': (None, None),
146 'EpisodeUserState': (None, None),
149 def migrate_change(c):
150 logger.info('Migrate seq %s', c['seq'])
151 doc = c['doc']
153 if not 'doc_type' in doc:
154 logger.warn('Document contains no doc_type: %r', doc)
155 return
157 doctype = doc['doc_type']
159 cls, migrate = MIGRATIONS[doctype]
161 if cls is None:
162 logger.warn("Skipping '%s'", doctype)
163 return
165 obj = cls.wrap(doc)
166 migrate(obj)
169 def migrate(since=0):
170 with ChangesStream(db,
171 feed="continuous",
172 heartbeat=True,
173 include_docs=True,
174 since=since,
175 ) as stream:
176 for change in stream:
177 migrate_change(change)