Add Cast.published field
[podjango:podjango-sof.git] / podjango / apps / cast / models.py
1 #  Copyright (C) 2008       Bradley M. Kuhn <bkuhn@ebb.org>
2 #  Copyright (C) 2006, 2007 Software Freedom Law Center, Inc.
3 #  Copyright (C) 2012       Will Thompson <will@willthompson.co.uk>
4 #
5 # This software's license gives you freedom; you can copy, convey,
6 # propogate, redistribute and/or modify this program under the terms of
7 # the GNU Affero General Public License (AGPL) as published by the Free
8 # Software Foundation (FSF), either version 3 of the License, or (at your
9 # option) any later version of the AGPL published by the FSF.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program in a file in the toplevel directory called
18 # "AGPLv3".  If not, see <http://www.gnu.org/licenses/>.
19 #
20 from django.db import models
21 from django.db.models.fields.files import FileDescriptor
22 from django.conf import settings
23 #from podjango.apps.staff.models import Person
24 from datetime import datetime, timedelta
25 import tagpy
26
27 #class CastTag(models.Model):
28 #    """Tagging for casts"""
29 #
30 #    label = models.CharField(max_length=100)
31 #    slug = models.SlugField()
32 #
33 #    class Meta:
34 #        db_table = 'cast_tags' # legacy
35 #
36 #    def __unicode__(self):
37 #        return self.label
38 #
39 #    def get_absolute_url(self):
40 #        return u"/cast/?tag=%s" % self.slug
41
42 class Season(models.Model):
43     """Represents a season of the show."""
44     number = models.PositiveSmallIntegerField(primary_key=True)
45
46     def __unicode__(self):
47         return "Season %u" % self.number
48
49     def sorted_casts(self):
50         return self.cast_set.order_by('-pub_date')
51
52     def sorted_past_casts(self):
53         return self.cast_set.filter(pub_date__lte=datetime.now()).order_by('-pub_date')
54
55     @models.permalink
56     def get_absolute_url(self):
57         return ('podjango.apps.cast.views.single_season', (), {
58             'season_id': self.number,
59         })
60
61 def get_default_season():
62     try:
63         return Season.objects.order_by('-number')[0]
64     except IndexError:
65         return None
66
67 class MP3FileDescriptor(FileDescriptor):
68     """
69     Just like ImageFileDescriptor, but for MP3Fields.
70     """
71     def __set__(self, instance, value):
72         previous_file = instance.__dict__.get(self.field.name)
73         super(MP3FileDescriptor, self).__set__(instance, value)
74
75         # To prevent recalculating mp3 duration when we are instantiating
76         # an object from the database, only update duration if
77         # the field had a value before this assignment.  Since the default
78         # value for FileField subclasses is an instance of field.attr_class,
79         # previous_file will only be None when we are called from
80         # Model.__init__().  The MP3Field.update_duration_field method
81         # hooked up to the post_init signal handles the Model.__init__() cases.
82         # Assignment happening outside of Model.__init__() will trigger the
83         # update right here.
84         if previous_file is not None:
85             self.field.update_duration_field(instance, force=True)
86
87 class MP3Field(models.FileField):
88     descriptor_class = MP3FileDescriptor
89
90     def __init__(self, duration_field=None, **kwargs):
91         self.duration_field = duration_field
92         super(MP3Field, self).__init__(**kwargs)
93
94     def contribute_to_class(self, cls, name):
95         super(MP3Field, self).contribute_to_class(cls, name)
96         models.signals.post_init.connect(self.update_duration_field, sender=cls)
97
98     def update_duration_field(self, instance, force=False, *args, **kwargs):
99         if self.duration_field is None:
100             return
101
102         file = getattr(instance, self.attname)
103
104         if not file and not force:
105             return
106
107         existing_duration = getattr(instance, self.duration_field)
108         if existing_duration and not force:
109             return
110
111         if file:
112             tagpy_fileref = tagpy.FileRef(file.path.encode('utf-8'))
113             if tagpy_fileref is None:
114                 return
115             properties = tagpy_fileref.audioProperties()
116             if properties is None:
117                 return
118
119             # Cool! The file is on disk now.
120             full_seconds = properties.length
121             seconds = full_seconds % 60
122             full_minutes = full_seconds / 60
123             minutes = full_minutes % 60
124             hours = full_minutes / 60
125
126             duration = "%d:%02d:%02d" % (hours, minutes, seconds)
127         else:
128             duration = ''
129
130         setattr(instance, self.duration_field, duration)
131
132 from south.modelsinspector import add_introspection_rules
133 add_introspection_rules([], ["^podjango\.apps\.cast\.models\.MP3Field"])
134
135 class Cast(models.Model):
136     """Cast"""
137
138     title = models.CharField(max_length=200)
139     slug = models.SlugField(unique=True)
140     summary = models.TextField(
141         help_text="A single paragraph describing the show. Use Markdown for formatting")
142     body = models.TextField(blank=True,
143         help_text="More information about the show: links, corrections, tracklists. Use Markdown for formatting")
144     published = models.BooleanField(
145         default=False,
146         help_text="Leave this unticked to stop this episode appearing in the "
147                   "episode list, podcast feed or upcoming episode box")
148     pub_date = models.DateTimeField(
149         help_text="The start time of the episode. Episodes are assumed to be an hour long")
150     season = models.ForeignKey(Season, default=get_default_season)
151 #    poster = models.ForeignKey(Person)
152 #    tags = models.ManyToManyField(CastTag, null=True, blank=True)
153 #    ogg_path = models.CharField(max_length=300, blank=True,
154 #                             help_text="Local filename of the Ogg file, relative to webroot.  File should be uploaded into the static media area for casts.")
155     mp3 = MP3Field(max_length=300, blank=True,
156         upload_to='episodes',
157         duration_field='duration')
158     #ogg_length = models.IntegerField(null=True, blank=True, help_text="size in bytes of ogg file")
159     duration = models.CharField(max_length=8, blank=True,
160         help_text="length in hh:mm:ss of mp3 file; calculated automatically, and just shown for reference")
161     date_created = models.DateTimeField(auto_now_add=True)
162     date_last_modified = models.DateTimeField(auto_now=True)
163     listen_again_id = models.CharField(max_length=4, blank=True,
164         help_text="The four-digit identifier from the CAM FM listen again page")
165
166     class Meta:
167         db_table = 'casts_entries' # legacy
168         verbose_name_plural = 'casts'
169         ordering = ('-pub_date',)
170         get_latest_by = 'pub_date'
171
172     def __unicode__(self):
173         return self.title
174
175     @models.permalink
176     def get_absolute_url(self):
177         return ('podjango.apps.cast.views.single_cast', (), {
178             'season_id': self.season.number,
179             'slug': self.slug,
180         })
181
182     def is_recent(self):
183         return self.pub_date > (datetime.now() - timedelta(days=14))
184         # question: does datetime.now() do a syscall each time is it called?
185
186 class CastImage(models.Model):
187     """Externally-hosted artwork for casts. Typically from Flickr."""
188     cast = models.ForeignKey(Cast, related_name='images')
189     url = models.URLField(help_text='The "Medium" size image URL')
190     source_url = models.URLField(help_text='The source of this image')
191     title = models.CharField(max_length=100)
192     author = models.CharField(max_length=100)
193
194     def __unicode__(self):
195         return self.title