test fix
[wolnelektury.git] / apps / catalogue / models.py
index 2202cc1..df7a79e 100644 (file)
@@ -17,9 +17,11 @@ from django.conf import settings
 
 from newtagging.models import TagBase, tags_updated
 from newtagging import managers
 
 from newtagging.models import TagBase, tags_updated
 from newtagging import managers
-from catalogue.fields import JSONField
+from catalogue.fields import JSONField, OverwritingFileField
+from catalogue.utils import ExistingFile
 
 from librarian import dcparser, html, epub, NoDublinCore
 
 from librarian import dcparser, html, epub, NoDublinCore
+import mutagen
 from mutagen import id3
 from slughifi import slughifi
 
 from mutagen import id3
 from slughifi import slughifi
 
@@ -175,24 +177,18 @@ def book_upload_path(ext=None, maxlen=100):
             name = slughifi(filename.split(".")[0])
         else:
             name = slughifi(media.name)
             name = slughifi(filename.split(".")[0])
         else:
             name = slughifi(media.name)
-        return 'lektura/%s.%s' % (name[:maxlen-len('lektura/.%s' % ext)-4], ext)
+        return 'book/%s/%s.%s' % (ext, name[:maxlen-len('book/%s/.%s' % (ext, ext))-4], ext)
     return get_dynamic_path
 
 
 class BookMedia(models.Model):
     type        = models.CharField(_('type'), choices=MEDIA_FORMATS, max_length="100")
     name        = models.CharField(_('name'), max_length="100")
     return get_dynamic_path
 
 
 class BookMedia(models.Model):
     type        = models.CharField(_('type'), choices=MEDIA_FORMATS, max_length="100")
     name        = models.CharField(_('name'), max_length="100")
-    file        = models.FileField(_('file'), upload_to=book_upload_path())
+    file        = OverwritingFileField(_('file'), upload_to=book_upload_path())
     uploaded_at = models.DateTimeField(_('creation date'), auto_now_add=True, editable=False)
     uploaded_at = models.DateTimeField(_('creation date'), auto_now_add=True, editable=False)
-    extra_info  = JSONField(_('extra information'), default='{}')
-
-    def book_count(self):
-        return self.book_set.count()
-    book_count.short_description = _('book count')
-
-    def books(self):
-        return mark_safe('<br/>'.join("<a href='%s'>%s</a>" % (reverse('admin:catalogue_book_change', args=[b.id]), b.title) for b in self.book_set.all()))
-    books.short_description = _('books')
+    extra_info  = JSONField(_('extra information'), default='{}', editable=False)
+    book = models.ForeignKey('Book', related_name='media')
+    source_sha1 = models.CharField(null=True, blank=True, max_length=40, editable=False)
 
     def __unicode__(self):
         return "%s (%s)" % (self.name, self.file.name.split("/")[-1])
 
     def __unicode__(self):
         return "%s (%s)" % (self.name, self.file.name.split("/")[-1])
@@ -202,25 +198,75 @@ class BookMedia(models.Model):
         verbose_name        = _('book media')
         verbose_name_plural = _('book media')
 
         verbose_name        = _('book media')
         verbose_name_plural = _('book media')
 
-    def save(self, force_insert=False, force_update=False, **kwargs):
-        media = super(BookMedia, self).save(force_insert, force_update, **kwargs)
-        if self.type == 'mp3':
-            file = self.file
-            extra_info = self.get_extra_info_value()
-            extra_info.update(self.get_mp3_info())
-            self.set_extra_info_value(extra_info)
-            media = super(BookMedia, self).save(force_insert, force_update, **kwargs)
-        return media
-
-    def get_mp3_info(self):
-        """Retrieves artist and director names from audio ID3 tags."""
+    def save(self, *args, **kwargs):
         try:
         try:
-            audio = id3.ID3(self.file.path)
-            artist_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE1'))
-            director_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE3'))
-        except:
-            artist_name = director_name = ''
-        return {'artist_name': artist_name, 'director_name': director_name}
+            old = BookMedia.objects.get(pk=self.pk)
+        except BookMedia.DoesNotExist, e:
+            pass
+        else:
+            # if name changed, change the file name, too
+            if self.name != old.name:
+                self.file.save(None, ExistingFile(self.file.path), save=False, leave=True)
+
+        super(BookMedia, self).save(*args, **kwargs)
+        extra_info = self.get_extra_info_value()
+        extra_info.update(self.read_meta())
+        self.set_extra_info_value(extra_info)
+        self.source_sha1 = self.read_source_sha1(self.file.path, self.type)
+        return super(BookMedia, self).save(*args, **kwargs)
+
+    def read_meta(self):
+        """
+            Reads some metadata from the audiobook.
+        """
+
+        artist_name = director_name = project = funded_by = ''
+        if self.type == 'mp3':
+            try:
+                audio = id3.ID3(self.file.path)
+                artist_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE1'))
+                director_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE3'))
+                project = ", ".join([t.data for t in audio.getall('PRIV') 
+                        if t.owner=='wolnelektury.pl?project'])
+                funded_by = ", ".join([t.data for t in audio.getall('PRIV') 
+                        if t.owner=='wolnelektury.pl?funded_by'])
+            except:
+                pass
+        elif self.type == 'ogg':
+            try:
+                audio = mutagen.File(self.file.path)
+                artist_name = ', '.join(audio.get('artist', []))
+                director_name = ', '.join(audio.get('conductor', []))
+                project = ", ".join(audio.get('project', []))
+                funded_by = ", ".join(audio.get('funded_by', []))
+            except:
+                pass
+        else:
+            return {}
+        return {'artist_name': artist_name, 'director_name': director_name,
+                'project': project, 'funded_by': funded_by}
+
+    @staticmethod
+    def read_source_sha1(filepath, filetype):
+        """
+            Reads source file SHA1 from audiobok metadata.
+        """
+
+        if filetype == 'mp3':
+            try:
+                audio = id3.ID3(filepath)
+                return [t.data for t in audio.getall('PRIV') 
+                        if t.owner=='wolnelektury.pl?flac_sha1'][0]
+            except:
+                return None
+        elif filetype == 'ogg':
+            try:
+                audio = mutagen.File(filepath)
+                return audio.get('flac_sha1', [None])[0] 
+            except:
+                return None
+        else:
+            return None
 
 
 class Book(models.Model):
 
 
 class Book(models.Model):
