68a362b5bf8016b3074c341bde34f24006d28857
[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             "first_publication_year",
277
278             "monthly_views_page",
279             "monthly_views_reader",
280
281             # content stats
282             "chars",
283             "chars_with_fn",
284             "words",
285             "words_with_fn",
286             "verses",
287             "chars_out_verse",
288             "verses_with_fn",
289             "chars_out_verse_with_fn",
290         ]
291     )]
292     fieldsets = [
293         (None, {"fields": [("wikidata", "wikidata_link")]}),
294         (
295             _("Identification"),
296             {
297                 "fields": [
298                     "title",
299                     ("slug", 'documents_book_link'),
300                     "authors",
301                     "translators",
302                     "language",
303                     "based_on",
304                     "original_year",
305                     "pd_year",
306                 ]
307             },
308         ),
309         (
310             _("Features"),
311             {
312                 "fields": [
313                     "epochs",
314                     "genres",
315                     "kinds",
316                 ]
317             },
318         ),
319         (
320             _("Plan"),
321             {
322                 "fields": [
323                     ("free_license", "polona_missing"),
324                     ("scans_source", "scans_source_link"),
325                     "text_source",
326                     "priority",
327                     "collections",
328                     "notes",
329                     ("estimated_chars", "estimated_verses", "estimate_source"),
330                     "estimated_costs",
331                     ("monthly_views_page", "monthly_views_reader"),
332                 ]
333             },
334         ),
335     ]
336
337     def get_queryset(self, request):
338         qs = super().get_queryset(request)
339         if request.resolver_match.view_name.endswith("changelist"):
340             qs = qs.prefetch_related("authors", "translators")
341             qs = qs.annotate(first_publication_year=Min('document_book__publish_log__timestamp__year'))
342         return qs
343
344     def estimated_costs(self, obj):
345         return "\n".join(
346             "{}: {} zł".format(
347                 work_type.name,
348                 cost or '—'
349             )
350             for work_type, cost in obj.get_estimated_costs().items()
351         )
352
353     def smart_title(self, obj):
354         if obj.title:
355             return obj.title
356         if obj.notes:
357             n = obj.notes
358             if len(n) > 100:
359                 n = n[:100] + '…'
360             return mark_safe(
361                 '<em><small>' + escape(n) + '</small></em>'
362             )
363         return '---'
364     smart_title.short_description = _('Title')
365     smart_title.admin_order_field = 'title'
366
367     def documents_book_link(self, obj):
368         for book in obj.document_books.all():
369             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))
370     documents_book_link.short_description = _('Book')
371
372     def scans_source_link(self, obj):
373         if obj.scans_source:
374             return format_html(
375                 '<a href="{url}" target="_blank">{url}</a>',
376                 url=obj.scans_source,
377             )
378         else:
379             return ""
380     scans_source_link.short_description = _('scans source')
381
382
383 admin.site.register(models.Book, BookAdmin)
384
385
386 admin.site.register(models.CollectionCategory)
387
388
389 class AuthorInline(admin.TabularInline):
390     model = models.Author.collections.through
391     autocomplete_fields = ["author"]
392
393
394 class BookInline(admin.TabularInline):
395     model = models.Book.collections.through
396     autocomplete_fields = ["book"]
397
398
399 class CollectionAdmin(admin.ModelAdmin):
400     list_display = ["name"]
401     autocomplete_fields = []
402     prepopulated_fields = {"slug": ("name",)}
403     search_fields = ["name"]
404     fields = ['name', 'slug', 'category', 'description', 'notes', 'estimated_costs']
405     readonly_fields = ['estimated_costs']
406     inlines = [AuthorInline, BookInline]
407
408     def estimated_costs(self, obj):
409         return "\n".join(
410             "{}: {} zł".format(
411                 work_type.name,
412                 cost or '—'
413             )
414             for work_type, cost in obj.get_estimated_costs().items()
415         )
416
417
418 admin.site.register(models.Collection, CollectionAdmin)
419
420
421
422 class CategoryAdmin(admin.ModelAdmin):
423     search_fields = ["name"]
424
425     def has_description(self, obj):
426         return bool(obj.description)
427     has_description.boolean = True
428     has_description.short_description = 'opis'
429
430
431 @admin.register(models.Epoch)
432 class EpochAdmin(CategoryAdmin):
433     list_display = [
434         'name',
435         'adjective_feminine_singular',
436         'adjective_nonmasculine_plural',
437         'has_description',
438     ]
439
440
441 @admin.register(models.Genre)
442 class GenreAdmin(CategoryAdmin):
443     list_display = [
444         'name',
445         'plural',
446         'is_epoch_specific',
447         'has_description',
448     ]
449
450
451 @admin.register(models.Kind)
452 class KindAdmin(CategoryAdmin):
453     list_display = [
454         'name',
455         'collective_noun',
456         'has_description',
457     ]
458
459
460
461 class WorkRateInline(admin.TabularInline):
462     model = models.WorkRate
463     autocomplete_fields = ['kinds', 'genres', 'epochs', 'collections']
464
465
466 class WorkTypeAdmin(admin.ModelAdmin):
467     inlines = [WorkRateInline]
468
469 admin.site.register(models.WorkType, WorkTypeAdmin)
470
471
472
473 @admin.register(models.Place)
474 class PlaceAdmin(WikidataAdminMixin, TabbedTranslationAdmin):
475     search_fields = ['name']