[Models] Index outdated, extend guid to 200 chars
[mygpo.git] / mygpo / podcasts / models.py
blob9caa771752eda25d45644baa898b4d5f3d1ddea2
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 class Meta:
28 abstract = True
31 class DescriptionModel(models.Model):
32 """ Model that has a description """
34 description = models.TextField(null=False, blank=True)
36 class Meta:
37 abstract = True
40 class LinkModel(models.Model):
41 """ Model that has a link """
43 link = models.URLField(null=True, max_length=1000)
45 class Meta:
46 abstract = True
49 class LanguageModel(models.Model):
50 """ Model that has a language """
52 language = models.CharField(max_length=10, null=True, blank=False,
53 db_index=True)
55 class Meta:
56 abstract = True
59 class LastUpdateModel(models.Model):
60 """ Model with timestamp of last update from its source """
62 # date and time at which the model has last been updated from its source
63 # (eg a podcast feed). None means that the object has been created as a
64 # stub, without information from the source.
65 last_update = models.DateTimeField(null=True)
67 class Meta:
68 abstract = True
71 class UpdateInfoModel(models.Model):
73 # this does not use "auto_now_add=True" so that data
74 # can be migrated with its creation timestamp intact; it can be
75 # switched on after the migration is complete
76 created = models.DateTimeField()
77 modified = models.DateTimeField(auto_now=True)
79 class Meta:
80 abstract = True
83 class LicenseModel(models.Model):
84 # URL to a license (usually Creative Commons)
85 license = models.CharField(max_length=100, null=True, blank=False,
86 db_index=True)
88 class Meta:
89 abstract = True
92 class FlattrModel(models.Model):
93 # A Flattr payment URL
94 flattr_url = models.URLField(null=True, blank=False, max_length=1000,
95 db_index=True)
97 class Meta:
98 abstract = True
101 class ContentTypesModel(models.Model):
102 # contains a comma-separated values of content types, eg 'audio,video'
103 content_types = models.CharField(max_length=20, null=False, blank=True)
105 class Meta:
106 abstract = True
109 class MergedIdsModel(models.Model):
111 class Meta:
112 abstract = True
115 class OutdatedModel(models.Model):
116 outdated = models.BooleanField(default=False, db_index=True)
118 class Meta:
119 abstract = True
122 class AuthorModel(models.Model):
123 author = models.CharField(max_length=150, null=True, blank=True)
125 class Meta:
126 abstract = True
129 class UrlsMixin(models.Model):
130 """ Methods for working with URL objects """
132 urls = GenericRelation('URL', related_query_name='urls')
134 class Meta:
135 abstract = True
138 class SlugsMixin(models.Model):
139 """ Methods for working with Slug objects """
141 slugs = GenericRelation('Slug', related_query_name='slugs')
143 class Meta:
144 abstract = True
146 @property
147 def slug(self):
148 """ The main slug of the podcast
150 TODO: should be retrieved from a (materialized) view """
151 slug = self.slugs.first()
152 if slug is None:
153 return None
154 return slug.slug
157 class MergedUUIDsMixin(models.Model):
158 """ Methods for working with MergedUUID objects """
160 merged_uuids = GenericRelation('MergedUUID',
161 related_query_name='merged_uuids')
163 class Meta:
164 abstract = True
166 class TagsMixin(models.Model):
167 """ Methods for working with Tag objects """
169 tags = GenericRelation('Tag', related_query_name='tags')
171 class Meta:
172 abstract = True
175 class OrderedModel(models.Model):
176 """ A model that can be ordered
178 The implementing Model must make sure that 'order' is sufficiently unique
181 order = models.PositiveSmallIntegerField()
183 class Meta:
184 abstract = True
185 ordering = ['order']
188 class PodcastGroup(UUIDModel, TitleModel):
189 """ Groups multiple podcasts together """
192 class PodcastQuerySet(models.QuerySet):
193 """ Custom queries for Podcasts """
195 def random(self):
196 """ Random podcasts
198 Excludes podcasts with missing title to guarantee some
199 minimum quality of the results """
200 return self.exclude(title='').order_by('?')
202 def flattr(self):
203 """ Podcasts providing Flattr information """
204 return self.exclude(flattr_url__isnull=True)
206 def license(self, license_url=None):
207 """ Podcasts with any / the given license """
208 if license_url:
209 return self.filter(license=license_url)
210 else:
211 return self.exclude(license__isnull=True)
215 class Podcast(UUIDModel, TitleModel, DescriptionModel, LinkModel,
216 LanguageModel, LastUpdateModel, UpdateInfoModel, LicenseModel,
217 FlattrModel, ContentTypesModel, MergedIdsModel, OutdatedModel,
218 AuthorModel, UrlsMixin, SlugsMixin, TagsMixin, MergedUUIDsMixin):
219 """ A Podcast """
221 logo_url = models.URLField(null=True, max_length=1000)
222 group = models.ForeignKey(PodcastGroup, null=True)
223 group_member_name = models.CharField(max_length=30, null=True, blank=False)
225 # if p1 is related to p2, p2 is also related to p1
226 related_podcasts = models.ManyToManyField('self', symmetrical=True)
228 #subscribers = SchemaListProperty(SubscriberData)
229 restrictions = models.CharField(max_length=20, null=True, blank=True)
230 common_episode_title = models.CharField(max_length=50, null=False, blank=True)
231 new_location = models.URLField(max_length=1000, null=True, blank=False)
232 latest_episode_timestamp = models.DateTimeField(null=True)
233 episode_count = models.PositiveIntegerField(default=0)
234 hub = models.URLField(null=True)
235 twitter = models.CharField(max_length=15, null=True, blank=False)
237 objects = PodcastQuerySet.as_manager()
239 def subscriber_count(self):
240 # TODO: implement
241 return 0
243 def __str__(self):
244 return self.title.encode('ascii', errors='replace')
246 def __unicode(self):
247 return self.title
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']
268 def __str__(self):
269 return self.title.encode('ascii', errors='replace')
271 def __unicode__(self):
272 return self.title
275 class ScopedModel(models.Model):
277 # A slug / URL is unique within a scope; no two podcasts can have the same
278 # URL (scope None), and no two episdoes of the same podcast (scope =
279 # podcast-ID) can have the same URL
280 scope = UUIDField(null=True, db_index=True)
282 class Meta:
283 abstract = True
286 class URL(OrderedModel, ScopedModel):
287 """ Podcasts and Episodes can have multiple URLs
289 URLs are ordered, and the first slug is considered the canonical one """
291 url = models.URLField(max_length=2048)
293 # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations
294 content_type = models.ForeignKey(ContentType)
295 object_id = UUIDField()
296 content_object = generic.GenericForeignKey('content_type', 'object_id')
298 class Meta:
299 unique_together = (
300 # a URL is unique per scope
301 ('url', 'scope'),
303 # URLs of an object must be ordered, so that no two slugs of one
304 # object have the same order key
305 ('content_type', 'object_id', 'order'),
308 verbose_name = 'URL'
309 verbose_name_plural = 'URLs'
312 class Tag(models.Model):
313 """ Tags any kind of Model
315 See also :class:`TagsMixin`
318 FEED = 1
319 DELICIOUS = 2
320 USER = 4
322 SOURCE_CHOICES = (
323 (FEED, 'Feed'),
324 (DELICIOUS, 'delicious'),
325 (USER, 'User'),
328 tag = models.SlugField()
329 source = models.PositiveSmallIntegerField(choices=SOURCE_CHOICES)
330 #user = models.ForeignKey(null=True)
332 # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations
333 content_type = models.ForeignKey(ContentType)
334 object_id = UUIDField()
335 content_object = generic.GenericForeignKey('content_type', 'object_id')
337 class Meta:
338 unique_together = (
339 # a tag can only be assigned once from one source to one item
340 # TODO: add user to tuple
341 ('tag', 'source', 'content_type', 'object_id'),
345 class Slug(OrderedModel, ScopedModel):
346 """ Slug for any kind of Model
348 Slugs are ordered, and the first slug is considered the canonical one.
349 See also :class:`SlugsMixin`
352 slug = models.SlugField(max_length=150, db_index=True)
354 # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations
355 content_type = models.ForeignKey(ContentType)
356 object_id = UUIDField()
357 content_object = generic.GenericForeignKey('content_type', 'object_id')
359 class Meta:
360 unique_together = (
361 # a slug is unique per type; eg a podcast can have the same slug
362 # as an episode, but no two podcasts can have the same slug
363 ('slug', 'scope'),
365 # slugs of an object must be ordered, so that no two slugs of one
366 # object have the same order key
367 ('content_type', 'object_id', 'order'),
370 def __repr__(self):
371 return '{cls}(slug={slug}, order={order}, content_object={obj}'.format(
372 cls=self.__class__.__name__,
373 slug=self.slug,
374 order=self.order,
375 obj=self.content_object
379 class MergedUUID(models.Model):
380 """ If objects are merged their UUIDs are stored for later reference
382 see also :class:`MergedUUIDsMixin`
385 uuid = UUIDField(unique=True)
387 # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations
388 content_type = models.ForeignKey(ContentType)
389 object_id = UUIDField()
390 content_object = generic.GenericForeignKey('content_type', 'object_id')
392 class Meta:
393 verbose_name = 'Merged UUID'
394 verbose_name_plural = 'Merged UUIDs'