Source code for citations.models

import requests
import xmltodict
from django.db import models

from citations.validators import validate_orcid


# Create your models here.
class Institutions(models.Model):
    """
    Model for Institutions that may be linked to several parties/Funding streams.

    Id is auto incrementing.
    """

    name = models.CharField(max_length=120)
    acronym = models.CharField(max_length=10)
    country = models.CharField(max_length=120)

    id = models.CharField(max_length=120, primary_key=True)

    def __str__(self):
        return self.name


class Parties(models.Model):
    """
    Model for parties with a list of affiliations.

    Id is unique, non auto incremental
    """

    first_name = models.CharField(max_length=30, editable=False)
    last_name = models.CharField(max_length=30, editable=False)
    middle_names = models.CharField(max_length=60, blank=True, editable=False)
    email = models.CharField(max_length=100, editable=True)
    orcid = models.CharField(max_length=19, blank=True, validators=[validate_orcid])
    affiliations = models.ManyToManyField(Institutions, blank=True)

    # Hashlib of name elements
    id = models.CharField(primary_key=True)

    def __str__(self):
        if self.middle_names is not None and self.middle_names != "":
            return f"{self.first_name} ({self.middle_names}) {self.last_name} <{self.email}>"
        return f"{self.first_name} {self.last_name} <{self.email}>"


class FundingStreams(models.Model):
    """
    Model for Funding Streams associated with a single Institute.

    Id is auto incrementing.
    """

    name = models.CharField(max_length=120)
    # Multiple affiliations per funding stream.
    affiliation = models.ForeignKey(Institutions, blank=True, on_delete=models.CASCADE)

    id = models.CharField(max_length=120, primary_key=True)

    def __str__(self):
        return f"{self.name}"


class References(models.Model):
    """
    Store external references to the CMIP7 citation service

    Id is unique, non auto incrementing.
    """

    title = models.CharField(max_length=300)
    citeas = models.TextField()
    id = models.CharField(max_length=200, primary_key=True)

    def __str__(self):
        return f"{self.title}"


class Citations(models.Model):
    """
    Model for citations that have multiple links on creation

    Id is auto incrementing.
    """

    # Still would be good to create citations/parties etc in any order

    id = models.CharField(max_length=300, primary_key=True)
    title = models.CharField(max_length=300)
    version = models.IntegerField()

    publication_timestamp = models.CharField(null=True, blank=True)

    abstract = models.TextField()
    drs_url = models.CharField()
    doi_url = models.CharField()
    rights = models.CharField(max_length=30)
    license = models.TextField()
    primary = models.ForeignKey(
        Parties,
        on_delete=models.PROTECT,  # Primary author cannot be deleted.
        related_name="primary_party",
    )
    contacts = models.ManyToManyField(
        Parties, related_name="contact_parties", blank=True, null=True
    )
    institutions = models.ManyToManyField(Institutions, blank=True, null=True)
    funders = models.ManyToManyField(FundingStreams, blank=True, null=True)

    editable = models.BooleanField(default=True)
    published = models.BooleanField(default=False)

    project_id = models.CharField(max_length=30, null=True, blank=True)
    activity_id = models.CharField(max_length=30, null=True, blank=True)
    institution_id = models.CharField(max_length=30, null=True, blank=True)
    source_id = models.CharField(max_length=30, null=True, blank=True)
    experiment_id = models.CharField(max_length=30, null=True, blank=True)
    domain_id = models.CharField(max_length=30, null=True, blank=True)

    # keywords = ArrayField(
    #     models.CharField(max_length=50),
    #     blank=True,
    #     default=list
    # )

    # References
    is_cited_by = models.ManyToManyField(
        References, blank=True, null=True, related_name="is_cited_by"
    )

    cites = models.ManyToManyField(
        References, blank=True, null=True, related_name="cites"
    )

    is_referenced_by = models.ManyToManyField(
        References, blank=True, null=True, related_name="is_referenced_by"
    )

    # An endpoint for obtaining a list of ESGF urls that can be rendered
    # data_access =


def locate_institute(inst: str):
    """
    Use ROR lookup API to find institute-level metadata
    """
    ROR_api = "https://api.ror.org/v2/organizations?query=" + "%20".join(
        inst.split(" ")
    )
    r = requests.get(ROR_api)
    if int(r.status_code) >= 300:
        print("Institute not found")
        return {"name": inst}

    resp = r.json()
    found = False
    inst_count = 0
    while not found and inst_count < 10:
        names = resp["items"][inst_count]["names"]
        for entry in names:
            if entry["value"] == inst:
                found = True
                break

        if not found:
            inst_count += 1

    if found:

        acronym = None
        for n in resp["items"][inst_count]["names"]:
            if "acronym" in n["types"]:
                acronym = n["value"]

        return {
            "name": inst,
            "acronym": acronym or "".join([i[0] for i in inst.split(" ")]),
            "country": resp["items"][inst_count]["locations"][0]["geonames_details"][
                "country_name"
            ],
        }
    else:
        return {"name": inst}


def extract_from_orcid(orcid):
    r = (
        xmltodict.parse(
            requests.get(
                f"https://pub.orcid.org/v3.0/expanded-search/?q=orcid%3A{orcid}"
            ).text
        )
        .get("expanded-search:expanded-search", {})
        .get("expanded-search:expanded-result", None)
    )

    # Demo loader for loading ORCID institutions to Party (if already known)
    if r is None:
        return None

    institutions = []
    for k, v in r.items():
        if k != "expanded-search:institution-name":
            continue

        if isinstance(v, str):
            v = [v]

        for inst in v:
            institutions.append(inst)

    return institutions