X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/069d8476540f79fb4c37ccd219d6b794656baf0d..0098b333f963f74628c4fb0768b6dd56d9c6a07b:/apps/catalogue/models.py diff --git a/apps/catalogue/models.py b/apps/catalogue/models.py index 87c4fa6ee..272228627 100644 --- a/apps/catalogue/models.py +++ b/apps/catalogue/models.py @@ -2,6 +2,8 @@ # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # +from datetime import datetime + from django.db import models from django.db.models import permalink, Q from django.utils.translation import ugettext_lazy as _ @@ -17,12 +19,14 @@ from django.conf import settings 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 import mutagen from mutagen import id3 from slughifi import slughifi +from sortify import sortify TAG_CATEGORIES = ( @@ -65,6 +69,9 @@ class Tag(TagBase): gazeta_link = models.CharField(blank=True, max_length=240) wiki_link = models.CharField(blank=True, max_length=240) + created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True) + changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True) + class UrlDeprecationWarning(DeprecationWarning): pass @@ -176,14 +183,14 @@ def book_upload_path(ext=None, maxlen=100): 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") - 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) extra_info = JSONField(_('extra information'), default='{}', editable=False) book = models.ForeignKey('Book', related_name='media') @@ -198,12 +205,20 @@ class BookMedia(models.Model): verbose_name_plural = _('book media') def save(self, *args, **kwargs): + try: + old = BookMedia.objects.get(pk=self.pk) + except BookMedia.DoesNotExist, e: + pass + else: + # if name changed, change the file name, too + if slughifi(self.name) != slughifi(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) - print self.extra_info, self.source_sha1 return super(BookMedia, self).save(*args, **kwargs) def read_meta(self): @@ -211,12 +226,16 @@ class BookMedia(models.Model): Reads some metadata from the audiobook. """ - artist_name = director_name = '' + 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': @@ -224,11 +243,14 @@ class BookMedia(models.Model): 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} + return {'artist_name': artist_name, 'director_name': director_name, + 'project': project, 'funded_by': funded_by} @staticmethod def read_source_sha1(filepath, filetype): @@ -240,7 +262,7 @@ class BookMedia(models.Model): try: audio = id3.ID3(filepath) return [t.data for t in audio.getall('PRIV') - if t.owner=='http://wolnelektury.pl?flac_sha1'][0] + if t.owner=='wolnelektury.pl?flac_sha1'][0] except: return None elif filetype == 'ogg': @@ -255,12 +277,14 @@ class BookMedia(models.Model): class Book(models.Model): title = models.CharField(_('title'), max_length=120) + sort_key = models.CharField(_('sort_key'), max_length=120, db_index=True, editable=False) slug = models.SlugField(_('slug'), max_length=120, unique=True, db_index=True) description = models.TextField(_('description'), blank=True) - created_at = models.DateTimeField(_('creation date'), auto_now_add=True) + created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True) + changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True) _short_html = models.TextField(_('short HTML'), editable=False) parent_number = models.IntegerField(_('parent number'), default=0) - extra_info = JSONField(_('extra information')) + extra_info = JSONField(_('extra information'), default='{}') gazeta_link = models.CharField(blank=True, max_length=240) wiki_link = models.CharField(blank=True, max_length=240) # files generated during publication @@ -282,7 +306,7 @@ class Book(models.Model): pass class Meta: - ordering = ('title',) + ordering = ('sort_key',) verbose_name = _('book') verbose_name_plural = _('books') @@ -290,6 +314,8 @@ class Book(models.Model): return self.title def save(self, force_insert=False, force_update=False, reset_short_html=True, **kwargs): + self.sort_key = sortify(self.title) + if reset_short_html: # Reset _short_html during save update = {} @@ -589,7 +615,7 @@ class Book(models.Model): tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category) if created: tag.name = tag_name - tag.sort_key = tag_sort_key.lower() + tag.sort_key = sortify(tag_sort_key.lower()) tag.save() book_tags.append(tag) @@ -735,6 +761,24 @@ class Book(models.Model): return ', '.join(names) + @classmethod + def tagged_top_level(cls, tags): + """ Returns top-level books tagged with `tags'. + + It only returns those books which don't have ancestors which are + also tagged with those tags. + + """ + # get relevant books and their tags + objects = cls.tagged.with_all(tags) + # eliminate descendants + l_tags = Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() for book in objects]) + descendants_keys = [book.pk for book in cls.tagged.with_any(l_tags)] + if descendants_keys: + objects = objects.exclude(pk__in=descendants_keys) + + return objects + class Fragment(models.Model): text = models.TextField() @@ -790,7 +834,8 @@ class FileRecord(models.Model): def _tags_updated_handler(sender, affected_tags, **kwargs): # reset tag global counter - Tag.objects.filter(pk__in=[tag.pk for tag in affected_tags]).update(book_count=None) + # we want Tag.changed_at updated for API to know the tag was touched + Tag.objects.filter(pk__in=[tag.pk for tag in affected_tags]).update(book_count=None, changed_at=datetime.now()) # if book tags changed, reset book tag counter if isinstance(sender, Book) and \