1 # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
 
   2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 
   5 from django.contrib import admin
 
   6 from django.db.models import Min
 
   7 from django import forms
 
   8 from django.urls import reverse
 
   9 from django.utils.html import escape, format_html
 
  10 from django.utils.safestring import mark_safe
 
  11 from django.utils.translation import gettext_lazy as _
 
  12 from admin_numeric_filter.admin import RangeNumericFilter, NumericFilterModelAdmin, RangeNumericForm
 
  13 from admin_ordering.admin import OrderableAdmin
 
  14 from fnpdjango.actions import export_as_csv_action
 
  15 from modeltranslation.admin import TabbedTranslationAdmin
 
  16 from reversion.admin import VersionAdmin
 
  18 import documents.models
 
  20 from .wikidata import WikidataAdminMixin
 
  23 class NotableBookInline(OrderableAdmin, admin.TabularInline):
 
  24     model = models.NotableBook
 
  25     autocomplete_fields = ['book']
 
  26     ordering_field_hide_input = True
 
  29 class WoblinkCatalogueWidget(forms.Select):
 
  32             "admin/js/vendor/jquery/jquery.min.js",
 
  33             "admin/js/vendor/select2/select2.full.min.js",
 
  34             "admin/js/vendor/select2/i18n/pl.js",
 
  35             "catalogue/woblink_admin.js",
 
  36             "admin/js/jquery.init.js",
 
  37             "admin/js/autocomplete.js",
 
  41                 "admin/css/vendor/select2/select2.min.css",
 
  42                 "admin/css/autocomplete.css",
 
  52         return reverse('catalogue_woblink_autocomplete', args=[self.category])
 
  54     def build_attrs(self, base_attrs, extra_attrs=None):
 
  55         attrs = super().build_attrs(base_attrs, extra_attrs=extra_attrs)
 
  56         attrs.setdefault("class", "")
 
  59                 "data-ajax--cache": "true",
 
  60                 "data-ajax--delay": 250,
 
  61                 "data-ajax--type": "GET",
 
  62                 "data-ajax--url": self.get_url(),
 
  64                 "data-model-name": '',
 
  65                 "data-field-name": '',
 
  66                 "data-theme": "admin-autocomplete",
 
  67                 "data-allow-clear": json.dumps(not self.is_required),
 
  69                 "data-placeholder": "", # Chyba że znaleziony?
 
  71                 "class": attrs["class"]
 
  72                 + (" " if attrs["class"] else "")
 
  73                 + "admin-autocomplete admin-woblink",
 
  78     def optgroups(self, name, value, attrs=None):
 
  79         """ Add synthetic option for keeping the current value. """
 
  89             for index, v in enumerate(value)
 
  92 class WoblinkAuthorWidget(WoblinkCatalogueWidget):
 
  95 class AuthorForm(forms.ModelForm):
 
 100             'woblink': WoblinkAuthorWidget,
 
 103 class AuthorAdmin(WikidataAdminMixin, TabbedTranslationAdmin, VersionAdmin):
 
 117     list_display_links = [
 
 118         "first_name", "last_name"
 
 121         ("year_of_death", RangeNumericFilter),
 
 129         ("genitive", admin.EmptyFieldListFilter)
 
 131     list_per_page = 10000000
 
 132     search_fields = ["first_name", "last_name", "wikidata"]
 
 133     readonly_fields = ["wikidata_link", "description_preview", "woblink_link"]
 
 134     prepopulated_fields = {"slug": ("first_name", "last_name")}
 
 135     autocomplete_fields = ["collections", "place_of_birth", "place_of_death"]
 
 143                 ("wikidata", "wikidata_link"),
 
 144                 ("woblink", "woblink_link"),
 
 151                     ("first_name", "last_name"),
 
 159                         "year_of_birth_inexact",
 
 160                         "year_of_birth_range",
 
 167                         "year_of_death_inexact",
 
 168                         "year_of_death_range",
 
 172                     ("description", "description_preview"),
 
 181                     "photo", "photo_source", "photo_attribution",
 
 187     def description_preview(self, obj):
 
 188         return obj.generate_description()
 
 190     def woblink_link(self, obj):
 
 193                 '<a href="https://woblink.com/autor/{slug}-{w}" target="_blank">{w}</a>',
 
 199     woblink_link.admin_order_field = "woblink"
 
 202 admin.site.register(models.Author, AuthorAdmin)
 
 205 class LicenseFilter(admin.SimpleListFilter):
 
 207     parameter_name = 'book_license'
 
 208     license_url_field = 'document_book__dc__license'
 
 209     license_name_field = 'document_book__dc__license_description'
 
 211     def lookups(self, requesrt, model_admin):
 
 215             ('pd', 'domena publiczna'),
 
 218     def queryset(self, request, queryset):
 
 221             return queryset.filter(**{
 
 222                 self.license_url_field + '__icontains': 'creativecommons.org'
 
 225             return queryset.filter(**{
 
 226                 self.license_url_field + '__icontains': 'artlibre.org'
 
 229             return queryset.filter(**{
 
 230                 self.license_name_field + '__icontains': 'domena publiczna'
 
 236 class CoverLicenseFilter(LicenseFilter):
 
 237     title = 'Licencja okładki'
 
 238     parameter_name = 'cover_license'
 
 239     license_url_field = 'document_book__dc_cover_image__license_url'
 
 240     license_name_field = 'document_book__dc_cover_image__license_name'
 
 243 def add_title(base_class, suffix):
 
 244     class TitledCategoryFilter(base_class):
 
 245         def __init__(self, *args, **kwargs):
 
 246             super().__init__(*args, **kwargs)
 
 248     return TitledCategoryFilter
 
 251 class FirstPublicationYearFilter(admin.ListFilter):
 
 252     title = 'Rok pierwszej publikacji'
 
 253     parameter_name = 'first_publication_year'
 
 254     template = 'admin/filter_numeric_range.html'
 
 256     def __init__(self, request, params, *args, **kwargs):
 
 257         super().__init__(request, params, *args, **kwargs)
 
 259         self.request = request
 
 261         if self.parameter_name + '_from' in params:
 
 262             value = params.pop(self.parameter_name + '_from')
 
 263             self.used_parameters[self.parameter_name + '_from'] = value
 
 265         if self.parameter_name + '_to' in params:
 
 266             value = params.pop(self.parameter_name + '_to')
 
 267             self.used_parameters[self.parameter_name + '_to'] = value
 
 269     def has_output(self):
 
 272     def queryset(self, request, queryset):
 
 275         value_from = self.used_parameters.get(self.parameter_name + '_from', None)
 
 276         if value_from is not None and value_from != '':
 
 278                 self.parameter_name + '__gte': self.used_parameters.get(self.parameter_name + '_from', None),
 
 281         value_to = self.used_parameters.get(self.parameter_name + '_to', None)
 
 282         if value_to is not None and value_to != '':
 
 284                 self.parameter_name + '__lte': self.used_parameters.get(self.parameter_name + '_to', None),
 
 287         return queryset.filter(**filters)
 
 289     def choices(self, changelist):
 
 291             'request': self.request,
 
 292             'parameter_name': self.parameter_name,
 
 293             'form': RangeNumericForm(name=self.parameter_name, data={
 
 294                 self.parameter_name + '_from': self.used_parameters.get(self.parameter_name + '_from', None),
 
 295                 self.parameter_name + '_to': self.used_parameters.get(self.parameter_name + '_to', None),
 
 299     def expected_parameters(self):
 
 301             '{}_from'.format(self.parameter_name),
 
 302             '{}_to'.format(self.parameter_name),
 
 306 class SourcesInline(admin.TabularInline):
 
 307     model = sources.models.BookSource
 
 311 class BookAdmin(WikidataAdminMixin, NumericFilterModelAdmin, VersionAdmin):
 
 312     inlines = [SourcesInline]
 
 324         "authors__first_name", "authors__last_name",
 
 325         "translators__first_name", "translators__last_name",
 
 326         "scans_source", "text_source", "notes", "estimate_source",
 
 328     autocomplete_fields = ["authors", "translators", "based_on", "epochs", "genres", "kinds"]
 
 329     filter_horizontal = ['collections']
 
 330     prepopulated_fields = {"slug": ("title",)}
 
 333         "based_on__language",
 
 334         ("pd_year", RangeNumericFilter),
 
 336         "collections__category",
 
 337         ("authors__collections", add_title(admin.RelatedFieldListFilter, ' autora')),
 
 338         ("authors__collections__category", add_title(admin.RelatedFieldListFilter, ' autora')),
 
 339         ("translators__collections", add_title(admin.RelatedFieldListFilter, ' tłumacza')), 
 
 340         ("translators__collections__category", add_title(admin.RelatedFieldListFilter, ' tłumacza')),
 
 341         "epochs", "kinds", "genres",
 
 343         "authors__gender", "authors__nationality",
 
 344         "translators__gender", "translators__nationality",
 
 346         ("authors__place_of_birth", add_title(admin.RelatedFieldListFilter, ' autora')),
 
 347         ("authors__place_of_death", add_title(admin.RelatedFieldListFilter, ' autora')),
 
 348         ("translators__place_of_birth", add_title(admin.RelatedFieldListFilter, ' tłumacza')),
 
 349         ("translators__place_of_death", add_title(admin.RelatedFieldListFilter, ' tłumacza')),
 
 351         "document_book__chunk__stage",
 
 358         FirstPublicationYearFilter,
 
 360     list_per_page = 1000000
 
 365         "documents_book_link",
 
 367         "monthly_views_page",
 
 368         "monthly_views_reader",
 
 370     actions = [export_as_csv_action(
 
 376             "authors_first_names",
 
 377             "authors_last_names",
 
 378             "translators_first_names",
 
 379             "translators_last_names",
 
 392             "document_book__project",
 
 394             "first_publication_year",
 
 396             "monthly_views_page",
 
 397             "monthly_views_reader",
 
 407             "chars_out_verse_with_fn",
 
 411         (None, {"fields": [("wikidata", "wikidata_link")]}),
 
 417                     ("slug", 'documents_book_link'),
 
 442                     ("free_license", "polona_missing"),
 
 443                     ("scans_source", "scans_source_link"),
 
 448                     ("estimated_chars", "estimated_verses", "estimate_source"),
 
 450                     ("monthly_views_page", "monthly_views_reader"),
 
 456     def get_queryset(self, request):
 
 457         qs = super().get_queryset(request)
 
 458         if request.resolver_match.view_name.endswith("changelist"):
 
 459             qs = qs.prefetch_related("authors", "translators")
 
 460             qs = qs.annotate(first_publication_year=Min('document_book__publish_log__timestamp__year'))
 
 463     def estimated_costs(self, obj):
 
 469             for work_type, cost in obj.get_estimated_costs().items()
 
 472     def smart_title(self, obj):
 
 480                 '<em><small>' + escape(n) + '</small></em>'
 
 483     smart_title.short_description = _('Title')
 
 484     smart_title.admin_order_field = 'title'
 
 486     def documents_book_link(self, obj):
 
 487         for book in obj.document_books.all():
 
 488             return mark_safe('<a style="position: absolute" href="{}"><img height="100" width="70" src="/cover/preview/{}/?height=100&width=70"></a>'.format(book.get_absolute_url(), book.slug))
 
 489     documents_book_link.short_description = _('Book')
 
 491     def scans_source_link(self, obj):
 
 494                 '<a href="{url}" target="_blank">{url}</a>',
 
 495                 url=obj.scans_source,
 
 499     scans_source_link.short_description = _('scans source')
 
 502 admin.site.register(models.Book, BookAdmin)
 
 505 admin.site.register(models.CollectionCategory, VersionAdmin)
 
 508 class AuthorInline(admin.TabularInline):
 
 509     model = models.Author.collections.through
 
 510     autocomplete_fields = ["author"]
 
 513 class BookInline(admin.TabularInline):
 
 514     model = models.Book.collections.through
 
 515     autocomplete_fields = ["book"]
 
 518 class CollectionAdmin(VersionAdmin):
 
 519     list_display = ["name"]
 
 520     autocomplete_fields = []
 
 521     prepopulated_fields = {"slug": ("name",)}
 
 522     search_fields = ["name"]
 
 523     fields = ['name', 'slug', 'category', 'description', 'notes', 'estimated_costs']
 
 524     readonly_fields = ['estimated_costs']
 
 525     inlines = [AuthorInline, BookInline]
 
 527     def estimated_costs(self, obj):
 
 533             for work_type, cost in obj.get_estimated_costs().items()
 
 537 admin.site.register(models.Collection, CollectionAdmin)
 
 541 class CategoryAdmin(VersionAdmin):
 
 542     search_fields = ["name"]
 
 544     def has_description(self, obj):
 
 545         return bool(obj.description)
 
 546     has_description.boolean = True
 
 547     has_description.short_description = 'opis'
 
 550 @admin.register(models.Epoch)
 
 551 class EpochAdmin(CategoryAdmin):
 
 554         'adjective_feminine_singular',
 
 555         'adjective_nonmasculine_plural',
 
 560 @admin.register(models.Genre)
 
 561 class GenreAdmin(CategoryAdmin):
 
 570 @admin.register(models.Kind)
 
 571 class KindAdmin(CategoryAdmin):
 
 580 class WorkRateInline(admin.TabularInline):
 
 581     model = models.WorkRate
 
 582     autocomplete_fields = ['kinds', 'genres', 'epochs', 'collections']
 
 585 class WorkTypeAdmin(VersionAdmin):
 
 586     inlines = [WorkRateInline]
 
 588 admin.site.register(models.WorkType, WorkTypeAdmin)
 
 592 @admin.register(models.Place)
 
 593 class PlaceAdmin(WikidataAdminMixin, TabbedTranslationAdmin, VersionAdmin):
 
 594     search_fields = ['name']
 
 597 @admin.register(models.Thema)
 
 598 class ThemaAdmin(VersionAdmin):
 
 599     list_display = ['code', 'name', 'usable', 'hidden', 'woblink_category']
 
 600     list_filter = ['usable', 'usable_as_main', 'hidden']
 
 601     search_fields = ['code', 'name', 'description', 'public_description']
 
 602     prepopulated_fields = {"slug": ["name"]}
 
 606 class WoblinkSeriesWidget(WoblinkCatalogueWidget):
 
 609 class AudienceForm(forms.ModelForm):
 
 611         model = models.Audience
 
 614             'woblink': WoblinkSeriesWidget,
 
 617 @admin.register(models.Audience)
 
 618 class AudienceAdmin(VersionAdmin):
 
 620     list_display = ['code', 'name', 'thema', 'woblink']
 
 621     search_fields = ['code', 'name', 'description', 'thema', 'woblink']
 
 622     prepopulated_fields = {"slug": ["name"]}
 
 623     fields = ['code', 'name', 'slug', 'description', 'thema', ('woblink', 'woblink_id')]
 
 624     readonly_fields = ['woblink_id']
 
 626     def woblink_id(self, obj):
 
 627         return obj.woblink or ''