Page MenuHomec4science

admin.py
No OneTemporary

File Metadata

Created
Sun, May 5, 18:23

admin.py

"""
This is the Open Access Check Tool (OACT).
The publication of scientific articles as Open Access (OA), usually in the variants "Green OA" and "Gold OA", allows free access to scientific research results and their largely unhindered dissemination. Often, however, the multitude of available publication conditions makes the decision in favor of a particular journal difficult: requirements of the funding agencies and publication guidelines of the universities and colleges must be carefully compared with the offers of the publishing houses, and separately concluded publication agreements can also offer additional benefits. The "OA Compliance Check Tool" provides a comprehensive overview of the possible publication conditions for a large number of journals, especially for the Swiss university landscape, and thus supports the decision-making process.
© All rights reserved. ECOLE POLYTECHNIQUE FEDERALE DE LAUSANNE, Switzerland, Scientific Information and Libraries, 2022
See LICENSE.TXT for more details.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
"""
""" Django admin module for the django_api application
All admin pages inherit from import_export.admin.ImportExportModelAdmin for JSON import/export.
"""
from django.contrib import admin
from django import forms
from datetime import date, datetime
import re
from import_export.admin import ImportExportModelAdmin
from django.contrib.admin import TabularInline
from django.contrib.admin import SimpleListFilter
from django.contrib.admin import RelatedOnlyFieldListFilter
from django.db.models import Prefetch
from django.forms.models import BaseInlineFormSet
from django.utils.translation import gettext_lazy as _
#from inline_actions.admin import InlineActionsMixin
#from inline_actions.admin import InlineActionsModelAdminMixin
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.utils.html import escape, mark_safe, format_html
from .models import Country
from .models import Language
from .models import Issn
from .models import Oa
from .models import Publisher
from .models import Journal
from .models import Organization
from .models import Version
from .models import Licence
from .models import Cost_factor_type
from .models import Cost_factor
from .models import Term
from .models import ConditionType
from .models import ConditionSubType
from .models import ConditionSet
from .models import OrganizationCondition
from .models import JournalCondition
# Register your models here.
@admin.register(Issn)
class IssnAdmin(ImportExportModelAdmin):
# TODO use RelatedOnlyFieldListFilter for publisher when data allows it
list_filter = ('issn_type', 'journal__publisher__name', )
list_display = ("id", "issn", 'journal')
class IssnInline(admin.TabularInline):
model = Issn
readonly_fields = ('issn', 'issn_type',)
# This Inline is stricty read-only for the moment
def has_change_permission(self, request, obj=None):
return False
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
@admin.register(Journal)
class JournalAdmin(ImportExportModelAdmin):
list_display = ("id", "name", "get_journal_issns",)
# TODO use RelatedOnlyFieldListFilter for publisher when data allows it
list_filter = ('oa_status', 'publisher', )
filter_horizontal = ('publisher', 'language', )
search_fields = ('name', 'classIssn__issn')
inlines = (IssnInline, )
@admin.display(description='ISSNs')
def get_journal_issns(self, obj):
return list(Issn.objects.filter(journal=obj))
@admin.register(Language)
class LanguageAdmin(ImportExportModelAdmin):
pass
@admin.register(Organization)
class OrganizationAdmin(ImportExportModelAdmin):
list_display = ("id", "name")
list_filter = ('is_funder', ('country', RelatedOnlyFieldListFilter))
filter_horizontal = ('country', )
search_fields = ('name', )
@admin.register(Version)
class VersionAdmin(ImportExportModelAdmin):
pass
@admin.register(Country)
class CountryAdmin(ImportExportModelAdmin):
pass
@admin.register(Oa)
class OaAdmin(ImportExportModelAdmin):
pass
@admin.register(Publisher)
class PublisherAdmin(ImportExportModelAdmin):
list_display = ("id", "name")
# Experimental: what will happen with 200+ countries in the database?
list_filter = (('country', RelatedOnlyFieldListFilter), )
filter_horizontal = ('country', )
@admin.register(Term)
class TermAdmin(ImportExportModelAdmin):
list_display = ("id", "__str__", )
list_filter = ('version', 'licence', 'ir_archiving')
search_fields = ("id", "comment", "embargo_months")
filter_horizontal = ('version', 'cost_factor', 'licence', )
# textarea input is better for comments
def get_form(self, request, obj=None, **kwargs):
kwargs['widgets'] = {'comment': forms.Textarea}
return super().get_form(request, obj, **kwargs)
@admin.register(ConditionType)
class ConditionTypeAdmin(ImportExportModelAdmin):
list_display = ("id", "condition_issuer")
@admin.register(ConditionSubType)
class ConditionTypeAdmin(ImportExportModelAdmin):
list_display = ("id", "label")
class JournalConditionFormset(forms.BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(JournalConditionFormset, self).__init__(*args, **kwargs)
self.queryset = self.queryset.select_related('journal', 'condition_set', 'condition_set__condition_type')
#self.queryset = self.queryset.prefetch_related('journal')
#print('JournalConditionFormset.queryset length ', len(self.queryset))
# print(self.queryset.prefetch_related('journal').__dict__)
class JournalConditionInlineForm(forms.ModelForm):
class Meta:
model = JournalCondition
exclude = ()
def __init__(self, *args, **kwargs):
super(JournalConditionInlineForm, self).__init__(*args, **kwargs)
#print('JournalConditionInlineForm created')
#self.fields['journal'].queryset = JournalCondition.objects.select_related('journal').all()
#class JournalConditionInline(InlineActionsMixin, TabularInline):
class JournalConditionInline(TabularInline):
model = JournalCondition
fields = ('journal', 'valid_from', 'valid_until')
# ordering = ('journal__name', 'valid_from', 'valid_until')
extra = 1
#inline_actions = ['connect_all_journals']
autocomplete_fields = ('journal', )
fk_name = 'condition_set'
formset = JournalConditionFormset
#form = JournalConditionInlineForm
# IN PROGRESS 2022-04-20 we'll get back to it later
#form = JournalConditionInlineForm
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs).select_related('journal')
print('JournalConditionInline.get_queryset() called')
print(qs.__dict__)
print(qs.all())
return qs
def dummy_get_formset(self, request, obj=None, **kwargs):
formset = super(JournalConditionInline, self).get_formset(request, obj, **kwargs)
queryset = formset.form.base_fields["journal"].queryset
formset.form.base_fields["journal"].queryset = queryset
return formset
# not working AB 2022-04-27
"""
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if 'queryset' in kwargs:
kwargs['queryset'] = kwargs['queryset'].select_related()
else:
db = kwargs.pop('using', None)
kwargs['queryset'] = db_field.remote_field.to._default_manager.using(db).complex_filter(db_field.remote_field.limit_choices_to).select_related()
return super(JournalConditionInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
"""
"""
def connect_all_journals(self, request, obj, parent_obj=None):
# Do stuff here, then return None to go to current view
return None
connect_all_journals.short_description = ("Connect Condition Set with some or all Journals")
"""
#def get_queryset(self, request):
# qs = super(JournalConditionInline, self).get_queryset(request).prefetch_related('journal', 'condition_set')
# return qs.select_related('journal', 'condition_set', 'condition_set__condition_type')
class SimpleJournalConditionInline(TabularInline):
model = JournalCondition
fields = ('journal', 'valid_from', 'valid_until')
# ordering = ('journal__name', 'valid_from', 'valid_until')
extra = 1
#autocomplete_fields = ('journal', )
fk_name = 'condition_set'
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs).select_related('journal')
#print('JournalConditionInline.get_queryset() called')
#print(qs.__dict__)
#print(qs.all())
return qs
class OrganizationConditionFormset(forms.BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(OrganizationConditionFormset, self).__init__(*args, **kwargs)
self.queryset = self.queryset.select_related("organization", 'condition_set')
class OrganizationConditionInline(TabularInline):
#class OrganizationConditionInline(InlineActionsMixin, TabularInline):
# model = OrganizationCondition
model = ConditionSet.organization.through
extra = 1
autocomplete_fields = ('organization', )
formset = OrganizationConditionFormset
def get_queryset(self, request):
qs = super(OrganizationConditionInline, self).get_queryset(request).prefetch_related('organization', 'condition_set')
return qs.select_related('organization', 'condition_set', 'condition_set__condition_type')
@admin.action(description='Apply selected condition sets to multiple Journals')
def connect_with_all_journals(modeladmin, request, queryset):
""" Action applicable to one or more ConditionSets: connect with a list of Journals
by entering the ISSN for the relevant ones, or all Journals if no ISSN is given.
Start and end dates must be provided during the action.
This action is useful to connect a new organization policy or publishing
agreement with the journals to which it applies.
"""
if request.POST.get('apply'):
try:
valid_from = date.fromisoformat(request.POST['valid_from'])
valid_until = date.fromisoformat(request.POST['valid_until'])
issn_list = set([x for x in re.split(' |,|;|\n|\r|\t', request.POST['issn_list']) if len(x) > 0])
print(issn_list)
if valid_from > valid_until:
raise ValueError
# print((valid_from, valid_until))
if len(issn_list) == 0:
all_journals = Journal.objects.all()
else:
journal_ids = list(Issn.objects.filter(issn__in=issn_list).values_list('journal', flat=True).distinct())
# print(journal_ids)
all_journals = Journal.objects.filter(id__in=journal_ids)
print(all_journals)
print(len(issn_list), len(all_journals))
# all_journals =[]
# The following block could certainly be optimized! AB 2021-08-12
for condition_set in queryset:
# print('-----------------')
# print(condition_set)
for j in all_journals:
# print(j)
# search for existing connections
existing_connections = JournalCondition.objects.filter(journal=j,
condition_set=condition_set,
valid_from__lt=date.today(),
valid_until__gt=date.today())
# print(existing_connections)
if len(existing_connections) == 0:
new_journal_condition = JournalCondition(journal=j,
condition_set=condition_set,
valid_from=valid_from,
valid_until=valid_until)
new_journal_condition.save()
else:
# This should not happen, or could it?
print(f'{j} already connected with {condition_set}')
return None
except ValueError:
pass
return render(request, 'admin/get_validity_dates.html', context={'queryset': queryset, 'objects': 'journals'})
@admin.action(description='Unlink selected condition sets from all Journals')
def disconnect_from_all_journals(modeladmin, request, queryset):
""" Action applicable to one or more ConditionSets:
disconnect from all journals
This action is useful in cases where it is easier to unlink the ConditionSet,
modify it and re-link later than to work through the ConditionSet admin page
(for example for org. policies applicable by default to all possible journals)
"""
warning = 'Are you sure you want to unlink all journals? Only the relationship is affected, '
warning += 'no journal data will be destroyed - but hey, think about it first.'
if request.POST.get('apply'):
try:
for condition_set in queryset:
# print('-----------------')
# print(condition_set)
condition_set.journal.clear()
condition_set.save()
return None
except ValueError:
pass
return render(request, 'admin/are_you_sure.html', context={'queryset': queryset, 'text': warning, 'function': 'disconnect_from_all_journals'})
@admin.action(description='Apply selected condition sets to all Organizations')
def connect_with_all_organizations(modeladmin, request, queryset):
""" Action applicable to one or more ConditionSets: connect with all Organizations.
Start and end dates must be provided during the action.
This action is useful to connect a new journal policy with all known organizations.
"""
if request.POST.get('apply'):
try:
valid_from = date.fromisoformat(request.POST['valid_from'])
valid_until = date.fromisoformat(request.POST['valid_until'])
if valid_from > valid_until:
raise ValueError
# print((valid_from, valid_until))
all_orgs = Organization.objects.all()
for condition_set in queryset:
# print('-----------------')
# print(condition_set)
for o in all_orgs:
# print(o)
# search for existing connections
existing_connections = OrganizationCondition.objects.filter(organization=o,
condition_set=condition_set,
valid_from__lt=date.today(),
valid_until__gt=date.today())
# print(existing_connections)
if len(existing_connections) == 0:
new_organization_condition = OrganizationCondition(organization=o,
condition_set=condition_set,
valid_from=valid_from,
valid_until=valid_until)
new_organization_condition.save()
return None
except ValueError:
pass
return render(request, 'admin/get_validity_dates.html', context={'queryset': queryset, 'objects': 'organizations'})
@admin.action(description='Set valid_until date')
def end_validity(modeladmin, request, queryset):
""" Action to set the end date for selected Journal-Condition relationships.
This action was introduced to add validity dates to batch-uploaded JournalConditions that lacked this information.
"""
if request.POST.get('apply'):
try:
valid_until = date.fromisoformat(request.POST['date'])
queryset.update(valid_until=valid_until)
return None
except ValueError:
pass
return render(request, 'admin/get_single_validity_date.html',
context={'queryset': queryset.prefetch_related(), 'limit': 'end', 'objects': 'selected journal-condition connections'})
@admin.action(description='Set valid_from date')
def start_validity(modeladmin, request, queryset):
""" Action to set the start date for selected Journal-Condition relationships.
This action was introduced to add validity dates to batch-uploaded JournalConditions that lacked this information.
"""
if request.POST.get('apply'):
try:
valid_from = date.fromisoformat(request.POST['date'])
queryset.update(valid_from=valid_from)
return None
except ValueError:
pass
return render(request, 'admin/get_single_validity_date.html',
context={'queryset': queryset, 'limit': 'start', 'objects': 'selected journal-condition connections'})
class ConditionSetAdminForm(forms.ModelForm):
class Meta:
model = ConditionSet
fields = ['condition_type', 'subtype', 'term', 'source', 'comment', 'organization', 'journal']
def __init__(self, *args, **kwargs):
#start = datetime.now()
super(ConditionSetAdminForm, self).__init__(*args, **kwargs)
#print(self.__dict__)
self.fields['term'].queryset = Term.objects.all().prefetch_related('licence', 'cost_factor', 'version')
#print('ConditionSetAdminForm.__init__(): ', datetime.now(), datetime.now()-start)
@admin.register(ConditionSet)
class ConditionSetAdmin(ImportExportModelAdmin):
# class ConditionSetAdmin(InlineActionsModelAdminMixin, ImportExportModelAdmin):
list_display = ("id", "condition_type", "comment")
search_fields = ['organization__name', 'journal__name', 'comment', 'id', 'condition_type__condition_issuer']
list_filter = ('condition_type', 'journal__publisher__name', 'organization__name', )
form = ConditionSetAdminForm
filter_horizontal = ('term', )
#exclude = ('organization', 'journal', )
inlines = (OrganizationConditionInline, JournalConditionInline, )
#inlines = (OrganizationConditionInline, )
actions = [connect_with_all_journals, connect_with_all_organizations, disconnect_from_all_journals]
# textarea input is better for comments
def get_form(self, request, obj=None, **kwargs):
kwargs['widgets'] = {'comment': forms.Textarea}
return super().get_form(request, obj, **kwargs)
def get_queryset(self, request):
#start = datetime.now()
test_model_qs = super(ConditionSetAdmin, self).get_queryset(request).select_related('condition_type')
# test_model_qs = test_model_qs.prefetch_related('organizationcondition_set', 'journalcondition_set')
# This could be the cause of 502 errors (out-of-memory killing)
#test_model_qs = test_model_qs.prefetch_related(Prefetch('journalcondition_set',
# queryset=JournalCondition.objects.select_related('condition_set', 'journal')))
#test_model_qs = test_model_qs.prefetch_related('journal')
#print(test_model_qs.__dict__)
#print('ConditionSetAdmin.get_queryset(): ', datetime.now(), datetime.now()-start)
return test_model_qs
class XConditionValidListFilter(SimpleListFilter):
""" Human-readable title which will be displayed in the
right admin sidebar just above the filter options.
"""
title = _('currently valid')
# Parameter for the filter that will be used in the URL query.
parameter_name = 'valid'
def lookups(self, request, model_admin):
"""
Returns a list of tuples. The first element in each
tuple is the coded value for the option that will
appear in the URL query. The second element is the
human-readable name for the option that will appear
in the right sidebar.
"""
return (
('true', _('True')),
('false', _('False')),
)
def queryset(self, request, queryset):
"""
Returns the filtered queryset based on the value
provided in the query string and retrievable via
`self.value()`.
"""
# Compare the requested value (either '80s' or '90s')
# to decide how to filter the queryset.
if self.value() == 'true':
return queryset.filter(valid_from__lte=date.today(),
valid_until__gte=date.today())
if self.value() == 'false':
return queryset.exclude(valid_from__lte=date.today(),
valid_until__gte=date.today())
class ConditionSetListDynamicFilter(SimpleListFilter):
""" Dynamic filter-by-publisher for ConditionSets. Only Publishers of Journals
connected to the currently displayed ConditionSets are proposed.
"""
title = _('condition sets (publisher-dependant)')
parameter_name = 'condition_set'
def lookups(self, request, model_admin):
if 'journal__publisher__name' in request.GET:
# A publisher name filter is in effect
journal_publisher_name = request.GET['journal__publisher__name']
print([journal_publisher_name])
#cs_by_publisher = model_admin.model.objects.filter(journal__publisher__name=journal_publisher_name)
#print(cs_by_publisher)
#jcs_by_publisher = model_admin.model.objects.all().filter(journal__publisher__name=journal_publisher_name).prefetch_related()
#condition_sets = set([c.condition_set for c in model_admin.model.objects.all().filter(journal__publisher__name=journal_publisher_name)])
#condition_sets = sorted(list(condition_sets))
cs = model_admin.model.objects.filter(journal__publisher__name=journal_publisher_name).values_list('condition_set')
condition_sets = ConditionSet.objects.filter(id__in=cs).order_by('id')
print(condition_sets)
#condition_sets = ConditionSet.objects.filter(journal__publisher__name=journal_publisher_name).order_by('id')
#condition_sets = model_admin.model.objects.filter(journal__publisher__name=journal_publisher_name).
else:
#condition_sets = set([c.condition_set for c in model_admin.model.objects.all()])
condition_sets = ConditionSet.objects.all().order_by('id')
return [(s.id, str(s)) for s in condition_sets]
def queryset(self, request, queryset):
if self.value():
return queryset.filter(condition_set__id__exact=self.value())
@admin.register(OrganizationCondition)
class OrganizationConditionAdmin(ImportExportModelAdmin):
""" Organization-ConditionSet connection admin page
"""
@admin.display(description='Condition Set')
def link_to_conditionset(self, obj):
""" Calculated field for the list display: link to the relevant ConditionSet
"""
link = reverse("admin:django_api_conditionset_change", args=[obj.condition_set.id])
return format_html(f'<a href="{link}">{obj.condition_set}</a>')
search_fields = ("id", "organization__name", "condition_set__id")
list_display = ("id", "organization_name", "link_to_conditionset", "valid_from", "valid_until")
list_select_related = ('condition_set', )
list_filter = ('condition_set__condition_type', XConditionValidListFilter,
# This has become very slow on 2021-12-09, will revisit later
#('condition_set', RelatedOnlyFieldListFilter),
'condition_set__id')
def organization_name(self, obj):
return obj.organization.name
def get_queryset(self, request):
qs = super(OrganizationConditionAdmin, self).get_queryset(request).prefetch_related()
return qs.select_related('condition_set', 'organization', 'condition_set__condition_type', )
@admin.register(Licence)
class LicenceAdmin(ImportExportModelAdmin):
pass
@admin.register(JournalCondition)
class JournalConditionAdmin(ImportExportModelAdmin):
@admin.display(description='Condition Set')
def link_to_conditionset(self, obj):
link = reverse("admin:django_api_conditionset_change", args=[obj.condition_set.id])
return format_html(f'<a href="{link}">{obj.condition_set}</a>')
search_fields = ("id", "journal__name", "condition_set__id")
list_display = ("id", "journal_name", "link_to_conditionset", "valid_from", "valid_until")
list_filter = ('condition_set__condition_type', XConditionValidListFilter,
'journal__publisher__name',
ConditionSetListDynamicFilter)
#list_filter = ('condition_set__condition_type', XConditionValidListFilter,
# 'journal__publisher__name',)
actions = (end_validity, start_validity, )
def journal_name(self, obj):
return obj.journal.name
def get_queryset(self, request):
qs = super(JournalConditionAdmin, self).get_queryset(request)
return qs.select_related('condition_set', 'journal')
# unsuccessful attempt
# def formfield_for_foreignkey(self, db_field, request, **kwargs):
# if db_field.name == "journal":
# kwargs["queryset"] = Journal.objects.filter(publisher__name__in=Publisher.objects.order_by().values('name').distinct())
# return super().formfield_for_foreignkey(db_field, request, **kwargs)
@admin.register(Cost_factor)
class Cost_factorAdmin(ImportExportModelAdmin):
list_display = ("id", "comment", "amount", "symbol")
list_filter = ('cost_factor_type', 'symbol')
# textarea input is better for comments
def get_form(self, request, obj=None, **kwargs):
kwargs['widgets'] = {'comment': forms.Textarea}
return super().get_form(request, obj, **kwargs)
@admin.register(Cost_factor_type)
class Cost_factor_typeAdmin(ImportExportModelAdmin):
list_display = ("id", "name")

Event Timeline