You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

481 lines
14 KiB
Python

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} "
f"{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 Price(models.Model):
"""
Price of a drug.
"""
user = models.OneToOneField(User, on_delete=models.CASCADE)
amount_mg = models.IntegerField()
price_gbp = models.FloatField()
note = models.CharField(max_length=255, blank=True, null=True)
# class Stack: references, times
# class StackUnit: reference, times, dose_mg
class Favourite(models.Model):
"""
Model of a drug. Owned by a user and customisable.
"""
user = models.OneToOneField(User, on_delete=models.CASCADE)
# Nickname
nickname = models.CharField(max_length=255, blank=True, null=True)
# Prices, how much certain mass of this substance costs
prices = models.ManyToManyField(Price, blank=True, null=True)
# Internals
original = models.ForeignKey(Drug, on_delete=models.SET_NULL, blank=True, null=True)
# Below duplicates Drug
# Lysergic acid diethylamide, Phenibut
name = models.CharField(max_length=255)
# 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.nickname}] ({self.common_name})"
# class Perms(models.Model):
# class Meta:
# permissions = (
# ("permission_name", "Permission description"),
# )