From a791ae7c71b918ca39e7083209019de03a164252 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Mon, 4 Feb 2019 09:59:28 +0100 Subject: [PATCH] More API views migrated. --- src/api/handlers.py | 169 +-------------------- src/api/templates/api/main.html | 22 +-- src/api/tests/res/responses/fragment.json | 10 +- src/api/tests/res/responses/fragments.json | 8 +- src/api/tests/res/responses/tags.json | 8 +- src/api/urls.py | 22 +-- src/catalogue/api/serializers.py | 34 ++++- src/catalogue/api/urls.py | 7 + src/catalogue/api/views.py | 63 +++++++- src/catalogue/models/fragment.py | 7 + 10 files changed, 132 insertions(+), 218 deletions(-) diff --git a/src/api/handlers.py b/src/api/handlers.py index 7872a1034..4296817e8 100644 --- a/src/api/handlers.py +++ b/src/api/handlers.py @@ -17,7 +17,7 @@ from sorl.thumbnail import default from api.models import BookUserData from catalogue.forms import BookImportForm -from catalogue.models import Book, Tag, BookMedia, Fragment, Collection +from catalogue.models import Book, Tag, BookMedia from catalogue.models.tag import prefetch_relations from paypal.rest import user_is_subscribed from picture.models import Picture @@ -100,25 +100,7 @@ def read_tags(tags, request, allowed): # RESTful handlers -class BookMediaHandler(BaseHandler): - """ Responsible for representing media in Books. """ - model = BookMedia - fields = ['name', 'type', 'url', 'artist', 'director'] - - @classmethod - def url(cls, media): - """ Link to media on site. """ - - return MEDIA_BASE + media.file.url - - @classmethod - def artist(cls, media): - return media.extra_info.get('artist_name', '') - - @classmethod - def director(cls, media): - return media.extra_info.get('director_name', '') class BookDetails(object): @@ -490,155 +472,6 @@ def add_file_getters(): add_file_getters() -class TagDetails(object): - """Custom Tag fields.""" - - @classmethod - def href(cls, tag): - """ Returns URI in the API for the tag. """ - - return API_BASE + reverse("api_tag", args=[category_plural[tag.category], tag.slug]) - - @classmethod - def url(cls, tag): - """ Returns URL on the site. """ - - return WL_BASE + tag.get_absolute_url() - - -class TagDetailHandler(BaseHandler, TagDetails): - """ Responsible for details of a single Tag object. """ - - fields = ['name', 'url', 'sort_key', 'description'] - - @piwik_track - def read(self, request, category, slug): - """ Returns details of a tag, identified by category and slug. """ - - try: - category_sng = category_singular[category] - except KeyError: - return rc.NOT_FOUND - - try: - return Tag.objects.get(category=category_sng, slug=slug) - except Tag.DoesNotExist: - return rc.NOT_FOUND - - -class TagsHandler(BaseHandler, TagDetails): - """ Main handler for Tag objects. - - Responsible for lists of Tag objects - and fields used for representing Tags. - - """ - allowed_methods = ('GET',) - model = Tag - fields = ['name', 'href', 'url', 'slug'] - - @piwik_track - def read(self, request, category=None, pk=None): - """ Lists all tags in the category (eg. all themes). """ - if pk is not None: - # FIXME: Unused? - try: - return Tag.objects.exclude(category='set').get(pk=pk) - except Book.DoesNotExist: - return rc.NOT_FOUND - - try: - category_sng = category_singular[category] - except KeyError: - return rc.NOT_FOUND - - after = request.GET.get('after') - count = request.GET.get('count') - - tags = Tag.objects.filter(category=category_sng).exclude(items=None).order_by('slug') - - book_only = request.GET.get('book_only') == 'true' - picture_only = request.GET.get('picture_only') == 'true' - if book_only: - tags = tags.filter(for_books=True) - if picture_only: - tags = tags.filter(for_pictures=True) - - if after: - tags = tags.filter(slug__gt=after) - - if count: - tags = tags[:count] - - return tags - - -class FragmentDetails(object): - """Custom Fragment fields.""" - - @classmethod - def href(cls, fragment): - """ Returns URI in the API for the fragment. """ - - return API_BASE + reverse("api_fragment", args=[fragment.book.slug, fragment.anchor]) - - @classmethod - def url(cls, fragment): - """ Returns URL on the site for the fragment. """ - - return WL_BASE + fragment.get_absolute_url() - - @classmethod - def themes(cls, fragment): - """ Returns a list of theme tags for the fragment. """ - - return fragment.tags.filter(category='theme') - - -class FragmentDetailHandler(BaseHandler, FragmentDetails): - fields = ['book', 'anchor', 'text', 'url', 'themes'] - - @piwik_track - def read(self, request, book, anchor): - """ Returns details of a fragment, identified by book slug and anchor. """ - try: - return Fragment.objects.get(book__slug=book, anchor=anchor) - except Fragment.DoesNotExist: - return rc.NOT_FOUND - - -class FragmentsHandler(BaseHandler, FragmentDetails): - """ Main handler for Fragments. - - Responsible for lists of Fragment objects - and fields used for representing Fragments. - - """ - model = Fragment - fields = ['book', 'url', 'anchor', 'href'] - allowed_methods = ('GET',) - - categories = {'author', 'epoch', 'kind', 'genre', 'book', 'theme'} - - @piwik_track - def read(self, request, tags): - """ Lists all fragments with given book, tags, themes. - - :param tags: should be a path of categories and slugs, i.e.: - books/book-slug/authors/an-author/themes/a-theme/ - - """ - try: - tags, ancestors = read_tags(tags, request, allowed=self.categories) - except ValueError: - return rc.NOT_FOUND - fragments = Fragment.tagged.with_all(tags).select_related('book') - if fragments.exists(): - return fragments - else: - return rc.NOT_FOUND - - class PictureHandler(BaseHandler): model = Picture fields = ('slug', 'title') diff --git a/src/api/templates/api/main.html b/src/api/templates/api/main.html index a718f2124..14dd04578 100755 --- a/src/api/templates/api/main.html +++ b/src/api/templates/api/main.html @@ -42,24 +42,24 @@
  • {% url "api_daisy_list" "" %} – {% trans "DAISY" %}
  • -
  • - {% url "api_tag_list" "authors" %} – {% trans "List of all authors" %}
  • -
  • - {% url "api_tag_list" "epochs" %} – {% trans "List of all epochs" %}
  • -
  • - {% url "api_tag_list" "genres" %} – {% trans "List of all genres" %}
  • -
  • - {% url "api_tag_list" "kinds" %} – {% trans "List of all kinds" %}
  • +
  • + {% url "catalogue_api_tag_list" "author" %} – {% trans "List of all authors" %}
  • +
  • + {% url "catalogue_api_tag_list" "epoch" %} – {% trans "List of all epochs" %}
  • +
  • + {% url "catalogue_api_tag_list" "genre" %} – {% trans "List of all genres" %}
  • +
  • + {% url "catalogue_api_tag_list" "kind" %} – {% trans "List of all kinds" %}
  • -
  • - {% url "api_tag_list" "themes" %} – {% trans "List of all themes" %}
  • +
  • + {% url "catalogue_api_tag_list" "theme" %} – {% trans "List of all themes" %}
  • {% url "catalogue_api_collections" %} – {% trans "Collections" %}
  • {% url "catalogue_api_book" "studnia-i-wahadlo" as e1 %} - {% url "api_tag" "authors" "edgar-allan-poe" as e2 %} + {% url "catalogue_api_tag" "author" "edgar-allan-poe" as e2 %} {% blocktrans %} Each element of those lists contains a link (in a "href") attibute which points to individual resource's details, i.e.: diff --git a/src/api/tests/res/responses/fragment.json b/src/api/tests/res/responses/fragment.json index 40db3f4f3..e579e506c 100644 --- a/src/api/tests/res/responses/fragment.json +++ b/src/api/tests/res/responses/fragment.json @@ -1,18 +1,18 @@ { - "url": "https://example.com/katalog/lektura/child.html#man-anchor", + "url": "http://testserver/katalog/lektura/child.html#man-anchor", "text": "A fragment", "book": { "kind": "", "full_sort_key": "$child$2", "author": "", - "url": "https://example.com/katalog/lektura/child/", + "url": "http://testserver/katalog/lektura/child/", "cover_color": "#000000", "title": "Child", "cover": "", "liked": null, "slug": "child", "epoch": "", - "href": "https://example.com/api/books/child/", + "href": "http://testserver/api/books/child/", "genre": "Wiersz", "simple_thumb": "", "has_audio": false, @@ -21,8 +21,8 @@ "anchor": "an-anchor", "themes": [ { - "url": "https://example.com/katalog/motyw/koniec/", - "href": "https://example.com/api/themes/koniec/", + "url": "http://testserver/katalog/motyw/koniec/", + "href": "http://testserver/api/themes/koniec/", "name": "Koniec", "slug": "koniec" } diff --git a/src/api/tests/res/responses/fragments.json b/src/api/tests/res/responses/fragments.json index 1733ba171..e464557a4 100644 --- a/src/api/tests/res/responses/fragments.json +++ b/src/api/tests/res/responses/fragments.json @@ -1,24 +1,24 @@ [ { - "url": "https://example.com/katalog/lektura/child.html#man-anchor", + "url": "http://testserver/katalog/lektura/child.html#man-anchor", "book": { "kind": "", "full_sort_key": "$child$2", "author": "", - "url": "https://example.com/katalog/lektura/child/", + "url": "http://testserver/katalog/lektura/child/", "cover_color": "#000000", "title": "Child", "cover": "", "liked": null, "slug": "child", "epoch": "", - "href": "https://example.com/api/books/child/", + "href": "http://testserver/api/books/child/", "genre": "Wiersz", "simple_thumb": "", "has_audio": false, "cover_thumb": "" }, "anchor": "an-anchor", - "href": "https://example.com/api/books/child/fragments/an-anchor/" + "href": "http://testserver/api/books/child/fragments/an-anchor/" } ] diff --git a/src/api/tests/res/responses/tags.json b/src/api/tests/res/responses/tags.json index 4729bebab..0232c9089 100644 --- a/src/api/tests/res/responses/tags.json +++ b/src/api/tests/res/responses/tags.json @@ -1,13 +1,13 @@ [ { - "url": "https://example.com/katalog/gatunek/sonet/", - "href": "https://example.com/api/genres/sonet/", + "url": "http://testserver/katalog/gatunek/sonet/", + "href": "http://testserver/api/genres/sonet/", "name": "Sonet", "slug": "sonet" }, { - "url": "https://example.com/katalog/gatunek/wiersz/", - "href": "https://example.com/api/genres/wiersz/", + "url": "http://testserver/katalog/gatunek/wiersz/", + "href": "http://testserver/api/genres/wiersz/", "name": "Wiersz", "slug": "wiersz" } diff --git a/src/api/urls.py b/src/api/urls.py index 7e393a26f..19e63dd16 100644 --- a/src/api/urls.py +++ b/src/api/urls.py @@ -51,12 +51,6 @@ shelf_resource = auth_resource(handler=handlers.UserShelfHandler) like_resource = auth_resource(handler=handlers.UserLikeHandler) -tag_list_resource = Resource(handler=handlers.TagsHandler) -tag_resource = Resource(handler=handlers.TagDetailHandler) - -fragment_resource = Resource(handler=handlers.FragmentDetailHandler) -fragment_list_resource = Resource(handler=handlers.FragmentsHandler) - picture_resource = auth_resource(handler=handlers.PictureHandler) blog_resource = Resource(handler=handlers.BlogEntryHandler) @@ -73,9 +67,6 @@ urlpatterns = [ url(r'^$', TemplateView.as_view(template_name='api/main.html'), name='api'), - # These are the new ones. - url(r'^', include('catalogue.api.urls')), - # info boxes (used by mobile app) url(r'book/(?P\d*?)/info\.html$', catalogue.views.book_info), url(r'tag/(?P\d*?)/info\.html$', catalogue.views.tag_info), @@ -88,12 +79,6 @@ urlpatterns = [ url(r'^like/(?P[a-z0-9-]+)/$', like_resource, name='api_like'), - # objects details - url(r'^(?P[a-z0-9-]+)/(?P[a-z0-9-]+)/$', - tag_resource, name="api_tag"), - url(r'^books/(?P[a-z0-9-]+)/fragments/(?P[a-z0-9-]+)/$', - fragment_resource, name="api_fragment"), - # books by tags url(tags_re + r'books/' + paginate_re, book_list_resource, name='api_book_list'), @@ -118,10 +103,5 @@ urlpatterns = [ url(r'^blog/$', blog_resource), - # fragments by book, tags, themes - # this should be paged - url(r'^(?P(?:(?:[a-z0-9-]+/){2}){1,6})fragments/$', fragment_list_resource), - - # tags by category - url(r'^(?P[a-z0-9-]+)/$', tag_list_resource, name='api_tag_list'), + url(r'^', include('catalogue.api.urls')), ] diff --git a/src/catalogue/api/serializers.py b/src/catalogue/api/serializers.py index 1705a5165..7c792d586 100644 --- a/src/catalogue/api/serializers.py +++ b/src/catalogue/api/serializers.py @@ -1,14 +1,14 @@ from rest_framework import serializers from api.fields import AbsoluteURLField, LegacyMixin -from catalogue.models import Book, Collection, Tag, BookMedia +from catalogue.models import Book, Collection, Tag, BookMedia, Fragment from .fields import BookLiked, ThumbnailField class TagSerializer(serializers.ModelSerializer): url = AbsoluteURLField() href = AbsoluteURLField( - view_name='api_tag', - view_args=('category:category_plural', 'slug') + view_name='catalogue_api_tag', + view_args=('category', 'slug') ) class Meta: @@ -16,6 +16,14 @@ class TagSerializer(serializers.ModelSerializer): fields = ['url', 'href', 'name', 'slug'] +class TagDetailSerializer(serializers.ModelSerializer): + url = AbsoluteURLField() + + class Meta: + model = Tag + fields = ['name', 'url', 'sort_key', 'description'] + + class BookSerializer(LegacyMixin, serializers.ModelSerializer): author = serializers.CharField(source='author_unicode') kind = serializers.CharField(source='kind_unicode') @@ -100,3 +108,23 @@ class CollectionSerializer(serializers.ModelSerializer): class Meta: model = Collection fields = ['url', 'books', 'description', 'title'] + + +class FragmentSerializer(serializers.ModelSerializer): + book = BookSerializer() + url = AbsoluteURLField() + href = AbsoluteURLField(source='get_api_url') + + class Meta: + model = Fragment + fields = ['book', 'url', 'anchor', 'href'] + + +class FragmentDetailSerializer(serializers.ModelSerializer): + book = BookSerializer() + url = AbsoluteURLField() + themes = TagSerializer(many=True) + + class Meta: + model = Fragment + fields = ['book', 'anchor', 'text', 'url', 'themes'] diff --git a/src/catalogue/api/urls.py b/src/catalogue/api/urls.py index 30f329a10..723526797 100644 --- a/src/catalogue/api/urls.py +++ b/src/catalogue/api/urls.py @@ -15,4 +15,11 @@ urlpatterns = [ url(r'^books/(?P[^/]+)/$', views.BookDetail.as_view(), name='catalogue_api_book'), url(r'^epub/(?P[a-z0-9-]+)/$', views.EpubView.as_view(), name='catalogue_api_epub'), + + url(r'^(?P(?:(?:[a-z0-9-]+/){2}){1,6})fragments/$', views.FragmentList.as_view()), + url(r'^books/(?P[a-z0-9-]+)/fragments/(?P[a-z0-9-]+)/$', + views.FragmentView.as_view(), name="catalogue_api_fragment"), + + url(r'^(?P[a-z]+)s/$', views.TagCategoryView.as_view(), name='catalogue_api_tag_list'), + url(r'^(?P[a-z]+)s/(?P[a-z0-9-]+)/$', views.TagView.as_view(), name="catalogue_api_tag"), ] diff --git a/src/catalogue/api/views.py b/src/catalogue/api/views.py index c20cccb77..aba794d05 100644 --- a/src/catalogue/api/views.py +++ b/src/catalogue/api/views.py @@ -1,8 +1,9 @@ from django.http import HttpResponse -from rest_framework.generics import ListAPIView, RetrieveAPIView +from rest_framework.generics import ListAPIView, RetrieveAPIView, get_object_or_404 from paypal.permissions import IsSubscribed +from api.handlers import read_tags from . import serializers -from catalogue.models import Book, Collection +from catalogue.models import Book, Collection, Tag, Fragment class CollectionList(ListAPIView): @@ -29,3 +30,61 @@ class EpubView(RetrieveAPIView): def get(self, *args, **kwargs): return HttpResponse(self.get_object().get_media('epub')) + + +class TagCategoryView(ListAPIView): + serializer_class = serializers.TagSerializer + + def get_queryset(self): + category = self.kwargs['category'] + tags = Tag.objects.filter(category=category).exclude(items=None).order_by('slug') + if self.request.query_params.get('book_only') == 'true': + tags = tags.filter(for_books=True) + if self.request.GET.get('picture_only') == 'true': + tags = filter(for_pictures=True) + + after = self.request.query_params.get('after') + count = self.request.query_params.get('count') + if after: + tags = tags.filter(slug__gt=after) + if count: + tags = tags[:count] + + return tags + + +class TagView(RetrieveAPIView): + serializer_class = serializers.TagDetailSerializer + + def get_object(self): + return get_object_or_404( + Tag, + category=self.kwargs['category'], + slug=self.kwargs['slug'] + ) + + +class FragmentList(ListAPIView): + serializer_class = serializers.FragmentSerializer + + def get_queryset(self): + try: + tags, ancestors = read_tags( + self.kwargs['tags'], + self.request, + allowed={'author', 'epoch', 'kind', 'genre', 'book', 'theme'} + ) + except ValueError: + raise Http404 + return Fragment.tagged.with_all(tags).select_related('book') + + +class FragmentView(RetrieveAPIView): + serializer_class = serializers.FragmentDetailSerializer + + def get_object(self): + return get_object_or_404( + Fragment, + book__slug=self.kwargs['book'], + anchor=self.kwargs['anchor'] + ) diff --git a/src/catalogue/models/fragment.py b/src/catalogue/models/fragment.py index a3dbdea54..dc159a59f 100644 --- a/src/catalogue/models/fragment.py +++ b/src/catalogue/models/fragment.py @@ -35,10 +35,17 @@ class Fragment(models.Model): def get_absolute_url(self): return '%s#m%s' % (reverse('book_text', args=[self.book.slug]), self.anchor) + def get_api_url(self): + return reverse('catalogue_api_fragment', args=[self.book.slug, self.anchor]) + def get_short_text(self): """Returns short version of the fragment.""" return self.short_text if self.short_text else self.text + @property + def themes(self): + return self.tags.filter(category='theme') + def flush_includes(self, languages=True): if not languages: return -- 2.20.1