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 in ('license', 'license_description', 'source_name',
87 'technical_editors', 'editors'):
88 f = extra_info.get(field)
93 f = getattr(book, field)
101 def book_changes(cls, request=None, since=0, until=None, fields=None):
102 since = datetime.fromtimestamp(int(since))
103 until = cls.until(until)
106 'time_checked': timestamp(until)
110 fields = cls.fields(request, 'book_fields')
117 for book in Book.objects.filter(changed_at__gte=since,
118 changed_at__lt=until):
119 book_d = cls.book_dict(book, fields)
120 updated.append(book_d)
122 changes['updated'] = updated
124 for book in Deleted.objects.filter(content_type=Book,
125 deleted_at__gte=since,
126 deleted_at__lt=until,
127 created_at__lt=since):
128 deleted.append(book.id)
130 changes['deleted'] = deleted
135 def tag_dict(tag, fields=None):
136 all_fields = ('name', 'category', 'sort_key', 'description',
137 'gazeta_link', 'wiki_link',
142 fields = (f for f in fields if f in all_fields)
150 obj[field] = tag.get_absolute_url()
152 elif field == 'books':
153 obj[field] = [b.id for b in Book.tagged_top_level([tag])]
155 elif field == 'sort_key':
156 obj[field] = tag.sort_key
159 f = getattr(tag, field)
167 def tag_changes(cls, request=None, since=0, until=None, fields=None, categories=None):
168 since = datetime.fromtimestamp(int(since))
169 until = cls.until(until)
172 'time_checked': timestamp(until)
176 fields = cls.fields(request, 'tag_fields')
178 categories = cls.fields(request, 'tag_categories')
180 all_categories = ('author', 'epoch', 'kind', 'genre')
182 categories = (c for c in categories if c in all_categories)
184 categories = all_categories
189 for tag in Tag.objects.filter(category__in=categories,
190 changed_at__gte=since,
191 changed_at__lt=until):
192 # only serve non-empty tags
194 tag_d = cls.tag_dict(tag, fields)
195 updated.append(tag_d)
196 elif tag.created_at < since:
197 deleted.append(tag.id)
199 changes['updated'] = updated
201 for tag in Deleted.objects.filter(category__in=categories,
203 deleted_at__gte=since,
204 deleted_at__lt=until,
205 created_at__lt=since):
206 deleted.append(tag.id)
208 changes['deleted'] = deleted
213 def changes(cls, request=None, since=0, until=None, book_fields=None,
214 tag_fields=None, tag_categories=None):
215 until = cls.until(until)
218 'time_checked': timestamp(until)
222 'books': cls.book_changes(request, since, until, book_fields),
223 'tags': cls.tag_changes(request, since, until, tag_fields, tag_categories),
226 for model in changes_by_type:
227 for field in changes_by_type[model]:
228 if field == 'time_checked':
230 changes.setdefault(field, {})[model] = changes_by_type[model][field]
234 class BookChangesHandler(CatalogueHandler):
235 allowed_methods = ('GET',)
237 def read(self, request, since):
238 return self.book_changes(request, since)
241 class TagChangesHandler(CatalogueHandler):
242 allowed_methods = ('GET',)
244 def read(self, request, since):
245 return self.tag_changes(request, since)
248 class ChangesHandler(CatalogueHandler):
249 allowed_methods = ('GET',)
251 def read(self, request, since):
252 return self.changes(request, since)