X-Git-Url: https://git.mdrn.pl/redakcja.git/blobdiff_plain/0d32008f4ab55669ec5eb13ed1af3c2741092ef5..732fe46593ff27a62c6f64f3d495c3d5619ece1c:/src/catalogue/views.py diff --git a/src/catalogue/views.py b/src/catalogue/views.py index 5551a5cf..635af5fd 100644 --- a/src/catalogue/views.py +++ b/src/catalogue/views.py @@ -1,672 +1,291 @@ -# -*- coding: utf-8 -*- -from collections import defaultdict -from datetime import datetime, date, timedelta -import logging -import os -from StringIO import StringIO -from urllib import unquote -from urlparse import urlsplit, urlunsplit - -from django.conf import settings -from django.contrib import auth +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.apps import apps +from django.db.models import Prefetch +from django.http import Http404 +from django.urls import reverse +from django.utils.formats import localize_input +from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User -from django.contrib.auth.decorators import login_required, permission_required -from django.core.urlresolvers import reverse -from django.db.models import Count, Q -from django.db import transaction -from django import http -from django.http import Http404, HttpResponse, HttpResponseForbidden -from django.http.response import HttpResponseRedirect -from django.shortcuts import get_object_or_404, render -from django.utils.encoding import iri_to_uri -from django.utils.http import urlquote_plus -from django.utils.translation import ugettext_lazy as _ +from django.shortcuts import get_object_or_404, redirect from django.views.decorators.http import require_POST -from django_cas_ng.decorators import user_passes_test - -from apiclient import NotAuthorizedError -from catalogue import forms -from catalogue import helpers -from catalogue.helpers import active_tab -from catalogue.models import (Book, Chunk, Image, BookPublishRecord, - ChunkPublishRecord, ImagePublishRecord, Project) -from fileupload.views import UploadView +from django.views.generic import DetailView, TemplateView +import apiclient +from . import models +import documents.models +from rest_framework.generics import ListAPIView, RetrieveAPIView +from rest_framework.filters import SearchFilter +from rest_framework.permissions import IsAdminUser +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework import serializers + + + + + +class CatalogueView(TemplateView): + template_name = "catalogue/catalogue.html" + + def get_context_data(self): + ctx = super().get_context_data() + ctx["authors"] = models.Author.objects.all().prefetch_related('book_set__document_books', 'translated_book_set__document_books') + + return ctx + + +class AuthorView(TemplateView): + model = models.Author + template_name = "catalogue/author_detail.html" + + def get_context_data(self, slug): + ctx = super().get_context_data() + authors = models.Author.objects.filter(slug=slug).prefetch_related( + Prefetch("book_set"), + Prefetch("translated_book_set"), + ) + ctx["author"] = authors.first() + return ctx + + +class BookView(DetailView): + model = models.Book + +class BookAPIView(RetrieveAPIView): + queryset = models.Book.objects.all() + lookup_field = 'slug' + + class serializer_class(serializers.ModelSerializer): + class AuthorSerializer(serializers.ModelSerializer): + literal = serializers.CharField(source='name') + + class Meta: + model = models.Author + fields = ['literal'] + + def category_serializer(m): + class CategorySerializer(serializers.ModelSerializer): + literal = serializers.CharField(source='name') + class Meta: + model = m + fields = ['literal'] + return CategorySerializer + + authors = AuthorSerializer(many=True) + translators = AuthorSerializer(many=True) + epochs = category_serializer(models.Epoch)(many=True) + kinds = category_serializer(models.Kind)(many=True) + genres = category_serializer(models.Genre)(many=True) + + class Meta: + model = models.Book + fields = [ + 'title', + 'authors', + 'translators', + 'epochs', + 'kinds', + 'genres', + 'scans_source', + 'text_source', + 'original_year', + 'pd_year', + ] + -# -# Quick hack around caching problems, TODO: use ETags -# -from django.views.decorators.cache import never_cache +class TermSearchFilter(SearchFilter): + search_param = 'term' -logger = logging.getLogger("fnp.catalogue") +class Terms(ListAPIView): + filter_backends = [TermSearchFilter] + search_fields = ['name'] -@active_tab('all') -@never_cache -def document_list(request): - return render(request, 'catalogue/document_list.html') + class serializer_class(serializers.Serializer): + label = serializers.CharField(source='name') -@active_tab('images') -@never_cache -def image_list(request, user=None): - return render(request, 'catalogue/image_list.html') +class EpochTerms(Terms): + queryset = models.Epoch.objects.all() +class KindTerms(Terms): + queryset = models.Kind.objects.all() +class GenreTerms(Terms): + queryset = models.Genre.objects.all() +class AuthorTerms(Terms): + search_fields = ['first_name', 'last_name'] + queryset = models.Author.objects.all() -@never_cache -def user(request, username): - user = get_object_or_404(User, username=username) - return render(request, 'catalogue/user_page.html', {"viewed_user": user}) +class EditorTerms(Terms): + search_fields = ['first_name', 'last_name', 'username'] + queryset = User.objects.all() + class serializer_class(serializers.Serializer): + label = serializers.SerializerMethodField() -@login_required -@active_tab('my') -@never_cache -def my(request): - last_books = sorted(request.session.get("wiki_last_books", {}).items(), - key=lambda x: x[1]['time'], reverse=True) - for k, v in last_books: - v['time'] = datetime.fromtimestamp(v['time']) - return render(request, 'catalogue/my_page.html', { - 'last_books': last_books, - "logout_to": '/', - }) - - -@active_tab('users') -def users(request): - return render(request, 'catalogue/user_list.html', { - 'users': User.objects.all().annotate(count=Count('chunk')).order_by( - '-count', 'last_name', 'first_name'), - }) - - -@active_tab('activity') -def activity(request, isodate=None): - today = date.today() - try: - day = helpers.parse_isodate(isodate) - except ValueError: - day = today - - if day > today: - raise Http404 - if day != today: - next_day = day + timedelta(1) - prev_day = day - timedelta(1) - - return render(request, 'catalogue/activity.html', locals()) - - -@never_cache -def logout_then_redirect(request): - auth.logout(request) - return http.HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?=')) - - -@permission_required('catalogue.add_book') -@active_tab('create') -def create_missing(request, slug=None): - if slug is None: - slug = '' - slug = slug.replace(' ', '-') - - if request.method == "POST": - form = forms.DocumentCreateForm(request.POST, request.FILES) - if form.is_valid(): - - if request.user.is_authenticated(): - creator = request.user - else: - creator = None - book = Book.create( - text=form.cleaned_data['text'], - creator=creator, - slug=form.cleaned_data['slug'], - title=form.cleaned_data['title'], - gallery=form.cleaned_data['gallery'], - ) - - return http.HttpResponseRedirect(reverse("catalogue_book", args=[book.slug])) - else: - form = forms.DocumentCreateForm(initial={ - "slug": slug, - "title": slug.replace('-', ' ').title(), - "gallery": slug, - }) - - return render(request, "catalogue/document_create_missing.html", { - "slug": slug, - "form": form, - - "logout_to": '/', - }) - - -@permission_required('catalogue.add_book') -@active_tab('upload') -def upload(request): - if request.method == "POST": - form = forms.DocumentsUploadForm(request.POST, request.FILES) - if form.is_valid(): - from slugify import slugify - - if request.user.is_authenticated(): - creator = request.user - else: - creator = None - - zip = form.cleaned_data['zip'] - skipped_list = [] - ok_list = [] - error_list = [] - slugs = {} - existing = [book.slug for book in Book.objects.all()] - for filename in zip.namelist(): - if filename[-1] == '/': - continue - title = os.path.basename(filename)[:-4] - slug = slugify(title) - if not (slug and filename.endswith('.xml')): - skipped_list.append(filename) - elif slug in slugs: - error_list.append((filename, slug, _('Slug already used for %s' % slugs[slug]))) - elif slug in existing: - error_list.append((filename, slug, _('Slug already used in repository.'))) - else: - try: - zip.read(filename).decode('utf-8') # test read - ok_list.append((filename, slug, title)) - except UnicodeDecodeError: - error_list.append((filename, title, _('File should be UTF-8 encoded.'))) - slugs[slug] = filename - - if not error_list: - for filename, slug, title in ok_list: - book = Book.create( - text=zip.read(filename).decode('utf-8'), - creator=creator, - slug=slug, - title=title, - ) - - return render(request, "catalogue/document_upload.html", { - "form": form, - "ok_list": ok_list, - "skipped_list": skipped_list, - "error_list": error_list, - - "logout_to": '/', - }) - else: - form = forms.DocumentsUploadForm() - - return render(request, "catalogue/document_upload.html", { - "form": form, - - "logout_to": '/', - }) - - -def serve_xml(request, book, slug): - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - xml = book.materialize(publishable=True) - response = http.HttpResponse(xml, content_type='application/xml') - response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug - return response - - -@never_cache -def book_xml(request, slug): - book = get_object_or_404(Book, slug=slug) - return serve_xml(request, book, slug) - - -@never_cache -def book_xml_dc(request, slug): - book = get_object_or_404(Book, dc_slug=slug) - return serve_xml(request, book, slug) - - -@never_cache -def book_txt(request, slug): - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - doc = book.wldocument() - text = doc.as_text().get_bytes() - response = http.HttpResponse(text, content_type='text/plain') - response['Content-Disposition'] = 'attachment; filename=%s.txt' % slug - return response - - -@never_cache -def book_html(request, slug): - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - doc = book.wldocument(parse_dublincore=False) - html = doc.as_html(options={'gallery': "'%s'" % book.gallery_url()}) - - html = html.get_bytes() if html is not None else '' - # response = http.HttpResponse(html, content_type='text/html') - # return response - # book_themes = {} - # for fragment in book.fragments.all().iterator(): - # for theme in fragment.tags.filter(category='theme').iterator(): - # book_themes.setdefault(theme, []).append(fragment) - - # book_themes = book_themes.items() - # book_themes.sort(key=lambda s: s[0].sort_key) - return render(request, 'catalogue/book_text.html', locals()) - - -@never_cache -def book_pdf(request, slug, mobile=False): - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - # TODO: move to celery - doc = book.wldocument() - # TODO: error handling - customizations = ['26pt', 'nothemes', 'nomargins', 'notoc'] if mobile else None - pdf_file = doc.as_pdf(cover=True, ilustr_path=book.gallery_path(), customizations=customizations) - from catalogue.ebook_utils import serve_file - return serve_file(pdf_file.get_filename(), - book.slug + '.pdf', 'application/pdf') - - -@never_cache -def book_epub(request, slug): - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - # TODO: move to celery - doc = book.wldocument() - # TODO: error handling - epub = doc.as_epub(ilustr_path=book.gallery_path()).get_bytes() - response = HttpResponse(content_type='application/epub+zip') - response['Content-Disposition'] = 'attachment; filename=%s' % book.slug + '.epub' - response.write(epub) - return response - - -@never_cache -def book_mobi(request, slug): - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - # TODO: move to celery - doc = book.wldocument() - # TODO: error handling - mobi = doc.as_mobi(ilustr_path=book.gallery_path()).get_bytes() - response = HttpResponse(content_type='application/x-mobipocket-ebook') - response['Content-Disposition'] = 'attachment; filename=%s' % book.slug + '.mobi' - response.write(mobi) - return response - - -@never_cache -def revision(request, slug, chunk=None): - try: - doc = Chunk.get(slug, chunk) - except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist): - raise Http404 - if not doc.book.accessible(request): - return HttpResponseForbidden("Not authorized.") - return http.HttpResponse(str(doc.revision())) - - -def book(request, slug): - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - if request.user.has_perm('catalogue.change_book'): - if request.method == "POST": - form = forms.BookForm(request.POST, instance=book) - if form.is_valid(): - form.save() - return http.HttpResponseRedirect(book.get_absolute_url()) - else: - form = forms.BookForm(instance=book) - publish_options_form = forms.PublishOptionsForm() - editable = True - else: - form = forms.ReadonlyBookForm(instance=book) - publish_options_form = forms.PublishOptionsForm() - editable = False - - publish_error = book.publishable_error() - publishable = publish_error is None - - return render(request, "catalogue/book_detail.html", { - "book": book, - "publishable": publishable, - "publishable_error": publish_error, - "form": form, - "publish_options_form": publish_options_form, - "editable": editable, - }) - - -def image(request, slug): - image = get_object_or_404(Image, slug=slug) - if not image.accessible(request): - return HttpResponseForbidden("Not authorized.") - - if request.user.has_perm('catalogue.change_image'): - if request.method == "POST": - form = forms.ImageForm(request.POST, instance=image) - if form.is_valid(): - form.save() - return http.HttpResponseRedirect(image.get_absolute_url()) - else: - form = forms.ImageForm(instance=image) - editable = True - else: - form = forms.ReadonlyImageForm(instance=image) - editable = False - - publish_error = image.publishable_error() - publishable = publish_error is None - - return render(request, "catalogue/image_detail.html", { - "object": image, - "publishable": publishable, - "publishable_error": publish_error, - "form": form, - "editable": editable, - }) - - -@permission_required('catalogue.add_chunk') -def chunk_add(request, slug, chunk): - try: - doc = Chunk.get(slug, chunk) - except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist): - raise Http404 - if not doc.book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - if request.method == "POST": - form = forms.ChunkAddForm(request.POST, instance=doc) - if form.is_valid(): - if request.user.is_authenticated(): - creator = request.user - else: - creator = None - doc.split(creator=creator, - slug=form.cleaned_data['slug'], - title=form.cleaned_data['title'], - gallery_start=form.cleaned_data['gallery_start'], - user=form.cleaned_data['user'], - stage=form.cleaned_data['stage'] - ) - - return http.HttpResponseRedirect(doc.book.get_absolute_url()) - else: - form = forms.ChunkAddForm(initial={ - "slug": str(doc.number + 1), - "title": "cz. %d" % (doc.number + 1, ), - }) - - return render(request, "catalogue/chunk_add.html", { - "chunk": doc, - "form": form, - }) + def get_label(self, obj): + return f'{obj.last_name}, {obj.first_name}' + +class BookTitleTerms(Terms): + queryset = models.Book.objects.all() + search_fields = ['title', 'slug'] + class serializer_class(serializers.Serializer): + label = serializers.CharField(source='title') -@login_required -def chunk_edit(request, slug, chunk): - try: - doc = Chunk.get(slug, chunk) - except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist): - raise Http404 - if not doc.book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - if request.method == "POST": - form = forms.ChunkForm(request.POST, instance=doc) - if form.is_valid(): - form.save() - go_next = request.GET.get('next', None) - if go_next: - go_next = urlquote_plus(unquote(iri_to_uri(go_next)), safe='/?=&') - else: - go_next = doc.book.get_absolute_url() - return http.HttpResponseRedirect(go_next) - else: - form = forms.ChunkForm(instance=doc) - - referer = request.META.get('HTTP_REFERER') - if referer: - parts = urlsplit(referer) - parts = ['', ''] + list(parts[2:]) - go_next = urlquote_plus(urlunsplit(parts)) - else: - go_next = '' - - return render(request, "catalogue/chunk_edit.html", { - "chunk": doc, - "form": form, - "go_next": go_next, - }) - - -@transaction.atomic -@login_required -@require_POST -def chunk_mass_edit(request): - ids = map(int, filter(lambda i: i.strip()!='', request.POST.get('ids').split(','))) - chunks = map(lambda i: Chunk.objects.get(id=i), ids) +class WLURITerms(Terms): + queryset = models.Book.objects.all() + search_fields = ['title', 'slug'] - stage = request.POST.get('stage') - if stage: - try: - stage = Chunk.tag_model.objects.get(slug=stage) - except Chunk.DoesNotExist, e: - stage = None - - for c in chunks: c.stage = stage - - username = request.POST.get('user') - logger.info("username: %s" % username) - logger.info(request.POST) - if username: - try: - user = User.objects.get(username=username) - except User.DoesNotExist, e: - user = None - - for c in chunks: c.user = user - - project_id = request.POST.get('project') - if project_id: - try: - project = Project.objects.get(pk=int(project_id)) - except (Project.DoesNotExist, ValueError), e: - project = None - for c in chunks: - book = c.book - book.project = project - book.save() + class serializer_class(serializers.Serializer): + label = serializers.CharField(source='wluri') - for c in chunks: c.save() +class ThemaTerms(Terms): + queryset = models.Thema.objects.filter(usable=True, hidden=False) + search_fields = ['code', 'name', 'description'] - return HttpResponse("", content_type="text/plain") + class serializer_class(serializers.Serializer): + label = serializers.CharField(source='code') + name = serializers.CharField() + description = serializers.CharField() -@transaction.atomic -@login_required -@require_POST -def image_mass_edit(request): - ids = map(int, filter(lambda i: i.strip()!='', request.POST.get('ids').split(','))) - images = map(lambda i: Image.objects.get(id=i), ids) - - stage = request.POST.get('stage') - if stage: - try: - stage = Image.tag_model.objects.get(slug=stage) - except Image.DoesNotExist, e: - stage = None - - for c in images: c.stage = stage - - username = request.POST.get('user') - logger.info("username: %s" % username) - logger.info(request.POST) - if username: - try: - user = User.objects.get(username=username) - except User.DoesNotExist, e: - user = None - - for c in images: c.user = user - - project_id = request.POST.get('project') - if project_id: +class WikidataView(APIView): + permission_classes = [IsAdminUser] + + def get_object(self, model, qid, save): try: - project = Project.objects.get(pk=int(project_id)) - except (Project.DoesNotExist, ValueError), e: - project = None - for c in images: - c.project = project + Model = apps.get_model('catalogue', model) + except LookupError: + raise Http404 + if not issubclass(Model, models.WikidataModel): + raise Http404 - for c in images: c.save() + obj = Model.objects.filter(wikidata=qid).first() + if obj is None: + obj = Model(wikidata=qid) + if not obj.pk and save: + obj.save() + else: + obj.wikidata_populate(save=False, force=True) + d = { + "id": obj.pk, + "__str__": str(obj), + } + for attname in dir(Model.Wikidata): + if attname.startswith("_"): + continue + for fieldname, lang in obj.wikidata_fields_for_attribute(attname): + try: + d[fieldname] = getattr(obj, fieldname) + except ValueError: + # Like accessing related field on non-saved object. + continue - return HttpResponse("", content_type="text/plain") + if isinstance(d[fieldname], models.WikidataModel): + d[fieldname] = { + "model": type(d[fieldname])._meta.model_name, + "id": d[fieldname].pk, + "wd": d[fieldname].wikidata, + "label": str(d[fieldname]) or d[fieldname]._wikidata_label, + } + elif hasattr(d[fieldname], 'all'): + d[fieldname] = [ + { + "model": type(item)._meta.model_name, + "id": item.pk, + "wd": item.wikidata, + "label": str(item) or item._wikidata_label + } for item in d[fieldname].all() + ] + elif hasattr(d[fieldname], 'as_hint_json'): + d[fieldname] = d[fieldname].as_hint_json() + elif hasattr(d[fieldname], 'storage'): + d[fieldname] = d[fieldname].url if d[fieldname] else None + else: + d[fieldname] = localize_input(d[fieldname]) + return Response(d) + + def get(self, request, model, qid): + return self.get_object(model, qid, save=False) + def post(self, request, model, qid): + return self.get_object(model, qid, save=True) -@permission_required('catalogue.change_book') -def book_append(request, slug): - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - if request.method == "POST": - form = forms.BookAppendForm(book, request.POST) - if form.is_valid(): - append_to = form.cleaned_data['append_to'] - append_to.append(book) - return http.HttpResponseRedirect(append_to.get_absolute_url()) - else: - form = forms.BookAppendForm(book) - return render(request, "catalogue/book_append_to.html", { - "book": book, - "form": form, +@require_POST +@login_required +def publish_author(request, pk): + author = get_object_or_404(models.Author, pk=pk) + data = { + "name_pl": author.name, + "description_pl": author.generate_description(), + } + apiclient.api_call(request.user, f"authors/{author.slug}/", data) + return redirect(reverse('admin:catalogue_author_change', args=[author.pk])) - "logout_to": '/', - }) + +@require_POST +@login_required +def publish_genre(request, pk): + obj = get_object_or_404(models.Genre, pk=pk) + data = { + "name_pl": obj.name, + "description_pl": obj.description, + "plural": obj.plural, + "is_epoch_specific": obj.is_epoch_specific, + } + apiclient.api_call(request.user, f"genres/{obj.slug}/", data) + return redirect(reverse('admin:catalogue_genre_change', args=[obj.pk])) @require_POST @login_required -def publish(request, slug): - form = forms.PublishOptionsForm(request.POST) - if form.is_valid(): - days = form.cleaned_data['days'] - beta = form.cleaned_data['beta'] - else: - days = 0 - beta = False - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - try: - protocol = 'https://' if request.is_secure() else 'http://' - book.publish(request.user, host=protocol + request.get_host(), days=days, beta=beta) - except NotAuthorizedError: - return http.HttpResponseRedirect(reverse('apiclient_oauth' if not beta else 'apiclient_beta_oauth')) - except BaseException, e: - return http.HttpResponse(repr(e)) - else: - return http.HttpResponseRedirect(book.get_absolute_url()) +def publish_kind(request, pk): + obj = get_object_or_404(models.Kind, pk=pk) + data = { + "name_pl": obj.name, + "description_pl": obj.description, + "collective_noun": obj.collective_noun, + } + apiclient.api_call(request.user, f"kinds/{obj.slug}/", data) + return redirect(reverse('admin:catalogue_kind_change', args=[obj.pk])) @require_POST @login_required -def publish_image(request, slug): - image = get_object_or_404(Image, slug=slug) - if not image.accessible(request): - return HttpResponseForbidden("Not authorized.") - - try: - image.publish(request.user) - except NotAuthorizedError: - return http.HttpResponseRedirect(reverse('apiclient_oauth')) - except BaseException, e: - return http.HttpResponse(e) - else: - return http.HttpResponseRedirect(image.get_absolute_url()) - - -class GalleryView(UploadView): - def get_object(self, request, slug): - book = get_object_or_404(Book, slug=slug) - if not book.gallery: - raise Http404 - return book - - def breadcrumbs(self): - return [ - (_('books'), reverse('catalogue_document_list')), - (self.object.title, self.object.get_absolute_url()), - (_('scan gallery'),), - ] - - def get_directory(self): - return "%s%s/" % (settings.IMAGE_DIR, self.object.gallery) - - -def active_users_list(request): - since = date(date.today().year, 1, 1) - by_user = defaultdict(lambda: 0) - by_email = defaultdict(lambda: 0) - names_by_email = defaultdict(set) - for change_model in (Chunk.change_model, Image.change_model): - for c in change_model.objects.filter( - created_at__gte=since).order_by( - 'author', 'author_email', 'author_name').values( - 'author', 'author_name', 'author_email').annotate( - c=Count('author'), ce=Count('author_email')).distinct(): - if c['author']: - by_user[c['author']] += c['c'] - else: - by_email[c['author_email']] += c['ce'] - if c['author_name'].strip(): - names_by_email[c['author_email']].add(c['author_name']) - for user in User.objects.filter(pk__in=by_user): - by_email[user.email] += by_user[user.pk] - names_by_email[user.email].add("%s %s" % (user.first_name, user.last_name)) - - active_users = [] - for email, count in by_email.items(): - active_users.append((email, names_by_email[email], count)) - active_users.sort(key=lambda x: -x[2]) - return render(request, 'catalogue/active_users_list.html', { - 'users': active_users, - 'since': since, - }) - - -@user_passes_test(lambda u: u.is_superuser) -def mark_final(request): - if request.method == 'POST': - form = forms.MarkFinalForm(data=request.POST) - if form.is_valid(): - form.save() - return HttpResponseRedirect(reverse('mark_final_completed')) - else: - form = forms.MarkFinalForm() - return render(request, 'catalogue/mark_final.html', {'form': form}) - - -def mark_final_completed(request): - return render(request, 'catalogue/mark_final_completed.html') +def publish_epoch(request, pk): + obj = get_object_or_404(models.Epoch, pk=pk) + data = { + "name_pl": obj.name, + "description_pl": obj.description, + "adjective_feminine_singular": obj.adjective_feminine_singular, + "adjective_nonmasculine_plural": obj.adjective_feminine_singular, + } + apiclient.api_call(request.user, f"epochs/{obj.slug}/", data) + return redirect(reverse('admin:catalogue_epoch_change', args=[obj.pk])) + + +@require_POST +@login_required +def publish_collection(request, pk): + collection = get_object_or_404(models.Collection, pk=pk) + data = { + "title": collection.name, + "description": collection.description, + "book_slugs": "\n".join( + book.slug + for book in collection.book_set.exclude(slug=None).exclude(slug='') + ) + } + apiclient.api_call( + request.user, + f"collections/{collection.slug}/", + data, + method='PUT', + as_json=True, + ) + return redirect(reverse( + 'admin:catalogue_collection_change', args=[collection.pk] + ))