Page MenuHomec4science

models.py
No OneTemporary

File Metadata

Created
Wed, May 22, 02:39

models.py

"""
This is the Open Access Compliance Check Tool (OACCT).
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 object models for the django_api application of the OACCT 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)
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)
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}"

Event Timeline