Add Book.ancestor m2m.
[wolnelektury.git] / apps / api / handlers.py
index b436024..a3a5ce5 100644 (file)
@@ -1,15 +1,16 @@
 # -*- 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.
 # -*- 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 datetime import datetime, timedelta
 import json
 from datetime import datetime, timedelta
 import json
-from urlparse import urljoin
 
 from django.conf import settings
 from django.contrib.sites.models import Site
 from django.core.cache import get_cache
 from django.core.urlresolvers import reverse
 
 from django.conf import settings
 from django.contrib.sites.models import Site
 from django.core.cache import get_cache
 from django.core.urlresolvers import reverse
+from django.utils.functional import lazy
+from django.utils.timezone import utc
 from piston.handler import AnonymousBaseHandler, BaseHandler
 from piston.utils import rc
 from sorl.thumbnail import default
 from piston.handler import AnonymousBaseHandler, BaseHandler
 from piston.utils import rc
 from sorl.thumbnail import default
@@ -18,13 +19,14 @@ from api.helpers import timestamp
 from api.models import Deleted
 from catalogue.forms import BookImportForm
 from catalogue.models import Book, Tag, BookMedia, Fragment, Collection
 from api.models import Deleted
 from catalogue.forms import BookImportForm
 from catalogue.models import Book, Tag, BookMedia, Fragment, Collection
-from catalogue.utils import related_tag_name
 from picture.models import Picture
 from picture.forms import PictureImportForm
 from picture.models import Picture
 from picture.forms import PictureImportForm
+from wolnelektury.utils import tz
 
 from stats.utils import piwik_track
 
 
 from stats.utils import piwik_track
 
