diff --git a/assets/src/App.js b/assets/src/App.js index b61db5cc..20af220f 100644 --- a/assets/src/App.js +++ b/assets/src/App.js @@ -1,62 +1,62 @@ import React from "react"; import "./App.css" import SearchFilterFields from './pages/SearchFilterFields' import Footer from './components/layout/Footer' import About from "./pages/About" import Help from "./pages/Help" import {Switch, Route,} from "react-router-dom" import Container from '@material-ui/core/Container'; import Box from '@material-ui/core/Box' import Nav from "./components/layout/Nav" import { makeStyles } from '@material-ui/core/styles'; const useStyles = makeStyles((theme) => ({ root: { flexGrow: 1, } })); function App () { const classes = useStyles(); return(
) } export default App \ No newline at end of file diff --git a/assets/src/components/ResultCard.js b/assets/src/components/ResultCard.js index 8f403477..1b86ad00 100644 --- a/assets/src/components/ResultCard.js +++ b/assets/src/components/ResultCard.js @@ -1,171 +1,171 @@ import React from "react" import { makeStyles } from '@material-ui/core/styles'; import "./ResultCard.css" import TermCard from "../components/TermCard" import Accordion from '@material-ui/core/Accordion'; import AccordionSummary from '@material-ui/core/AccordionSummary'; import AccordionDetails from '@material-ui/core/AccordionDetails'; import Typography from '@material-ui/core/Typography'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import Badge from '@material-ui/core/Badge'; import { HiOutlineDocumentDuplicate } from "react-icons/hi"; import { HiOutlineDocument } from "react-icons/hi"; import Grid from '@material-ui/core/Grid' import Box from '@material-ui/core/Box' import Container from '@material-ui/core/Container'; const useStyles = makeStyles((theme) =>({ root: { flexGrow: 1, }, chip: { margin: 0.5, }, })) function ResultCard({result}) { const classes = useStyles(); //create one array to organize the frontend output const termresult = [] const termArray = result?.map(i=>( // get condition details i.term?.map(j =>( // termresult.push([j, i.id, i.comment, i.condition_type]) termresult.push([j, [i.id, i.comment, i.condition_type]]) )) )) //groupyBy array function groupBy(objectArray, property) { // console.log(objectArray) return objectArray.reduce((acc, obj) => { // console.log(obj[0]) const key = obj[0][property][0].description; if (!acc[key]) { acc[key] = []; } // Add object to list for given key's value acc[key].push(obj); return acc; }, {}); } const groupedTerm = groupBy(termresult, 'version') //first version // console.log(groupedTerm[1]) //convert object into array const termItem = Object.entries(groupedTerm) //manage the display order function orderVersion (version) { if (version[0] ==="Submitted version") { version.unshift(3) } else if (version[0] === "Published version") { version.unshift(1) } else if (version[0] === "Accepted version") { version.unshift(2) } } //apply the function for each version termItem?.map(i=>( orderVersion(i) )) termItem.sort() console.log(termItem) const displayVersion = termItem?.map(item =>(
} aria-controls="panel1a-content" id="panel1a-header" > {item?.map(i => ( {typeof i === "string" && i} {typeof i === "object" && i.length > 1 ? : typeof i === "object" && } )) } {item?.map(j => ( typeof j === "object" && j?.map(k => ( )) )) }
)) return ( //level 0
{displayVersion}
) } export default ResultCard \ No newline at end of file diff --git a/assets/src/components/TermCard.js b/assets/src/components/TermCard.js index 22bafec4..e1e7ce0d 100644 --- a/assets/src/components/TermCard.js +++ b/assets/src/components/TermCard.js @@ -1,232 +1,242 @@ import React from "react" import { makeStyles } from '@material-ui/core/styles'; import "./termcard.css" import Chip from '@material-ui/core/Chip'; import DoneIcon from '@material-ui/icons/Done'; import HighlightOffIcon from '@material-ui/icons/HighlightOff'; import Accordion from '@material-ui/core/Accordion'; import AccordionSummary from '@material-ui/core/AccordionSummary'; import AccordionDetails from '@material-ui/core/AccordionDetails'; import Typography from '@material-ui/core/Typography'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import DateRangeIcon from '@material-ui/icons/DateRange'; import { FaCoins } from 'react-icons/fa'; import { RiFilePaper2Line } from 'react-icons/ri'; import Button from '@material-ui/core/Button'; import { HiLink } from "react-icons/hi"; import { GrInfo } from "react-icons/gr"; import Container from '@material-ui/core/Container' import Grid from '@material-ui/core/Grid' -import Box from '@material-ui/core/Box' +import Card from '@material-ui/core/Card'; +import CardActions from '@material-ui/core/CardActions'; +import CardContent from '@material-ui/core/CardContent'; const useStyles = makeStyles((theme) =>({ card: { width: '100%', marginTop: "1rem", textAlign:'left' }, root: { flexGrow: 1, textAlign:'left', }, chip: { margin: 0.5, }, heading: { fontSize: theme.typography.pxToRem(15), fontWeight: theme.typography.fontWeightRegular, }, })) function TermCard({term}) { const classes = useStyles(); const licenceIcon = term[0].licence?.map(i=>( <> )) function cost() { const price = term[0].cost_factor?.map(i => ( i.amount )) const price_symbol = term[0].cost_factor?.map(j => ( j.symbol )) const cost_name = term[0].cost_factor?.map(k => ( k.cost_factor_type.name )) const id = term[0].cost_factor?.map(k => ( k.cost_factor_type.id )) return ( } label={cost_name[0] + " : " + price[0] + " " + price_symbol} // clickable={handleClick} // color="secondary" // onDelete={handleDelete} className={classes.chip} style={{ background: "#FFFFFF"}} title="This is more information" /> ) } const termArchive = term[0].ir_archiving && term[0].ir_archiving ? ( } label={"IR Archiving"} // variant="outlined" // clickable={handleClick} // color="secondary" // onDelete={handleDelete} style={{ background: "#FFFFFF"}} title="This is more information" /> ): ( } label="IR Archiving" // clickable={handleClick} // color="secondary" // onDelete={handleDelete} style={{ background: "#f50057"}} title="This is more information" /> ) const embargo = term[0].ir_archiving && term[0].ir_archiving ? ( } label={"Embargo: " + term[0].embargo_months + " Month(s)"} // variant="outlined" // clickable={handleClick} // color="secondary" // onDelete={handleDelete} style={{ background: "#FFFFFF"}} title="This is more information" /> ): () return ( - - - - } - aria-controls="panel1a-content" - id="panel1a-header" - > - + + + + {term.map( j=> ( + j[1] && +

Condition set Title: {j[1]}

+ )) + } Term ID: {term[0].id} {term.map( j=> ( j[0] &&

From condition set Id: {j[0]}

)) } + +
+ + + + + + + + + + {cost()} {licenceIcon} {termArchive} {embargo} - -
- - - - - {term[0].comment ? + + + + + {term[0].comment ?

"{term[0].comment}"

:null} {term[0].source ? - + :null} +
+ + - - -
-
) } export default TermCard \ No newline at end of file diff --git a/django_api/admin.py b/django_api/admin.py index f279f4f1..880cb8e9 100644 --- a/django_api/admin.py +++ b/django_api/admin.py @@ -1,237 +1,237 @@ from django.contrib import admin from datetime import date from import_export.admin import ImportExportModelAdmin from django.contrib.admin import TabularInline #from inline_actions.admin import InlineActionsMixin #from inline_actions.admin import InlineActionsModelAdminMixin from django.shortcuts import render from django.http import HttpResponseRedirect 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 ConditionSet from .models import OrganizationCondition from .models import JournalCondition # Register your models here. @admin.register(Journal) class JournalAdmin(ImportExportModelAdmin): list_display = ("id", "name", "journal_issns",) list_filter = ('publisher__name', 'oa_status') filter_horizontal = ('publisher', 'language', ) def journal_issns(self, obj): # TBD pass @admin.register(Language) class LanguageAdmin(ImportExportModelAdmin): pass @admin.register(Organization) class OrganizationAdmin(ImportExportModelAdmin): list_display = ("id", "name") list_filter = ('is_funder',) filter_horizontal = ('country', ) @admin.register(Version) class VersionAdmin(ImportExportModelAdmin): pass @admin.register(Country) class CountryAdmin(ImportExportModelAdmin): pass @admin.register(Issn) class IssnAdmin(ImportExportModelAdmin): list_filter = ('issn_type', ) @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__name', ) filter_horizontal = ('country', ) @admin.register(Term) class TermAdmin(ImportExportModelAdmin): list_filter = ('version', 'licence') filter_horizontal = ('version', 'cost_factor', 'licence', ) @admin.register(ConditionType) class ConditionTypeAdmin(ImportExportModelAdmin): list_display = ("id", "condition_issuer") #class JournalConditionInline(InlineActionsMixin, TabularInline): class JournalConditionInline(TabularInline): model = JournalCondition extra = 1 inline_actions = ['connect_all_journals'] 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 all Journals") class OrganizationConditionInline(TabularInline): #class OrganizationConditionInline(InlineActionsMixin, TabularInline): model = OrganizationCondition extra = 1 def connect_with_all_journals(modeladmin, request, queryset): - print(request.POST) + # print(request.POST) 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_journals = Journal.objects.all() for condition_set in queryset: # print('-----------------') # print(condition_set) for j in all_journals: - print(j) + # 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() return None except ValueError: pass return render(request, 'admin/get_validity_dates.html', context={'queryset': queryset, 'objects': 'journals'}) connect_with_all_journals.short_description = 'Apply selected condition sets to all Journals' def connect_with_all_organizations(modeladmin, request, queryset): - print(request.POST) + # print(request.POST) 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) + # 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'}) connect_with_all_organizations.short_description = 'Apply selected condition sets to all Organizations' @admin.register(ConditionSet) class ConditionSetAdmin(ImportExportModelAdmin): # class ConditionSetAdmin(InlineActionsModelAdminMixin, ImportExportModelAdmin): list_display = ("id", "condition_type", "comment") search_fields = ['organization__name', 'journal__name', 'comment'] list_filter = ('condition_type', ) filter_horizontal = ('term', ) inlines = (OrganizationConditionInline, JournalConditionInline, ) actions = [connect_with_all_journals, connect_with_all_organizations] @admin.register(OrganizationCondition) class OrganizationConditionAdmin(ImportExportModelAdmin): list_display = ("id", "organization_name", "condition_set", "valid_from", "valid_until") list_filter = ('condition_set__condition_type', ) def organization_name(self, obj): return obj.organization.name @admin.register(Licence) class LicenceAdmin(ImportExportModelAdmin): pass @admin.register(JournalCondition) class JournalConditionAdmin(ImportExportModelAdmin): list_display = ("id", "journal_name", "condition_set", "valid_from", "valid_until") list_filter = ('condition_set__condition_type', ) def journal_name(self, obj): return obj.journal.name # 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') @admin.register(Cost_factor_type) class Cost_factor_typeAdmin(ImportExportModelAdmin): list_display = ("id", "name") diff --git a/django_api/models.py b/django_api/models.py index 2e0c90be..602bbe5c 100644 --- a/django_api/models.py +++ b/django_api/models.py @@ -1,212 +1,218 @@ from django.db import models from django.contrib.auth.models import User import datetime from django.utils.translation import gettext as _ # Ref: database_model_20210421_MB.drawio 21.04.2021 class Country(models.Model): name = models.CharField(verbose_name="Country name", max_length=120, null=True) iso_code = models.CharField(max_length=3, null=True) def __str__(self): return f"{self.name}" class Meta: ordering = ('name',) class Language(models.Model): name = models.CharField(verbose_name="Language name", max_length=120, null=True) iso_code = models.CharField(max_length=3, null=True) def __str__(self): return f"{self.name}" class Meta: ordering = ('name',) class Oa(models.Model): status = models.CharField(max_length=1000, null=True) description = models.CharField(max_length=1000, null=True) subscription = models.BooleanField(default=False) accepted_manuscript = models.BooleanField(default=False) apc = models.BooleanField(default=False) final_version = models.BooleanField(default=False) def __str__(self): return f"{self.status}" class Meta: ordering = ('-subscription',) class Publisher(models.Model): name = models.CharField(verbose_name="Publisher name", max_length=1000, null=True) city = models.CharField(max_length=100, null=True) state = models.CharField(max_length=3, null=True) country = models.ManyToManyField("Country") starting_year = models.IntegerField() website = models.URLField(max_length=1000) oa_policies = models.URLField(max_length=1000) def __str__(self): return f"{self.name}" class Meta: ordering = ('name',) class Journal(models.Model): name = models.CharField(verbose_name="Journal name", max_length=800, blank=True, null=True) # search journal with name name_short_iso_4 = models.CharField(max_length=300, blank=True, null=True) publisher = models.ManyToManyField(Publisher) website = models.URLField(max_length=300, blank=True, null=True) language = models.ManyToManyField(Language) # issn = models.ManyToManyField("Issn") # search journal with ISSN oa_options = models.URLField(max_length=1000, blank=True, null=True) oa_status = models.ForeignKey("Oa", on_delete=models.CASCADE) starting_year = models.IntegerField(blank=True, null=True) def __str__(self): return f"{self.name} from {self.website}" class Meta: ordering = ('name',) class Issn(models.Model): PRINT = 1 ELECTRONIC = 2 OTHER = 3 TYPE_CHOICES = ( (PRINT, 'Print'), (ELECTRONIC, 'Electronic'), (OTHER, 'Other'), ) issn = models.CharField(max_length=9, null=False) journal = models.ForeignKey(Journal, on_delete=models.CASCADE, blank=True, null=True) # search journal with ISSN issn_type = models.CharField( choices=TYPE_CHOICES, max_length=10, blank=True ) def __str__(self): return f"{self.issn} ({dict(self.TYPE_CHOICES)[int(self.issn_type)]})" class Meta: ordering = ('issn',) class Organization(models.Model): name = models.CharField(verbose_name="Organization name", max_length=600, null=True) website = models.URLField(max_length=600, blank=True, null=True) country = models.ManyToManyField("Country") ror = models.CharField(max_length=255, blank=True, null=True) fundref = models.CharField(max_length=255, blank=True, null=True) starting_year = models.IntegerField(blank=True, null=True) is_funder = models.BooleanField(default=False) def __str__(self): return f"{self.name}" class Meta: ordering = ('name',) class Version(models.Model): description = models.CharField(max_length=300, null=False) def __str__(self): return f"{self.description}" class Licence(models.Model): name_or_abbrev = models.CharField(max_length=300, null=False) website = models.URLField(max_length=600, null=True) def __str__(self): return f"{self.name_or_abbrev}" class Cost_factor_type(models.Model): name = models.CharField(max_length=300, null=False) def __str__(self): return f"{self.name}" class Cost_factor(models.Model): cost_factor_type = models.ForeignKey(Cost_factor_type, on_delete=models.CASCADE, blank=True, null=True) amount = models.IntegerField(null=False) symbol = models.CharField(max_length=10, null=False) comment = models.CharField(max_length=120, default="") def __str__(self): return f"{self.amount} {self.symbol}" class Term(models.Model): version = models.ManyToManyField(Version) cost_factor = models.ManyToManyField(Cost_factor) embargo_months = models.IntegerField(blank=True, null=True) ir_archiving = models.BooleanField(default=False) licence = models.ManyToManyField(Licence) comment = models.CharField(max_length=600, null=True) source = models.URLField(max_length=600, null=True) def __str__(self): - # TODO fix this when the actual data become available - return f"{self.id} about {';'.join([x.description for x in self.version.all()])} - {self.comment}" - # return f"{self.id} - {self.comment}" + # TODO why does this break JSON uploads in the admin interface? + # return f"{self.id} about {';'.join([x.description for x in self.version.all()])} - {self.comment}" + return f"{self.id} - {self.comment}" + + class Meta: + ordering = ('-ir_archiving','embargo_months', 'comment') class ConditionType(models.Model): condition_issuer = models.CharField(max_length=300, null=False) def __str__(self): return f"{self.condition_issuer}" class ConditionSet(models.Model): condition_type = models.ForeignKey(ConditionType, on_delete=models.CASCADE, blank=True, null=True) organization = models.ManyToManyField( Organization, through='OrganizationCondition', through_fields=('condition_set', 'organization') ) journal = models.ManyToManyField( Journal, through='JournalCondition', through_fields=('condition_set', 'journal') ) term = models.ManyToManyField(Term) comment = models.CharField(max_length=100, null=True, default=":-)") def __str__(self): return f"{self.id} {self.condition_type}|{self.comment}" + class Meta: + ordering = ('-condition_type__pk','comment') + class OrganizationCondition(models.Model): organization = models.ForeignKey(Organization, on_delete=models.CASCADE, blank=True, null=True) condition_set = models.ForeignKey(ConditionSet, on_delete=models.CASCADE, blank=True, null=True) valid_from = models.DateField(blank=True, null=True) valid_until = models.DateField(blank=True, null=True) def __str__(self): return f"{self.id} {self.organization}/{self.condition_set}" class JournalCondition(models.Model): journal = models.ForeignKey(Journal, on_delete=models.CASCADE, blank=True, null=True) condition_set = models.ForeignKey(ConditionSet, on_delete=models.CASCADE, blank=True, null=True) valid_from = models.DateField(blank=True, null=True) valid_until = models.DateField(blank=True, null=True) def __str__(self): return f"{self.id} {self.journal.name}/{self.condition_set}" diff --git a/django_api/serializers.py b/django_api/serializers.py index b5ee1da4..224975a3 100644 --- a/django_api/serializers.py +++ b/django_api/serializers.py @@ -1,108 +1,128 @@ from rest_framework import serializers from dj_rql.drf.serializers import RQLMixin from .models import * + + class JournalSerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = Journal fields = '__all__' depth = 4 + class OrgaSerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = Organization fields = '__all__' depth = 4 + class TermSerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = Term fields = '__all__' depth = 4 + class ConditionSetSerializer(RQLMixin,serializers.ModelSerializer): term = TermSerializer(many=True, read_only=True) class Meta: model = ConditionSet # pre filter for rql # fields = ['id','condition_type','term','journal','organization'] # add for informations purpose fields = '__all__' depth = 4 + class TermSerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = Term fields = '__all__' depth = 2 + class CountrySerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = Country fields = '__all__' depth = 4 + class LanguageSerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = Language fields = '__all__' depth = 4 + class IssnSerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = Issn fields = '__all__' depth = 4 + + class OaSerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = Oa fields = '__all__' depth = 4 + class PublisherSerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = Publisher fields = '__all__' depth = 4 + class VersionSerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = Version fields = '__all__' depth = 4 + class LicenceSerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = Licence fields = '__all__' depth = 4 + class Cost_factor_typeSerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = Cost_factor_type fields = '__all__' depth = 4 + class Cost_factorSerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = Cost_factor fields = '__all__' depth = 4 + + class ConditionTypeSerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = ConditionType fields = '__all__' depth = 4 + class OrganizationConditionSerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = OrganizationCondition fields = '__all__' depth = 4 + class JournalConditionSerializer(RQLMixin,serializers.ModelSerializer): class Meta: model = JournalCondition fields = '__all__' depth = 4