915f857453c764e23f357b5fd2592ffbdaa194c8
[mygpo.git] / mygpo / core / slugs.py
blob915f857453c764e23f357b5fd2592ffbdaa194c8
1 from itertools import count
3 from couchdbkit.ext.django.schema import *
5 from django.utils.text import slugify
7 from mygpo.decorators import repeat_on_conflict
10 def assign_slug(obj, generator):
11 if obj.slug:
12 return
14 slug = generator(obj).get_slug()
15 _set_slug(obj=obj, slug=slug)
18 def assign_missing_episode_slugs(podcast):
19 common_title = podcast.get_common_episode_title()
21 episodes = EpisodesMissingSlugs(podcast.get_id())
24 for episode in episodes:
25 slug = EpisodeSlug(episode, common_title).get_slug()
26 _set_slug(obj=episode, slug=slug)
29 @repeat_on_conflict(['obj'])
30 def _set_slug(obj, slug):
31 if slug:
32 obj.set_slug(slug)
33 obj.save()
37 class SlugGenerator(object):
38 """ Generates a unique slug for an object """
41 def __init__(self, obj):
42 if obj.slug:
43 raise ValueError('%(obj)s already has slug %(slug)s' % \
44 dict(obj=obj, slug=obj.slug))
46 self.base_slug = self._get_base_slug(obj)
49 @staticmethod
50 def _get_base_slug(obj):
51 if not obj.title:
52 return None
53 base_slug = slugify(obj.title)
54 return base_slug
57 @staticmethod
58 def _get_existing_slugs():
59 return []
62 def get_slug(self):
63 """ Gets existing slugs and appends numbers until slug is unique """
64 if not self.base_slug:
65 return None
67 existing_slugs = self._get_existing_slugs()
69 if not self.base_slug in existing_slugs:
70 return str(self.base_slug)
72 for n in count(1):
73 tmp_slug = '%s-%d' % (self.base_slug, n)
74 if not tmp_slug in existing_slugs:
75 # slugify returns SafeUnicode, we need a plain string
76 return str(tmp_slug)
80 class PodcastGroupSlug(SlugGenerator):
81 """ Generates slugs for Podcast Groups """
83 def _get_existing_slugs(self):
84 from mygpo.db.couchdb.podcast import podcast_slugs
85 return podcast_slugs(self.base_slug)
89 class PodcastSlug(PodcastGroupSlug):
90 """ Generates slugs for Podcasts """
92 @staticmethod
93 def _get_base_slug(podcast):
94 base_slug = SlugGenerator._get_base_slug(podcast)
96 if not base_slug:
97 return None
99 # append group_member_name to slug
100 if podcast.group_member_name:
101 member_slug = slugify(podcast.group_member_name)
102 if member_slug and not member_slug in base_slug:
103 base_slug = '%s-%s' % (base_slug, member_slug)
105 return base_slug
109 class EpisodeSlug(SlugGenerator):
110 """ Generates slugs for Episodes """
112 def __init__(self, episode, common_title):
113 self.common_title = common_title
114 super(EpisodeSlug, self).__init__(episode)
115 self.podcast_id = episode.podcast
118 def _get_base_slug(self, obj):
120 number = obj.get_episode_number(self.common_title)
121 if number:
122 return str(number)
124 short_title = obj.get_short_title(self.common_title)
125 if short_title:
126 return slugify(short_title)
128 if obj.title:
129 return slugify(obj.title)
131 return None
134 def _get_existing_slugs(self):
135 """ Episode slugs have to be unique within the Podcast """
136 from mygpo.db.couchdb.episode import episode_slugs_per_podcast
137 return episode_slugs_per_podcast(self.podcast_id, self.base_slug)
140 class ObjectsMissingSlugs(object):
141 """ A collections of objects missing a slug """
143 def __init__(self, cls, wrapper=None, start=[None], end=[{}]):
144 self.cls = cls
145 self.doc_type = cls._doc_type
146 self.wrapper = wrapper
147 self.start = start
148 self.end = end
149 self.kwargs = {}
152 def __len__(self):
153 from mygpo.db.couchdb.common import missing_slug_count
154 return missing_slug_count(self.doc_type, self.start, self.end)
157 def __iter__(self):
158 from mygpo.db.couchdb.common import missing_slugs
159 return missing_slugs(self.doc_type, self.start, self.end, self.wrapper, **self.kwargs)
163 class PodcastsMissingSlugs(ObjectsMissingSlugs):
164 """ Podcasts that don't have a slug (but could have one) """
166 def __init__(self):
167 from mygpo.core.models import Podcast
168 super(PodcastsMissingSlugs, self).__init__(Podcast, self._podcast_wrapper)
169 self.kwargs = {'wrap': False}
171 @staticmethod
172 def _podcast_wrapper(r):
173 from mygpo.core.models import Podcast, PodcastGroup
175 doc = r['doc']
177 if doc['doc_type'] == 'Podcast':
178 return Podcast.wrap(doc)
179 else:
180 pid = r['key'][2]
181 pg = PodcastGroup.wrap(doc)
182 return pg.get_podcast_by_id(pid)
184 def __iter__(self):
185 for r in super(PodcastsMissingSlugs, self).__iter__():
186 yield self._podcast_wrapper(r)
189 class EpisodesMissingSlugs(ObjectsMissingSlugs):
190 """ Episodes that don't have a slug (but could have one) """
192 def __init__(self, podcast_id=None):
193 from mygpo.core.models import Episode
195 if podcast_id:
196 start = [podcast_id, None]
197 end = [podcast_id, {}]
198 else:
199 start = [None, None]
200 end = [{}, {}]
202 super(EpisodesMissingSlugs, self).__init__(Episode,
203 self._episode_wrapper, start, end)
205 @staticmethod
206 def _episode_wrapper(doc):
207 from mygpo.core.models import Episode
209 return Episode.wrap(doc)
212 class PodcastGroupsMissingSlugs(ObjectsMissingSlugs):
213 """ Podcast Groups that don't have a slug (but could have one) """
215 def __init__(self):
216 from mygpo.core.models import PodcastGroup
217 super(PodcastGroupsMissingSlugs, self).__init__(PodcastGroup,
218 self._group_wrapper)
220 @staticmethod
221 def _group_wrapper(doc):
222 from mygpo.core.models import PodcastGroup
223 return PodcastGroup.wrap(doc)
226 class SlugMixin(DocumentSchema):
227 slug = StringProperty()
228 merged_slugs = StringListProperty()
230 def set_slug(self, slug):
231 """ Set the main slug of the object """
233 if self.slug:
234 self.merged_slugs.append(self.slug)
236 self.merged_slugs = list(set(self.merged_slugs) - set([slug]))
238 self.slug = slug
241 def remove_slug(self, slug):
242 """ Removes the slug from the object """
244 # remove main slug
245 if self.slug == slug:
246 self.slug = None
248 # remove from merged slugs
249 self.merged_slugs = list(set(self.merged_slugs) - set([slug]))