From 13816ecaf32becc44267eca490e67264811e3394 Mon Sep 17 00:00:00 2001 From: jebus Date: Wed, 27 Aug 2025 06:46:09 +1200 Subject: [PATCH] Make URL a Field instead a property (#63) This PR adds a new Field to all existing Models. As its a data migrations the Migration is added. Co-authored-by: Tim Lorsbach Reviewed-on: https://git.envipath.com/enviPath/enviPy/pulls/63 --- epdb/management/commands/localize_urls.py | 50 + epdb/migrations/0001_initial.py | 594 ++++++++++ ...abilitydomain_url_compound_url_and_more.py | 1021 +++++++++++++++++ ...atabase_alter_apitoken_options_and_more.py | 128 +++ ...abilitydomain_url_compound_url_and_more.py | 229 ++++ epdb/models.py | 77 +- epdb/views.py | 11 +- 7 files changed, 2063 insertions(+), 47 deletions(-) create mode 100644 epdb/management/commands/localize_urls.py create mode 100644 epdb/migrations/0001_initial.py create mode 100644 epdb/migrations/0001_squashed_0003_applicabilitydomain_url_compound_url_and_more.py create mode 100644 epdb/migrations/0002_externaldatabase_alter_apitoken_options_and_more.py create mode 100644 epdb/migrations/0003_applicabilitydomain_url_compound_url_and_more.py diff --git a/epdb/management/commands/localize_urls.py b/epdb/management/commands/localize_urls.py new file mode 100644 index 00000000..b8d3547a --- /dev/null +++ b/epdb/management/commands/localize_urls.py @@ -0,0 +1,50 @@ +from django.apps import apps +from django.core.management.base import BaseCommand + +from django.db.models import F, Value +from django.db.models.functions import Replace + + +class Command(BaseCommand): + + def add_arguments(self, parser): + parser.add_argument( + '--old', + type=str, + help='Old Host, most likely https://envipath.org/', + required=True, + ) + parser.add_argument( + '--new', + type=str, + help='New Host, most likely http://localhost:8000/', + required=True, + ) + + def handle(self, *args, **options): + MODELS = [ + 'User', + 'Group', + 'Package', + 'Compound', + 'CompoundStructure', + 'Pathway', + 'Edge', + 'Node', + 'Reaction', + 'SimpleAmbitRule', + 'SimpleRDKitRule', + 'ParallelRule', + 'SequentialRule', + 'Scenario', + 'Setting', + 'MLRelativeReasoning', + 'EnviFormer', + 'ApplicabilityDomain', + ] + for model in MODELS: + obj_cls = apps.get_model("epdb", model) + print(f"Localizing urls for {model}") + obj_cls.objects.update( + url=Replace(F('url'), Value(options['old']), Value(options['new'])) + ) diff --git a/epdb/migrations/0001_initial.py b/epdb/migrations/0001_initial.py new file mode 100644 index 00000000..e6cc4f69 --- /dev/null +++ b/epdb/migrations/0001_initial.py @@ -0,0 +1,594 @@ +# Generated by Django 5.2.1 on 2025-07-22 20:58 + +import datetime +import django.contrib.auth.models +import django.contrib.auth.validators +import django.contrib.postgres.fields +import django.db.models.deletion +import django.utils.timezone +import model_utils.fields +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('contenttypes', '0002_remove_content_type_name'), + ] + + operations = [ + migrations.CreateModel( + name='Compound', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), + ], + ), + migrations.CreateModel( + name='EPModel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + ), + migrations.CreateModel( + name='Permission', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('permission', models.CharField(choices=[('read', 'Read'), ('write', 'Write'), ('all', 'All')], max_length=32)), + ], + ), + migrations.CreateModel( + name='License', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('link', models.URLField(verbose_name='link')), + ('image_link', models.URLField(verbose_name='Image link')), + ], + ), + migrations.CreateModel( + name='Rule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + ), + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('email', models.EmailField(max_length=254, unique=True)), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='APIToken', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('hashed_key', models.CharField(max_length=128, unique=True)), + ('created', models.DateTimeField(auto_now_add=True)), + ('expires_at', models.DateTimeField(blank=True, default=datetime.datetime(2025, 10, 20, 20, 58, 48, 351675, tzinfo=datetime.timezone.utc), null=True)), + ('name', models.CharField(blank=True, help_text='Optional name for the token', max_length=100)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='CompoundStructure', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), + ('smiles', models.TextField(verbose_name='SMILES')), + ('canonical_smiles', models.TextField(verbose_name='Canonical SMILES')), + ('inchikey', models.TextField(max_length=27, verbose_name='InChIKey')), + ('normalized_structure', models.BooleanField(default=False)), + ('compound', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.compound')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='compound', + name='default_structure', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='compound_default_structure', to='epdb.compoundstructure', verbose_name='Default Structure'), + ), + migrations.CreateModel( + name='Edge', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), + ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + ), + migrations.CreateModel( + name='EnviFormer', + fields=[ + ('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')), + ('threshold', models.FloatField(default=0.5)), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.epmodel',), + ), + migrations.CreateModel( + name='PluginModel', + fields=[ + ('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.epmodel',), + ), + migrations.CreateModel( + name='RuleBaseRelativeReasoning', + fields=[ + ('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.epmodel',), + ), + migrations.CreateModel( + name='Group', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(verbose_name='Group name')), + ('public', models.BooleanField(default=False, verbose_name='Public Group')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('group_member', models.ManyToManyField(related_name='groups_in_group', to='epdb.group', verbose_name='Group member')), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Group Owner')), + ('user_member', models.ManyToManyField(related_name='users_in_group', to=settings.AUTH_USER_MODEL, verbose_name='User members')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='user', + name='default_group', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_group', to='epdb.group', verbose_name='Default Group'), + ), + migrations.CreateModel( + name='Node', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), + ('depth', models.IntegerField(verbose_name='Node depth')), + ('default_node_label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='default_node_structure', to='epdb.compoundstructure', verbose_name='Default Node Label')), + ('node_labels', models.ManyToManyField(related_name='node_structures', to='epdb.compoundstructure', verbose_name='All Node Labels')), + ('out_edges', models.ManyToManyField(to='epdb.edge', verbose_name='Outgoing Edges')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='edge', + name='end_nodes', + field=models.ManyToManyField(related_name='edge_products', to='epdb.node', verbose_name='End Nodes'), + ), + migrations.AddField( + model_name='edge', + name='start_nodes', + field=models.ManyToManyField(related_name='edge_educts', to='epdb.node', verbose_name='Start Nodes'), + ), + migrations.CreateModel( + name='Package', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('reviewed', models.BooleanField(default=False, verbose_name='Reviewstatus')), + ('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.license', verbose_name='License')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='epmodel', + name='package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'), + ), + migrations.AddField( + model_name='compound', + name='package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'), + ), + migrations.AddField( + model_name='user', + name='default_package', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.package', verbose_name='Default Package'), + ), + migrations.CreateModel( + name='SequentialRule', + fields=[ + ('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.rule',), + ), + migrations.CreateModel( + name='SimpleRule', + fields=[ + ('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.rule',), + ), + migrations.AddField( + model_name='rule', + name='package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'), + ), + migrations.AddField( + model_name='rule', + name='polymorphic_ctype', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'), + ), + migrations.CreateModel( + name='Pathway', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='node', + name='pathway', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'), + ), + migrations.AddField( + model_name='edge', + name='pathway', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'), + ), + migrations.CreateModel( + name='Reaction', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')), + ('multi_step', models.BooleanField(verbose_name='Multistep Reaction')), + ('medline_references', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), null=True, size=None, verbose_name='Medline References')), + ('educts', models.ManyToManyField(related_name='reaction_educts', to='epdb.compoundstructure', verbose_name='Educts')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')), + ('products', models.ManyToManyField(related_name='reaction_products', to='epdb.compoundstructure', verbose_name='Products')), + ('rules', models.ManyToManyField(related_name='reaction_rule', to='epdb.rule', verbose_name='Rule')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='edge', + name='edge_label', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.reaction', verbose_name='Edge label'), + ), + migrations.CreateModel( + name='Scenario', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('scenario_date', models.CharField(default='No date', max_length=256)), + ('scenario_type', models.CharField(default='Not specified', max_length=256)), + ('additional_information', models.JSONField(verbose_name='Additional Information')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')), + ('parent', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.scenario')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='rule', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='reaction', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='pathway', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='node', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='edge', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='compoundstructure', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='compound', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.CreateModel( + name='Setting', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('public', models.BooleanField(default=False)), + ('global_default', models.BooleanField(default=False)), + ('max_depth', models.IntegerField(default=5, verbose_name='Setting Max Depth')), + ('max_nodes', models.IntegerField(default=30, verbose_name='Setting Max Number of Nodes')), + ('model_threshold', models.FloatField(blank=True, default=0.25, null=True, verbose_name='Setting Model Threshold')), + ('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.epmodel', verbose_name='Setting EPModel')), + ('rule_packages', models.ManyToManyField(blank=True, related_name='setting_rule_packages', to='epdb.package', verbose_name='Setting Rule Packages')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='pathway', + name='setting', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Setting'), + ), + migrations.AddField( + model_name='user', + name='default_setting', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.setting', verbose_name='The users default settings'), + ), + migrations.CreateModel( + name='MLRelativeReasoning', + fields=[ + ('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')), + ('threshold', models.FloatField(default=0.5)), + ('model_status', models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL')), + ('eval_results', models.JSONField(blank=True, default=dict, null=True)), + ('data_packages', models.ManyToManyField(related_name='data_packages', to='epdb.package', verbose_name='Data Packages')), + ('eval_packages', models.ManyToManyField(related_name='eval_packages', to='epdb.package', verbose_name='Evaluation Packages')), + ('rule_packages', models.ManyToManyField(related_name='rule_packages', to='epdb.package', verbose_name='Rule Packages')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.epmodel',), + ), + migrations.CreateModel( + name='ApplicabilityDomain', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('num_neighbours', models.FloatField(default=5)), + ('reliability_threshold', models.FloatField(default=0.5)), + ('local_compatibilty_threshold', models.FloatField(default=0.5)), + ('model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.mlrelativereasoning')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SimpleAmbitRule', + fields=[ + ('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')), + ('smirks', models.TextField(verbose_name='SMIRKS')), + ('reactant_filter_smarts', models.TextField(null=True, verbose_name='Reactant Filter SMARTS')), + ('product_filter_smarts', models.TextField(null=True, verbose_name='Product Filter SMARTS')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.simplerule',), + ), + migrations.CreateModel( + name='SimpleRDKitRule', + fields=[ + ('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')), + ('reaction_smarts', models.TextField(verbose_name='SMIRKS')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.simplerule',), + ), + migrations.CreateModel( + name='SequentialRuleOrdering', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order_index', models.IntegerField()), + ('sequential_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.sequentialrule')), + ('simple_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.simplerule')), + ], + ), + migrations.AddField( + model_name='sequentialrule', + name='simple_rules', + field=models.ManyToManyField(through='epdb.SequentialRuleOrdering', to='epdb.simplerule', verbose_name='Simple rules'), + ), + migrations.CreateModel( + name='ParallelRule', + fields=[ + ('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')), + ('simple_rules', models.ManyToManyField(to='epdb.simplerule', verbose_name='Simple rules')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.rule',), + ), + migrations.AlterUniqueTogether( + name='compound', + unique_together={('uuid', 'package')}, + ), + migrations.CreateModel( + name='GroupPackagePermission', + fields=[ + ('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')), + ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')), + ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.group', verbose_name='Permission to')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Permission on')), + ], + options={ + 'unique_together': {('package', 'group')}, + }, + bases=('epdb.permission',), + ), + migrations.CreateModel( + name='UserPackagePermission', + fields=[ + ('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')), + ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Permission on')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')), + ], + options={ + 'unique_together': {('package', 'user')}, + }, + bases=('epdb.permission',), + ), + migrations.CreateModel( + name='UserSettingPermission', + fields=[ + ('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')), + ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')), + ('setting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Permission on')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')), + ], + options={ + 'unique_together': {('setting', 'user')}, + }, + bases=('epdb.permission',), + ), + ] diff --git a/epdb/migrations/0001_squashed_0003_applicabilitydomain_url_compound_url_and_more.py b/epdb/migrations/0001_squashed_0003_applicabilitydomain_url_compound_url_and_more.py new file mode 100644 index 00000000..d1d3071a --- /dev/null +++ b/epdb/migrations/0001_squashed_0003_applicabilitydomain_url_compound_url_and_more.py @@ -0,0 +1,1021 @@ +# Generated by Django 5.2.1 on 2025-08-26 18:11 + +import django.contrib.auth.models +import django.contrib.auth.validators +import django.contrib.postgres.fields +import django.db.migrations.operations.special +import django.db.models.deletion +import django.utils.timezone +import model_utils.fields +import uuid +from django.conf import settings +from django.db import migrations, models + + +def populate_url(apps, schema_editor): + MODELS = [ + 'User', + 'Group', + 'Package', + 'Compound', + 'CompoundStructure', + 'Pathway', + 'Edge', + 'Node', + 'Reaction', + 'SimpleAmbitRule', + 'SimpleRDKitRule', + 'ParallelRule', + 'SequentialRule', + 'Scenario', + 'Setting', + 'MLRelativeReasoning', + 'EnviFormer', + 'ApplicabilityDomain', + ] + for model in MODELS: + obj_cls = apps.get_model("epdb", model) + print(f"Populating url for {model}") + for obj in obj_cls.objects.all(): + obj.url = assemble_url(obj) + if obj.url is None: + raise ValueError(f"Could not assemble url for {obj}") + obj.save() + + +def assemble_url(obj): + from django.conf import settings as s + match obj.__class__.__name__: + case 'User': + return '{}/user/{}'.format(s.SERVER_URL, obj.uuid) + case 'Group': + return '{}/user/{}'.format(s.SERVER_URL, obj.uuid) + case 'Package': + return '{}/package/{}'.format(s.SERVER_URL, obj.uuid) + case 'Compound': + return '{}/compound/{}'.format(obj.package.url, obj.uuid) + case 'CompoundStructure': + return '{}/structure/{}'.format(obj.compound.url, obj.uuid) + case 'SimpleAmbitRule': + return '{}/simple-ambit-rule/{}'.format(obj.package.url, obj.uuid) + case 'SimpleRDKitRule': + return '{}/simple-rdkit-rule/{}'.format(obj.package.url, obj.uuid) + case 'ParallelRule': + return '{}/parallel-rule/{}'.format(obj.package.url, obj.uuid) + case 'SequentialRule': + return '{}/sequential-rule/{}'.format(obj.compound.url, obj.uuid) + case 'Reaction': + return '{}/reaction/{}'.format(obj.package.url, obj.uuid) + case 'Pathway': + return '{}/pathway/{}'.format(obj.package.url, obj.uuid) + case 'Node': + return '{}/node/{}'.format(obj.pathway.url, obj.uuid) + case 'Edge': + return '{}/edge/{}'.format(obj.pathway.url, obj.uuid) + case 'MLRelativeReasoning': + return '{}/model/{}'.format(obj.package.url, obj.uuid) + case 'EnviFormer': + return '{}/model/{}'.format(obj.package.url, obj.uuid) + case 'ApplicabilityDomain': + return '{}/model/{}/applicability-domain/{}'.format(obj.model.package.url, obj.model.uuid, obj.uuid) + case 'Scenario': + return '{}/scenario/{}'.format(obj.package.url, obj.uuid) + case 'Setting': + return '{}/setting/{}'.format(s.SERVER_URL, obj.uuid) + case _: + raise ValueError(f"Unknown model {obj.__class__.__name__}") + + +class Migration(migrations.Migration): + replaces = [('epdb', '0001_initial'), ('epdb', '0002_externaldatabase_alter_apitoken_options_and_more'), + ('epdb', '0003_applicabilitydomain_url_compound_url_and_more')] + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('contenttypes', '0002_remove_content_type_name'), + ] + + operations = [ + migrations.CreateModel( + name='Compound', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', + django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, + verbose_name='Aliases')), + ], + ), + migrations.CreateModel( + name='EPModel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('polymorphic_ctype', + models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, + related_name='polymorphic_%(app_label)s.%(class)s_set+', + to='contenttypes.contenttype')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + ), + migrations.CreateModel( + name='Permission', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('permission', + models.CharField(choices=[('read', 'Read'), ('write', 'Write'), ('all', 'All')], max_length=32)), + ], + ), + migrations.CreateModel( + name='License', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('link', models.URLField(verbose_name='link')), + ('image_link', models.URLField(verbose_name='Image link')), + ], + ), + migrations.CreateModel( + name='Rule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', + django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, + verbose_name='Aliases')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + ), + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, + help_text='Designates that this user has all permissions without explicitly assigning them.', + verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, + help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', + max_length=150, unique=True, + validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], + verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('is_staff', models.BooleanField(default=False, + help_text='Designates whether the user can log into this admin site.', + verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, + help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', + verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('email', models.EmailField(max_length=254, unique=True)), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('groups', models.ManyToManyField(blank=True, + help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', + related_name='user_set', related_query_name='user', to='auth.group', + verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', + related_name='user_set', related_query_name='user', + to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='CompoundStructure', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', + django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, + verbose_name='Aliases')), + ('smiles', models.TextField(verbose_name='SMILES')), + ('canonical_smiles', models.TextField(verbose_name='Canonical SMILES')), + ('inchikey', models.TextField(max_length=27, verbose_name='InChIKey')), + ('normalized_structure', models.BooleanField(default=False)), + ('compound', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.compound')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='compound', + name='default_structure', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, + related_name='compound_default_structure', to='epdb.compoundstructure', + verbose_name='Default Structure'), + ), + migrations.CreateModel( + name='Edge', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', + django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, + verbose_name='Aliases')), + ('polymorphic_ctype', + models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, + related_name='polymorphic_%(app_label)s.%(class)s_set+', + to='contenttypes.contenttype')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + ), + migrations.CreateModel( + name='EnviFormer', + fields=[ + ('epmodel_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, + primary_key=True, serialize=False, to='epdb.epmodel')), + ('threshold', models.FloatField(default=0.5)), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.epmodel',), + ), + migrations.CreateModel( + name='PluginModel', + fields=[ + ('epmodel_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, + primary_key=True, serialize=False, to='epdb.epmodel')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.epmodel',), + ), + migrations.CreateModel( + name='RuleBaseRelativeReasoning', + fields=[ + ('epmodel_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, + primary_key=True, serialize=False, to='epdb.epmodel')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.epmodel',), + ), + migrations.CreateModel( + name='Group', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(verbose_name='Group name')), + ('public', models.BooleanField(default=False, verbose_name='Public Group')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('group_member', + models.ManyToManyField(related_name='groups_in_group', to='epdb.group', verbose_name='Group member')), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, + verbose_name='Group Owner')), + ('user_member', models.ManyToManyField(related_name='users_in_group', to=settings.AUTH_USER_MODEL, + verbose_name='User members')), + ('url', models.TextField(null=True, verbose_name='URL')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='user', + name='default_group', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, + related_name='default_group', to='epdb.group', verbose_name='Default Group'), + ), + migrations.CreateModel( + name='Node', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', + django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, + verbose_name='Aliases')), + ('depth', models.IntegerField(verbose_name='Node depth')), + ('default_node_label', + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='default_node_structure', + to='epdb.compoundstructure', verbose_name='Default Node Label')), + ('node_labels', models.ManyToManyField(related_name='node_structures', to='epdb.compoundstructure', + verbose_name='All Node Labels')), + ('out_edges', models.ManyToManyField(to='epdb.edge', verbose_name='Outgoing Edges')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='edge', + name='end_nodes', + field=models.ManyToManyField(related_name='edge_products', to='epdb.node', verbose_name='End Nodes'), + ), + migrations.AddField( + model_name='edge', + name='start_nodes', + field=models.ManyToManyField(related_name='edge_educts', to='epdb.node', verbose_name='Start Nodes'), + ), + migrations.CreateModel( + name='Package', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('reviewed', models.BooleanField(default=False, verbose_name='Reviewstatus')), + ('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, + to='epdb.license', verbose_name='License')), + ('url', models.TextField(null=True, verbose_name='URL')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='epmodel', + name='package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', + verbose_name='Package'), + ), + migrations.AddField( + model_name='compound', + name='package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', + verbose_name='Package'), + ), + migrations.AddField( + model_name='user', + name='default_package', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.package', + verbose_name='Default Package'), + ), + migrations.CreateModel( + name='SequentialRule', + fields=[ + ('rule_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, + primary_key=True, serialize=False, to='epdb.rule')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.rule',), + ), + migrations.CreateModel( + name='SimpleRule', + fields=[ + ('rule_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, + primary_key=True, serialize=False, to='epdb.rule')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.rule',), + ), + migrations.AddField( + model_name='rule', + name='package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', + verbose_name='Package'), + ), + migrations.AddField( + model_name='rule', + name='polymorphic_ctype', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, + related_name='polymorphic_%(app_label)s.%(class)s_set+', + to='contenttypes.contenttype'), + ), + migrations.CreateModel( + name='Pathway', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', + django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, + verbose_name='Aliases')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', + verbose_name='Package')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='node', + name='pathway', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', + verbose_name='belongs to'), + ), + migrations.AddField( + model_name='edge', + name='pathway', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', + verbose_name='belongs to'), + ), + migrations.CreateModel( + name='Reaction', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('aliases', + django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, + verbose_name='Aliases')), + ('multi_step', models.BooleanField(verbose_name='Multistep Reaction')), + ('medline_references', + django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), null=True, size=None, + verbose_name='Medline References')), + ('educts', models.ManyToManyField(related_name='reaction_educts', to='epdb.compoundstructure', + verbose_name='Educts')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', + verbose_name='Package')), + ('products', models.ManyToManyField(related_name='reaction_products', to='epdb.compoundstructure', + verbose_name='Products')), + ('rules', models.ManyToManyField(related_name='reaction_rule', to='epdb.rule', verbose_name='Rule')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='edge', + name='edge_label', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.reaction', + verbose_name='Edge label'), + ), + migrations.CreateModel( + name='Scenario', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('scenario_date', models.CharField(default='No date', max_length=256)), + ('scenario_type', models.CharField(default='Not specified', max_length=256)), + ('additional_information', models.JSONField(verbose_name='Additional Information')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', + verbose_name='Package')), + ('parent', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, + to='epdb.scenario')), + ('url', models.TextField(null=True, verbose_name='URL')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='rule', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='reaction', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='pathway', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='node', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='edge', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='compoundstructure', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.AddField( + model_name='compound', + name='scenarios', + field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'), + ), + migrations.CreateModel( + name='Setting', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('public', models.BooleanField(default=False)), + ('global_default', models.BooleanField(default=False)), + ('max_depth', models.IntegerField(default=5, verbose_name='Setting Max Depth')), + ('max_nodes', models.IntegerField(default=30, verbose_name='Setting Max Number of Nodes')), + ('model_threshold', + models.FloatField(blank=True, default=0.25, null=True, verbose_name='Setting Model Threshold')), + ('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, + to='epdb.epmodel', verbose_name='Setting EPModel')), + ('rule_packages', + models.ManyToManyField(blank=True, related_name='setting_rule_packages', to='epdb.package', + verbose_name='Setting Rule Packages')), + ('url', models.TextField(null=True, verbose_name='URL')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='pathway', + name='setting', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, + to='epdb.setting', verbose_name='Setting'), + ), + migrations.AddField( + model_name='user', + name='default_setting', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.setting', + verbose_name='The users default settings'), + ), + migrations.CreateModel( + name='MLRelativeReasoning', + fields=[ + ('epmodel_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, + primary_key=True, serialize=False, to='epdb.epmodel')), + ('threshold', models.FloatField(default=0.5)), + ('model_status', models.CharField( + choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), + ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', + 'Model is built and can be used for predictions, Model is not evaluated yet.'), + ('EVALUATING', 'Model is evaluating'), + ('FINISHED', 'Model has finished building and evaluation.'), + ('ERROR', 'Model has failed.')], default='INITIAL')), + ('eval_results', models.JSONField(blank=True, default=dict, null=True)), + ('data_packages', + models.ManyToManyField(related_name='data_packages', to='epdb.package', verbose_name='Data Packages')), + ('eval_packages', models.ManyToManyField(related_name='eval_packages', to='epdb.package', + verbose_name='Evaluation Packages')), + ('rule_packages', + models.ManyToManyField(related_name='rule_packages', to='epdb.package', verbose_name='Rule Packages')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.epmodel',), + ), + migrations.CreateModel( + name='SimpleAmbitRule', + fields=[ + ('simplerule_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, + primary_key=True, serialize=False, to='epdb.simplerule')), + ('smirks', models.TextField(verbose_name='SMIRKS')), + ('reactant_filter_smarts', models.TextField(null=True, verbose_name='Reactant Filter SMARTS')), + ('product_filter_smarts', models.TextField(null=True, verbose_name='Product Filter SMARTS')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.simplerule',), + ), + migrations.CreateModel( + name='SimpleRDKitRule', + fields=[ + ('simplerule_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, + primary_key=True, serialize=False, to='epdb.simplerule')), + ('reaction_smarts', models.TextField(verbose_name='SMIRKS')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.simplerule',), + ), + migrations.CreateModel( + name='SequentialRuleOrdering', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order_index', models.IntegerField()), + ('sequential_rule', + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.sequentialrule')), + ('simple_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.simplerule')), + ], + ), + migrations.AddField( + model_name='sequentialrule', + name='simple_rules', + field=models.ManyToManyField(through='epdb.SequentialRuleOrdering', to='epdb.simplerule', + verbose_name='Simple rules'), + ), + migrations.CreateModel( + name='ParallelRule', + fields=[ + ('rule_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, + primary_key=True, serialize=False, to='epdb.rule')), + ('simple_rules', models.ManyToManyField(to='epdb.simplerule', verbose_name='Simple rules')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('epdb.rule',), + ), + migrations.AlterUniqueTogether( + name='compound', + unique_together={('uuid', 'package')}, + ), + migrations.CreateModel( + name='GroupPackagePermission', + fields=[ + ('permission_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, + to='epdb.permission')), + ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, + verbose_name='UUID of this object')), + ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.group', + verbose_name='Permission to')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', + verbose_name='Permission on')), + ], + options={ + 'unique_together': {('package', 'group')}, + }, + bases=('epdb.permission',), + ), + migrations.CreateModel( + name='UserPackagePermission', + fields=[ + ('permission_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, + to='epdb.permission')), + ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, + verbose_name='UUID of this object')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', + verbose_name='Permission on')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, + verbose_name='Permission to')), + ], + options={ + 'unique_together': {('package', 'user')}, + }, + bases=('epdb.permission',), + ), + migrations.CreateModel( + name='UserSettingPermission', + fields=[ + ('permission_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, + to='epdb.permission')), + ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, + verbose_name='UUID of this object')), + ('setting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', + verbose_name='Permission on')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, + verbose_name='Permission to')), + ], + options={ + 'unique_together': {('setting', 'user')}, + }, + bases=('epdb.permission',), + ), + migrations.CreateModel( + name='ExternalDatabase', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('name', models.CharField(max_length=100, unique=True, verbose_name='Database Name')), + ('full_name', models.CharField(blank=True, max_length=255, verbose_name='Full Database Name')), + ('description', models.TextField(blank=True, verbose_name='Description')), + ('base_url', models.URLField(blank=True, null=True, verbose_name='Base URL')), + ('url_pattern', models.CharField(blank=True, + help_text="URL pattern with {id} placeholder, e.g., 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'", + max_length=500, verbose_name='URL Pattern')), + ('is_active', models.BooleanField(default=True, verbose_name='Is Active')), + ], + options={ + 'verbose_name': 'External Database', + 'verbose_name_plural': 'External Databases', + 'db_table': 'epdb_external_database', + 'ordering': ['name'], + }, + ), + migrations.AlterModelOptions( + name='edge', + options={}, + ), + migrations.RemoveField( + model_name='edge', + name='polymorphic_ctype', + ), + migrations.CreateModel( + name='ApplicabilityDomain', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')), + ('name', models.TextField(default='no name', verbose_name='Name')), + ('description', models.TextField(default='no description', verbose_name='Descriptions')), + ('kv', models.JSONField(blank=True, default=dict, null=True)), + ('num_neighbours', models.IntegerField(default=5)), + ('reliability_threshold', models.FloatField(default=0.5)), + ('local_compatibilty_threshold', models.FloatField(default=0.5)), + ('model', + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.mlrelativereasoning')), + ('functional_groups', models.JSONField(blank=True, default=dict, null=True)), + ('url', models.TextField(null=True, verbose_name='URL')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='mlrelativereasoning', + name='app_domain', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, + to='epdb.applicabilitydomain'), + ), + migrations.CreateModel( + name='APIToken', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('hashed_key', + models.CharField(help_text='SHA-256 hash of the token key', max_length=128, unique=True)), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('expires_at', + models.DateTimeField(blank=True, help_text='Token expiration time (null for no expiration)', + null=True)), + ('name', models.CharField(help_text='Descriptive name for this token', max_length=100)), + ('user', + models.ForeignKey(help_text='User who owns this token', on_delete=django.db.models.deletion.CASCADE, + related_name='api_tokens', to=settings.AUTH_USER_MODEL)), + ('is_active', models.BooleanField(default=True, help_text='Whether this token is active')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ], + options={ + 'ordering': ['-created'], + 'verbose_name': 'API Token', + 'verbose_name_plural': 'API Tokens', + 'db_table': 'epdb_api_token', + }, + ), + migrations.CreateModel( + name='ExternalIdentifier', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, + verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('object_id', models.IntegerField()), + ('identifier_value', models.CharField(max_length=255, verbose_name='Identifier Value')), + ('url', models.URLField(blank=True, null=True, verbose_name='Direct URL')), + ('is_primary', + models.BooleanField(default=False, help_text='Mark this as the primary identifier for this database', + verbose_name='Is Primary')), + ('content_type', + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ('database', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.externaldatabase', + verbose_name='External Database')), + ], + options={ + 'verbose_name': 'External Identifier', + 'verbose_name_plural': 'External Identifiers', + 'db_table': 'epdb_external_identifier', + 'indexes': [models.Index(fields=['content_type', 'object_id'], name='epdb_extern_content_b76813_idx'), + models.Index(fields=['database', 'identifier_value'], + name='epdb_extern_databas_486422_idx')], + 'unique_together': {('content_type', 'object_id', 'database', 'identifier_value')}, + }, + ), + migrations.AddField( + model_name='compound', + name='url', + field=models.TextField(null=True, verbose_name='URL'), + ), + migrations.AddField( + model_name='compoundstructure', + name='url', + field=models.TextField(null=True, verbose_name='URL'), + ), + migrations.AddField( + model_name='edge', + name='url', + field=models.TextField(null=True, verbose_name='URL'), + ), + migrations.AddField( + model_name='epmodel', + name='url', + field=models.TextField(null=True, verbose_name='URL'), + ), + migrations.AddField( + model_name='node', + name='url', + field=models.TextField(null=True, verbose_name='URL'), + ), + migrations.AddField( + model_name='pathway', + name='url', + field=models.TextField(null=True, verbose_name='URL'), + ), + migrations.AddField( + model_name='reaction', + name='url', + field=models.TextField(null=True, verbose_name='URL'), + ), + migrations.AddField( + model_name='rule', + name='url', + field=models.TextField(null=True, verbose_name='URL'), + ), + migrations.AddField( + model_name='user', + name='url', + field=models.TextField(null=True, verbose_name='URL'), + ), + migrations.RunPython( + code=populate_url, + reverse_code=django.db.migrations.operations.special.RunPython.noop, + ), + migrations.AlterField( + model_name='applicabilitydomain', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='compound', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='compoundstructure', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='edge', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='epmodel', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='group', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='node', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='package', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='pathway', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='reaction', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='rule', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='scenario', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='setting', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='user', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + ] diff --git a/epdb/migrations/0002_externaldatabase_alter_apitoken_options_and_more.py b/epdb/migrations/0002_externaldatabase_alter_apitoken_options_and_more.py new file mode 100644 index 00000000..44215433 --- /dev/null +++ b/epdb/migrations/0002_externaldatabase_alter_apitoken_options_and_more.py @@ -0,0 +1,128 @@ +# Generated by Django 5.2.1 on 2025-08-25 18:07 + +import django.db.models.deletion +import django.utils.timezone +import model_utils.fields +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('epdb', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='ExternalDatabase', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('name', models.CharField(max_length=100, unique=True, verbose_name='Database Name')), + ('full_name', models.CharField(blank=True, max_length=255, verbose_name='Full Database Name')), + ('description', models.TextField(blank=True, verbose_name='Description')), + ('base_url', models.URLField(blank=True, null=True, verbose_name='Base URL')), + ('url_pattern', models.CharField(blank=True, help_text="URL pattern with {id} placeholder, e.g., 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'", max_length=500, verbose_name='URL Pattern')), + ('is_active', models.BooleanField(default=True, verbose_name='Is Active')), + ], + options={ + 'verbose_name': 'External Database', + 'verbose_name_plural': 'External Databases', + 'db_table': 'epdb_external_database', + 'ordering': ['name'], + }, + ), + migrations.AlterModelOptions( + name='apitoken', + options={'ordering': ['-created'], 'verbose_name': 'API Token', 'verbose_name_plural': 'API Tokens'}, + ), + migrations.AlterModelOptions( + name='edge', + options={}, + ), + migrations.RemoveField( + model_name='edge', + name='polymorphic_ctype', + ), + migrations.AddField( + model_name='apitoken', + name='is_active', + field=models.BooleanField(default=True, help_text='Whether this token is active'), + ), + migrations.AddField( + model_name='apitoken', + name='modified', + field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'), + ), + migrations.AddField( + model_name='applicabilitydomain', + name='functional_groups', + field=models.JSONField(blank=True, default=dict, null=True), + ), + migrations.AddField( + model_name='mlrelativereasoning', + name='app_domain', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain'), + ), + migrations.AlterField( + model_name='apitoken', + name='created', + field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'), + ), + migrations.AlterField( + model_name='apitoken', + name='expires_at', + field=models.DateTimeField(blank=True, help_text='Token expiration time (null for no expiration)', null=True), + ), + migrations.AlterField( + model_name='apitoken', + name='hashed_key', + field=models.CharField(help_text='SHA-256 hash of the token key', max_length=128, unique=True), + ), + migrations.AlterField( + model_name='apitoken', + name='name', + field=models.CharField(help_text='Descriptive name for this token', max_length=100), + ), + migrations.AlterField( + model_name='apitoken', + name='user', + field=models.ForeignKey(help_text='User who owns this token', on_delete=django.db.models.deletion.CASCADE, related_name='api_tokens', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='applicabilitydomain', + name='num_neighbours', + field=models.IntegerField(default=5), + ), + migrations.AlterModelTable( + name='apitoken', + table='epdb_api_token', + ), + migrations.CreateModel( + name='ExternalIdentifier', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('object_id', models.IntegerField()), + ('identifier_value', models.CharField(max_length=255, verbose_name='Identifier Value')), + ('url', models.URLField(blank=True, null=True, verbose_name='Direct URL')), + ('is_primary', models.BooleanField(default=False, help_text='Mark this as the primary identifier for this database', verbose_name='Is Primary')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ('database', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.externaldatabase', verbose_name='External Database')), + ], + options={ + 'verbose_name': 'External Identifier', + 'verbose_name_plural': 'External Identifiers', + 'db_table': 'epdb_external_identifier', + 'indexes': [models.Index(fields=['content_type', 'object_id'], name='epdb_extern_content_b76813_idx'), models.Index(fields=['database', 'identifier_value'], name='epdb_extern_databas_486422_idx')], + 'unique_together': {('content_type', 'object_id', 'database', 'identifier_value')}, + }, + ), + ] diff --git a/epdb/migrations/0003_applicabilitydomain_url_compound_url_and_more.py b/epdb/migrations/0003_applicabilitydomain_url_compound_url_and_more.py new file mode 100644 index 00000000..6c60499d --- /dev/null +++ b/epdb/migrations/0003_applicabilitydomain_url_compound_url_and_more.py @@ -0,0 +1,229 @@ +# Generated by Django 5.2.1 on 2025-08-26 17:05 + +from django.db import migrations, models + + +def populate_url(apps, schema_editor): + MODELS = [ + 'User', + 'Group', + 'Package', + 'Compound', + 'CompoundStructure', + 'Pathway', + 'Edge', + 'Node', + 'Reaction', + 'SimpleAmbitRule', + 'SimpleRDKitRule', + 'ParallelRule', + 'SequentialRule', + 'Scenario', + 'Setting', + 'MLRelativeReasoning', + 'EnviFormer', + 'ApplicabilityDomain', + ] + for model in MODELS: + obj_cls = apps.get_model("epdb", model) + print(f"Populating url for {model}") + for obj in obj_cls.objects.all(): + obj.url = assemble_url(obj) + if obj.url is None: + raise ValueError(f"Could not assemble url for {obj}") + obj.save() + + +def assemble_url(obj): + from django.conf import settings as s + match obj.__class__.__name__: + case 'User': + return '{}/user/{}'.format(s.SERVER_URL, obj.uuid) + case 'Group': + return '{}/user/{}'.format(s.SERVER_URL, obj.uuid) + case 'Package': + return '{}/package/{}'.format(s.SERVER_URL, obj.uuid) + case 'Compound': + return '{}/compound/{}'.format(obj.package.url, obj.uuid) + case 'CompoundStructure': + return '{}/structure/{}'.format(obj.compound.url, obj.uuid) + case 'SimpleAmbitRule': + return '{}/simple-ambit-rule/{}'.format(obj.package.url, obj.uuid) + case 'SimpleRDKitRule': + return '{}/simple-rdkit-rule/{}'.format(obj.package.url, obj.uuid) + case 'ParallelRule': + return '{}/parallel-rule/{}'.format(obj.package.url, obj.uuid) + case 'SequentialRule': + return '{}/sequential-rule/{}'.format(obj.compound.url, obj.uuid) + case 'Reaction': + return '{}/reaction/{}'.format(obj.package.url, obj.uuid) + case 'Pathway': + return '{}/pathway/{}'.format(obj.package.url, obj.uuid) + case 'Node': + return '{}/node/{}'.format(obj.pathway.url, obj.uuid) + case 'Edge': + return '{}/edge/{}'.format(obj.pathway.url, obj.uuid) + case 'MLRelativeReasoning': + return '{}/model/{}'.format(obj.package.url, obj.uuid) + case 'EnviFormer': + return '{}/model/{}'.format(obj.package.url, obj.uuid) + case 'ApplicabilityDomain': + return '{}/model/{}/applicability-domain/{}'.format(obj.model.package.url, obj.model.uuid, obj.uuid) + case 'Scenario': + return '{}/scenario/{}'.format(obj.package.url, obj.uuid) + case 'Setting': + return '{}/setting/{}'.format(s.SERVER_URL, obj.uuid) + case _: + raise ValueError(f"Unknown model {obj.__class__.__name__}") + + +class Migration(migrations.Migration): + dependencies = [ + ('epdb', '0002_externaldatabase_alter_apitoken_options_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='applicabilitydomain', + name='url', + field=models.TextField(null=True, unique=False, verbose_name='URL'), + ), + migrations.AddField( + model_name='compound', + name='url', + field=models.TextField(null=True, unique=False, verbose_name='URL'), + ), + migrations.AddField( + model_name='compoundstructure', + name='url', + field=models.TextField(null=True, unique=False, verbose_name='URL'), + ), + migrations.AddField( + model_name='edge', + name='url', + field=models.TextField(null=True, unique=False, verbose_name='URL'), + ), + migrations.AddField( + model_name='epmodel', + name='url', + field=models.TextField(null=True, unique=False, verbose_name='URL'), + ), + migrations.AddField( + model_name='group', + name='url', + field=models.TextField(null=True, unique=False, verbose_name='URL'), + ), + migrations.AddField( + model_name='node', + name='url', + field=models.TextField(null=True, unique=False, verbose_name='URL'), + ), + migrations.AddField( + model_name='package', + name='url', + field=models.TextField(null=True, unique=False, verbose_name='URL'), + ), + migrations.AddField( + model_name='pathway', + name='url', + field=models.TextField(null=True, unique=False, verbose_name='URL'), + ), + migrations.AddField( + model_name='reaction', + name='url', + field=models.TextField(null=True, unique=False, verbose_name='URL'), + ), + migrations.AddField( + model_name='rule', + name='url', + field=models.TextField(null=True, unique=False, verbose_name='URL'), + ), + migrations.AddField( + model_name='scenario', + name='url', + field=models.TextField(null=True, unique=False, verbose_name='URL'), + ), + migrations.AddField( + model_name='setting', + name='url', + field=models.TextField(null=True, unique=False, verbose_name='URL'), + ), + migrations.AddField( + model_name='user', + name='url', + field=models.TextField(null=True, unique=False, verbose_name='URL'), + ), + + migrations.RunPython(populate_url, reverse_code=migrations.RunPython.noop), + + migrations.AlterField( + model_name='applicabilitydomain', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='compound', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='compoundstructure', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='edge', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='epmodel', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='group', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='node', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='package', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='pathway', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='reaction', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='rule', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='scenario', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='setting', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + migrations.AlterField( + model_name='user', + name='url', + field=models.TextField(null=True, unique=True, verbose_name='URL'), + ), + ] diff --git a/epdb/models.py b/epdb/models.py index a6d4cee1..8b1ce008 100644 --- a/epdb/models.py +++ b/epdb/models.py @@ -37,9 +37,8 @@ logger = logging.getLogger(__name__) class User(AbstractUser): email = models.EmailField(unique=True) - - uuid = models.UUIDField(null=False, blank=False, verbose_name='UUID of this object', unique=True, - default=uuid4) + uuid = models.UUIDField(null=False, blank=False, verbose_name='UUID of this object', unique=True, default=uuid4) + url = models.TextField(blank=False, null=True, verbose_name='URL', unique=True) default_package = models.ForeignKey('epdb.Package', verbose_name='Default Package', null=True, on_delete=models.SET_NULL) default_group = models.ForeignKey('Group', verbose_name='Default Group', null=True, blank=False, @@ -50,8 +49,13 @@ class User(AbstractUser): USERNAME_FIELD = "email" REQUIRED_FIELDS = ['username'] - @property - def url(self): + def save(self, *args, **kwargs): + if not self.url: + self.url = self._url() + + super().save(*args, **kwargs) + + def _url(self): return '{}/user/{}'.format(s.SERVER_URL, self.uuid) def prediction_settings(self): @@ -169,6 +173,7 @@ class APIToken(TimeStampedModel): class Group(TimeStampedModel): uuid = models.UUIDField(null=False, blank=False, verbose_name='UUID of this object', unique=True, default=uuid4) + url = models.TextField(blank=False, null=True, verbose_name='URL', unique=True) name = models.TextField(blank=False, null=False, verbose_name='Group name') owner = models.ForeignKey("User", verbose_name='Group Owner', on_delete=models.CASCADE) public = models.BooleanField(verbose_name='Public Group', default=False) @@ -179,8 +184,13 @@ class Group(TimeStampedModel): def __str__(self): return f"{self.name} (pk={self.pk})" - @property - def url(self): + def save(self, *args, **kwargs): + if not self.url: + self.url = self._url() + + super().save(*args, **kwargs) + + def _url(self): return '{}/group/{}'.format(s.SERVER_URL, self.uuid) @@ -476,11 +486,18 @@ class EnviPathModel(TimeStampedModel): name = models.TextField(blank=False, null=False, verbose_name='Name', default='no name') description = models.TextField(blank=False, null=False, verbose_name='Descriptions', default='no description') + url = models.TextField(blank=False, null=True, verbose_name='URL', unique=True) + kv = JSONField(null=True, blank=True, default=dict) - @property + def save(self, *args, **kwargs): + if not self.url: + self.url = self._url() + + super().save(*args, **kwargs) + @abc.abstractmethod - def url(self): + def _url(self): pass def simple_json(self, include_description=False): @@ -585,8 +602,7 @@ class Package(EnviPathModel): def models(self): return EPModel.objects.filter(package=self) - @property - def url(self): + def _url(self): return '{}/package/{}'.format(s.SERVER_URL, self.uuid) def get_applicable_rules(self): @@ -632,8 +648,7 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin def normalized_structure(self): return CompoundStructure.objects.get(compound=self, normalized_structure=True) - @property - def url(self): + def _url(self): return '{}/compound/{}'.format(self.package.url, self.uuid) @transaction.atomic @@ -773,8 +788,7 @@ class CompoundStructure(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdenti super().save(*args, **kwargs) - @property - def url(self): + def _url(self): return '{}/structure/{}'.format(self.compound.url, self.uuid) @staticmethod @@ -917,8 +931,7 @@ class SimpleAmbitRule(SimpleRule): r.save() return r - @property - def url(self): + def _url(self): return '{}/simple-ambit-rule/{}'.format(self.package.url, self.uuid) def apply(self, smiles): @@ -953,8 +966,7 @@ class SimpleRDKitRule(SimpleRule): def apply(self, smiles): return FormatConverter.apply(smiles, self.reaction_smarts) - @property - def url(self): + def _url(self): return '{}/simple-rdkit-rule/{}'.format(self.package.url, self.uuid) @@ -963,8 +975,7 @@ class SimpleRDKitRule(SimpleRule): class ParallelRule(Rule): simple_rules = models.ManyToManyField('epdb.SimpleRule', verbose_name='Simple rules') - @property - def url(self): + def _url(self): return '{}/parallel-rule/{}'.format(self.package.url, self.uuid) @property @@ -1003,8 +1014,7 @@ class SequentialRule(Rule): simple_rules = models.ManyToManyField('epdb.SimpleRule', verbose_name='Simple rules', through='SequentialRuleOrdering') - @property - def url(self): + def _url(self): return '{}/sequential-rule/{}'.format(self.compound.url, self.uuid) @property @@ -1039,8 +1049,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin external_identifiers = GenericRelation('ExternalIdentifier') - @property - def url(self): + def _url(self): return '{}/reaction/{}'.format(self.package.url, self.uuid) @staticmethod @@ -1167,8 +1176,7 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin): def edges(self): return Edge.objects.filter(pathway=self) - @property - def url(self): + def _url(self): return '{}/pathway/{}'.format(self.package.url, self.uuid) # Mode @@ -1386,8 +1394,7 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin): out_edges = models.ManyToManyField('epdb.Edge', verbose_name='Outgoing Edges') depth = models.IntegerField(verbose_name='Node depth', null=False, blank=False) - @property - def url(self): + def _url(self): return '{}/node/{}'.format(self.pathway.url, self.uuid) def d3_json(self): @@ -1463,8 +1470,7 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin): start_nodes = models.ManyToManyField('epdb.Node', verbose_name='Start Nodes', related_name='edge_educts') end_nodes = models.ManyToManyField('epdb.Node', verbose_name='End Nodes', related_name='edge_products') - @property - def url(self): + def _url(self): return '{}/edge/{}'.format(self.pathway.url, self.uuid) def d3_json(self): @@ -1556,8 +1562,7 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin): class EPModel(PolymorphicModel, EnviPathModel): package = models.ForeignKey('epdb.Package', verbose_name='Package', on_delete=models.CASCADE, db_index=True) - @property - def url(self): + def _url(self): return '{}/model/{}'.format(self.package.url, self.uuid) @@ -2173,8 +2178,7 @@ class Scenario(EnviPathModel): additional_information = models.JSONField(verbose_name='Additional Information') - @property - def url(self): + def _url(self): return '{}/scenario/{}'.format(self.package.url, self.uuid) @staticmethod @@ -2238,8 +2242,7 @@ class Setting(EnviPathModel): blank=True) model_threshold = models.FloatField(null=True, blank=True, verbose_name='Setting Model Threshold', default=0.25) - @property - def url(self): + def _url(self): return '{}/setting/{}'.format(s.SERVER_URL, self.uuid) @cached_property diff --git a/epdb/views.py b/epdb/views.py index e1ffd681..b8c02c56 100644 --- a/epdb/views.py +++ b/epdb/views.py @@ -439,16 +439,7 @@ def scenarios(request): if request.GET.get('all'): return JsonResponse({ "objects": [ - {"name": s.name, "url": s.full_url, "reviewed": True} - for s in reviewed_scenario_qs.annotate( - full_url=Concat( - Value(s.SERVER_URL + '/package/'), - F("package__uuid"), - Value("/scenario/"), - F("uuid"), - output_field=CharField(), - ) - ) + {"name": s.name, "url": s.url, "reviewed": True} for s in reviewed_scenario_qs ] })