Basically a fork from https://github.com/blochberger/sokman but with the intention of adding a visual interface as well
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

375 lines
10 KiB

from typing import Optional, Set, Tuple
from django.contrib import admin, messages
from django.db.models import Count, F, Q
from django.db.models.query import QuerySet
from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _
from .models import (
Author,
ExclusionCriterion,
Publication,
SearchTerm,
SemanticScholar,
Source,
Tag,
)
# Filters
class PublicationVariantFilter(admin.SimpleListFilter):
title = _("is variant")
parameter_name = 'variant'
def lookups(self, request: HttpRequest, model_admin) -> Tuple[Tuple[str, str], ...]:
return (
('yes', _("yes")),
('no', _("no")),
)
def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet:
if self.value() == 'yes':
return queryset.filter(variant_of__isnull=False)
if self.value() == 'no':
return queryset.filter(variant_of__isnull=True)
return queryset
class PublicationRelevanceFilter(admin.SimpleListFilter):
title = _("is relevant")
parameter_name = 'is_relevant'
def lookups(self, request: HttpRequest, model_admin) -> Tuple[Tuple[str, str], ...]:
return (
('yes', _("yes")),
('no', _("no")),
)
def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet:
if self.value() == 'yes':
return queryset.filter(exclusion_criteria__isnull=True)
if self.value() == 'no':
return queryset.filter(exclusion_criteria__isnull=False)
return queryset
class PublicationStageFilter(admin.SimpleListFilter):
title = _("stage")
parameter_name = 'stage'
def lookups(self, request: HttpRequest, model_admin) -> Tuple[Tuple[str, str], ...]:
return (
('primary', _("primary")),
('secondary', _("secondary")),
('2-secondary', _("2-secondary")),
('tertiary', _("tertiary")),
('excluded', _("excluded")),
('-', _("-")),
)
def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet:
if self.value() == 'excluded':
return queryset.filter(exclusion_criteria__isnull=False)
relevant = queryset.filter(exclusion_criteria__isnull=True)
if self.value() == 'primary':
return relevant.filter(sources__isnull=False)
if self.value() == 'secondary':
return relevant.filter(
referenced_by__exclusion_criteria__isnull=True,
referenced_by__sources__isnull=False,
sources__isnull=True,
)
if self.value() == 'tertiary':
return relevant.filter(
references__exclusion_criteria__isnull=True,
references__sources__isnull=False,
sources__isnull=True,
).exclude(
referenced_by__exclusion_criteria__isnull=True,
referenced_by__sources__isnull=False,
)
if self.value() == '2-secondary':
ids: Set[int] = {
publication.id
for publication in queryset
if publication.stage == '2-secondary'
}
return queryset.filter(id__in=ids)
if self.value() == '-':
ids: Set[int] = {
publication.id
for publication in queryset
if publication.stage is None
}
return queryset.filter(id__in=ids)
return queryset
class TagCategoryFilter(admin.SimpleListFilter):
title = _("category")
parameter_name = 'category'
def lookups(self, request: HttpRequest, model_admin) -> Tuple[Tuple[str, str], ...]:
return (
(str(tag.pk), tag.name)
for tag in Tag.objects.filter(implies__isnull=True)
)
def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet:
if value := self.value():
pk = int(value)
category = Tag.objects.get(pk=pk)
# TODO Make transitive?
return queryset.filter(implies=category)
return queryset
# Inlines
class AuthorPublicationsInline(admin.TabularInline):
model = Author.publications.through
extra = 0
ordering = ['position']
autocomplete_fields = ('publication',)
class ExclusionCriterionPublications(admin.TabularInline):
model = ExclusionCriterion.publications.through
extra = 0
autocomplete_fields = ('publication',)
class PublicationAuthorsInline(admin.TabularInline):
model = Publication.authors.through
extra = 0
ordering = ['position']
autocomplete_fields = ('author',)
class PublicationSourcesInline(admin.TabularInline):
model = Publication.sources.through
extra = 0
autocomplete_fields = ('source', 'search_term')
class PublicationCitationsInline(admin.TabularInline):
verbose_name = "citation"
model = Publication.referenced_by.through
fk_name = 'reference'
extra = 0
ordering = ['identifier']
autocomplete_fields = ('publication',)
class PublicationReferencesInline(admin.TabularInline):
verbose_name = "reference"
model = Publication.references.through
fk_name = 'publication'
extra = 0
ordering = ['identifier']
autocomplete_fields = ('reference',)
class PublicationTagsInline(admin.TabularInline):
model = Publication.tags.through
extra = 0
autocomplete_fields = ('tag',)
class TagPublicationsInline(admin.TabularInline):
model = Tag.publications.through
extra = 0
autocomplete_fields = ('publication',)
# Models
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ('name', 'publication_count', 'relevant_publication_count')
search_fields = ('name',)
inlines = (AuthorPublicationsInline,)
def get_queryset(self, request: HttpRequest) -> QuerySet:
return Author.objects.annotate(
publication_count=Count('publications', distinct=True),
relevant_publication_count=Count(
'publications',
filter=Q(publications__exclusion_criteria__isnull=True),
distinct=True,
),
)
def publication_count(self, obj: Author) -> int:
return obj.publication_count
def relevant_publication_count(self, obj: Author) -> int:
return obj.relevant_publication_count
publication_count.short_description = "publications"
publication_count.admin_order_field = 'publication_count'
relevant_publication_count.short_description = "rel. publications"
relevant_publication_count.admin_order_field = 'relevant_publication_count'
@admin.register(ExclusionCriterion)
class ExclusionCriteriaAdmin(admin.ModelAdmin):
list_display = ('name', 'publication_count')
search_fields = ('name',)
inlines = (ExclusionCriterionPublications,)
def get_queryset(self, request: HttpRequest) -> QuerySet:
return ExclusionCriterion.objects.annotate(publication_count=Count('publications'))
def publication_count(self, obj: ExclusionCriterion) -> int:
return obj.publication_count
publication_count.short_description = "publications"
publication_count.admin_order_field = 'publication_count'
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ('name', 'publication_count', 'total_publications')
list_filter = (TagCategoryFilter,)
search_fields = ('name',)
autocomplete_fields = ('implies',)
inlines = (TagPublicationsInline,)
def get_queryset(self, request: HttpRequest) -> QuerySet:
return Tag.objects.annotate(publication_count=Count('publications'))
#def _implied_by(self, obj: Tag) -> str:
# return ", ".join(map(str, obj.implied_by.order_by('name')))
def publication_count(self, obj: Tag) -> int:
return obj.publication_count
publication_count.short_description = "publications"
publication_count.admin_order_field = "publication_count"
@admin.register(SemanticScholar)
class SemanticScholarAdmin(admin.ModelAdmin):
list_display = ('paper_id', 'publication')
search_fields = ('paper_id', 'publication')
autocomplete_fields = ('publication',)
@admin.register(SearchTerm)
class SearchTermAdmin(admin.ModelAdmin):
list_display = ('name', 'publication_count')
search_fields = ('name',)
def get_queryset(self, request: HttpRequest) -> QuerySet:
return SearchTerm.objects.annotate(
publication_count=Count(
'publicationsource__publication',
filter=Q(publicationsource__publication__exclusion_criteria__isnull=True),
distinct=True,
),
)
def publication_count(self, obj: SearchTerm) -> int:
return obj.publication_count
publication_count.short_description = "publications"
publication_count.admin_order_field = 'publication_count'
@admin.register(Source)
class SourceAdmin(admin.ModelAdmin):
list_display = ('name', 'publication_count')
search_fields = ('name',)
def get_queryset(self, request: HttpRequest) -> QuerySet:
return Source.objects.annotate(
publication_count=Count('publications', distinct=True),
)
def publication_count(self, obj: Source) -> int:
return obj.publication_count
publication_count.short_description = "publications"
publication_count.admin_order_field = 'publication_count'
@admin.register(Publication)
class PublicationAdmin(admin.ModelAdmin):
search_fields = ('cite_key', 'doi', 'title')
list_display = (
#'cite_key',
'title',
'year',
'citation_count',
'references_count',
'page_count',
'stage',
'classified',
)
list_filter = (
PublicationStageFilter,
PublicationRelevanceFilter,
'classified',
'peer_reviewed',
PublicationVariantFilter,
#'year',
#'sources',
)
inlines = (
PublicationAuthorsInline,
PublicationReferencesInline,
PublicationCitationsInline,
PublicationSourcesInline,
PublicationTagsInline,
)
autocomplete_fields = ('exclusion_criteria', 'variant_of')
actions = ('cite',)
def get_queryset(self, request: HttpRequest) -> QuerySet:
return Publication.objects.annotate(
citation_count=Count(
'referenced_by',
filter=Q(exclusion_criteria__isnull=True),
distinct=True,
),
references_count=Count(
'references',
filter=Q(exclusion_criteria__isnull=True),
distinct=True,
),
page_count=1 + F('last_page') - F('first_page'),
)
def citation_count(self, obj: Publication) -> int:
return obj.citation_count
def references_count(self, obj: Publication) -> int:
return obj.references_count
def page_count(self, obj: Publication) -> int:
return obj.page_count
def cite(self, request: HttpRequest, queryset: QuerySet):
cite_keys = queryset.order_by('cite_key').values_list('cite_key', flat=True).distinct()
cite_str = ", ".join(list(cite_keys))
self.message_user(request, f"\\cite{{{cite_str}}}", level=messages.SUCCESS)
citation_count.short_description = "citations"
citation_count.admin_order_field = 'citation_count'
references_count.short_description = "references"
references_count.admin_order_field = 'references_count'
page_count.short_description = "pages"
page_count.admin_order_field = 'page_count'