@@ -239,9 +285,7 @@ class Book(models.Model):
     pdf_file      = models.FileField(_('PDF file'), upload_to=book_upload_path('pdf'), blank=True)
     epub_file     = models.FileField(_('EPUB file'), upload_to=book_upload_path('epub'), blank=True)    
     txt_file      = models.FileField(_('TXT file'), upload_to=book_upload_path('txt'), blank=True)        
     pdf_file      = models.FileField(_('PDF file'), upload_to=book_upload_path('pdf'), blank=True)
     epub_file     = models.FileField(_('EPUB file'), upload_to=book_upload_path('epub'), blank=True)    
     txt_file      = models.FileField(_('TXT file'), upload_to=book_upload_path('txt'), blank=True)        
-    # other files
-    medias        = models.ManyToManyField(BookMedia, blank=True)
-    
+
     parent        = models.ForeignKey('self', blank=True, null=True, related_name='children')
     objects  = models.Manager()
     tagged   = managers.ModelTaggedItemManager(Tag)
     parent        = models.ForeignKey('self', blank=True, null=True, related_name='children')
     objects  = models.Manager()
     tagged   = managers.ModelTaggedItemManager(Tag)
@@ -320,7 +364,7 @@ class Book(models.Model):
             else:
                 return False                          
         else:
             else:
                 return False                          
         else:
-            if self.medias.filter(book=self, type=type).count() > 0:
+            if self.media.filter(type=type).exists():
                 return True
             else:
                 return False
                 return True
             else:
                 return False
@@ -338,7 +382,7 @@ class Book(models.Model):
             elif type == "pdf":
                 return self.pdf_file
             else:                                             
             elif type == "pdf":
                 return self.pdf_file
             else:                                             
-                return self.medias.filter(book=self, type=type)
+                return self.media.filter(type=type)
         else:
             return None
 
         else:
             return None
 
@@ -372,7 +416,7 @@ class Book(models.Model):
             if self.has_media("txt"):
                 formats.append(u'<a href="%s">TXT</a>' % self.get_media('txt').url)
             # other files
             if self.has_media("txt"):
                 formats.append(u'<a href="%s">TXT</a>' % self.get_media('txt').url)
             # other files
-            for m in self.medias.order_by('type'):
+            for m in self.media.order_by('type'):
                 formats.append(u'<a href="%s">%s</a>' % (m.file.url, m.type.upper()))
 
             formats = [mark_safe(format) for format in formats]
                 formats.append(u'<a href="%s">%s</a>' % (m.file.url, m.type.upper()))
 
             formats = [mark_safe(format) for format in formats]
@@ -777,23 +821,14 @@ def _tags_updated_handler(sender, affected_tags, **kwargs):
 tags_updated.connect(_tags_updated_handler)
 
 
 tags_updated.connect(_tags_updated_handler)
 
 
-def _m2m_changed_handler(sender, instance, action, reverse, pk_set, **kwargs):
-    """ refresh all the short_html stuff on BookMedia delete """
-    if sender == Book.medias.through and reverse and action == 'pre_clear':
-        for book in instance.book_set.all():
-            book.save()
-m2m_changed.connect(_m2m_changed_handler)
-
 def _pre_delete_handler(sender, instance, **kwargs):
 def _pre_delete_handler(sender, instance, **kwargs):
-    """ explicitly clear m2m, so that Books can be refreshed """
+    """ refresh Book on BookMedia delete """
     if sender == BookMedia:
     if sender == BookMedia:
-        instance.book_set.clear()
+        instance.book.save()
 pre_delete.connect(_pre_delete_handler)
 
 def _post_save_handler(sender, instance, **kwargs):
     """ refresh all the short_html stuff on BookMedia update """
     if sender == BookMedia:
 pre_delete.connect(_pre_delete_handler)
 
 def _post_save_handler(sender, instance, **kwargs):
     """ refresh all the short_html stuff on BookMedia update """
     if sender == BookMedia:
-        for book in instance.book_set.all():
-            book.save()
+        instance.book.save()
 post_save.connect(_post_save_handler)
 post_save.connect(_post_save_handler)
-