Page MenuHomec4science

models.py
No OneTemporary

File Metadata

Created
Mon, Jun 24, 04:00

models.py

"""
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