more api changes, preparing for Android app
[wolnelektury.git] / apps / catalogue / models.py
index 00baa28..c1290fb 100644 (file)
@@ -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.
 #
 # 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 _
 from django.db import models
 from django.db.models import permalink, Q
 from django.utils.translation import ugettext_lazy as _
@@ -22,6 +24,7 @@ from catalogue.fields import JSONField
 from librarian import dcparser, html, epub, NoDublinCore
 from mutagen import id3
 from slughifi import slughifi
 from librarian import dcparser, html, epub, NoDublinCore
 from mutagen import id3
 from slughifi import slughifi
+from sortify import sortify
 
 
 TAG_CATEGORIES = (
 
 
 TAG_CATEGORIES = (
@@ -64,6 +67,9 @@ class Tag(TagBase):
     gazeta_link = models.CharField(blank=True, max_length=240)
     wiki_link = models.CharField(blank=True, max_length=240)
 
     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)
+
     categories_rev = {
         'autor': 'author',
         'epoka': 'epoch',
     categories_rev = {
         'autor': 'author',
         'epoka': 'epoch',
@@ -168,7 +174,7 @@ 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))], ext)
+        return 'lektura/%s.%s' % (name[:maxlen-len('lektura/.%s' % ext)-4], ext)
     return get_dynamic_path
 
 
     return get_dynamic_path
 
 
@@ -218,12 +224,14 @@ class BookMedia(models.Model):
 
 class Book(models.Model):
     title         = models.CharField(_('title'), max_length=120)
 
 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)
     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)
     _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
     gazeta_link   = models.CharField(blank=True, max_length=240)
     wiki_link     = models.CharField(blank=True, max_length=240)
     # files generated during publication
@@ -247,7 +255,7 @@ class Book(models.Model):
         pass
 
     class Meta:
         pass
 
     class Meta:
-        ordering = ('title',)
+        ordering = ('sort_key',)
         verbose_name = _('book')
         verbose_name_plural = _('books')
 
         verbose_name = _('book')
         verbose_name_plural = _('books')
 
@@ -255,6 +263,8 @@ class Book(models.Model):
         return self.title
 
     def save(self, force_insert=False, force_update=False, reset_short_html=True, **kwargs):
         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 = {}
         if reset_short_html:
             # Reset _short_html during save
             update = {}
@@ -554,11 +564,11 @@ class Book(models.Model):
                 tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category)
                 if created:
                     tag.name = tag_name
                 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)
 
                     tag.save()
                 book_tags.append(tag)
 
-        book.tags = book_tags + book_shelves
+        book.tags = set(book_tags + book_shelves)
 
         book_tag = book.book_tag()
 
 
         book_tag = book.book_tag()
 
@@ -700,6 +710,24 @@ class Book(models.Model):
 
         return ', '.join(names)
 
 
         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()
 
 class Fragment(models.Model):
     text = models.TextField()
@@ -755,7 +783,8 @@ class FileRecord(models.Model):
 
 def _tags_updated_handler(sender, affected_tags, **kwargs):
     # reset tag global counter
 
 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 \
 
     # if book tags changed, reset book tag counter
     if isinstance(sender, Book) and \