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 <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#63
This commit is contained in:
2025-08-27 06:46:09 +12:00
parent 6a4c8d96c3
commit 13816ecaf3
7 changed files with 2063 additions and 47 deletions

View File

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