Book _short_html fix, Fragment _short_html translations
[wolnelektury.git] / apps / catalogue / models.py
index 81965ce..2097e17 100644 (file)
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
 from django.db import models
 from django.db.models import permalink, Q
 from django.utils.translation import ugettext_lazy as _
 from django.db import models
 from django.db.models import permalink, Q
 from django.utils.translation import ugettext_lazy as _
@@ -6,13 +9,16 @@ from django.contrib.auth.models import User
 from django.core.files import File
 from django.template.loader import render_to_string
 from django.utils.safestring import mark_safe
 from django.core.files import File
 from django.template.loader import render_to_string
 from django.utils.safestring import mark_safe
+from django.utils.translation import get_language
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
+from datetime import datetime
 
 from newtagging.models import TagBase
 from newtagging import managers
 from catalogue.fields import JSONField
 
 from librarian import html, dcparser
 
 from newtagging.models import TagBase
 from newtagging import managers
 from catalogue.fields import JSONField
 
 from librarian import html, dcparser
+from mutagen import id3
 
 
 TAG_CATEGORIES = (
 
 
 TAG_CATEGORIES = (
@@ -22,6 +28,7 @@ TAG_CATEGORIES = (
     ('genre', _('genre')),
     ('theme', _('theme')),
     ('set', _('set')),
     ('genre', _('genre')),
     ('theme', _('theme')),
     ('set', _('set')),
+    ('book', _('book')),
 )
 
 
 )
 
 
@@ -45,12 +52,26 @@ class Tag(TagBase):
     
     user = models.ForeignKey(User, blank=True, null=True)
     book_count = models.IntegerField(_('book count'), default=0, blank=False, null=False)
     
     user = models.ForeignKey(User, blank=True, null=True)
     book_count = models.IntegerField(_('book count'), default=0, blank=False, null=False)
+    death = models.IntegerField(_(u'year of death'), blank=True, null=True)
+    gazeta_link = models.CharField(blank=True,  max_length=240)
+    wiki_link = models.CharField(blank=True,  max_length=240)
     
     def has_description(self):
         return len(self.description) > 0
     has_description.short_description = _('description')
     has_description.boolean = True
 
     
     def has_description(self):
         return len(self.description) > 0
     has_description.short_description = _('description')
     has_description.boolean = True
 
+    def alive(self):
+        return self.death is None
+    
+    def in_pd(self):
+        """ tests whether an author is in public domain """
+        return self.death is not None and self.goes_to_pd() <= datetime.now().year
+    
+    def goes_to_pd(self):
+        """ calculates the year of public domain entry for an author """
+        return self.death + 71 if self.death is not None else None
+
     @permalink
     def get_absolute_url(self):
         return ('catalogue.views.tagged_object_list', [self.slug])
     @permalink
     def get_absolute_url(self):
         return ('catalogue.views.tagged_object_list', [self.slug])
@@ -86,6 +107,9 @@ class Book(models.Model):
     _short_html = models.TextField(_('short HTML'), editable=False)
     parent_number = models.IntegerField(_('parent number'), default=0)
     extra_info = JSONField(_('extra information'))
     _short_html = models.TextField(_('short HTML'), editable=False)
     parent_number = models.IntegerField(_('parent number'), default=0)
     extra_info = JSONField(_('extra information'))
+    gazeta_link = models.CharField(blank=True,  max_length=240)
+    wiki_link = models.CharField(blank=True,  max_length=240)
+
     
     # Formats
     xml_file = models.FileField(_('XML file'), upload_to=book_upload_path('xml'), blank=True)
     
     # Formats
     xml_file = models.FileField(_('XML file'), upload_to=book_upload_path('xml'), blank=True)
@@ -93,40 +117,74 @@ class Book(models.Model):
     pdf_file = models.FileField(_('PDF file'), upload_to=book_upload_path('pdf'), blank=True)
     odt_file = models.FileField(_('ODT file'), upload_to=book_upload_path('odt'), 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)
     odt_file = models.FileField(_('ODT file'), upload_to=book_upload_path('odt'), blank=True)
     txt_file = models.FileField(_('TXT file'), upload_to=book_upload_path('txt'), blank=True)
+    mp3_file = models.FileField(_('MP3 file'), upload_to=book_upload_path('mp3'), blank=True)
+    ogg_file = models.FileField(_('OGG file'), upload_to=book_upload_path('ogg'), blank=True)
     
     parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
     
     objects = models.Manager()
     tagged = managers.ModelTaggedItemManager(Tag)
     tags = managers.TagDescriptor(Tag)
     
     parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
     
     objects = models.Manager()
     tagged = managers.ModelTaggedItemManager(Tag)
     tags = managers.TagDescriptor(Tag)
-
     
     @property
     def name(self):
         return self.title
     
     def short_html(self):
     
     @property
     def name(self):
         return self.title
     
     def short_html(self):
-        if len(self._short_html):
-            return mark_safe(self._short_html)
+        key = '_short_html_%s' % get_language()
+        short_html = getattr(self, key)
+        
+        if len(short_html):
+            return mark_safe(short_html)
         else:
         else:
-            tags = self.tags.filter(~Q(category__in=('set', 'theme')))
-            tags = [u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name) for tag in tags]
+            tags = self.tags.filter(~Q(category__in=('set', 'theme', 'book')))
+            tags = [mark_safe(u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name)) for tag in tags]
 
             formats = []
             if self.html_file:
 
             formats = []
             if self.html_file:
