diff --git a/assets/src/components/layout/logo.svg b/assets/src/components/layout/logo.svg index 32f89a50..57220298 100644 --- a/assets/src/components/layout/logo.svg +++ b/assets/src/components/layout/logo.svg @@ -1,183 +1,158 @@ + id="defs1878" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + transform="translate(-6.2778928,-8.0669299)"> + + + + O + + + + A + + + + C + + + + T + + + Open Access Check Tool diff --git a/assets/src/pages/SearchFilterFields.css b/assets/src/pages/SearchFilterFields.css index bcb46368..0cba9a2e 100644 --- a/assets/src/pages/SearchFilterFields.css +++ b/assets/src/pages/SearchFilterFields.css @@ -1,53 +1,63 @@ + #bannertext { + margin-top: 25px; + background-color: #d2fcfb ; + color: black; + font-size: 1.1em; + text-align: center; + padding-left: 50px; + padding-right: 50px; + } + .form-input { margin-bottom: 1rem !important; } .App-btn { background-color: #3771C8 !important; color: white !important; width: 99% ; } .field-comment { font-size: .75em } .App-btn:hover { background-color: #D40000; } @media only screen and (min-width: 768px) { .form-input { margin-right: 1rem !important; } .App-btn { width: 99%; background-color: #3771C8 !important; color: white !important; } .App-btn:hover { background-color: #D40000 !important; } } @media only screen and (min-width: 1024px) { .form-input { margin-right: 1rem !important; } .App-btn { width: 99%; background-color: #3771C8 ; color: white; bottom: -5px; } .App-btn:hover { background-color: #D40000; } - } \ No newline at end of file + } diff --git a/assets/src/pages/SearchFilterFields.js b/assets/src/pages/SearchFilterFields.js index 3f35b7a9..cd8b450b 100644 --- a/assets/src/pages/SearchFilterFields.js +++ b/assets/src/pages/SearchFilterFields.js @@ -1,1276 +1,1284 @@ /* 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 . */ import React, {useContext, useState, useEffect} from 'react'; import "./SearchFilterFields.css" import { makeStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; import FormControl from '@material-ui/core/FormControl'; import TextField from '@material-ui/core/TextField'; import Autocomplete from '@material-ui/lab/Autocomplete'; import { searchCondi, searchorganizationonly, searchjournalonly, searchInstitFunder, searchCondi3 } from '../services/requests/Condition' import {getJournal} from '../services/requests/Journal' import {getFunder} from '../services/requests/Funder' import {getInstitution} from '../services/requests/Institution' import Accordion from '@material-ui/core/Accordion'; import AccordionSummary from '@material-ui/core/AccordionSummary'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import Typography from '@material-ui/core/Typography'; import AccordionDetails from '@material-ui/core/AccordionDetails'; import Grid from '@material-ui/core/Grid' import Box from '@material-ui/core/Box' import Container from '@material-ui/core/Container'; import {Context} from "../ContextProvider" import ResultCard from "../components/ResultCard" import DetailCard from "../components/DetailCard" import CircularProgress from '@material-ui/core/CircularProgress' import Fab from '@material-ui/core/Fab' import ShareIcon from '@material-ui/icons/Share' import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import DialogContentText from '@material-ui/core/DialogContentText'; import DialogTitle from '@material-ui/core/DialogTitle'; import Slide from '@material-ui/core/Slide'; import Welcome from './welcome'; import { useHistory,useLocation } from "react-router-dom"; import PropTypes from 'prop-types'; // import { FiFlag } from 'react-icons/fi'; import FlagOutlinedIcon from '@material-ui/icons/FlagOutlined'; import Tooltip from '@material-ui/core/Tooltip'; function useQuery() { return new URLSearchParams(useLocation().search); } const Transition = React.forwardRef(function Transition(props, ref) { return ; }); // ID of condition type that must be excluded in some API requests const j_only_id = 3 const o_only_id = 1 const useStyles = makeStyles((theme) => ({ root: { flexGrow: 1, }, chip: { margin: 0.5, }, })); /** * Contain the main logic of OACT tools to filter and send the appropriate request. * @version 0.0.1 * @author [Hugo Galuppo](https://github.com/hgpulse) */ export default function SearchFilterFields() { /** Access to URL parameter */ const history = useHistory(); console.log(history) let query = useQuery() //state that allow to hide or show the share url button const [open, setOpen] = React.useState(false) const classes = useStyles(); //call the custom hook to share the state between different level componant const { getSelectedInstitId, getSelectedJournalId, getSelectedFunderId, institList, journalList, funderList, institId, journalId, funderId, setInstitId, setJournalId, setFunderId, setUrl, url } = useContext(Context) //responses const [conditions, setConditions] = useState([]); const [details, setDetails] = useState([]); const [result, updateResult] = useState([]); //Manage the loading state to hide or show the spinner in the search bar const [loading, setLoading] = useState(false); // const [url, setUrl] = useState(window.location.href); useEffect(() => { setDetails('null') setUrl(window.location.href) //handle Url param console.log(history) if (history.location.pathname === "/check") { console.log("this an url to check") setDetails('fromUrl') // alert(query.get("institution")) if (query.get("institution") && !query.get("funder") && !query.get("journal")){ //get organizations conditions // alert(`get api organization Condition only: ${institId}`) //condtion type is not journal only = 1 // Get the user const sendSearchInstitOnly = async () => { try { const resp = await searchorganizationonly(query.get("institution"), j_only_id) console.log(resp.data) setConditions(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } setLoading(false) } console.log(details) const sendGetrequest = async () => { try { const resp = await getInstitution(query.get("institution")) // console.log(`instit name from api: ${resp.data.name}`) // setInstitName(resp.data.name) updateResult(arr => [...arr, resp.data]) // if (details === "null") { // setDetails(resp.data) // } // else { // setDetails(prevArray => [...prevArray, resp.data]) // } } catch (err) { // Handle Error Here console.error(err); } } sendSearchInstitOnly().then( sendGetrequest() ) history.push({pathname:`check`, search:`institution=${query.get("institution")}`}) } else if (!query.get("institution") && !query.get("journal") && query.get("funder")){ //get funder conditions // alert(`get api funder Condition only: ${funderId}`) //condtion type is not journal only = 1 const sendSearchOrgaOnly = async () => { try { const resp = await searchorganizationonly(query.get("funder"), j_only_id) console.log(resp.data) setConditions(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } setLoading(false) } const sendGetrequest = async () => { try { const resp = await getFunder(query.get("funder")) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } sendSearchOrgaOnly().then( sendGetrequest() ) history.push({pathname:`check`, search:`funder=${query.get("funder")}`}) } else if (!query.get("funder") && !query.get("institution") && query.get("journal")){ //get journals conditions // alert(`get api journal Condition only: ${journalId}`) //condtion type is not institution only = 2 //get journal detail const sendSearchJournalOnly = async () => { try { const resp = await searchjournalonly(query.get("journal"), o_only_id) console.log(resp.data) setConditions(arr => [...arr, resp.data]) // setConditions(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } setLoading(false) } const sendGetrequest = async () => { try { const resp = await getJournal(query.get("journal")) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } sendSearchJournalOnly().then( sendGetrequest() ) history.push({pathname:`check`, search:`journal=${query.get("journal")}`}) } else if (query.get("institution") && query.get("funder") && !query.get("journal")) { //alert(`get api Filter Conditions SET--> Journal: ${journalId} VS Institution: ${institId}`) //condtion type journal/condition = 3 const sendSearchCondi = async () => { try { const resp = await searchInstitFunder(query.get("institution"), query.get("funder"), j_only_id) console.log(resp.data) setConditions(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } setLoading(false) } const sendGetInstit = async () => { try { const resp = await getInstitution(query.get("institution")) console.log(resp.data) // detailArray.push(resp.data) // setDetails(detailArray) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } const sendGetFunder = async () => { try { const resp = await getFunder(query.get("funder")) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } sendSearchCondi().then( sendGetInstit().then(sendGetFunder()) ) history.push({pathname:`check`, search: `institution=${query.get("institution")}&funder=${query.get("funder")}`}) } else if (query.get("institution") && query.get("journal") && !query.get("funder")) { //alert(`get api Filter Conditions SET--> Journal: ${journalId} VS Institution: ${institId}`) //condtion type journal/condition = 3 const sendSearchCondi = async () => { try { const resp = await searchCondi(query.get("journal"),query.get("institution")) console.log(resp.data) setConditions(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } setLoading(false) } const sendGetInstit = async () => { try { const resp = await getInstitution(query.get("institution")) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } const sendGetJournal = async () => { try { const resp = await getJournal(query.get("journal")) console.log(resp.data) // detailArray.push(resp.data) // setDetails(detailArray) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } sendSearchCondi().then( sendGetInstit().then(sendGetJournal) ) history.push({pathname:`check`, search: `institution=${query.get("institution")}&journal=${query.get("journal")}`}) } else if (!query.get("institution") && query.get("journal") && query.get("funder")) { // alert(`get api Filter Conditions SET--> Journal: ${journalId} VS Institution: ${funderId}`) //condtion type journal/institution/funder conditions = 3 const sendGetCondi = async () => { try { const resp = await searchCondi(query.get("journal"),query.get("funder")) console.log(resp.data) setConditions(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } setLoading(false) } const sendGetFunder = async () => { try { const resp = await getFunder(query.get("funder")) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } const sendGetJournal = async () => { try { const resp = await getJournal(query.get("journal")) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } sendGetCondi().then( sendGetFunder().then( sendGetJournal() ) ) history.push({pathname:`check`, search: `funder=${query.get("funder")}&journal=${query.get("journal")}`}) } else if (query.get("institution") && query.get("journal") && query.get("funder")) { // alert(`get api Filter Conditions SET--> Journal: ${journalId} VS Institution: ${funderId}`) //condtion type journal/institution/funder conditions = 3 console.log("main check !") //(institution + journal) const detailArray = [] const sendGetCondi = async () => { try { const resp = await searchCondi3(query.get("institution"),query.get("journal"),query.get("funder")) console.log(resp.data) setConditions(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } setLoading(false) } const sendGetInstit = async () => { try { const resp = await getInstitution(query.get("institution")) console.log(resp.data) detailArray.push(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } const sendGetFunder = async () => { try { const resp = await getFunder(query.get("funder")) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } const sendGetJournal = async () => { try { const resp = await getJournal(query.get("journal")) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } //order requests sendGetCondi() sendGetInstit().then( sendGetFunder() ).then( sendGetJournal() ) history.push({pathname:`check`, search: `institution=${query.get("institution")}&funder=${query.get("funder")}&journal=${query.get("journal")}`}) } } }, []) //useEffect on Url state change React.useEffect(() => { //condition to avoid infinite loop if (history.location.pathname === "/") { setConditions([]) setDetails('null') updateResult([]) setUrl(window.location.href) } }, [url]); function handleReport () { // ## Create mail template to report a modification, contain the actual Url and the reference Term Card window.open(`mailto:publishsupport@epfl.ch?subject= OACT Modification request for ${encodeURIComponent(url)} &body=Request Description:`) } //copy url to clipboard function handlShare(e) { setOpen(true) navigator.clipboard.writeText(url) } const handleClose = () => { setOpen(false); }; function handleInstit(e, newInputValue) { if (newInputValue){ getSelectedInstitId(newInputValue) return } // if (institName){ // getSelectedInstitId(institName) // return // } setInstitId("") } function handleFunder(e, newInputValue) { console.log(newInputValue) if (newInputValue){ getSelectedFunderId(newInputValue) return } setFunderId("") } function handleJournal(e, newInputValue) { if (newInputValue){ getSelectedJournalId(newInputValue) return } setJournalId("") } function handleSubmit(e) { setLoading(true) e.preventDefault() //reset precedent results setConditions([]) setDetails([]) updateResult([]) if (!institId && !journalId && !funderId){ setLoading(false) setDetails('null') } if (institId && !journalId && !funderId){ //get organizations conditions // alert(`get api organization Condition only: ${institId}`) //condtion type is not journal only = 1 // Get the user const sendSearchInstitOnly = async () => { try { const resp = await searchorganizationonly(institId, j_only_id) console.log(resp.data) setConditions(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } setLoading(false) } console.log(details) const sendGetrequest = async () => { try { const resp = await getInstitution(institId) console.log(resp.data) updateResult(arr => [...arr, resp.data]) // if (details === "null") { // setDetails(resp.data) // } // else { // setDetails(prevArray => [...prevArray, resp.data]) // } } catch (err) { // Handle Error Here console.error(err); } } sendSearchInstitOnly().then( sendGetrequest() ) history.push({pathname:`check`, search:`institution=${institId}`}) } else if (!institId && !journalId && funderId){ //get funder conditions // alert(`get api funder Condition only: ${funderId}`) //condtion type is not journal only = 1 const sendSearchOrgaOnly = async () => { try { const resp = await searchorganizationonly(funderId, j_only_id) console.log(resp.data) setConditions(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } setLoading(false) } const sendGetrequest = async () => { try { const resp = await getFunder(funderId) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } sendSearchOrgaOnly().then( sendGetrequest() ) history.push({pathname:`check`, search:`funder=${funderId}`}) } else if (!funderId && !institId && journalId){ //get journals conditions // alert(`get api journal Condition only: ${journalId}`) //condtion type is not institution only = 2 //get journal detail const sendSearchJournalOnly = async () => { try { const resp = await searchjournalonly(journalId, o_only_id) console.log(resp.data) setConditions(arr => [...arr, resp.data]) // setConditions(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } setLoading(false) } const sendGetrequest = async () => { try { const resp = await getJournal(journalId) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } sendSearchJournalOnly().then( sendGetrequest() ) history.push({pathname:`check`, search:`journal=${journalId}`}) } else if (institId && funderId && !journalId) { //alert(`get api Filter Conditions SET--> Journal: ${journalId} VS Institution: ${institId}`) //condtion type journal/condition = 3 const sendSearchCondi = async () => { try { const resp = await searchInstitFunder(institId, funderId, j_only_id) console.log(resp.data) setConditions(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } setLoading(false) } const sendGetInstit = async () => { try { const resp = await getInstitution(institId) console.log(resp.data) //manage the order output // detailArray.push(resp.data) // setDetails(detailArray) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } const sendGetFunder = async () => { try { const resp = await getFunder(funderId) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } sendSearchCondi().then( sendGetInstit().then(sendGetFunder()) ) history.push({pathname:`check`, search: `institution=${institId}&funder=${funderId}`}) } else if (institId && journalId && !funderId) { //alert(`get api Filter Conditions SET--> Journal: ${journalId} VS Institution: ${institId}`) //condtion type journal/condition = 3 const sendSearchCondi = async () => { try { const resp = await searchCondi(journalId,institId) console.log(resp.data) setConditions(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } setLoading(false) } const sendGetInstit = async () => { try { const resp = await getInstitution(institId) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } const sendGetJournal = async () => { try { const resp = await getJournal(journalId) console.log(resp.data) // detailArray.push(resp.data) // setDetails(detailArray) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } sendSearchCondi().then( sendGetInstit().then(sendGetJournal) ) history.push({pathname:`check`, search: `institution=${institId}&journal=${journalId}`}) } else if (!institId && journalId && funderId) { // alert(`get api Filter Conditions SET--> Journal: ${journalId} VS Institution: ${funderId}`) //condtion type journal/institution/funder conditions = 3 const sendGetCondi = async () => { try { const resp = await searchCondi(journalId,funderId) console.log(resp.data) setConditions(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } setLoading(false) } const sendGetFunder = async () => { try { const resp = await getFunder(funderId) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } const sendGetJournal = async () => { try { const resp = await getJournal(journalId) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } sendGetCondi().then( sendGetJournal().then( sendGetFunder() ) ) history.push({pathname:`check`, search: `funder=${funderId}&journal=${journalId}`}) } else if (institId && journalId && funderId) { // alert(`get api Filter Conditions SET--> Journal: ${journalId} VS Institution: ${funderId}`) //condtion type journal/institution/funder conditions = 3 console.log("main check !") //(institution + journal) const detailArray = [] const sendGetCondi = async () => { try { const resp = await searchCondi3(institId,journalId,funderId) console.log(resp.data) setConditions(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } setLoading(false) } const sendGetInstit = async () => { try { const resp = await getInstitution(institId) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } const sendGetFunder = async () => { try { const resp = await getFunder(funderId) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } const sendGetJournal = async () => { try { const resp = await getJournal(journalId) console.log(resp.data) updateResult(arr => [...arr, resp.data]) } catch (err) { // Handle Error Here console.error(err); } } //order the request sendGetCondi() sendGetInstit().then( sendGetFunder() ).then( sendGetJournal() ) history.push({pathname:`check`, search: `institution=${institId}&funder=${funderId}&journal=${journalId}`}) } } console.log(`all conditions SET: ${conditions}`) console.log(details) console.log(`Selected Institution ID: ${institId} , Selected Funder: ${funderId}, Selected Journal ID: ${journalId}`) function detailsResult() { console.log(`details: ${details}`) console.log(result) if (details !== 'null') { return (
} aria-controls="panel1a-content" id="panel1a-header" >

