[Categories] use categories from Django ORM
[mygpo.git] / mygpo / maintenance / migrate.py
blob7e58e76183262150c074acd540ed5260019b3aee
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
8 from django.utils.text import slugify
9 from django.db import reset_queries
11 from mygpo.podcasts.models import Tag
12 from mygpo.users.models import Chapter as C, EpisodeUserState, Client
13 from mygpo.chapters.models import Chapter
14 from mygpo.subscriptions.models import Subscription, PodcastConfig
15 from mygpo.podcastlists.models import PodcastList, PodcastListEntry
16 from mygpo.history.models import EpisodeHistoryEntry
17 from mygpo.podcasts.models import Episode, Podcast, PodcastGroup
18 from mygpo.directory.models import Category as C
19 from mygpo.categories.models import Category, CategoryEntry, CategoryTag
20 from mygpo.favorites.models import FavoriteEpisode
21 from mygpo.votes.models import Vote
23 import logging
24 logger = logging.getLogger(__name__)
27 def to_maxlength(cls, field, val):
28 """ Cut val to the maximum length of cls's field """
29 max_length = cls._meta.get_field(field).max_length
30 orig_length = len(val)
31 if orig_length > max_length:
32 val = val[:max_length]
33 logger.warn('%s.%s length reduced from %d to %d',
34 cls.__name__, field, orig_length, max_length)
36 return val
39 #class EpisodeUserState(Document, SettingsMixin):
40 # ref_url = StringProperty(required=True)
41 # podcast_ref_url = StringProperty(required=True)
44 def migrate_estate(state):
45 """ migrate a podcast state """
47 try:
48 user = User.objects.get(profile__uuid=state.user)
49 except User.DoesNotExist:
50 logger.warn("User with ID '{id}' does not exist".format(
51 id=state.user))
52 return
54 try:
55 podcast = Podcast.objects.all().get_by_any_id(state.podcast)
56 except Podcast.DoesNotExist:
57 logger.warn("Podcast with ID '{id}' does not exist".format(
58 id=state.podcast))
59 return
61 try:
62 episode = Episode.objects.filter(podcast=podcast).get_by_any_id(state.episode)
63 except Episode.DoesNotExist:
64 logger.warn("Episode with ID '{id}' does not exist".format(
65 id=state.episode))
66 return
68 logger.info('Migrating episode state ({id}) for user {user} and episode {episode}'
69 .format(id=state._id, user=user, episode=episode))
71 for chapter in state.chapters:
72 migrate_chapter(user, episode, chapter)
74 for action in state.actions:
75 migrate_eaction(user, episode, state, action)
77 is_favorite = state.settings.get('is_favorite', False)
78 if is_favorite:
79 logger.info('Favorite episode')
80 FavoriteEpisode.objects.get_or_create(user=user, episode=episode)
81 else:
82 FavoriteEpisode.objects.filter(user=user, episode=episode).delete()
85 def migrate_chapter(user, episode, c):
87 chapter, created = Chapter.objects.get_or_create(
88 user=user,
89 episode=episode,
90 start=c.start,
91 end=c.end,
92 defaults = {
93 'label': c.label or '',
94 'advertisement': c.advertisement,
99 def migrate_eaction(user, episode, state, ea):
101 logger.info('Migrating {action} action'.format(action=ea.action))
103 if ea.device is None:
104 client = None
106 else:
107 try:
108 client = user.client_set.get(id=ea.device)
109 except Client.DoesNotExist:
110 logger.warn("Client '{cid}' does not exist; skipping".format(
111 cid=ea.device))
112 return
114 created = datetime.utcfromtimestamp(ea.upload_timestamp) if ea.upload_timestamp else datetime.utcnow()
115 entry, created = EpisodeHistoryEntry.objects.get_or_create(
116 user=user,
117 client=client,
118 episode=episode,
119 action=ea.action,
120 timestamp=ea.timestamp,
121 defaults = {
122 'created': created,
123 'started': ea.started,
124 'stopped': ea.playmark,
125 'total': ea.total,
126 'podcast_ref_url': state.podcast_ref_url,
127 'episode_ref_url': state.ref_url,
131 if created:
132 logger.info('Episode History Entry created: {user} {action} {episode}'
133 'on {client} @ {timestamp}'.format(user=user,
134 action=entry.action, episode=episode, client=client,
135 timestamp=entry.timestamp))
138 def migrate_category(cat):
140 logger.info('Migrating category {category}'.format(category=cat))
141 category, created = Category.objects.get_or_create(
142 title=to_maxlength(Category, 'title', cat.label)
145 for spelling in cat.spellings + [cat.label]:
146 s, c = CategoryTag.objects.get_or_create(
147 tag=slugify(to_maxlength(CategoryTag, 'tag', spelling.strip())),
148 defaults={
149 'category': category,
153 for podcast_id in cat.podcasts:
154 if isinstance(podcast_id, dict):
155 podcast_id = podcast_id['podcast']
156 logger.info(repr(podcast_id))
158 try:
159 podcast = Podcast.objects.all().get_by_any_id(podcast_id)
160 except Podcast.DoesNotExist:
161 logger.warn("Podcast with ID '{podcast_id}' does not exist".format(
162 podcast_id=podcast_id))
163 continue
165 entry, c = CategoryEntry.objects.get_or_create(category=category,
166 podcast=podcast)
168 category.save()
171 from couchdbkit import Database
172 db = Database('http://127.0.0.1:5984/mygpo_userdata_copy')
173 from couchdbkit.changes import ChangesStream, fold, foreach
176 MIGRATIONS = {
177 'PodcastUserState': (None, None),
178 'User': (None, None),
179 'Suggestions': (None, None),
180 'EpisodeUserState': (EpisodeUserState, migrate_estate),
181 'PodcastList': (None, None),
182 'Category': (C, migrate_category),
185 def migrate_change(c):
186 logger.info('Migrate seq %s', c['seq'])
187 doc = c['doc']
189 if not 'doc_type' in doc:
190 logger.warn('Document contains no doc_type: %r', doc)
191 return
193 doctype = doc['doc_type']
195 cls, migrate = MIGRATIONS[doctype]
197 if cls is None:
198 logger.warn("Skipping '%s'", doctype)
199 return
201 obj = cls.wrap(doc)
202 migrate(obj)
203 reset_queries()
206 def migrate(since=0, db=db):
207 with ChangesStream(db,
208 feed="continuous",
209 heartbeat=True,
210 include_docs=True,
211 since=since,
212 ) as stream:
213 for change in stream:
214 migrate_change(change)