import logging import uuid from django.contrib.auth.models import AbstractUser from django.db import models logger = logging.getLogger(__name__) SITE_CHOICES = ( ("5HT2C", "5-HT2C"), ("5HT2A", "5-HT2A"), ("GABAB", "GABAB"), ("NMDA", "NMDA"), ) MECHANISM_CHOICES = ( ("AGONISM", "Agonism"), ("ANTAGONISM", "Antagomism"), ("MODULATION", "Modulation"), ) ROA_CHOICES = ( ("ORAL", "Oral"), ("SMOKED", "Smoked/vaped"), ("INSUFFLATED", "Insufflated"), ("INJECTED", "Injected"), ("SUBLINGUAL", "Sublingual"), ("TRANSDERMAL", "Transdermal"), ("RECTAL", "Rectal"), ) SOURCE_TYPE_CHOICES = ( ("PSITE", "Professional pharmaceutical data repository"), ("DWIKI", "Dedicated peer-reviewed community wiki"), ("CWIKI", "Peer-reviewed community wiki"), ("WIKI", "Private wiki"), ("DFORUM", "Dedicated community forum"), ("FORUM", "Community forum"), ) DOSAGE_UNIT_CHOICES = ( ("mg", "mg"), ("g", "g"), ("ug", "ug"), ) DOSAGE_TIMING_CHOICES = ( ("SECONDS", "Seconds"), ("MINUTES", "Minutes"), ("HOURS", "Hours"), ("DAYS", "Days :D"), ("WEEKS", "Weeks :O"), ("MONTHS", "Months :-|"), ("YEARS", "Years x_X"), ) SEI_TYPE_CHOICES = ( ("PHYSICAL", "Physical"), ("COGNITIVE", "Cognitive"), ("VISUAL", "Visual"), ("AUDITORY", "Auditory"), ("MULTISENSORY", "Multi-sensory"), ("TRANSPERSONAL", "Transpersonal"), ) SEI_SUBTYPE_CHOICES = ( # Common psychedelic subtypes ("ENTACTOGENIC", "Entactogenic (touch-enhancing)"), ("ENTHEOGENIC", "Entheogenic (spirituality-enhancing)"), ("PSYCHEDELIC", "Psychedelic (mind-manifesting)"), ("DISSOCIATIVE", "Dissociative"), ("HALLUCINOGENIC", "Hallucinogenic (hallucination-inducing)"), # Unpleasant psychedelic-esque subtypes ("DELIRIANT", "Deliriant"), ("PSYCHOTOMIMETIC", "Psychotomimetic (psychosis-inducing)"), # Common recreational drug subtypes ("STIMULATING", "Stimulating"), ("SEDATING", "Sedating"), ("DEPRESSANT", "Depressant"), ("EUPHORIC", "Euphoric"), # Common pharmaceutical subtypes ("ANXIOLYTIC", "Anxiolytic"), ("ANTIPSYCHOTIC", "Antipsychotic"), # Common nootropic subtypes ("PSYCHOSTIMULANT", "Psychostimulant"), ("EUGEROIC", "Eugeroic (wakefulness-promoting)"), ("NOOTROPIC", "Nootropic"), ) class User(AbstractUser): # Stripe customer ID stripe_id = models.CharField(max_length=255, null=True, blank=True) customer_id = models.UUIDField(default=uuid.uuid4, null=True, blank=True) billing_provider_id = models.CharField(max_length=255, null=True, blank=True) email = models.EmailField(unique=True) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._original = self def get_notification_settings(self): return NotificationSettings.objects.get_or_create(user=self)[0] class NotificationSettings(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) ntfy_topic = models.CharField(max_length=255, null=True, blank=True) ntfy_url = models.CharField(max_length=255, null=True, blank=True) def __str__(self): return f"Notification settings for {self.user}" class Source(models.Model): """ A source of information. Any information. """ name = models.CharField(max_length=255, unique=True) type = models.CharField(max_length=255, choices=SOURCE_TYPE_CHOICES) # Base endpoint for automated crawler processes endpoint = models.CharField(max_length=1024, blank=True, null=True) # Score, affects ordering score = models.IntegerField(blank=True) def __str__(self): return f"{self.name} ({self.type})" class Entry(models.Model): """ A snippet of information located on a Source. Used to gather conflicting information and store it coherently. """ source = models.ForeignKey(Source, on_delete=models.CASCADE) # Slug of the article on the Source url = models.CharField(max_length=1024, null=True, blank=True) # Authorship information, if present author = models.CharField(max_length=255, null=True, blank=True) # Extra information can be added description = models.CharField(max_length=1024, null=True, blank=True) def __str__(self): return f"{self.source.name} - {self.url}" class Dosage(models.Model): """ Registers the correlation between dose and intensity. Linked to Entry to analyse conflicting records. """ entry = models.ForeignKey("core.Entry", on_delete=models.CASCADE) roa = models.CharField(max_length=255, choices=ROA_CHOICES) # Unit of mass as drugs are diverse # Dosages varying between micrograms and grams unit = models.CharField(max_length=255, choices=DOSAGE_UNIT_CHOICES) # I can no longer say I am sober, but it is slight threshold_lower = models.FloatField(null=True, blank=True) threshold_upper = models.FloatField(null=True, blank=True) # Light light_lower = models.FloatField(null=True, blank=True) light_upper = models.FloatField(null=True, blank=True) # Average dose for a user common_lower = models.FloatField(null=True, blank=True) common_upper = models.FloatField(null=True, blank=True) # Strong intensity, many sober activities may become impossible strong_lower = models.FloatField(null=True, blank=True) strong_upper = models.FloatField(null=True, blank=True) # Highest intensity heavy_lower = models.FloatField(null=True, blank=True) heavy_upper = models.FloatField(null=True, blank=True) def __str__(self): text = ( f"{self.threshold_lower} {self.light_lower} {self.common_lower} " "{self.strong_lower} {self.heavy_lower}" ) return f"{self.roa} {text} ({self.unit})" class Timing(models.Model): """ Registers the time between administration to various experience levels. Linked to Entry to analyse conflicting records. """ entry = models.ForeignKey("core.Entry", on_delete=models.CASCADE) roa = models.CharField(max_length=255, choices=ROA_CHOICES) # Unit of time as drugs are diverse # Half-lives varying between seconds and months unit = models.CharField( max_length=255, choices=DOSAGE_TIMING_CHOICES, default="HOURS" ) # It has just now begin, I can no longer say I am sober onset_lower = models.FloatField(null=True, blank=True) onset_upper = models.FloatField(null=True, blank=True) # The intensity is accelerating comeup_lower = models.FloatField(null=True, blank=True) comeup_upper = models.FloatField(null=True, blank=True) # The maximum intensity has been reached # How long this state occurs peak_lower = models.FloatField(null=True, blank=True) peak_upper = models.FloatField(null=True, blank=True) # How long it takes to get back to baseline offset_lower = models.FloatField(null=True, blank=True) offset_upper = models.FloatField(null=True, blank=True) total_lower = models.FloatField(null=True, blank=True) total_upper = models.FloatField(null=True, blank=True) def __str__(self): return f"{self.roa} {self.unit} {self.total_lower}-{self.total_upper}" class SEI(models.Model): """ Subjective Effect Index from Psychonaut Wiki. Registers a subjective effect in a category, allowing a description to be specified. """ # PHYSICAL, COGNITIVE, etc # type = models.CharField( # max_length=255, choices=SEI_TYPE_CHOICES, default="PHYSICAL" # ) # subtype = models.CharField( # max_length=255, # choices=SEI_SUBTYPE_CHOICES, # ) # WIP: consider euphoric, depressant, relaxant name = models.CharField(max_length=255) url = models.CharField(max_length=1024, blank=True, null=True) # Specify how # description = models.CharField(max_length=4096, blank=True, null=True) def __str__(self): return f"{self.name}" class Effect(models.Model): """ Registers subjective effects for a drug. Linked to multiple subjective effect indexes all from the same entry. Linked to Entry to analyse conflicting records. """ entry = models.ForeignKey("core.Entry", on_delete=models.CASCADE) # List of subjective effects, since they would likely be from the same entry subjective_effects = models.ManyToManyField(SEI) def __str__(self): return f"{self.entry} {self.subjective_effects}" class Action(models.Model): """ Site action record for a drug. Registers a certain kind of action from a list of receptor sites with a given affinity. Linked to Entry to analyse conflicting records. """ entry = models.ForeignKey("core.Entry", on_delete=models.CASCADE) # Site - like 5HT2A for LSD site = models.CharField(max_length=255, choices=SITE_CHOICES) # Usually agonist or antagonist mechanism = models.CharField(max_length=255, choices=MECHANISM_CHOICES) # Free integer for binding affinity affinity = models.IntegerField(blank=True) # For MAOI, SSRI reversible = models.BooleanField(blank=True, null=True) # MAOI A (I) # Site: Monoamine Oxidase A # Mechanism: Inhibition # Affinity: Permanent (999999) # Moclobemide # Site: Monoamine Oxidase A # Mechanism: Inhibition # Affinity: Reversible (1) def __str__(self): return f"{self.site} {self.mechanism} {self.affinity}" class ExperienceDose(models.Model): # Seconds since the start of the experiment # To be converted to T+00:22 format seconds_since_start = models.IntegerField() # Dosage, integer dose = models.IntegerField() # Dose per kg of body weight dose_per_kg = models.IntegerField() # Unit of mass as drugs are diverse unit = models.CharField(max_length=255, choices=DOSAGE_UNIT_CHOICES) # Route of administration, oral, smoked, injected, etc roa = models.CharField(max_length=255, choices=ROA_CHOICES) # The drug that was taken, linked to our database drug = models.ForeignKey("core.Drug", on_delete=models.CASCADE) # The form of the drug: pills, powder, crystals, tabs, etc form = models.CharField(max_length=255) # TODO: Subjective effects (use AI/ML) # TODO: Sentiment analysis (use AI/ML) # TODO: Time-based effects (use AI/ML) def __str__(self): return f"{self.form} {self.dose} {self.unit} {self.roa}" class Experience(models.Model): """ Registers a subjective experience from a user. """ # Where did the data come from? entry = models.ForeignKey("core.Entry", on_delete=models.CASCADE) # List of the doses and drugs taken doses = models.ManyToManyField(ExperienceDose) # Erowid-specific fields body_weight_kg = models.IntegerField(blank=True, null=True) year_of_experience = models.IntegerField(blank=True, null=True) gender = models.CharField(max_length=255, blank=True, null=True) age_at_experience = models.IntegerField(blank=True, null=True) date_published = models.DateTimeField(blank=True, null=True) views = models.IntegerField(blank=True, null=True) date_crawled = models.DateTimeField(blank=True, null=True) tags = models.CharField(max_length=255, blank=True, null=True) experience_id = models.IntegerField(blank=True, null=True) # Description of the experience text = models.TextField() def __str__(self): return f"Experience ({len(self.text)} chars)" class Drug(models.Model): """ Model of a drug. Not open to interpretation. """ # Lysergic acid diethylamide, Phenibut name = models.CharField(max_length=255, unique=True) # Psychedelic, Sedative, Stimulant drug_class = models.CharField(max_length=255, blank=True, null=True) # LSD common_name = models.CharField(max_length=1024, blank=True, null=True) # Factsheets, posts links = models.ManyToManyField(Entry, blank=True) # Dosages, how much to take to get a certain effect dosages = models.ManyToManyField(Dosage, blank=True) # Timings, how long to wait to reach maximum intensity (and others) timings = models.ManyToManyField(Timing, blank=True) # Effects, what does it do on a subjective level? effects = models.ManyToManyField(Effect, blank=True) # Actions, what does it do on an objective level? actions = models.ManyToManyField(Action, blank=True) # Experiences, what do people experience? experiences = models.ManyToManyField(Experience, blank=True) def __str__(self): return f"{self.name} ({self.common_name})" # class Perms(models.Model): # class Meta: # permissions = ( # ("permission_name", "Permission description"), # )