Selected option(s)

{result?.map(i => ( ))}
) } } function conditionResults () { return (
{conditions?.map(i=> ( ))}
) } return ( + +
+ Your input on OACT is welcome! Do not hesitate to contact publishsupport@epfl.ch for comments or questions, + or use the "Modification request" buttons in the results for feedback regarding specific details. +
+
+ +
option.name)} renderOption={(params) => ( {params} )} // getOptionLabel={(option) => option.name} // filterOptions={filterOptions} onInputChange={handleInstit} // inputValue={institName} renderInput={(params) => ( )} /> { (institList.length > 0) ?

{institList.length} institutions

:

Loading...

}
option.name)} renderOption={(params) => ( {params} )} onInputChange={handleFunder} renderInput={(params) => ( )} /> { (funderList.length > 0) ?

{funderList.length} funders

:

Loading...

}
option.name)} renderOption={(params) => ( {params} )} onInputChange={handleJournal} renderInput={(params) => ( )} /> { (journalList.length > 0) ?

{journalList.length} journals

:

Loading...

}
{!loading && } {loading && }
{detailsResult()} {conditionResults()} { history.location.pathname === "/" && } {/* { history.location.pathname === "/" && */} {/* } */} {"Share your Result!"} {url}
); } SearchFilterFields.propTypes = { /** Store the selected option/field Result from API. */ details: PropTypes.object, /** Store the individual response for each request. */ result: PropTypes.object, /** Store at the same place the aggregation of all request result */ conditions: PropTypes.object, /** Manage the loading wheels inside the check button. */ loading: PropTypes.bool } diff --git a/django_api/management/commands/oacct-purge.py b/django_api/management/commands/oacct-purge.py new file mode 100644 index 00000000..080a5c0c --- /dev/null +++ b/django_api/management/commands/oacct-purge.py @@ -0,0 +1,45 @@ +from django.core.management import BaseCommand + +from django_api.models import Issn +from django_api.models import Oa +from django_api.models import Publisher +from django_api.models import Journal +from django_api.models import Organization +from django_api.models import Version +from django_api.models import Licence +from django_api.models import Cost_factor_type +from django_api.models import Cost_factor +from django_api.models import Term +from django_api.models import ConditionType +from django_api.models import ConditionSet +from django_api.models import OrganizationCondition +from django_api.models import JournalCondition + + +class Command(BaseCommand): + help = ( + 'Purge non-constant data tables except users' + ) + + def handle(self, *args, **options): + Issn.objects.all().delete() + print('Issns purged') + Publisher.objects.all().delete() + print('Publishers purged') + OrganizationCondition.objects.all().delete() + print('OrganizationConditions purged') + JournalCondition.objects.all().delete() + print('OrganizationConditions purged') + Journal.objects.all().delete() + print('Journals purged') + Organization.objects.all().delete() + print('Organizations purged') + Licence.objects.all().delete() + print('Licences purged') + Cost_factor.objects.all().delete() + print('Cost_factors purged') + Term.objects.all().delete() + print('Terms purged') + ConditionSet.objects.all().delete() + print('ConditionSets purged') + print('Purge complete') diff --git a/django_api/migrations/0079_auto_20220628_0838.py b/django_api/migrations/0079_auto_20220628_0838.py new file mode 100644 index 00000000..e10fbc2c --- /dev/null +++ b/django_api/migrations/0079_auto_20220628_0838.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2.13 on 2022-06-28 06:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_api', '0078_auto_20220323_1720'), + ] + + operations = [ + migrations.AlterField( + model_name='publisher', + name='city', + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AlterField( + model_name='publisher', + name='oa_policies', + field=models.URLField(blank=True, max_length=1000), + ), + migrations.AlterField( + model_name='publisher', + name='state', + field=models.CharField(blank=True, max_length=3, null=True), + ), + migrations.AlterField( + model_name='publisher', + name='website', + field=models.URLField(blank=True, max_length=1000), + ), + ] diff --git a/django_api/models.py b/django_api/models.py index 4e653ccb..ecdc3c0c 100644 --- a/django_api/models.py +++ b/django_api/models.py @@ -1,496 +1,496 @@ """ 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 . """ """ Django object models for the django_api application of the OACT project. Ref: database_model_20210421_MB.drawio 21.04.2021 """ from django.db import models from django.contrib.auth.models import User import datetime from django.utils.translation import gettext as _ class Country(models.Model): """ Countries: used as attributes by Publishers and Organizations :param name: full English name :type name: str, optional :param iso_code: ISO 3166-1 Alpha-3 code https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3#Officially_assigned_code_elements :type iso_code: str, optional """ 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: verbose_name_plural = 'Countries' ordering = ('name',) class Language(models.Model): """ Languages: used as attributes by Journals :param name: full English name :type name: str, optional :param iso_code: ISO 639-2 code https://en.wikipedia.org/wiki/ISO_639-2 :type iso_code: str, optional """ 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): """ Open Access status: used as attribute by Journals :param status: short name, ideally one word i.e. Green, Gold, UNKNOWN... :type status: str, optional :param description: description text up to 1000 characters :type status: str, optional :param subscription: does a journal with this status require a subscription? :type subscription: bool :param accepted_manuscript: does a journal with this status generally allow to distribute the accepted manuscript? :type accepted_manuscript: bool :param apc: does a journal with this status require Article Processing Charges (APCs)? :type apc: bool :param final_version: does a journal with this status generally allow to distribute the published version? :type final_version: bool """ 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',) verbose_name = "Open Access status" verbose_name_plural = "Open Access statuses" class Publisher(models.Model): """ Publishers: corporations or societies in charge of Journals :param name: name :type status: str, optional :param city: location of the main office :type city: str, optional :param state: if applicable, state or province :type state: str, optional :param country: home country or countries :type country: many-to-many relationship with the `Country` class :param starting_year: founding year :type starting_year: int, optional :param website: main web site :type website: URL :param oa_policies: web link to general Open Access policy if applicable :type oa_policies: URL """ 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) + city = models.CharField(max_length=100, null=True, blank=True) + state = models.CharField(max_length=3, null=True, blank=True) country = models.ManyToManyField("Country") starting_year = models.IntegerField(blank=True, null=True) - website = models.URLField(max_length=1000) - oa_policies = models.URLField(max_length=1000) + website = models.URLField(max_length=1000, blank=True) + oa_policies = models.URLField(max_length=1000, blank=True) def __str__(self): return f"{self.name}" class Meta: ordering = ('name',) class Issn(models.Model): """ Issns: a multiple property of Journals :param journal: Journal object to which the ISSN belongs :type journal: class `Journal`, optional :param issn: ISSN code such as 1234-5678 :type issn: str :param issn_type: Print, Electronic or Other :type issn_type: str """ PRINT = '1' ELECTRONIC = '2' OTHER = '3' TYPE_CHOICES = ( (PRINT, 'Print'), (ELECTRONIC, 'Electronic'), (OTHER, 'Other'), ) journal = models.ForeignKey("Journal", null=True, on_delete=models.CASCADE, related_name = "classIssn") #journal.classissn issn = models.CharField(max_length=9, null=False) """ISSN code such as 1234-5678 """ issn_type = models.CharField( choices=TYPE_CHOICES, max_length=10, blank=True ) def __str__(self): return f"{self.issn} ({dict(self.TYPE_CHOICES)[self.issn_type]})" class Meta: ordering = ('issn',) class Journal(models.Model): """ Journals: one of the big entities in the application :param name: journal title :type name: str :param name_short_iso_4: ISO 4 abbreviation of the title :type name_short_iso_4: str :param publisher: zero or more publishers in charge of the Journal :type publisher: many-to-many relationshio with class `Publisher` :param website: home page of the journal :type website: URL :param language: the journal publishes articles in these zero or more languages :type language: many-to-many relationship with class `Journal` :param oa_options: web page with the journal's Open Access conditions :type oa_options: URL, optional :param oa_status: Open Access status :type oa_status: reference to an `Oa` object :param starting_year: founding year :type starting_year: int, optional :param end_year: end year if applicable :type ending_year: int, optional :param doaj_seal: did the journal obtain the DOAJ Seal? https://doaj.org/apply/seal/ :type doaj_seal: bool :param doaj_status: is the journal accepted in the Directory of Open Access Journals? https://doaj.org :type doaj_status: bool :param lockss: is the journal archived by LOCKSS? https://www.lockss.org/about :type lockss: bool :param nlch: please remind me what this is supposed to be :type nlch: bool :param portico: did the journal obtain the DOAJ Seal? https://doaj.org/apply/seal/ :type portico: is the journal archived by Portico? https://www.portico.org/ :param qoam_av_score: Quality Open Access Marker (QOAM) score https://www.qoam.eu/ :type qoam_av_score: decimal number """ 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) # 2021-08-11: only one-to-many relationship between Journal and ISSN # issn = models.ForeignKey("Issn", null=True, on_delete=models.CASCADE) oa_options = models.URLField(max_length=1000, blank=True, null=True) oa_status = models.ForeignKey("Oa", related_name ="oa_status", on_delete=models.CASCADE, null=True) starting_year = models.IntegerField(blank=True, null=True) end_year = models.IntegerField(blank=True, null=True) doaj_seal = models.BooleanField(default=False) doaj_status = models.BooleanField(default=False) lockss = models.BooleanField(default=False) nlch = models.BooleanField(default=False) portico = models.BooleanField(default=False) qoam_av_score = models.DecimalField(decimal_places=2, max_digits=5, blank=True, null=True) def __str__(self): return f"{self.name} from {self.website}" class Meta: ordering = ('name',) class Organization(models.Model): """ Organizations: one of the big entities in the application, organizations (research institutions or funders) who employ or fund the authors/researchers :param name: name of the organization :type name: str :param website: web site of the organization :type website: URL, optional :param country: zero or more home countries :type country: many-to-many relationship with class `Country` :param ror: Research Organization Registry (ROR) indentifier https://ror.org/ :type ror: str, optional :param fundref: Crossref Funder Registry identifier https://www.crossref.org/services/funder-registry/ :type fundref: str, optional :param starting_year: founding year :type starting_year: int, optional :param is_funder: if True, the organization is a funding agency, if False a research organization :type is_funder: bool :param ir_name: name of the oeganization's institutional repository for publications, if applicable :type ir_name: str, optional :param ir_url: address of the oeganization's institutional repository for publications, if applicable :type ir_name: URL, optional """ 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) ir_name = models.CharField(verbose_name="Institutional repository name", max_length=40, null=True, blank=True) ir_url = models.URLField(verbose_name="Institutional repository URL", max_length=100, null=True, blank=True) def __str__(self): return f"{self.name}" class Meta: ordering = ('name',) class Version(models.Model): """ Possible versions of an article during its life cycle: submitted version, accepted version, published version :param name: name of the version :type name: str """ description = models.CharField(max_length=300, null=False) def __str__(self): return f"{self.description}" class Licence(models.Model): """ Licenses that can or must be applied to an article version :param name_or_abbrev: name or abbreviation for the license: copyright, CC-BY,... :type name_or_abbrev: str :param website: web page that describes the license terms :type website: URL, optional """ name_or_abbrev = models.CharField(max_length=300, null=False) website = models.URLField(max_length=600, null=True, blank=True) class Meta: ordering = ('name_or_abbrev',) def __str__(self): return f"{self.name_or_abbrev}" class Cost_factor_type(models.Model): """ Cost factor types: amount, discount... :param name: name of the type :type name: str """ name = models.CharField(max_length=300, null=False) def __str__(self): return f"{self.name}" class Cost_factor(models.Model): """ Cost factors: financial terms applicable to use an Open Access option :param cost_factor_type: type of the cost factor :type ost_factor_type: reference to a `Cost_factor` object :param amount: actual cost or discount :type amount: int :param symbol: currency code or % :type symbol: str :param comment: extra information in free text :type comment: str, optionaé """ 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="") class Meta: ordering = ('amount',) def __str__(self): return f"{self.id} - {self.amount} {self.symbol} - {self.comment}" class Term(models.Model): """ Terms: possible options to disseminate an article in Open Access :param version: zero or more versions for which the Term is applicable (currently only 1 is supported by the application) :type version: many-to-many relationship to the `Version` class :param cost_factor: zero or more possible cost factors :type cost_factor: many-to-many relationship to the `Cost_factor` class :param licence: zero or more possible licenses :type licence: many-to-many relationship to the `Licence` class :param embargo_months: duration of a possible embargo in months :type embargo_months: int :param ir_archiving: is archiving in an institutional repository allowed/required or not? :type ir_archiving: bool :param comment: extra information as free text :type comment: str, optional """ version = models.ManyToManyField(Version) cost_factor = models.ManyToManyField(Cost_factor) licence = models.ManyToManyField(Licence) embargo_months = models.IntegerField(blank=True, null=True) ir_archiving = models.BooleanField(default=False) comment = models.CharField(max_length=1000, null=True, blank=True) def __str__(self): try: # Maybe these fields should not allow NULL values? if self.embargo_months is None: embargo = 'no_' else: embargo = str(self.embargo_months) if self.comment is None: comment = '' else: comment = str(self.comment) term_data = (str(self.id), ';'.join([str(x) for x in self.version.all()]), ';'.join([str(x) for x in self.licence.all()]), ';'.join([str(x) for x in self.cost_factor.all()]), f'Archiving{str(self.ir_archiving)} {embargo}months', comment,) return ' - '.join(term_data) except RecursionError: # The JSON import in the admin module somehow throws a ValueError during the loading process # probably due to incomplete information in the many2many relationships # Then the error log apparently triggers a cascade of errors until # the RecursionError level is hit. Falling back to a basic __str__ # for the RecursionError seems to bypass the problem. return f"[Term.__str__() error] {self.id} - {self.comment}" class Meta: ordering = ('-ir_archiving', 'embargo_months', 'comment') class ConditionType(models.Model): """ Condition types: issued by a journal, by an organization, or agreement between both? :param condition_issuer: `organization-only`, `agreement` or `journal-only` :type condition_issuer: str """ condition_issuer = models.CharField(max_length=300, null=False) def __str__(self): return f"{self.condition_issuer}" class ConditionSubType(models.Model): """ Condition subtypes: in case we need to distinguish more finely than the 3 main condition types :param label: name of the subtype :type label_issuer: str """ label = models.CharField(max_length=300, null=False) def __str__(self): return f"{self.label}" @classmethod def get_default_pk(cls): """ An automatic subtype is attributed to any newly created `CondtionSet` object """ condition_subtype, created = cls.objects.get_or_create(label='Automatic') return condition_subtype.pk class ConditionSet(models.Model): """ Condition sets: collections of Open Access terms applicable to zero or more Journals and zero or more Organizations for some specific reason (policy document, agreement, contract...). :param condition_type: type for the condition set :type condition_type: reference to a `ConditionType` object :param subtype: subtype for the condition set :type subtype: eference to a `ConditionSubType` object :param organization: zero or more organisations to which the condition set is applicable :type organization: many-to-many relationship with the `Organization` class with `OrganizationCondition` objects as connectors :param journal: zero or more journals to which the condition set is applicable :type journal: many-to-many relationship with the `Journal` class with `JournalCondition` objects as connectors :param term: zero or more terms included in the condition set :type term: many-to-many relationship with the `Term` class :param source: web page with information about the condition set (origin, perimeter, etc.) :type source: URL, optional :param comment: description of the condition set as free text (will be used as a title in the frontend) :type comment: str, optional """ condition_type = models.ForeignKey(ConditionType, on_delete=models.CASCADE, blank=True, null=True) subtype = models.ForeignKey(ConditionSubType, on_delete=models.CASCADE, default=ConditionSubType.get_default_pk, 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) source = models.URLField(max_length=600, null=True, blank=True) comment = models.CharField(max_length=100, null=True, blank=True) def __str__(self): return f"{self.id} {self.condition_type}|{self.comment}" class Meta: # TODO does this work??? 2ndary sort showing institution first, funder second # ordering = ('condition_type__pk', 'organization__is_funder', 'subtype__id', 'comment') # No it does not, it duplicates most journal policies (one copy for funders, one for institutions) ordering = ('condition_type__pk', 'subtype__id', 'comment') class OrganizationCondition(models.Model): """ Organization-ConditionSet connector, linking `organization` with `condition`. The first (`valid_from`) and last (`valid_until`) known days of validity are recorded. """ 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) class Meta: verbose_name = "Organization/condition_set relationship" def __str__(self): return f"{self.id} {self.organization.name}/ConditionSet {self.condition_set.id}" class JournalCondition(models.Model): """ Journal-ConditionSet connector, linking `journal` with `condition`. The first (`valid_from`) and last (`valid_until`) known days of validity are recorded. """ 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) class Meta: verbose_name = "Journal/condition_set relationship" def __str__(self): return f"{self.id} {self.journal.name}/{self.condition_set}"