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)
20 class TitleModel(models
.Model
):
21 """ Model that has a title """
23 title
= models
.CharField(max_length
=1000, null
=False, blank
=True,
25 subtitle
= models
.CharField(max_length
=1000, null
=False, blank
=True)
31 class DescriptionModel(models
.Model
):
32 """ Model that has a description """
34 description
= models
.TextField(null
=False, blank
=True)
40 class LinkModel(models
.Model
):
41 """ Model that has a link """
43 link
= models
.URLField(null
=True, max_length
=1000)
49 class LanguageModel(models
.Model
):
50 """ Model that has a language """
52 language
= models
.CharField(max_length
=10, null
=True, blank
=False,
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)
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)
83 class LicenseModel(models
.Model
):
84 # URL to a license (usually Creative Commons)
85 license
= models
.CharField(max_length
=100, null
=True, blank
=False,
92 class FlattrModel(models
.Model
):
93 # A Flattr payment URL
94 flattr_url
= models
.URLField(null
=True, blank
=False, max_length
=1000,
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)
109 class MergedIdsModel(models
.Model
):
115 class OutdatedModel(models
.Model
):
116 outdated
= models
.BooleanField(default
=False, db_index
=True)
122 class AuthorModel(models
.Model
):
123 author
= models
.CharField(max_length
=150, null
=True, blank
=True)
129 class UrlsMixin(models
.Model
):
130 """ Methods for working with URL objects """
132 urls
= GenericRelation('URL', related_query_name
='urls')
138 class SlugsMixin(models
.Model
):
139 """ Methods for working with Slug objects """
141 slugs
= GenericRelation('Slug', related_query_name
='slugs')
148 """ The main slug of the podcast
150 TODO: should be retrieved from a (materialized) view """
151 slug
= self
.slugs
.first()
157 class MergedUUIDsMixin(models
.Model
):
158 """ Methods for working with MergedUUID objects """
160 merged_uuids
= GenericRelation('MergedUUID',
161 related_query_name
='merged_uuids')
166 class TagsMixin(models
.Model
):
167 """ Methods for working with Tag objects """
169 tags
= GenericRelation('Tag', related_query_name
='tags')
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()
188 class PodcastGroup(UUIDModel
, TitleModel
):
189 """ Groups multiple podcasts together """
192 class PodcastQuerySet(models
.QuerySet
):
193 """ Custom queries for Podcasts """
198 Excludes podcasts with missing title to guarantee some
199 minimum quality of the results """
200 return self
.exclude(title
='').order_by('?')
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 """
209 return self
.filter(license
=license_url
)
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
):
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
):
244 return self
.title
.encode('ascii', errors
='replace')
250 class Episode(UUIDModel
, TitleModel
, DescriptionModel
, LinkModel
,
251 LanguageModel
, LastUpdateModel
, UpdateInfoModel
, LicenseModel
,
252 FlattrModel
, ContentTypesModel
, MergedIdsModel
, OutdatedModel
,
253 AuthorModel
, UrlsMixin
, SlugsMixin
, MergedUUIDsMixin
):
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)
266 ordering
= ['-released']
269 return self
.title
.encode('ascii', errors
='replace')
271 def __unicode__(self
):
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)
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')
300 # a URL is unique per 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'),
309 verbose_name_plural
= 'URLs'
312 class Tag(models
.Model
):
313 """ Tags any kind of Model
315 See also :class:`TagsMixin`
324 (DELICIOUS
, 'delicious'),
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')
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')
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
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'),
371 return '{cls}(slug={slug}, order={order}, content_object={obj}'.format(
372 cls
=self
.__class
__.__name
__,
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')
393 verbose_name
= 'Merged UUID'
394 verbose_name_plural
= 'Merged UUIDs'