1 from collections
import defaultdict
3 from itertools
import count
5 from couchdbkit
.ext
.django
.schema
import *
7 from django
.utils
.text
import slugify
9 from mygpo
.decorators
import repeat_on_conflict
10 from mygpo
.utils
import partition
13 def assign_slug(obj
, generator
):
17 slug
= generator(obj
).get_slug()
18 _set_slug(obj
=obj
, slug
=slug
)
21 def assign_missing_episode_slugs(podcast
):
22 common_title
= podcast
.get_common_episode_title()
24 episodes
= EpisodesMissingSlugs(podcast
.get_id())
27 for episode
in episodes
:
28 slug
= EpisodeSlug(episode
, common_title
).get_slug()
29 _set_slug(obj
=episode
, slug
=slug
)
32 @repeat_on_conflict(['obj'])
33 def _set_slug(obj
, slug
):
40 class SlugGenerator(object):
41 """ Generates a unique slug for an object """
44 def __init__(self
, obj
, override_existing
=False):
45 if obj
.slug
and not override_existing
:
46 raise ValueError('%(obj)s already has slug %(slug)s' % \
47 dict(obj
=obj
, slug
=obj
.slug
))
49 self
.base_slug
= self
._get
_base
_slug
(obj
)
53 def _get_base_slug(obj
):
56 base_slug
= slugify(obj
.title
)
61 def _get_existing_slugs():
66 """ Gets existing slugs and appends numbers until slug is unique """
67 if not self
.base_slug
:
70 existing_slugs
= self
._get
_existing
_slugs
()
72 if not self
.base_slug
in existing_slugs
:
73 return str(self
.base_slug
)
76 tmp_slug
= '%s-%d' % (self
.base_slug
, n
)
77 if not tmp_slug
in existing_slugs
:
78 # slugify returns SafeUnicode, we need a plain string
83 class PodcastGroupSlug(SlugGenerator
):
84 """ Generates slugs for Podcast Groups """
86 def _get_existing_slugs(self
):
87 from mygpo
.db
.couchdb
.podcast
import podcast_slugs
88 return podcast_slugs(self
.base_slug
)
92 class PodcastSlug(PodcastGroupSlug
):
93 """ Generates slugs for Podcasts """
96 def _get_base_slug(podcast
):
97 base_slug
= SlugGenerator
._get
_base
_slug
(podcast
)
102 # append group_member_name to slug
103 if podcast
.group_member_name
:
104 member_slug
= slugify(podcast
.group_member_name
)
105 if member_slug
and not member_slug
in base_slug
:
106 base_slug
= '%s-%s' % (base_slug
, member_slug
)
112 class EpisodeSlug(SlugGenerator
):
113 """ Generates slugs for Episodes """
115 def __init__(self
, episode
, common_title
, override_existing
=False):
116 self
.common_title
= common_title
117 super(EpisodeSlug
, self
).__init
__(episode
, override_existing
)
118 self
.podcast_id
= episode
.podcast
121 def _get_base_slug(self
, obj
):
123 number
= obj
.get_episode_number(self
.common_title
)
127 short_title
= obj
.get_short_title(self
.common_title
)
129 return slugify(short_title
)
132 return slugify(obj
.title
)
137 def _get_existing_slugs(self
):
138 """ Episode slugs have to be unique within the Podcast """
139 from mygpo
.db
.couchdb
.episode
import episode_slugs_per_podcast
140 return episode_slugs_per_podcast(self
.podcast_id
, self
.base_slug
)
143 class ObjectsMissingSlugs(object):
144 """ A collections of objects missing a slug """
146 def __init__(self
, cls
, wrapper
=None, start
=[None], end
=[{}]):
148 self
.doc_type
= cls
._doc
_type
149 self
.wrapper
= wrapper
156 from mygpo
.db
.couchdb
.common
import missing_slug_count
157 return missing_slug_count(self
.doc_type
, self
.start
, self
.end
)
161 from mygpo
.db
.couchdb
.common
import missing_slugs
162 return missing_slugs(self
.doc_type
, self
.start
, self
.end
, self
.wrapper
, **self
.kwargs
)
166 class PodcastsMissingSlugs(ObjectsMissingSlugs
):
167 """ Podcasts that don't have a slug (but could have one) """
170 from mygpo
.core
.models
import Podcast
171 super(PodcastsMissingSlugs
, self
).__init
__(Podcast
, self
._podcast
_wrapper
)
172 self
.kwargs
= {'wrap': False}
175 def _podcast_wrapper(r
):
176 from mygpo
.core
.models
import Podcast
, PodcastGroup
180 if doc
['doc_type'] == 'Podcast':
181 return Podcast
.wrap(doc
)
184 pg
= PodcastGroup
.wrap(doc
)
185 return pg
.get_podcast_by_id(pid
)
188 for r
in super(PodcastsMissingSlugs
, self
).__iter
__():
189 yield self
._podcast
_wrapper
(r
)
192 class EpisodesMissingSlugs(ObjectsMissingSlugs
):
193 """ Episodes that don't have a slug (but could have one) """
195 def __init__(self
, podcast_id
=None):
196 from mygpo
.core
.models
import Episode
199 start
= [podcast_id
, None]
200 end
= [podcast_id
, {}]
205 super(EpisodesMissingSlugs
, self
).__init
__(Episode
,
206 self
._episode
_wrapper
, start
, end
)
209 def _episode_wrapper(doc
):
210 from mygpo
.core
.models
import Episode
212 return Episode
.wrap(doc
)
215 class PodcastGroupsMissingSlugs(ObjectsMissingSlugs
):
216 """ Podcast Groups that don't have a slug (but could have one) """
219 from mygpo
.core
.models
import PodcastGroup
220 super(PodcastGroupsMissingSlugs
, self
).__init
__(PodcastGroup
,
224 def _group_wrapper(doc
):
225 from mygpo
.core
.models
import PodcastGroup
226 return PodcastGroup
.wrap(doc
)
229 class SlugMixin(DocumentSchema
):
230 slug
= StringProperty()
231 merged_slugs
= StringListProperty()
233 def set_slug(self
, slug
):
234 """ Set the main slug of the object """
237 self
.merged_slugs
.append(self
.slug
)
239 self
.merged_slugs
= list(set(self
.merged_slugs
) - set([slug
]))
244 def remove_slug(self
, slug
):
245 """ Removes the slug from the object """
248 if self
.slug
== slug
:
251 # remove from merged slugs
252 self
.merged_slugs
= list(set(self
.merged_slugs
) - set([slug
]))
255 def get_duplicate_slugs(episodes
):
256 """ Finds duplicate slugs and yields (slug, duplicates) pairs for each slug
258 Such a pair is only yielded for each slug that actually has a duplicate.
259 The "duplicates" list does not contain the selected "winner" of a set of
262 # we build a dict of {slug: [episode1, episode2, ...], ...}
263 # for each slug all episodes are given that use this slug
264 slugs
= defaultdict(list)
266 for episode
in episodes
:
267 all_slugs
= filter(None, [episode
.slug
] + episode
.merged_slugs
)
268 for slug
in all_slugs
:
269 slugs
[slug
].append(episode
)
271 # filter out unique slugs
272 dups
= {s
: eps
for (s
, eps
) in slugs
.items() if len(eps
) > 1}
274 for slug
, episodes
in dups
.items():
275 merged
, main
= partition(episodes
, lambda e
: e
.slug
== slug
)
277 main
, merged
= list(main
), list(merged
)
279 # we want to determine exactly one winner, the rest is in "merged"
284 winner
= merged
.pop()
287 winner
, merged
= main
[0], main
[1:] + merged
289 # for every loser, remove the slug