[Models] add SlugsMixin to PodcastGroup
[mygpo.git] / mygpo / podcasts / models.py
blob5950cf76813ea32b6ee88e458c6d6b11ffb01f38
1 from __future__ import unicode_literals
3 from django.db import models
4 from django.contrib.contenttypes.models import ContentType
5 from django.contrib.contenttypes.fields import GenericRelation
6 from django.contrib.contenttypes import generic
8 from uuidfield import UUIDField
11 class UUIDModel(models.Model):
12 """ Models that have an UUID as primary key """
14 id = UUIDField(primary_key=True)
16 class Meta:
17 abstract = True
20 class TitleModel(models.Model):
21 """ Model that has a title """
23 title = models.CharField(max_length=1000, null=False, blank=True,
24 db_index=True)
25 subtitle = models.CharField(max_length=1000, null=False, blank=True)
27 def __str__(self):
28 return self.title.encode('ascii', errors='replace')
30 def __unicode(self):
31 return self.title
33 class Meta:
34 abstract = True
37 class DescriptionModel(models.Model):
38 """ Model that has a description """
40 description = models.TextField(null=False, blank=True)
42 class Meta:
43 abstract = True
46 class LinkModel(models.Model):
47 """ Model that has a link """
49 link = models.URLField(null=True, max_length=1000)
51 class Meta:
52 abstract = True
55 class LanguageModel(models.Model):
56 """ Model that has a language """
58 language = models.CharField(max_length=10, null=True, blank=False,
59 db_index=True)
61 class Meta:
62 abstract = True
65 class LastUpdateModel(models.Model):
66 """ Model with timestamp of last update from its source """
68 # date and time at which the model has last been updated from its source
69 # (eg a podcast feed). None means that the object has been created as a
70 # stub, without information from the source.
71 last_update = models.DateTimeField(null=True)
73 class Meta:
74 abstract = True
77 class UpdateInfoModel(models.Model):
79 # this does not use "auto_now_add=True" so that data
80 # can be migrated with its creation timestamp intact; it can be
81 # switched on after the migration is complete
82 created = models.DateTimeField()
83 modified = models.DateTimeField(auto_now=True)
85 class Meta:
86 abstract = True
89 class LicenseModel(models.Model):
90 # URL to a license (usually Creative Commons)
91 license = models.CharField(max_length=100, null=True, blank=False,
92 db_index=True)
94 class Meta:
95 abstract = True
98 class FlattrModel(models.Model):
99 # A Flattr payment URL
100 flattr_url = models.URLField(null=True, blank=False, max_length=1000,
101 db_index=True)
103 class Meta:
104 abstract = True
107 class ContentTypesModel(models.Model):
108 # contains a comma-separated values of content types, eg 'audio,video'
109 content_types = models.CharField(max_length=20, null=False, blank=True)
111 class Meta:
112 abstract = True
115 class MergedIdsModel(models.Model):
117 class Meta:
118 abstract = True
121 class OutdatedModel(models.Model):
122 outdated = models.BooleanField(default=False, db_index=True)
124 class Meta:
125 abstract = True
128 class AuthorModel(models.Model):
129 author = models.CharField(max_length=150, null=True, blank=True)
131 class Meta:
132 abstract = True
135 class UrlsMixin(models.Model):
136 """ Methods for working with URL objects """
138 urls = GenericRelation('URL', related_query_name='urls')
140 class Meta:
141 abstract = True
144 class SlugsMixin(models.Model):
145 """ Methods for working with Slug objects """
147 slugs = GenericRelation('Slug', related_query_name='slugs')
149 class Meta:
150 abstract = True
152 @property
153 def slug(self):
154 """ The main slug of the podcast
156 TODO: should be retrieved from a (materialized) view """
157 slug = self.slugs.first()
158 if slug is None:
159 return None
160 return slug.slug
163 class MergedUUIDsMixin(models.Model):
164 """ Methods for working with MergedUUID objects """
166 merged_uuids = GenericRelation('MergedUUID',
167 related_query_name='merged_uuids')
169 class Meta:
170 abstract = True
172 class TagsMixin(models.Model):
173 """ Methods for working with Tag objects """
175 tags = GenericRelation('Tag', related_query_name='tags')
177 class Meta:
178 abstract = True
181 class OrderedModel(models.Model):
182 """ A model that can be ordered
184 The implementing Model must make sure that 'order' is sufficiently unique
187 order = models.PositiveSmallIntegerField()
189 class Meta:
190 abstract = True
191 ordering = ['order']
194 class PodcastGroup(UUIDModel, TitleModel, SlugsMixin):
195 """ Groups multiple podcasts together """
198 class PodcastQuerySet(models.QuerySet):
199 """ Custom queries for Podcasts """
201 def random(self):
202 """ Random podcasts
204 Excludes podcasts with missing title to guarantee some
205 minimum quality of the results """
206 return self.exclude(title='').order_by('?')
208 def flattr(self):
209 """ Podcasts providing Flattr information """
210 return self.exclude(flattr_url__isnull=True)
212 def license(self, license_url=None):
213 """ Podcasts with any / the given license """
214 if license_url:
215 return self.filter(license=license_url)
216 else:
217 return self.exclude(license__isnull=True)
221 class Podcast(UUIDModel, TitleModel, DescriptionModel, LinkModel,
222 LanguageModel, LastUpdateModel, UpdateInfoModel, LicenseModel,
223 FlattrModel, ContentTypesModel, MergedIdsModel, OutdatedModel,
224 AuthorModel, UrlsMixin, SlugsMixin, TagsMixin, MergedUUIDsMixin):
225 """ A Podcast """
227 logo_url = models.URLField(null=True, max_length=1000)
228 group = models.ForeignKey(PodcastGroup, null=True)
229 group_member_name = models.CharField(max_length=30, null=True, blank=False)
231 # if p1 is related to p2, p2 is also related to p1
232 related_podcasts = models.ManyToManyField('self', symmetrical=True)
234 #subscribers = SchemaListProperty(SubscriberData)
235 restrictions = models.CharField(max_length=20, null=True, blank=True)
236 common_episode_title = models.CharField(max_length=50, null=False, blank=True)
237 new_location = models.URLField(max_length=1000, null=True, blank=False)
238 latest_episode_timestamp = models.DateTimeField(null=True)
239 episode_count = models.PositiveIntegerField(default=0)
240 hub = models.URLField(null=True)
241 twitter = models.CharField(max_length=15, null=True, blank=False)
243 objects = PodcastQuerySet.as_manager()
245 def subscriber_count(self):
246 # TODO: implement
247 return 0
250 class Episode(UUIDModel, TitleModel, DescriptionModel, LinkModel,
251 LanguageModel, LastUpdateModel, UpdateInfoModel, LicenseModel,
252 FlattrModel, ContentTypesModel, MergedIdsModel, OutdatedModel,
253 AuthorModel, UrlsMixin, SlugsMixin, MergedUUIDsMixin):
254 """ An episode """
256 guid = models.CharField(max_length=200, null=True)
257 content = models.TextField()
258 released = models.DateTimeField(null=True)
259 duration = models.PositiveIntegerField(null=True)
260 filesize = models.BigIntegerField(null=True)
261 mimetypes = models.CharField(max_length=50)
262 podcast = models.ForeignKey(Podcast)
263 listeners = models.PositiveIntegerField(null=True)
265 class Meta:
266 ordering = ['-released']
269 class ScopedModel(models.Model):
271 # A slug / URL is unique within a scope; no two podcasts can have the same
272 # URL (scope None), and no two episdoes of the same podcast (scope =
273 # podcast-ID) can have the same URL
274 scope = UUIDField(null=True, db_index=True)
276 class Meta:
277 abstract = True
280 class URL(OrderedModel, ScopedModel):
281 """ Podcasts and Episodes can have multiple URLs
283 URLs are ordered, and the first slug is considered the canonical one """
285 url = models.URLField(max_length=2048)
287 # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations
288 content_type = models.ForeignKey(ContentType)
289 object_id = UUIDField()
290 content_object = generic.GenericForeignKey('content_type', 'object_id')
292 class Meta:
293 unique_together = (
294 # a URL is unique per scope
295 ('url', 'scope'),
297 # URLs of an object must be ordered, so that no two slugs of one
298 # object have the same order key
299 ('content_type', 'object_id', 'order'),
302 verbose_name = 'URL'
303 verbose_name_plural = 'URLs'
306 class Tag(models.Model):
307 """ Tags any kind of Model
309 See also :class:`TagsMixin`
312 FEED = 1
313 DELICIOUS = 2
314 USER = 4
316 SOURCE_CHOICES = (
317 (FEED, 'Feed'),
318 (DELICIOUS, 'delicious'),
319 (USER, 'User'),
322 tag = models.SlugField()
323 source = models.PositiveSmallIntegerField(choices=SOURCE_CHOICES)
324 #user = models.ForeignKey(null=True)
326 # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations
327 content_type = models.ForeignKey(ContentType)
328 object_id = UUIDField()
329 content_object = generic.GenericForeignKey('content_type', 'object_id')
331 class Meta:
332 unique_together = (
333 # a tag can only be assigned once from one source to one item
334 # TODO: add user to tuple
335 ('tag', 'source', 'content_type', 'object_id'),
339 class Slug(OrderedModel, ScopedModel):
340 """ Slug for any kind of Model
342 Slugs are ordered, and the first slug is considered the canonical one.
343 See also :class:`SlugsMixin`
346 slug = models.SlugField(max_length=150, db_index=True)
348 # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations
349 content_type = models.ForeignKey(ContentType)
350 object_id = UUIDField()
351 content_object = generic.GenericForeignKey('content_type', 'object_id')
353 class Meta:
354 unique_together = (
355 # a slug is unique per type; eg a podcast can have the same slug
356 # as an episode, but no two podcasts can have the same slug
357 ('slug', 'scope'),
359 # slugs of an object must be ordered, so that no two slugs of one
360 # object have the same order key
361 ('content_type', 'object_id', 'order'),
364 def __repr__(self):
365 return '{cls}(slug={slug}, order={order}, content_object={obj}'.format(
366 cls=self.__class__.__name__,
367 slug=self.slug,
368 order=self.order,
369 obj=self.content_object
373 class MergedUUID(models.Model):
374 """ If objects are merged their UUIDs are stored for later reference
376 see also :class:`MergedUUIDsMixin`
379 uuid = UUIDField(unique=True)
381 # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations
382 content_type = models.ForeignKey(ContentType)
383 object_id = UUIDField()
384 content_object = generic.GenericForeignKey('content_type', 'object_id')
386 class Meta:
387 verbose_name = 'Merged UUID'
388 verbose_name_plural = 'Merged UUIDs'