1 # -*- coding: utf-8 -*-
2 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
5 from datetime import datetime, timedelta
6 from piston.handler import BaseHandler
7 from django.conf import settings
9 from api.helpers import timestamp
10 from api.models import Deleted
11 from catalogue.models import Book, Tag
14 class CatalogueHandler(BaseHandler):
17 def fields(request, name):
18 fields_str = request.GET.get(name) if request is not None else None
19 return fields_str.split(',') if fields_str is not None else None
23 """ Returns time suitable for use as upper time boundary for check.
25 Defaults to 'five minutes ago' to avoid issues with time between
26 change stamp set and model save.
27 Cuts the microsecond part to avoid issues with DBs where time has
31 # set to five minutes ago, to avoid concurrency issues
33 t = datetime.now() - timedelta(seconds=settings.API_WAIT)
34 # set to whole second in case DB supports something smaller
35 return t.replace(microsecond=0)
38 def book_dict(book, fields=None):
39 all_fields = ('url', 'title', 'description',
40 'gazeta_link', 'wiki_link',
41 'xml', 'epub', 'txt', 'pdf', 'html',
42 'mp3', 'ogg', 'daisy',
43 'parent', 'parent_number',
45 'license', 'license_description', 'source_name',
46 'technical_editors', 'editors',
50 fields = (f for f in fields if f in all_fields)
54 extra_info = book.get_extra_info_value()
59 if field in ('xml', 'epub', 'txt', 'pdf', 'html'):
60 f = getattr(book, field+'_file')
67 elif field in ('mp3', 'ogg', 'daisy'):
69 for m in book.media.filter(type=field):
78 obj[field] = book.get_absolute_url()
81 obj[field] = [t.id for t in book.tags.exclude(category__in=('book', 'set'))]
83 elif field == 'author':
84 obj[field] = ", ".join(t.name for t in book.tags.filter(category='author'))
86 elif field == 'parent':
87 obj[field] = book.parent_id
89 elif field in ('license', 'license_description', 'source_name',
90 'technical_editors', 'editors'):
91 f = extra_info.get(field)
96 f = getattr(book, field)
104 def book_changes(cls, request=None, since=0, until=None, fields=None):
105 since = datetime.fromtimestamp(int(since))
106 until = cls.until(until)
109 'time_checked': timestamp(until)
113 fields = cls.fields(request, 'book_fields')
120 for book in Book.objects.filter(changed_at__gte=since,
121 changed_at__lt=until):
122 book_d = cls.book_dict(book, fields)
123 updated.append(book_d)
125 changes['updated'] = updated
127 for book in Deleted.objects.filter(content_type=Book,
128 deleted_at__gte=since,
129 deleted_at__lt=until,
130 created_at__lt=since):
131 deleted.append(book.id)
133 changes['deleted'] = deleted
138 def tag_dict(tag, fields=None):
139 all_fields = ('name', 'category', 'sort_key', 'description',
140 'gazeta_link', 'wiki_link',
145 fields = (f for f in fields if f in all_fields)
153 obj[field] = tag.get_absolute_url()
155 elif field == 'books':
156 obj[field] = [b.id for b in Book.tagged_top_level([tag])]
158 elif field == 'sort_key':
159 obj[field] = tag.sort_key
162 f = getattr(tag, field)
170 def tag_changes(cls, request=None, since=0, until=None, fields=None, categories=None):
171 since = datetime.fromtimestamp(int(since))
172 until = cls.until(until)
175 'time_checked': timestamp(until)
179 fields = cls.fields(request, 'tag_fields')
181 categories = cls.fields(request, 'tag_categories')
183 all_categories = ('author', 'epoch', 'kind', 'genre')
185 categories = (c for c in categories if c in all_categories)
187 categories = all_categories
192 for tag in Tag.objects.filter(category__in=categories,
193 changed_at__gte=since,
194 changed_at__lt=until):
195 # only serve non-empty tags
197 tag_d = cls.tag_dict(tag, fields)
198 updated.append(tag_d)
199 elif tag.created_at < since:
200 deleted.append(tag.id)
202 changes['updated'] = updated
204 for tag in Deleted.objects.filter(category__in=categories,
206 deleted_at__gte=since,
207 deleted_at__lt=until,
208 created_at__lt=since):
209 deleted.append(tag.id)
211 changes['deleted'] = deleted
216 def changes(cls, request=None, since=0, until=None, book_fields=None,
217 tag_fields=None, tag_categories=None):
218 until = cls.until(until)
221 'time_checked': timestamp(until)
225 'books': cls.book_changes(request, since, until, book_fields),
226 'tags': cls.tag_changes(request, since, until, tag_fields, tag_categories),
229 for model in changes_by_type:
230 for field in changes_by_type[model]:
231 if field == 'time_checked':
233 changes.setdefault(field, {})[model] = changes_by_type[model][field]
237 class BookChangesHandler(CatalogueHandler):
238 allowed_methods = ('GET',)
240 def read(self, request, since):
241 return self.book_changes(request, since)
244 class TagChangesHandler(CatalogueHandler):
245 allowed_methods = ('GET',)
247 def read(self, request, since):
248 return self.tag_changes(request, since)
251 class ChangesHandler(CatalogueHandler):
252 allowed_methods = ('GET',)
254 def read(self, request, since):
255 return self.changes(request, since)