7acfa4010182e8bc362d74a93e4e1208b18054c2
[redakcja.git] / src / catalogue / admin.py
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.
3 #
4 from django.contrib import admin
5 from django.db.models import Min
6 from django.utils.html import escape, format_html
7 from django.utils.safestring import mark_safe
8 from django.utils.translation import gettext_lazy as _
9 from admin_numeric_filter.admin import RangeNumericFilter, NumericFilterModelAdmin, RangeNumericForm
10 from admin_ordering.admin import OrderableAdmin
11 from fnpdjango.actions import export_as_csv_action
12 from modeltranslation.admin import TabbedTranslationAdmin
13 from . import models
14 import documents.models
15 from .wikidata import WikidataAdminMixin
16
17
18 class NotableBookInline(OrderableAdmin, admin.TabularInline):
19     model = models.NotableBook
20     autocomplete_fields = ['book']
21     ordering_field_hide_input = True
22
23
24 class AuthorAdmin(WikidataAdminMixin, TabbedTranslationAdmin):
25     list_display = [
26         "first_name",
27         "last_name",
28         "status",
29         "year_of_death",
30         "gender",
31         "nationality",
32         "priority",
33         "wikidata_link",
34         "slug",
35     ]
36     list_display_links = [
37         "first_name", "last_name"
38     ]
39     list_filter = [
40         ("year_of_death", RangeNumericFilter),
41         "priority",
42         "collections",
43         "status",
44         "gender",
45         "nationality",
46         "place_of_birth",
47         "place_of_death",
48         ("genitive", admin.EmptyFieldListFilter)
49     ]
50     list_per_page = 10000000
51     search_fields = ["first_name", "last_name", "wikidata"]
52     readonly_fields = ["wikidata_link", "description_preview"]
53
54     fieldsets = [
55         (None, {"fields": [("wikidata", "wikidata_link")]}),
56         (
57             _("Identification"),
58             {
59                 "fields": [
60                     ("first_name", "last_name"),
61                     "slug",
62                     "genitive",
63                     "gender",
64                     "nationality",
65                     ("date_of_birth", "year_of_birth", "year_of_birth_inexact", "year_of_birth_range", "place_of_birth"),
66                     ("date_of_death", "year_of_death", "year_of_death_inexact", "year_of_death_range", "place_of_death"),
67                     ("description", "description_preview"),
68                     "status",
69                     "collections",
70                     "priority",
71                     
72                     "notes",
73                     "gazeta_link",
74                     "culturepl_link",
75                     "plwiki",
76                     "photo", "photo_source", "photo_attribution",
77                 ]
78             },
79         ),
80     ]
81     
82     prepopulated_fields = {"slug": ("first_name", "last_name")}
83     autocomplete_fields = ["collections", "place_of_birth", "place_of_death"]
84     inlines = [
85         NotableBookInline,
86     ]
87
88     def description_preview(self, obj):
89         return obj.generate_description()
90
91
92 admin.site.register(models.Author, AuthorAdmin)
93
94
95 class LicenseFilter(admin.SimpleListFilter):
96     title = 'Licencja'
97     parameter_name = 'book_license'
98     license_url_field = 'document_book__dc__license'
99     license_name_field = 'document_book__dc__license_description'
100
101     def lookups(self, requesrt, model_admin):
102         return [
103             ('cc', 'CC'),
104             ('fal', 'FAL'),
105             ('pd', 'domena publiczna'),
106         ]
107
108     def queryset(self, request, queryset):
109         v = self.value()
110         if v == 'cc': 
111             return queryset.filter(**{
112                 self.license_url_field + '__icontains': 'creativecommons.org'
113             })
114         elif v == 'fal':
115             return queryset.filter(**{
116                 self.license_url_field + '__icontains': 'artlibre.org'
117             })
118         elif v == 'pd':
119             return queryset.filter(**{
120                 self.license_name_field + '__icontains': 'domena publiczna'
121             })
122         else:
123             return queryset
124
125
126 class CoverLicenseFilter(LicenseFilter):
127     title = 'Licencja okładki'
128     parameter_name = 'cover_license'
129     license_url_field = 'document_book__dc_cover_image__license_url'
130     license_name_field = 'document_book__dc_cover_image__license_name'
131
132
133 def add_title(base_class, suffix):
134     class TitledCategoryFilter(base_class):
135         def __init__(self, *args, **kwargs):
136             super().__init__(*args, **kwargs)
137             self.title += suffix
138     return TitledCategoryFilter
139
140
141 class FirstPublicationYearFilter(admin.ListFilter):
142     title = 'Rok pierwszej publikacji'
143     parameter_name = 'first_publication_year'
144     template = 'admin/filter_numeric_range.html'
145
146     def __init__(self, request, params, *args, **kwargs):
147         super().__init__(request, params, *args, **kwargs)
148
149         self.request = request
150
151         if self.parameter_name + '_from' in params:
152             value = params.pop(self.parameter_name + '_from')
153             self.used_parameters[self.parameter_name + '_from'] = value
154
155         if self.parameter_name + '_to' in params:
156             value = params.pop(self.parameter_name + '_to')
157             self.used_parameters[self.parameter_name + '_to'] = value
158
159     def has_output(self):
160         return True
161
162     def queryset(self, request, queryset):
163         filters = {}
164
165         value_from = self.used_parameters.get(self.parameter_name + '_from', None)
166         if value_from is not None and value_from != '':
167             filters.update({
168                 self.parameter_name + '__gte': self.used_parameters.get(self.parameter_name + '_from', None),
169             })
170
171         value_to = self.used_parameters.get(self.parameter_name + '_to', None)
172         if value_to is not None and value_to != '':
173             filters.update({
174                 self.parameter_name + '__lte': self.used_parameters.get(self.parameter_name + '_to', None),
175             })
176
177         return queryset.filter(**filters)
178
179     def choices(self, changelist):
180         return ({
181             'request': self.request,
182             'parameter_name': self.parameter_name,
183             'form': RangeNumericForm(name=self.parameter_name, data={
184                 self.parameter_name + '_from': self.used_parameters.get(self.parameter_name + '_from', None),
185                 self.parameter_name + '_to': self.used_parameters.get(self.parameter_name + '_to', None),
186             }),
187         }, )
188
189     def expected_parameters(self):
190         return [
191             '{}_from'.format(self.parameter_name),
192             '{}_to'.format(self.parameter_name),
193         ]
194
195
196 class BookAdmin(WikidataAdminMixin, NumericFilterModelAdmin):
197     list_display = [
198         "smart_title",
199         "authors_str",
200         "translators_str",
201         "language",
202         "pd_year",
203         "priority",
204         "wikidata_link",
205     ]
206     search_fields = [
207         "title", "wikidata",
208         "authors__first_name", "authors__last_name",
209         "translators__first_name", "translators__last_name",
210         "scans_source", "text_source", "notes", "estimate_source",
211     ]
212     autocomplete_fields = ["authors", "translators", "based_on", "collections", "epochs", "genres", "kinds"]
213     prepopulated_fields = {"slug": ("title",)}
214     list_filter = [
215         "language",
216         "based_on__language",
217         ("pd_year", RangeNumericFilter),
218         "collections",
219         "collections__category",
220         ("authors__collections", add_title(admin.RelatedFieldListFilter, ' autora')),
221         ("authors__collections__category", add_title(admin.RelatedFieldListFilter, ' autora')),
222         ("translators__collections", add_title(admin.RelatedFieldListFilter, ' tłumacza')), 
223         ("translators__collections__category", add_title(admin.RelatedFieldListFilter, ' tłumacza')),
224         "epochs", "kinds", "genres",
225         "priority",
226         "authors__gender", "authors__nationality",
227         "translators__gender", "translators__nationality",
228
229         ("authors__place_of_birth", add_title(admin.RelatedFieldListFilter, ' autora')),
230         ("authors__place_of_death", add_title(admin.RelatedFieldListFilter, ' autora')),
231         ("translators__place_of_birth", add_title(admin.RelatedFieldListFilter, ' tłumacza')),
232         ("translators__place_of_death", add_title(admin.RelatedFieldListFilter, ' tłumacza')),
233
234         "document_book__chunk__stage",
235
236         LicenseFilter,
237         CoverLicenseFilter,
238         'free_license',
239         'polona_missing',
240
241         FirstPublicationYearFilter,
242     ]
243     list_per_page = 1000000
244
245     readonly_fields = [
246         "wikidata_link",
247         "estimated_costs",
248         "documents_book_link",
249         "scans_source_link",
250         "monthly_views_page",
251         "monthly_views_reader",
252     ]
253     actions = [export_as_csv_action(
254         fields=[
255             "id",
256             "wikidata",
257             "slug",
258             "title",
259             "authors_first_names",
260             "authors_last_names",
261             "translators_first_names",
262             "translators_last_names",
263             "language",
264             "based_on",
265             "scans_source",
266             "text_source",
267             "notes",
268             "priority",
269             "pd_year",
270             "gazeta_link",
271             "estimated_chars",
272             "estimated_verses",
273             "estimate_source",
274
275             "document_book__project",
276             "audience",
277             "first_publication_year",
278
279             "monthly_views_page",
280             "monthly_views_reader",
281
282             # content stats
283             "chars",
284             "chars_with_fn",
285             "words",
286             "words_with_fn",
287             "verses",
288             "chars_out_verse",
289             "verses_with_fn",
290             "chars_out_verse_with_fn",
291         ]
292     )]
293     fieldsets = [
294         (None, {"fields": [("wikidata", "wikidata_link")]}),
295         (
296             _("Identification"),
297             {
298                 "fields": [
299                     "title",
300                     ("slug", 'documents_book_link'),
301                     "authors",
302                     "translators",
303                     "language",
304                     "based_on",
305                     "original_year",
306                     "pd_year",
307                 ]
308             },
309         ),
310         (
311             _("Features"),
312             {
313                 "fields": [
314                     "epochs",
315                     "genres",
316                     "kinds",
317                 ]
318             },
319         ),
320         (
321             _("Plan"),
322             {
323                 "fields": [
324                     ("free_license", "polona_missing"),
325                     ("scans_source", "scans_source_link"),
326                     "text_source",
327                     "priority",
328                     "collections",
329                     "notes",
330                     ("estimated_chars", "estimated_verses", "estimate_source"),
331                     "estimated_costs",
332                     ("monthly_views_page", "monthly_views_reader"),
333                 ]
334             },
335         ),
336     ]
337
338     def get_queryset(self, request):
339         qs = super().get_queryset(request)
340         if request.resolver_match.view_name.endswith("changelist"):
341             qs = qs.prefetch_related("authors", "translators")
342             qs = qs.annotate(first_publication_year=Min('document_book__publish_log__timestamp__year'))
343         return qs
344
345     def estimated_costs(self, obj):
346         return "\n".join(
347             "{}: {} zł".format(
348                 work_type.name,
349                 cost or '—'
350             )
351             for work_type, cost in obj.get_estimated_costs().items()
352         )
353
354     def smart_title(self, obj):
355         if obj.title:
356             return obj.title
357         if obj.notes:
358             n = obj.notes
359             if len(n) > 100:
360                 n = n[:100] + '…'
361             return mark_safe(
362                 '<em><small>' + escape(n) + '</small></em>'
363             )
364         return '---'
365     smart_title.short_description = _('Title')
366     smart_title.admin_order_field = 'title'
367
368     def documents_book_link(self, obj):
369         for book in obj.document_books.all():
370             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))
371     documents_book_link.short_description = _('Book')
372
373     def scans_source_link(self, obj):
374         if obj.scans_source:
375             return format_html(
376                 '<a href="{url}" target="_blank">{url}</a>',
377                 url=obj.scans_source,
378             )
379         else:
380             return ""
381     scans_source_link.short_description = _('scans source')
382
383
384 admin.site.register(models.Book, BookAdmin)
385
386
387 admin.site.register(models.CollectionCategory)
388
389
390 class AuthorInline(admin.TabularInline):
391     model = models.Author.collections.through
392     autocomplete_fields = ["author"]
393
394
395 class BookInline(admin.TabularInline):
396     model = models.Book.collections.through
397     autocomplete_fields = ["book"]
398
399
400 class CollectionAdmin(admin.ModelAdmin):
401     list_display = ["name"]
402     autocomplete_fields = []
403     prepopulated_fields = {"slug": ("name",)}
404     search_fields = ["name"]
405     fields = ['name', 'slug', 'category', 'description', 'notes', 'estimated_costs']
406     readonly_fields = ['estimated_costs']
407     inlines = [AuthorInline, BookInline]
408
409     def estimated_costs(self, obj):
410         return "\n".join(
411             "{}: {} zł".format(
412                 work_type.name,
413                 cost or '—'
414             )
415             for work_type, cost in obj.get_estimated_costs().items()
416         )
417
418
419 admin.site.register(models.Collection, CollectionAdmin)
420
421
422
423 class CategoryAdmin(admin.ModelAdmin):
424     search_fields = ["name"]
425
426     def has_description(self, obj):
427         return bool(obj.description)
428     has_description.boolean = True
429     has_description.short_description = 'opis'
430
431
432 @admin.register(models.Epoch)
433 class EpochAdmin(CategoryAdmin):
434     list_display = [
435         'name',
436         'adjective_feminine_singular',
437         'adjective_nonmasculine_plural',
438         'has_description',
439     ]
440
441
442 @admin.register(models.Genre)
443 class GenreAdmin(CategoryAdmin):
444     list_display = [
445         'name',
446         'plural',
447         'is_epoch_specific',
448         'has_description',
449     ]
450
451
452 @admin.register(models.Kind)
453 class KindAdmin(CategoryAdmin):
454     list_display = [
455         'name',
456         'collective_noun',
457         'has_description',
458     ]
459
460
461
462 class WorkRateInline(admin.TabularInline):
463     model = models.WorkRate
464     autocomplete_fields = ['kinds', 'genres', 'epochs', 'collections']
465
466
467 class WorkTypeAdmin(admin.ModelAdmin):
468     inlines = [WorkRateInline]
469
470 admin.site.register(models.WorkType, WorkTypeAdmin)
471
472
473
474 @admin.register(models.Place)
475 class PlaceAdmin(WikidataAdminMixin, TabbedTranslationAdmin):
476     search_fields = ['name']