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
 
  17 import documents.models
 
  18 from .wikidata import WikidataAdminMixin
 
  21 class NotableBookInline(OrderableAdmin, admin.TabularInline):
 
  22     model = models.NotableBook
 
  23     autocomplete_fields = ['book']
 
  24     ordering_field_hide_input = True
 
  27 class WoblinkAuthorWidget(forms.Select):
 
  29         js = ("catalogue/woblink_admin.js",)
 
  37         return reverse('catalogue_woblink_author_autocomplete')
 
  39     def build_attrs(self, base_attrs, extra_attrs=None):
 
  40         attrs = super().build_attrs(base_attrs, extra_attrs=extra_attrs)
 
  41         attrs.setdefault("class", "")
 
  44                 "data-ajax--cache": "true",
 
  45                 "data-ajax--delay": 250,
 
  46                 "data-ajax--type": "GET",
 
  47                 "data-ajax--url": self.get_url(),
 
  49                 "data-model-name": '',
 
  50                 "data-field-name": '',
 
  51                 "data-theme": "admin-autocomplete",
 
  52                 "data-allow-clear": json.dumps(not self.is_required),
 
  54                 "data-placeholder": "", # Chyba że znaleziony?
 
  56                 "class": attrs["class"]
 
  57                 + (" " if attrs["class"] else "")
 
  58                 + "admin-autocomplete admin-woblink",
 
  63 class AuthorForm(forms.ModelForm):
 
  68             'woblink': WoblinkAuthorWidget,
 
  71 class AuthorAdmin(WikidataAdminMixin, TabbedTranslationAdmin):
 
  85     list_display_links = [
 
  86         "first_name", "last_name"
 
  89         ("year_of_death", RangeNumericFilter),
 
  97         ("genitive", admin.EmptyFieldListFilter)
 
  99     list_per_page = 10000000
 
 100     search_fields = ["first_name", "last_name", "wikidata"]
 
 101     readonly_fields = ["wikidata_link", "description_preview", "woblink_link"]
 
 102     prepopulated_fields = {"slug": ("first_name", "last_name")}
 
 103     autocomplete_fields = ["collections", "place_of_birth", "place_of_death"]
 
 111                 ("wikidata", "wikidata_link"),
 
 112                 ("woblink", "woblink_link"),
 
 119                     ("first_name", "last_name"),
 
 127                         "year_of_birth_inexact",
 
 128                         "year_of_birth_range",
 
 135                         "year_of_death_inexact",
 
 136                         "year_of_death_range",
 
 140                     ("description", "description_preview"),
 
 149                     "photo", "photo_source", "photo_attribution",
 
 155     def description_preview(self, obj):
 
 156         return obj.generate_description()
 
 158     def woblink_link(self, obj):
 
 161                 '<a href="https://woblink.com/autor/{slug}-{w}" target="_blank">{w}</a>',
 
 167     woblink_link.admin_order_field = "woblink"
 
 170 admin.site.register(models.Author, AuthorAdmin)
 
 173 class LicenseFilter(admin.SimpleListFilter):
 
 175     parameter_name = 'book_license'
 
 176     license_url_field = 'document_book__dc__license'
 
 177     license_name_field = 'document_book__dc__license_description'
 
 179     def lookups(self, requesrt, model_admin):
 
 183             ('pd', 'domena publiczna'),
 
 186     def queryset(self, request, queryset):
 
 189             return queryset.filter(**{
 
 190                 self.license_url_field + '__icontains': 'creativecommons.org'
 
 193             return queryset.filter(**{
 
 194                 self.license_url_field + '__icontains': 'artlibre.org'
 
 197             return queryset.filter(**{
 
 198                 self.license_name_field + '__icontains': 'domena publiczna'
 
 204 class CoverLicenseFilter(LicenseFilter):
 
 205     title = 'Licencja okładki'
 
 206     parameter_name = 'cover_license'
 
 207     license_url_field = 'document_book__dc_cover_image__license_url'
 
 208     license_name_field = 'document_book__dc_cover_image__license_name'
 
 211 def add_title(base_class, suffix):
 
 212     class TitledCategoryFilter(base_class):
 
 213         def __init__(self, *args, **kwargs):
 
 214             super().__init__(*args, **kwargs)
 
 216     return TitledCategoryFilter
 
 219 class FirstPublicationYearFilter(admin.ListFilter):
 
 220     title = 'Rok pierwszej publikacji'
 
 221     parameter_name = 'first_publication_year'
 
 222     template = 'admin/filter_numeric_range.html'
 
 224     def __init__(self, request, params, *args, **kwargs):
 
 225         super().__init__(request, params, *args, **kwargs)
 
 227         self.request = request
 
 229         if self.parameter_name + '_from' in params:
 
 230             value = params.pop(self.parameter_name + '_from')
 
 231             self.used_parameters[self.parameter_name + '_from'] = value
 
 233         if self.parameter_name + '_to' in params:
 
 234             value = params.pop(self.parameter_name + '_to')
 
 235             self.used_parameters[self.parameter_name + '_to'] = value
 
 237     def has_output(self):
 
 240     def queryset(self, request, queryset):
 
 243         value_from = self.used_parameters.get(self.parameter_name + '_from', None)
 
 244         if value_from is not None and value_from != '':
 
 246                 self.parameter_name + '__gte': self.used_parameters.get(self.parameter_name + '_from', None),
 
 249         value_to = self.used_parameters.get(self.parameter_name + '_to', None)
 
 250         if value_to is not None and value_to != '':
 
 252                 self.parameter_name + '__lte': self.used_parameters.get(self.parameter_name + '_to', None),
 
 255         return queryset.filter(**filters)
 
 257     def choices(self, changelist):
 
 259             'request': self.request,
 
 260             'parameter_name': self.parameter_name,
 
 261             'form': RangeNumericForm(name=self.parameter_name, data={
 
 262                 self.parameter_name + '_from': self.used_parameters.get(self.parameter_name + '_from', None),
 
 263                 self.parameter_name + '_to': self.used_parameters.get(self.parameter_name + '_to', None),
 
 267     def expected_parameters(self):
 
 269             '{}_from'.format(self.parameter_name),
 
 270             '{}_to'.format(self.parameter_name),
 
 274 class BookAdmin(WikidataAdminMixin, NumericFilterModelAdmin):
 
 286         "authors__first_name", "authors__last_name",
 
 287         "translators__first_name", "translators__last_name",
 
 288         "scans_source", "text_source", "notes", "estimate_source",
 
 290     autocomplete_fields = ["authors", "translators", "based_on", "collections", "epochs", "genres", "kinds"]
 
 291     prepopulated_fields = {"slug": ("title",)}
 
 294         "based_on__language",
 
 295         ("pd_year", RangeNumericFilter),
 
 297         "collections__category",
 
 298         ("authors__collections", add_title(admin.RelatedFieldListFilter, ' autora')),
 
 299         ("authors__collections__category", add_title(admin.RelatedFieldListFilter, ' autora')),
 
 300         ("translators__collections", add_title(admin.RelatedFieldListFilter, ' tłumacza')), 
 
 301         ("translators__collections__category", add_title(admin.RelatedFieldListFilter, ' tłumacza')),
 
 302         "epochs", "kinds", "genres",
 
 304         "authors__gender", "authors__nationality",
 
 305         "translators__gender", "translators__nationality",
 
 307         ("authors__place_of_birth", add_title(admin.RelatedFieldListFilter, ' autora')),
 
 308         ("authors__place_of_death", add_title(admin.RelatedFieldListFilter, ' autora')),
 
 309         ("translators__place_of_birth", add_title(admin.RelatedFieldListFilter, ' tłumacza')),
 
 310         ("translators__place_of_death", add_title(admin.RelatedFieldListFilter, ' tłumacza')),
 
 312         "document_book__chunk__stage",
 
 319         FirstPublicationYearFilter,
 
 321     list_per_page = 1000000
 
 326         "documents_book_link",
 
 328         "monthly_views_page",
 
 329         "monthly_views_reader",
 
 331     actions = [export_as_csv_action(
 
 337             "authors_first_names",
 
 338             "authors_last_names",
 
 339             "translators_first_names",
 
 340             "translators_last_names",
 
 353             "document_book__project",
 
 355             "first_publication_year",
 
 357             "monthly_views_page",
 
 358             "monthly_views_reader",
 
 368             "chars_out_verse_with_fn",
 
 372         (None, {"fields": [("wikidata", "wikidata_link")]}),
 
 378                     ("slug", 'documents_book_link'),
 
 402                     ("free_license", "polona_missing"),
 
 403                     ("scans_source", "scans_source_link"),
 
 408                     ("estimated_chars", "estimated_verses", "estimate_source"),
 
 410                     ("monthly_views_page", "monthly_views_reader"),
 
 416     def get_queryset(self, request):
 
 417         qs = super().get_queryset(request)
 
 418         if request.resolver_match.view_name.endswith("changelist"):
 
 419             qs = qs.prefetch_related("authors", "translators")
 
 420             qs = qs.annotate(first_publication_year=Min('document_book__publish_log__timestamp__year'))
 
 423     def estimated_costs(self, obj):
 
 429             for work_type, cost in obj.get_estimated_costs().items()
 
 432     def smart_title(self, obj):
 
 440                 '<em><small>' + escape(n) + '</small></em>'
 
 443     smart_title.short_description = _('Title')
 
 444     smart_title.admin_order_field = 'title'
 
 446     def documents_book_link(self, obj):
 
 447         for book in obj.document_books.all():
 
 448             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))
 
 449     documents_book_link.short_description = _('Book')
 
 451     def scans_source_link(self, obj):
 
 454                 '<a href="{url}" target="_blank">{url}</a>',
 
 455                 url=obj.scans_source,
 
 459     scans_source_link.short_description = _('scans source')
 
 462 admin.site.register(models.Book, BookAdmin)
 
 465 admin.site.register(models.CollectionCategory)
 
 468 class AuthorInline(admin.TabularInline):
 
 469     model = models.Author.collections.through
 
 470     autocomplete_fields = ["author"]
 
 473 class BookInline(admin.TabularInline):
 
 474     model = models.Book.collections.through
 
 475     autocomplete_fields = ["book"]
 
 478 class CollectionAdmin(admin.ModelAdmin):
 
 479     list_display = ["name"]
 
 480     autocomplete_fields = []
 
 481     prepopulated_fields = {"slug": ("name",)}
 
 482     search_fields = ["name"]
 
 483     fields = ['name', 'slug', 'category', 'description', 'notes', 'estimated_costs']
 
 484     readonly_fields = ['estimated_costs']
 
 485     inlines = [AuthorInline, BookInline]
 
 487     def estimated_costs(self, obj):
 
 493             for work_type, cost in obj.get_estimated_costs().items()
 
 497 admin.site.register(models.Collection, CollectionAdmin)
 
 501 class CategoryAdmin(admin.ModelAdmin):
 
 502     search_fields = ["name"]
 
 504     def has_description(self, obj):
 
 505         return bool(obj.description)
 
 506     has_description.boolean = True
 
 507     has_description.short_description = 'opis'
 
 510 @admin.register(models.Epoch)
 
 511 class EpochAdmin(CategoryAdmin):
 
 514         'adjective_feminine_singular',
 
 515         'adjective_nonmasculine_plural',
 
 520 @admin.register(models.Genre)
 
 521 class GenreAdmin(CategoryAdmin):
 
 530 @admin.register(models.Kind)
 
 531 class KindAdmin(CategoryAdmin):
 
 540 class WorkRateInline(admin.TabularInline):
 
 541     model = models.WorkRate
 
 542     autocomplete_fields = ['kinds', 'genres', 'epochs', 'collections']
 
 545 class WorkTypeAdmin(admin.ModelAdmin):
 
 546     inlines = [WorkRateInline]
 
 548 admin.site.register(models.WorkType, WorkTypeAdmin)
 
 552 @admin.register(models.Place)
 
 553 class PlaceAdmin(WikidataAdminMixin, TabbedTranslationAdmin):
 
 554     search_fields = ['name']
 
 557 @admin.register(models.Thema)
 
 558 class ThemaAdmin(admin.ModelAdmin):
 
 559     list_display = ['code', 'name', 'usable', 'hidden', 'woblink_category']
 
 560     list_filter = ['usable', 'usable_as_main', 'hidden']
 
 561     search_fields = ['code', 'name', 'description', 'public_description']
 
 562     prepopulated_fields = {"slug": ["name"]}
 
 565 @admin.register(models.Audience)
 
 566 class ThemaAdmin(admin.ModelAdmin):
 
 567     list_display = ['code', 'name', 'thema']
 
 568     search_fields = ['code', 'name', 'description', 'thema']
 
 569     prepopulated_fields = {"slug": ["name"]}