-                formats.append(u'<a href="%s">Czytaj online</a>' % reverse('book_text', kwargs={'slug': self.slug}))
+                formats.append(u'<a href="%s">%s</a>' % (reverse('book_text', kwargs={'slug': self.slug}), _('Read online')))
             if self.pdf_file:
             if self.pdf_file:
-                formats.append(u'<a href="%s">Plik PDF</a>' % self.pdf_file.url)
+                formats.append(u'<a href="%s">PDF</a>' % self.pdf_file.url)
             if self.odt_file:
             if self.odt_file:
-                formats.append(u'<a href="%s">Plik ODT</a>' % self.odt_file.url)
+                formats.append(u'<a href="%s">ODT</a>' % self.odt_file.url)
             if self.txt_file:
             if self.txt_file:
-                formats.append(u'<a href="%s">Plik TXT</a>' % self.txt_file.url)
+                formats.append(u'<a href="%s">TXT</a>' % self.txt_file.url)
+            if self.mp3_file:
+                formats.append(u'<a href="%s">MP3</a>' % self.mp3_file.url)
+            if self.ogg_file:
+                formats.append(u'<a href="%s">OGG</a>' % self.ogg_file.url)
             
             
-            self._short_html = unicode(render_to_string('catalogue/book_short.html',
-                {'book': self, 'tags': tags, 'formats': formats}))
-            self.save()
-            return mark_safe(self._short_html)
+            formats = [mark_safe(format) for format in formats]
+            
+            setattr(self, key, unicode(render_to_string('catalogue/book_short.html',
+                {'book': self, 'tags': tags, 'formats': formats})))
+            self.save(reset_short_html=False)
+            return mark_safe(getattr(self, key))
     
     
+    def save(self, force_insert=False, force_update=False, reset_short_html=True):
+        if reset_short_html:
+            # Reset _short_html during save
+            for key in filter(lambda x: x.startswith('_short_html'), self.__dict__):
+                self.__setattr__(key, '')
+        
+        book = super(Book, self).save(force_insert, force_update)
+        
+        if self.mp3_file:
+            print self.mp3_file, self.mp3_file.path
+            extra_info = self.get_extra_info_value()
+            extra_info.update(self.get_mp3_info())
+            self.set_extra_info_value(extra_info)
+            book = super(Book, self).save(force_insert, force_update)
+        
+        return book
+    
+    def get_mp3_info(self):
+        """Retrieves artist and director names from audio ID3 tags."""
+        audio = id3.ID3(self.mp3_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'))
+        return {'artist_name': artist_name, 'director_name': director_name}
+        
     def has_description(self):
         return len(self.description) > 0
     has_description.short_description = _('description')
     def has_description(self):
         return len(self.description) > 0
     has_description.short_description = _('description')
@@ -165,12 +223,12 @@ class Book(models.Model):
             book_shelves = []
         else:
             if not overwrite:
             book_shelves = []
         else:
             if not overwrite:
-                raise Book.AlreadyExists('Book %s already exists' % book_slug)
+                raise Book.AlreadyExists(_('Book %s already exists') % book_slug)
             # Save shelves for this book
             book_shelves = list(book.tags.filter(category='set'))
         
         book.title = book_info.title
             # Save shelves for this book
             book_shelves = list(book.tags.filter(category='set'))
         
         book.title = book_info.title
-        book.extra_info = book_info.to_dict()
+        book.set_extra_info_value(book_info.to_dict())
         book._short_html = ''
         book.save()
         
         book._short_html = ''
         book.save()
         
@@ -188,18 +246,39 @@ class Book(models.Model):
                 tag.category = category
                 tag.save()
             book_tags.append(tag)
                 tag.category = category
                 tag.save()
             book_tags.append(tag)
+            
+        book_tag, created = Tag.objects.get_or_create(slug=('l-' + book.slug)[:120])
+        if created:
+            book_tag.name = book.title[:50]
+            book_tag.sort_key = ('l-' + book.slug)[:120]
+            book_tag.category = 'book'
+            book_tag.save()
+        book_tags.append(book_tag)
+        
         book.tags = book_tags
         
         if hasattr(book_info, 'parts'):
             for n, part_url in enumerate(book_info.parts):
                 base, slug = part_url.rsplit('/', 1)
         book.tags = book_tags
         
         if hasattr(book_info, 'parts'):
             for n, part_url in enumerate(book_info.parts):
                 base, slug = part_url.rsplit('/', 1)
-                child_book = Book.objects.get(slug=slug)
-                child_book.parent = book
-                child_book.parent_number = n
-                child_book.save()
+                try:
+                    child_book = Book.objects.get(slug=slug)
+                    child_book.parent = book
+                    child_book.parent_number = n
+                    child_book.save()
+                except Book.DoesNotExist, e:
+                    raise Book.DoesNotExist(_('Book with slug = "%s" does not exist.') % slug)
         
         
+        book_descendants = list(book.children.all())
+        while len(book_descendants) > 0:
+            child_book = book_descendants.pop(0)
+            for fragment in child_book.fragments.all():
+                fragment.tags = set(list(fragment.tags) + [book_tag])
+            book_descendants += list(child_book.children.all())
+            
         # Save XML and HTML files
         # Save XML and HTML files
-        book.xml_file.save('%s.xml' % book.slug, File(file(xml_file)), save=False)
+        if not isinstance(xml_file, File):
+            xml_file = File(file(xml_file))
+        book.xml_file.save('%s.xml' % book.slug, xml_file, save=False)
         
         html_file = NamedTemporaryFile()
         if html.transform(book.xml_file.path, html_file):
         
         html_file = NamedTemporaryFile()
         if html.transform(book.xml_file.path, html_file):
@@ -230,7 +309,7 @@ class Book(models.Model):
                         tag.save()
                     themes.append(tag)
                 new_fragment.save()
                         tag.save()
                     themes.append(tag)
                 new_fragment.save()
-                new_fragment.tags = list(book.tags) + themes
+                new_fragment.tags = set(list(book.tags) + themes + [book_tag])
                 book_themes += themes
             
             book_themes = set(book_themes)
                 book_themes += themes
             
             book_themes = set(book_themes)
@@ -264,16 +343,18 @@ class Fragment(models.Model):
     tags = managers.TagDescriptor(Tag)
     
     def short_html(self):
     tags = managers.TagDescriptor(Tag)
     
     def short_html(self):
-        if len(self._short_html):
-            return mark_safe(self._short_html)
+        key = '_short_html_%s' % get_language()
+        short_html = getattr(self, key)         
+        if len(short_html):
+            return mark_safe(short_html)
         else:
         else:
-            book_authors = [u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name
+            book_authors = [mark_safe(u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name)
                 for tag in self.book.tags if tag.category == 'author']
             
                 for tag in self.book.tags if tag.category == 'author']
             
-            self._short_html = unicode(render_to_string('catalogue/fragment_short.html',
-                {'fragment': self, 'book': self.book, 'book_authors': book_authors}))
+            setattr(self, key, unicode(render_to_string('catalogue/fragment_short.html',
+                {'fragment': self, 'book': self.book, 'book_authors': book_authors})))
             self.save()
             self.save()
-            return mark_safe(self._short_html)
+            return mark_safe(getattr(self, key))
     
     def get_absolute_url(self):
         return '%s#m%s' % (reverse('book_text', kwargs={'slug': self.book.slug}), self.anchor)
     
     def get_absolute_url(self):
         return '%s#m%s' % (reverse('book_text', kwargs={'slug': self.book.slug}), self.anchor)
@@ -283,3 +364,30 @@ class Fragment(models.Model):
         verbose_name = _('fragment')
         verbose_name_plural = _('fragments')
 
         verbose_name = _('fragment')
         verbose_name_plural = _('fragments')
 
+
+class BookStub(models.Model):
+    title = models.CharField(_('title'), max_length=120)
+    author = models.CharField(_('author'), max_length=120)
+    pd = models.IntegerField(_('goes to public domain'), null=True, blank=True)
+    slug = models.SlugField(_('slug'), max_length=120, unique=True, db_index=True)
+    translator = models.TextField(_('translator'), blank=True)
+    translator_death = models.TextField(_('year of translator\'s death'), blank=True)
+
+    def in_pd(self):
+        return self.pd is not None and self.pd <= datetime.now().year
+
+    @property
+    def name(self):
+        return self.title
+    
+    @permalink
+    def get_absolute_url(self):
+        return ('catalogue.views.book_detail', [self.slug])
+
+    def __unicode__(self):
+        return self.title
+    
+    class Meta:
+        ordering = ('title',)
+        verbose_name = _('book stub')
+        verbose_name_plural = _('book stubs')
\ No newline at end of file