-API_BASE = WL_BASE = MEDIA_BASE = 'http://' + Site.objects.get_current().domain
+API_BASE = WL_BASE = MEDIA_BASE = lazy(
+    lambda: u'http://' + Site.objects.get_current().domain, unicode)()
 
 
 category_singular = {
 
 
 category_singular = {
@@ -35,7 +37,7 @@ category_singular = {
     'themes': 'theme',
     'books': 'book',
 }
     'themes': 'theme',
     'books': 'book',
 }
-category_plural={}
+category_plural = {}
 for k, v in category_singular.items():
     category_plural[v] = k
 
 for k, v in category_singular.items():
     category_plural[v] = k
 
@@ -51,10 +53,11 @@ def read_tags(tags, allowed):
     :raises: ValueError when tags can't be found
     """
     if not tags:
     :raises: ValueError when tags can't be found
     """
     if not tags:
-        return []
+        return [], []
 
     tags = tags.strip('/').split('/')
     real_tags = []
 
     tags = tags.strip('/').split('/')
     real_tags = []
+    books = []
     while tags:
         category = tags.pop(0)
         slug = tags.pop(0)
     while tags:
         category = tags.pop(0)
         slug = tags.pop(0)
@@ -67,15 +70,14 @@ def read_tags(tags, allowed):
         if not category in allowed:
             raise ValueError('Category not allowed.')
 
         if not category in allowed:
             raise ValueError('Category not allowed.')
 
-        # !^%@#$^#!
         if category == 'book':
         if category == 'book':
-            slug = 'l-' + slug
+            books.append(Book.objects.get(slug=slug))
 
         try:
             real_tags.append(Tag.objects.get(category=category, slug=slug))
         except Tag.DoesNotExist:
             raise ValueError('Tag not found')
 
         try:
             real_tags.append(Tag.objects.get(category=category, slug=slug))
         except Tag.DoesNotExist:
             raise ValueError('Tag not found')
-    return real_tags
+    return real_tags, books
 
 
 # RESTful handlers
 
 
 # RESTful handlers
@@ -100,7 +102,6 @@ class BookMediaHandler(BaseHandler):
     @classmethod
     def director(cls, media):
         return media.extra_info.get('director_name', '')
     @classmethod
     def director(cls, media):
         return media.extra_info.get('director_name', '')
-        
 
 
 class BookDetails(object):
 
 
 class BookDetails(object):
@@ -184,7 +185,7 @@ class AnonymousBooksHandler(AnonymousBaseHandler, BookDetails):
              are returned.
         """
         try:
              are returned.
         """
         try:
-            tags = read_tags(tags, allowed=book_tag_categories)
+            tags, ancestors_ = read_tags(tags, allowed=book_tag_categories)
         except ValueError:
             return rc.NOT_FOUND
 
         except ValueError:
             return rc.NOT_FOUND
 
@@ -196,7 +197,7 @@ class AnonymousBooksHandler(AnonymousBaseHandler, BookDetails):
                 books = Book.tagged.with_all(tags)
         else:
             books = Book.objects.all()
                 books = Book.tagged.with_all(tags)
         else:
             books = Book.objects.all()
-            
+
         if top_level:
             books = books.filter(parent=None)
         if audiobooks:
         if top_level:
             books = books.filter(parent=None)
         if audiobooks:
@@ -245,7 +246,7 @@ def _tags_getter(category):
 def _tag_getter(category):
     @classmethod
     def get_tag(cls, book):
 def _tag_getter(category):
     @classmethod
     def get_tag(cls, book):
-        return ", ".join(related_tag_name(t) for t in book.related_info()['tags'].get(category, []))
+        return ', '.join(tag.name for tag in book.tags.filter(category=category))
     return get_tag
 for plural, singular in category_singular.items():
     setattr(BookDetails, plural, _tags_getter(singular))
     return get_tag
 for plural, singular in category_singular.items():
     setattr(BookDetails, plural, _tags_getter(singular))
@@ -293,7 +294,6 @@ class CollectionDetailHandler(BaseHandler, CollectionDetails):
 
     @piwik_track
     def read(self, request, slug):
 
     @piwik_track
     def read(self, request, slug):
-        print slug
         """ Returns details of a collection, identified by slug. """
         try:
             return Collection.objects.get(slug=slug)
         """ Returns details of a collection, identified by slug. """
         try:
             return Collection.objects.get(slug=slug)
@@ -368,7 +368,7 @@ class TagsHandler(BaseHandler, TagDetails):
         except KeyError, e:
             return rc.NOT_FOUND
 
         except KeyError, e:
             return rc.NOT_FOUND
 
-        tags = Tag.objects.filter(category=category_sng).exclude(book_count=0)
+        tags = Tag.objects.filter(category=category_sng).exclude(items=None)
         if tags.exists():
             return tags
         else:
         if tags.exists():
             return tags
         else:
@@ -382,7 +382,7 @@ class FragmentDetails(object):
     def href(cls, fragment):
         """ Returns URI in the API for the fragment. """
 
     def href(cls, fragment):
         """ Returns URI in the API for the fragment. """
 
-        return API_BASE + reverse("api_fragment", 
+        return API_BASE + reverse("api_fragment",
             args=[fragment.book.slug, fragment.anchor])
 
     @classmethod
             args=[fragment.book.slug, fragment.anchor])
 
     @classmethod
@@ -432,7 +432,7 @@ class FragmentsHandler(BaseHandler, FragmentDetails):
 
         """
         try:
 
         """
         try:
-            tags = read_tags(tags, allowed=self.categories)
+            tags, ancestors = read_tags(tags, allowed=self.categories)
         except ValueError:
             return rc.NOT_FOUND
         fragments = Fragment.tagged.with_all(tags).select_related('book')
         except ValueError:
             return rc.NOT_FOUND
         fragments = Fragment.tagged.with_all(tags).select_related('book')
@@ -466,7 +466,7 @@ class CatalogueHandler(BaseHandler):
         """
         # set to five minutes ago, to avoid concurrency issues
         if t is None:
         """
         # set to five minutes ago, to avoid concurrency issues
         if t is None:
-            t = datetime.now() - timedelta(seconds=settings.API_WAIT)
+            t = datetime.utcnow().replace(tzinfo=utc) - timedelta(seconds=settings.API_WAIT)
         # set to whole second in case DB supports something smaller
         return t.replace(microsecond=0)
 
         # set to whole second in case DB supports something smaller
         return t.replace(microsecond=0)
 
@@ -513,7 +513,7 @@ class CatalogueHandler(BaseHandler):
                 obj[field] = book.get_absolute_url()
 
             elif field == 'tags':
                 obj[field] = book.get_absolute_url()
 
             elif field == 'tags':
-                obj[field] = [t.id for t in book.tags.exclude(category__in=('book', 'set')).iterator()]
+                obj[field] = [t.id for t in book.tags.exclude(category='set').iterator()]
 
             elif field == 'author':
                 obj[field] = ", ".join(t.name for t in book.tags.filter(category='author').iterator())
 
             elif field == 'author':
                 obj[field] = ", ".join(t.name for t in book.tags.filter(category='author').iterator())
@@ -537,7 +537,7 @@ class CatalogueHandler(BaseHandler):
 
     @classmethod
     def book_changes(cls, request=None, since=0, until=None, fields=None):
 
     @classmethod
     def book_changes(cls, request=None, since=0, until=None, fields=None):
-        since = datetime.fromtimestamp(int(since))
+        since = datetime.fromtimestamp(int(since), tz)
         until = cls.until(until)
 
         changes = {
         until = cls.until(until)
 
         changes = {
@@ -559,7 +559,7 @@ class CatalogueHandler(BaseHandler):
         if updated:
             changes['updated'] = updated
 
         if updated:
             changes['updated'] = updated
 
-        for book in Deleted.objects.filter(content_type=Book, 
+        for book in Deleted.objects.filter(content_type=Book,
                     deleted_at__gte=since,
                     deleted_at__lt=until,
                     created_at__lt=since).iterator():
                     deleted_at__gte=since,
                     deleted_at__lt=until,
                     created_at__lt=since).iterator():
@@ -603,7 +603,7 @@ class CatalogueHandler(BaseHandler):
 
     @classmethod
     def tag_changes(cls, request=None, since=0, until=None, fields=None, categories=None):
 
     @classmethod
     def tag_changes(cls, request=None, since=0, until=None, fields=None, categories=None):
-        since = datetime.fromtimestamp(int(since))
+        since = datetime.fromtimestamp(int(since), tz)
         until = cls.until(until)
 
         changes = {
         until = cls.until(until)
 
         changes = {
@@ -624,20 +624,23 @@ class CatalogueHandler(BaseHandler):
         updated = []
         deleted = []
 
         updated = []
         deleted = []
 
-        for tag in Tag.objects.filter(category__in=categories, 
+        for tag in Tag.objects.filter(category__in=categories,
                     changed_at__gte=since,
                     changed_at__gte=since,
-                    changed_at__lt=until).iterator():
-            # only serve non-empty tags
-            if tag.book_count:
-                tag_d = cls.tag_dict(tag, fields)
-                updated.append(tag_d)
-            elif tag.created_at < since:
-                deleted.append(tag.id)
+                    changed_at__lt=until
+                    ).exclude(items=None).iterator():
+            tag_d = cls.tag_dict(tag, fields)
+            updated.append(tag_d)
+        for tag in Tag.objects.filter(category__in=categories,
+                    created_at__lt=since,
+                    changed_at__gte=since,
+                    changed_at__lt=until,
+                    items=None).iterator():
+            deleted.append(tag.id)
         if updated:
             changes['updated'] = updated
 
         for tag in Deleted.objects.filter(category__in=categories,
         if updated:
             changes['updated'] = updated
 
         for tag in Deleted.objects.filter(category__in=categories,
-                content_type=Tag, 
+                content_type=Tag,
                     deleted_at__gte=since,
                     deleted_at__lt=until,
                     created_at__lt=since).iterator():
                     deleted_at__gte=since,
                     deleted_at__lt=until,
                     created_at__lt=since).iterator():