From 70a7d8a52b644598a6effa7479490166503e52e4 Mon Sep 17 00:00:00 2001 From: zethson Date: Thu, 21 Nov 2024 12:16:38 +0100 Subject: [PATCH] Add Person and Referene Signed-off-by: zethson --- .gitignore | 2 + ourprojects/__init__.py | 2 +- ...rson_project_persons_reference_and_more.py | 323 ++++++++++++++++++ ourprojects/models.py | 126 ++++++- 4 files changed, 450 insertions(+), 3 deletions(-) create mode 100644 ourprojects/migrations/0003_artifactreference_person_project_persons_reference_and_more.py diff --git a/.gitignore b/.gitignore index 440bdf6..0cd8bef 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,5 @@ docs/conf.py _docs_tmp* docs/test-ourprojects/ test-ourprojects/ +test.ipynb +run-tests diff --git a/ourprojects/__init__.py b/ourprojects/__init__.py index 423fead..753b650 100644 --- a/ourprojects/__init__.py +++ b/ourprojects/__init__.py @@ -32,4 +32,4 @@ def __getattr__(name): import lamindb del __getattr__ # delete so that imports work out - from .models import Project + from .models import Project, Person, Reference diff --git a/ourprojects/migrations/0003_artifactreference_person_project_persons_reference_and_more.py b/ourprojects/migrations/0003_artifactreference_person_project_persons_reference_and_more.py new file mode 100644 index 0000000..70b252b --- /dev/null +++ b/ourprojects/migrations/0003_artifactreference_person_project_persons_reference_and_more.py @@ -0,0 +1,323 @@ +# Generated by Django 5.1.3 on 2024-11-20 12:42 + +import django.core.validators +import django.db.models.deletion +import lnschema_core.fields +import lnschema_core.ids +import lnschema_core.models +import lnschema_core.users +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("lnschema_core", "0069_squashed"), + ("ourprojects", "0002_alter_artifactproject_artifact_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="ArtifactReference", + fields=[ + ( + "created_at", + lnschema_core.fields.DateTimeField( + auto_now_add=True, db_index=True + ), + ), + ("id", models.BigAutoField(primary_key=True, serialize=False)), + ( + "label_ref_is_name", + lnschema_core.fields.BooleanField( + blank=True, default=None, null=True + ), + ), + ( + "feature_ref_is_name", + lnschema_core.fields.BooleanField( + blank=True, default=None, null=True + ), + ), + ( + "artifact", + lnschema_core.fields.ForeignKey( + blank=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="links_reference", + to="lnschema_core.artifact", + ), + ), + ( + "created_by", + lnschema_core.fields.ForeignKey( + blank=True, + default=lnschema_core.users.current_user_id, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="lnschema_core.user", + ), + ), + ( + "feature", + lnschema_core.fields.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="links_artifactreference", + to="lnschema_core.feature", + ), + ), + ( + "run", + lnschema_core.fields.ForeignKey( + blank=True, + default=lnschema_core.models.current_run, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="lnschema_core.run", + ), + ), + ], + options={ + "abstract": False, + }, + bases=(lnschema_core.models.LinkORM, models.Model), + ), + migrations.CreateModel( + name="Person", + fields=[ + ( + "created_at", + lnschema_core.fields.DateTimeField( + auto_now_add=True, db_index=True + ), + ), + ( + "updated_at", + lnschema_core.fields.DateTimeField(auto_now=True, db_index=True), + ), + ("id", models.AutoField(primary_key=True, serialize=False)), + ( + "uid", + lnschema_core.fields.CharField( + blank=True, + default=lnschema_core.ids.base62_12, + max_length=12, + unique=True, + ), + ), + ( + "name", + lnschema_core.fields.CharField( + blank=True, db_index=True, default=None, max_length=255 + ), + ), + ( + "email", + lnschema_core.fields.EmailField( + blank=True, default=None, max_length=254, null=True + ), + ), + ( + "internal", + lnschema_core.fields.BooleanField( + blank=True, default=False, null=True + ), + ), + ( + "_previous_runs", + models.ManyToManyField(related_name="+", to="lnschema_core.run"), + ), + ( + "created_by", + lnschema_core.fields.ForeignKey( + blank=True, + default=lnschema_core.users.current_user_id, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="lnschema_core.user", + ), + ), + ( + "run", + lnschema_core.fields.ForeignKey( + blank=True, + default=lnschema_core.models.current_run, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="lnschema_core.run", + ), + ), + ], + options={ + "abstract": False, + }, + bases=( + lnschema_core.models.CanCurate, + models.Model, + lnschema_core.models.ValidateFields, + ), + ), + migrations.AddField( + model_name="project", + name="persons", + field=models.ManyToManyField( + related_name="project_persons", to="ourprojects.person" + ), + ), + migrations.CreateModel( + name="Reference", + fields=[ + ( + "created_at", + lnschema_core.fields.DateTimeField( + auto_now_add=True, db_index=True + ), + ), + ( + "updated_at", + lnschema_core.fields.DateTimeField(auto_now=True, db_index=True), + ), + ("id", models.AutoField(primary_key=True, serialize=False)), + ( + "uid", + lnschema_core.fields.CharField( + blank=True, + default=lnschema_core.ids.base62_12, + max_length=12, + unique=True, + ), + ), + ( + "name", + lnschema_core.fields.CharField( + blank=True, db_index=True, default=None, max_length=255 + ), + ), + ( + "abbr", + lnschema_core.fields.CharField( + blank=True, + db_index=True, + default=None, + max_length=32, + null=True, + unique=True, + ), + ), + ("url", lnschema_core.fields.URLField(blank=True, null=True)), + ( + "pubmed_id", + lnschema_core.fields.BigIntegerField(blank=True, null=True), + ), + ( + "doi", + lnschema_core.fields.CharField( + blank=True, + db_index=True, + default=None, + max_length=255, + null=True, + validators=[ + django.core.validators.RegexValidator( + message="Must be a DOI (e.g., 10.1000/xyz123 or https://doi.org/10.1000/xyz123)", + regex="^(?:https?://(?:dx\\.)?doi\\.org/|doi:|DOI:)?10\\.\\d+/.*$", + ) + ], + ), + ), + ( + "preprint", + lnschema_core.fields.BooleanField( + blank=True, default=None, null=True + ), + ), + ( + "journal", + lnschema_core.fields.TextField(blank=True, default=None, null=True), + ), + ( + "description", + lnschema_core.fields.TextField(blank=True, default=None, null=True), + ), + ( + "text", + lnschema_core.fields.TextField(blank=True, default=None, null=True), + ), + ( + "published_at", + lnschema_core.fields.DateTimeField( + blank=True, default=None, null=True + ), + ), + ( + "_previous_runs", + models.ManyToManyField(related_name="+", to="lnschema_core.run"), + ), + ( + "artifacts", + models.ManyToManyField( + related_name="references", + through="ourprojects.ArtifactReference", + to="lnschema_core.artifact", + ), + ), + ( + "created_by", + lnschema_core.fields.ForeignKey( + blank=True, + default=lnschema_core.users.current_user_id, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="lnschema_core.user", + ), + ), + ( + "persons", + models.ManyToManyField( + related_name="reference_persons", to="ourprojects.person" + ), + ), + ( + "run", + lnschema_core.fields.ForeignKey( + blank=True, + default=lnschema_core.models.current_run, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="lnschema_core.run", + ), + ), + ], + options={ + "abstract": False, + }, + bases=( + lnschema_core.models.CanCurate, + models.Model, + lnschema_core.models.ValidateFields, + ), + ), + migrations.AddField( + model_name="artifactreference", + name="reference", + field=lnschema_core.fields.ForeignKey( + blank=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="links_artifact", + to="ourprojects.reference", + ), + ), + migrations.AddField( + model_name="project", + name="references", + field=models.ManyToManyField( + related_name="project_references", to="ourprojects.reference" + ), + ), + ] diff --git a/ourprojects/models.py b/ourprojects/models.py index aa384d3..91fb186 100644 --- a/ourprojects/models.py +++ b/ourprojects/models.py @@ -1,9 +1,21 @@ from __future__ import annotations +from datetime import datetime # noqa + +from django.core.validators import RegexValidator from django.db import models from django.db.models import CASCADE, PROTECT from lnschema_core import ids -from lnschema_core.fields import BooleanField, CharField, ForeignKey +from lnschema_core.fields import ( + BigIntegerField, + BooleanField, + CharField, + DateTimeField, + ForeignKey, + TextField, + URLField, + EmailField +) from lnschema_core.models import ( Artifact, CanCurate, @@ -16,8 +28,33 @@ ) +class Person(Record, CanCurate, TracksRun, TracksUpdates, ValidateFields): + """Internal and external persons that can be a part of projects or references. + + Example: + >>> person = Person( + ... name="A paper title", + ... internal=False, + ... ).save() + """ + + class Meta(Record.Meta, TracksRun.Meta, TracksUpdates.Meta): + abstract = False + + id: int = models.AutoField(primary_key=True) + """Internal id, valid only in one DB instance.""" + uid: str = CharField(max_length=12, unique=True, default=ids.base62_12) + """Universal id, valid across DB instances.""" + name: str = CharField(db_index=True) + """Name of the person (forename(s) lastname).""" + email: str = EmailField(null=True, default=None) + """Email of the person.""" + internal: bool = BooleanField(null=True, default=False) + """Whether the person is internal to the organization or not.""" + + class Project(Record, CanCurate, TracksRun, TracksUpdates, ValidateFields): - """Projects. + """Projects with associated persons and references. Example: >>> Project = Project( @@ -42,10 +79,95 @@ class Meta(Record.Meta, TracksRun.Meta, TracksUpdates.Meta): max_length=255, null=True, default=None, blank=True ) """A URL to view.""" + persons: Person = models.ManyToManyField( + Person, + related_name="project_persons" + ) + references: Reference = models.ManyToManyField( + "Reference", + related_name="project_references" + ) + """References associated with this project.""" artifacts: Artifact = models.ManyToManyField( Artifact, through="ArtifactProject", related_name="Projects" ) """Artifacts labeled with this Project.""" + + +class Reference(Record, CanCurate, TracksRun, TracksUpdates, ValidateFields): + """References such as a publication or document, with unique identifiers and metadata. + + Example: + >>> reference = Reference( + ... name="A paper title", + ... doi="A doi", + ... ).save() + """ + + class Meta(Record.Meta, TracksRun.Meta, TracksUpdates.Meta): + abstract = False + + id: int = models.AutoField(primary_key=True) + """Internal id, valid only in one DB instance.""" + uid: str = CharField(max_length=12, unique=True, default=ids.base62_12) + """Universal id, valid across DB instances.""" + name: str = CharField(db_index=True) + """Title or name of the reference document.""" + abbr: str | None = CharField( + max_length=32, + db_index=True, + unique=True, + null=True, + ) + """A unique abbreviation for the reference.""" + url: str | None = URLField(null=True, blank=True) + """URL linking to the reference.""" + pubmed_id: int | None = BigIntegerField(null=True) + """A PudMmed ID.""" + doi: int | None = CharField( + null=True, + db_index=True, + validators=[ + RegexValidator( + regex=r"^(?:https?://(?:dx\.)?doi\.org/|doi:|DOI:)?10\.\d+/.*$", + message="Must be a DOI (e.g., 10.1000/xyz123 or https://doi.org/10.1000/xyz123)", + ) + ], + ) + """Digital Object Identifier (DOI) for the reference.""" + preprint: bool = BooleanField(null=True, default=None) + """Whether the reference is from a preprint.""" + journal: str = TextField(null=True) + """Name of the journal.""" + description: str = TextField(null=True) + """Description of the reference.""" + text: str | None = TextField(null=True) + """Abstract or full text of the reference.""" + published_at: datetime = DateTimeField(null=True, default=None) + """Publication date.""" + persons: Person = models.ManyToManyField( + Person, + related_name="reference_persons" + ) + artifacts: Artifact = models.ManyToManyField( + Artifact, through="ArtifactReference", related_name="references" + ) + """Artifacts labeled with this reference.""" + + +class ArtifactReference(Record, LinkORM, TracksRun): + id: int = models.BigAutoField(primary_key=True) + artifact: Artifact = ForeignKey(Artifact, CASCADE, related_name="links_reference") + reference: Reference = ForeignKey(Reference, PROTECT, related_name="links_artifact") + feature: Feature = ForeignKey( + Feature, + PROTECT, + null=True, + default=None, + related_name="links_artifactreference", + ) + label_ref_is_name: bool | None = BooleanField(null=True, default=None) + feature_ref_is_name: bool | None = BooleanField(null=True, default=None) class ArtifactProject(Record, LinkORM, TracksRun):