Add CRUD for Drug
This commit is contained in:
parent
0249b66be7
commit
37534b31bf
23
app/urls.py
23
app/urls.py
|
@ -20,7 +20,7 @@ from django.contrib.auth.views import LogoutView
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from two_factor.urls import urlpatterns as tf_urls
|
from two_factor.urls import urlpatterns as tf_urls
|
||||||
|
|
||||||
from core.views import base, demo, notifications
|
from core.views import base, demo, drugs, notifications
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("__debug__/", include("debug_toolbar.urls")),
|
path("__debug__/", include("debug_toolbar.urls")),
|
||||||
|
@ -40,4 +40,25 @@ urlpatterns = [
|
||||||
notifications.NotificationsUpdate.as_view(),
|
notifications.NotificationsUpdate.as_view(),
|
||||||
name="notifications_update",
|
name="notifications_update",
|
||||||
),
|
),
|
||||||
|
path("drugs/<str:type>/", drugs.DrugList.as_view(), name="drugs"),
|
||||||
|
path(
|
||||||
|
"drugs/<str:type>/create/",
|
||||||
|
drugs.DrugCreate.as_view(),
|
||||||
|
name="drug_create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"drugs/<str:type>/update/<str:pk>/",
|
||||||
|
drugs.DrugUpdate.as_view(),
|
||||||
|
name="drug_update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"drugs/<str:type>/delete/<str:pk>/",
|
||||||
|
drugs.DrugDelete.as_view(),
|
||||||
|
name="drug_delete",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"drugs/clear/all/",
|
||||||
|
drugs.DrugClear.as_view(),
|
||||||
|
name="drug_clear",
|
||||||
|
),
|
||||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import stripe
|
# import stripe
|
||||||
from django.conf import settings
|
# from django.conf import settings
|
||||||
|
|
||||||
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
||||||
# from redis import StrictRedis
|
# from redis import StrictRedis
|
||||||
|
|
||||||
# r = StrictRedis(unix_socket_path="/var/run/redis/redis.sock", db=0)
|
# r = StrictRedis(unix_socket_path="/var/run/redis/redis.sock", db=0)
|
||||||
|
|
||||||
if settings.STRIPE_TEST:
|
# if settings.STRIPE_TEST:
|
||||||
stripe.api_key = settings.STRIPE_API_KEY_TEST
|
# stripe.api_key = settings.STRIPE_API_KEY_TEST
|
||||||
else:
|
# else:
|
||||||
stripe.api_key = settings.STRIPE_API_KEY_PROD
|
# stripe.api_key = settings.STRIPE_API_KEY_PROD
|
||||||
|
|
|
@ -2,7 +2,20 @@ from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
|
|
||||||
from .forms import CustomUserCreationForm
|
from .forms import CustomUserCreationForm
|
||||||
from .models import NotificationSettings, User
|
from .models import (
|
||||||
|
SEI,
|
||||||
|
Action,
|
||||||
|
Dosage,
|
||||||
|
Drug,
|
||||||
|
Effect,
|
||||||
|
Entry,
|
||||||
|
Experience,
|
||||||
|
ExperienceDose,
|
||||||
|
NotificationSettings,
|
||||||
|
Source,
|
||||||
|
Timing,
|
||||||
|
User,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
|
@ -34,3 +47,13 @@ class NotificationSettingsAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
admin.site.register(User, CustomUserAdmin)
|
admin.site.register(User, CustomUserAdmin)
|
||||||
admin.site.register(NotificationSettings, NotificationSettingsAdmin)
|
admin.site.register(NotificationSettings, NotificationSettingsAdmin)
|
||||||
|
admin.site.register(Drug)
|
||||||
|
admin.site.register(Entry)
|
||||||
|
admin.site.register(Dosage)
|
||||||
|
admin.site.register(Timing)
|
||||||
|
admin.site.register(Effect)
|
||||||
|
admin.site.register(Action)
|
||||||
|
admin.site.register(Experience)
|
||||||
|
admin.site.register(Source)
|
||||||
|
admin.site.register(SEI)
|
||||||
|
admin.site.register(ExperienceDose)
|
||||||
|
|
|
@ -3,7 +3,9 @@ from django.contrib.auth.forms import UserCreationForm
|
||||||
from django.forms import ModelForm
|
from django.forms import ModelForm
|
||||||
from mixins.restrictions import RestrictedFormMixin
|
from mixins.restrictions import RestrictedFormMixin
|
||||||
|
|
||||||
from .models import NotificationSettings, User
|
from mxs.restrictions import RestrictedFormMixinStaff
|
||||||
|
|
||||||
|
from .models import Drug, NotificationSettings, User
|
||||||
|
|
||||||
# Create your forms here.
|
# Create your forms here.
|
||||||
|
|
||||||
|
@ -47,3 +49,32 @@ class CustomUserCreationForm(UserCreationForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class DrugForm(RestrictedFormMixinStaff, ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Drug
|
||||||
|
fields = (
|
||||||
|
"name",
|
||||||
|
"drug_class",
|
||||||
|
"common_name",
|
||||||
|
"links",
|
||||||
|
"dosages",
|
||||||
|
"timings",
|
||||||
|
"effects",
|
||||||
|
"actions",
|
||||||
|
"experiences",
|
||||||
|
)
|
||||||
|
help_texts = {
|
||||||
|
"name": "Lysergic acid diethylamide, Phenibut",
|
||||||
|
"drug_class": "Psychedelic, Sedative, Stimulant",
|
||||||
|
"common_name": "LSD",
|
||||||
|
"links": "Factsheets, posts",
|
||||||
|
"dosages": "Dosages, how much to take to get a certain effect",
|
||||||
|
"timings": (
|
||||||
|
"Timings, how long to wait to reach maximum intensity (and " "others)"
|
||||||
|
),
|
||||||
|
"effects": "Effects, what does it do on a subjective level?",
|
||||||
|
"actions": "Actions, what does it do on an objective level?",
|
||||||
|
"experiences": "Experiences, what do people experience?",
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
# Generated by Django 4.2.8 on 2024-01-01 17:37
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Action',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('site', models.CharField(choices=[('5HT2C', '5-HT2C'), ('5HT2A', '5-HT2A'), ('GABAB', 'GABAB'), ('NMDA', 'NMDA')], max_length=255)),
|
||||||
|
('mechanism', models.CharField(choices=[('AGONISM', 'Agonism'), ('ANTAGONISM', 'Antagomism'), ('MODULATION', 'Modulation')], max_length=255)),
|
||||||
|
('affinity', models.IntegerField(blank=True)),
|
||||||
|
('reversible', models.BooleanField(blank=True, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Dosage',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('unit', models.CharField(choices=[('mg', 'mg'), ('g', 'g'), ('ug', 'ug')], max_length=255)),
|
||||||
|
('threshold', models.IntegerField()),
|
||||||
|
('light', models.IntegerField()),
|
||||||
|
('common', models.IntegerField()),
|
||||||
|
('strong', models.IntegerField()),
|
||||||
|
('heavy', models.IntegerField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Drug',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255, unique=True)),
|
||||||
|
('drug_class', models.CharField(max_length=255)),
|
||||||
|
('common_name', models.CharField(max_length=1024, unique=True)),
|
||||||
|
('actions', models.ManyToManyField(to='core.action')),
|
||||||
|
('dosages', models.ManyToManyField(to='core.dosage')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Entry',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('slug', models.CharField(blank=True, max_length=1024, null=True)),
|
||||||
|
('author', models.CharField(blank=True, max_length=255, null=True)),
|
||||||
|
('description', models.CharField(blank=True, max_length=1024, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SEI',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('type', models.CharField(choices=[('PHYSICAL', 'Physical'), ('COGNITIVE', 'Cognitive'), ('VISUAL', 'Visual'), ('AUDITORY', 'Auditory'), ('MULTISENSORY', 'Multi-sensory'), ('TRANSPERSONAL', 'Transpersonal')], default='PHYSICAL', max_length=255)),
|
||||||
|
('subtype', models.CharField(choices=[('ENTACTOGENIC', 'Entactogenic (touch-enhancing)'), ('ENTHEOGENIC', 'Entheogenic (spirituality-enhancing)'), ('PSYCHEDELIC', 'Psychedelic (mind-manifesting)'), ('DISSOCIATIVE', 'Dissociative'), ('HALLUCINOGENIC', 'Hallucinogenic (hallucination-inducing)'), ('DELIRIANT', 'Deliriant'), ('PSYCHOTOMIMETIC', 'Psychotomimetic (psychosis-inducing)'), ('STIMULATING', 'Stimulating'), ('SEDATING', 'Sedating'), ('DEPRESSANT', 'Depressant'), ('EUPHORIC', 'Euphoric'), ('ANXIOLYTIC', 'Anxiolytic'), ('ANTIPSYCHOTIC', 'Antipsychotic'), ('PSYCHOSTIMULANT', 'Psychostimulant'), ('EUGEROIC', 'Eugeroic (wakefulness-promoting)'), ('NOOTROPIC', 'Nootropic')], max_length=255)),
|
||||||
|
('description', models.CharField(blank=True, max_length=4096, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Source',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255, unique=True)),
|
||||||
|
('type', models.CharField(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')], max_length=255)),
|
||||||
|
('endpoint', models.CharField(blank=True, max_length=1024, null=True)),
|
||||||
|
('score', models.IntegerField(blank=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Timing',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('unit', models.CharField(choices=[('SECONDS', 'Seconds'), ('MINUTES', 'Minutes'), ('HOURS', 'Hours'), ('DAYS', 'Days :D'), ('WEEKS', 'Weeks :O'), ('MONTHS', 'Months :-|'), ('YEARS', 'Years x_X')], default='HOURS', max_length=255)),
|
||||||
|
('onset', models.IntegerField()),
|
||||||
|
('comeup', models.IntegerField()),
|
||||||
|
('peak', models.IntegerField()),
|
||||||
|
('offset', models.IntegerField(blank=True, null=True)),
|
||||||
|
('total', models.IntegerField()),
|
||||||
|
('entry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.entry')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ExperienceDose',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('seconds_since_start', models.IntegerField()),
|
||||||
|
('dose', models.IntegerField()),
|
||||||
|
('dose_per_kg', models.IntegerField()),
|
||||||
|
('unit', models.CharField(choices=[('mg', 'mg'), ('g', 'g'), ('ug', 'ug')], max_length=255)),
|
||||||
|
('roa', models.CharField(max_length=255)),
|
||||||
|
('form', models.CharField(max_length=255)),
|
||||||
|
('drug', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.drug')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Experience',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('body_weight_kg', models.IntegerField(blank=True, null=True)),
|
||||||
|
('year_of_experience', models.IntegerField(blank=True, null=True)),
|
||||||
|
('gender', models.CharField(blank=True, max_length=255, 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(blank=True, max_length=255, null=True)),
|
||||||
|
('experience_id', models.IntegerField(blank=True, null=True)),
|
||||||
|
('text', models.TextField()),
|
||||||
|
('doses', models.ManyToManyField(to='core.experiencedose')),
|
||||||
|
('entry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.entry')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='entry',
|
||||||
|
name='source',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.source'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Effect',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('entry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.entry')),
|
||||||
|
('subjective_effects', models.ManyToManyField(to='core.sei')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='drug',
|
||||||
|
name='effects',
|
||||||
|
field=models.ManyToManyField(to='core.effect'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='drug',
|
||||||
|
name='experiences',
|
||||||
|
field=models.ManyToManyField(to='core.experience'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='drug',
|
||||||
|
name='links',
|
||||||
|
field=models.ManyToManyField(to='core.entry'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='drug',
|
||||||
|
name='timings',
|
||||||
|
field=models.ManyToManyField(to='core.timing'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='dosage',
|
||||||
|
name='entry',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.entry'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='action',
|
||||||
|
name='entry',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.entry'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,146 @@
|
||||||
|
# Generated by Django 4.2.8 on 2024-01-01 17:56
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0002_action_dosage_drug_entry_sei_source_timing_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='timing',
|
||||||
|
name='comeup',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='timing',
|
||||||
|
name='offset',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='timing',
|
||||||
|
name='onset',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='timing',
|
||||||
|
name='peak',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='timing',
|
||||||
|
name='total',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timing',
|
||||||
|
name='comeup_lower',
|
||||||
|
field=models.FloatField(default=0),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timing',
|
||||||
|
name='comeup_upper',
|
||||||
|
field=models.FloatField(default=0),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timing',
|
||||||
|
name='offset_lower',
|
||||||
|
field=models.FloatField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timing',
|
||||||
|
name='offset_upper',
|
||||||
|
field=models.FloatField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timing',
|
||||||
|
name='onset_lower',
|
||||||
|
field=models.FloatField(default=0),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timing',
|
||||||
|
name='onset_upper',
|
||||||
|
field=models.FloatField(default=0),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timing',
|
||||||
|
name='peak_lower',
|
||||||
|
field=models.FloatField(default=0),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timing',
|
||||||
|
name='peak_upper',
|
||||||
|
field=models.FloatField(default=0),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timing',
|
||||||
|
name='total_lower',
|
||||||
|
field=models.FloatField(default=0),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timing',
|
||||||
|
name='total_upper',
|
||||||
|
field=models.FloatField(default=0),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='dosage',
|
||||||
|
name='common',
|
||||||
|
field=models.FloatField(),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='dosage',
|
||||||
|
name='heavy',
|
||||||
|
field=models.FloatField(),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='dosage',
|
||||||
|
name='light',
|
||||||
|
field=models.FloatField(),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='dosage',
|
||||||
|
name='strong',
|
||||||
|
field=models.FloatField(),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='dosage',
|
||||||
|
name='threshold',
|
||||||
|
field=models.FloatField(),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='drug',
|
||||||
|
name='actions',
|
||||||
|
field=models.ManyToManyField(blank=True, to='core.action'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='drug',
|
||||||
|
name='dosages',
|
||||||
|
field=models.ManyToManyField(blank=True, to='core.dosage'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='drug',
|
||||||
|
name='effects',
|
||||||
|
field=models.ManyToManyField(blank=True, to='core.effect'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='drug',
|
||||||
|
name='experiences',
|
||||||
|
field=models.ManyToManyField(blank=True, to='core.experience'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='drug',
|
||||||
|
name='links',
|
||||||
|
field=models.ManyToManyField(blank=True, to='core.entry'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='drug',
|
||||||
|
name='timings',
|
||||||
|
field=models.ManyToManyField(blank=True, to='core.timing'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Generated by Django 4.2.8 on 2024-01-01 17:59
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0003_remove_timing_comeup_remove_timing_offset_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='dosage',
|
||||||
|
old_name='common',
|
||||||
|
new_name='common_lower',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='dosage',
|
||||||
|
old_name='heavy',
|
||||||
|
new_name='heavy_lower',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='dosage',
|
||||||
|
old_name='light',
|
||||||
|
new_name='light_lower',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='dosage',
|
||||||
|
old_name='strong',
|
||||||
|
new_name='strong_lower',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='dosage',
|
||||||
|
old_name='threshold',
|
||||||
|
new_name='threshold_lower',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='dosage',
|
||||||
|
name='common_upper',
|
||||||
|
field=models.FloatField(default=0),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='dosage',
|
||||||
|
name='heavy_upper',
|
||||||
|
field=models.FloatField(default=0),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='dosage',
|
||||||
|
name='light_upper',
|
||||||
|
field=models.FloatField(default=0),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='dosage',
|
||||||
|
name='strong_upper',
|
||||||
|
field=models.FloatField(default=0),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='dosage',
|
||||||
|
name='threshold_upper',
|
||||||
|
field=models.FloatField(default=0),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Generated by Django 4.2.8 on 2024-01-01 18:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0004_rename_common_dosage_common_lower_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='dosage',
|
||||||
|
name='roa',
|
||||||
|
field=models.CharField(choices=[('ORAL', 'Oral'), ('SMOKED', 'Smoked/vaped'), ('INSUFFLATED', 'Insufflated'), ('INJECTED', 'Injected'), ('SUBLINGUAL', 'Sublingual'), ('TRANSDERMAL', 'Transdermal'), ('RECTAL', 'Rectal')], default='ORAL', max_length=255),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timing',
|
||||||
|
name='roa',
|
||||||
|
field=models.CharField(choices=[('ORAL', 'Oral'), ('SMOKED', 'Smoked/vaped'), ('INSUFFLATED', 'Insufflated'), ('INJECTED', 'Injected'), ('SUBLINGUAL', 'Sublingual'), ('TRANSDERMAL', 'Transdermal'), ('RECTAL', 'Rectal')], default='ORAL', max_length=255),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='experiencedose',
|
||||||
|
name='roa',
|
||||||
|
field=models.CharField(choices=[('ORAL', 'Oral'), ('SMOKED', 'Smoked/vaped'), ('INSUFFLATED', 'Insufflated'), ('INJECTED', 'Injected'), ('SUBLINGUAL', 'Sublingual'), ('TRANSDERMAL', 'Transdermal'), ('RECTAL', 'Rectal')], max_length=255),
|
||||||
|
),
|
||||||
|
]
|
183
core/models.py
183
core/models.py
|
@ -19,6 +19,16 @@ MECHANISM_CHOICES = (
|
||||||
("MODULATION", "Modulation"),
|
("MODULATION", "Modulation"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ROA_CHOICES = (
|
||||||
|
("ORAL", "Oral"),
|
||||||
|
("SMOKED", "Smoked/vaped"),
|
||||||
|
("INSUFFLATED", "Insufflated"),
|
||||||
|
("INJECTED", "Injected"),
|
||||||
|
("SUBLINGUAL", "Sublingual"),
|
||||||
|
("TRANSDERMAL", "Transdermal"),
|
||||||
|
("RECTAL", "Rectal"),
|
||||||
|
)
|
||||||
|
|
||||||
SOURCE_TYPE_CHOICES = (
|
SOURCE_TYPE_CHOICES = (
|
||||||
("PSITE", "Professional pharmaceutical data repository"),
|
("PSITE", "Professional pharmaceutical data repository"),
|
||||||
("DWIKI", "Dedicated peer-reviewed community wiki"),
|
("DWIKI", "Dedicated peer-reviewed community wiki"),
|
||||||
|
@ -53,6 +63,30 @@ SEI_TYPE_CHOICES = (
|
||||||
("TRANSPERSONAL", "Transpersonal"),
|
("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):
|
class User(AbstractUser):
|
||||||
# Stripe customer ID
|
# Stripe customer ID
|
||||||
|
@ -99,11 +133,14 @@ class Entry(models.Model):
|
||||||
Used to gather conflicting information and store it coherently.
|
Used to gather conflicting information and store it coherently.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
source = models.ForeignKey(Source)
|
source = models.ForeignKey(Source, on_delete=models.CASCADE)
|
||||||
|
|
||||||
# Slug of the article on the Source
|
# Slug of the article on the Source
|
||||||
slug = models.CharField(max_length=1024, null=True, blank=True)
|
slug = 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
|
# Extra information can be added
|
||||||
description = models.CharField(max_length=1024, null=True, blank=True)
|
description = models.CharField(max_length=1024, null=True, blank=True)
|
||||||
|
|
||||||
|
@ -114,26 +151,33 @@ class Dosage(models.Model):
|
||||||
Linked to Entry to analyse conflicting records.
|
Linked to Entry to analyse conflicting records.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
entry = models.ForeignKey("core.Entry")
|
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
|
# Unit of mass as drugs are diverse
|
||||||
# Dosages varying between micrograms and grams
|
# Dosages varying between micrograms and grams
|
||||||
unit = models.CharField(max_length=255, choices=DOSAGE_UNIT_CHOICES)
|
unit = models.CharField(max_length=255, choices=DOSAGE_UNIT_CHOICES)
|
||||||
|
|
||||||
# I can no longer say I am sober, but it is slight
|
# I can no longer say I am sober, but it is slight
|
||||||
threshold = models.IntegerField()
|
threshold_lower = models.FloatField()
|
||||||
|
threshold_upper = models.FloatField()
|
||||||
|
|
||||||
# Light
|
# Light
|
||||||
light = models.IntegerField()
|
light_lower = models.FloatField()
|
||||||
|
light_upper = models.FloatField()
|
||||||
|
|
||||||
# Average dose for a user
|
# Average dose for a user
|
||||||
common = models.IntegerField()
|
common_lower = models.FloatField()
|
||||||
|
common_upper = models.FloatField()
|
||||||
|
|
||||||
# Strong intensity, many sober activities may become impossible
|
# Strong intensity, many sober activities may become impossible
|
||||||
strong = models.IntegerField()
|
strong_lower = models.FloatField()
|
||||||
|
strong_upper = models.FloatField()
|
||||||
|
|
||||||
# Highest intensity
|
# Highest intensity
|
||||||
heavy = models.IntegerField()
|
heavy_lower = models.FloatField()
|
||||||
|
heavy_upper = models.FloatField()
|
||||||
|
|
||||||
|
|
||||||
class Timing(models.Model):
|
class Timing(models.Model):
|
||||||
|
@ -142,7 +186,9 @@ class Timing(models.Model):
|
||||||
Linked to Entry to analyse conflicting records.
|
Linked to Entry to analyse conflicting records.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
entry = models.ForeignKey("core.Entry")
|
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
|
# Unit of time as drugs are diverse
|
||||||
# Half-lives varying between seconds and months
|
# Half-lives varying between seconds and months
|
||||||
|
@ -151,19 +197,24 @@ class Timing(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
# It has just now begin, I can no longer say I am sober
|
# It has just now begin, I can no longer say I am sober
|
||||||
onset = models.IntegerField()
|
onset_lower = models.FloatField()
|
||||||
|
onset_upper = models.FloatField()
|
||||||
|
|
||||||
# The intensity is accelerating
|
# The intensity is accelerating
|
||||||
comeup = models.IntegerField()
|
comeup_lower = models.FloatField()
|
||||||
|
comeup_upper = models.FloatField()
|
||||||
|
|
||||||
# The maximum intensity has been reached
|
# The maximum intensity has been reached
|
||||||
# How long this state occurs
|
# How long this state occurs
|
||||||
peak = models.IntegerField()
|
peak_lower = models.FloatField()
|
||||||
|
peak_upper = models.FloatField()
|
||||||
|
|
||||||
# How long it takes to get back to baseline
|
# How long it takes to get back to baseline
|
||||||
offset = models.IntegerField(null=True, blank=True)
|
offset_lower = models.FloatField(null=True, blank=True)
|
||||||
|
offset_upper = models.FloatField(null=True, blank=True)
|
||||||
|
|
||||||
total = models.IntegerField()
|
total_lower = models.FloatField()
|
||||||
|
total_upper = models.FloatField()
|
||||||
|
|
||||||
|
|
||||||
class SEI(models.Model):
|
class SEI(models.Model):
|
||||||
|
@ -178,6 +229,11 @@ class SEI(models.Model):
|
||||||
max_length=255, choices=SEI_TYPE_CHOICES, default="PHYSICAL"
|
max_length=255, choices=SEI_TYPE_CHOICES, default="PHYSICAL"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
subtype = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
choices=SEI_SUBTYPE_CHOICES,
|
||||||
|
)
|
||||||
|
|
||||||
# WIP: consider euphoric, depressant, relaxant
|
# WIP: consider euphoric, depressant, relaxant
|
||||||
|
|
||||||
# Specify how
|
# Specify how
|
||||||
|
@ -191,7 +247,7 @@ class Effect(models.Model):
|
||||||
Linked to Entry to analyse conflicting records.
|
Linked to Entry to analyse conflicting records.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
entry = models.ForeignKey("core.Entry")
|
entry = models.ForeignKey("core.Entry", on_delete=models.CASCADE)
|
||||||
|
|
||||||
# List of subjective effects, since they would likely be from the same entry
|
# List of subjective effects, since they would likely be from the same entry
|
||||||
subjective_effects = models.ManyToManyField(SEI)
|
subjective_effects = models.ManyToManyField(SEI)
|
||||||
|
@ -205,7 +261,7 @@ class Action(models.Model):
|
||||||
Linked to Entry to analyse conflicting records.
|
Linked to Entry to analyse conflicting records.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
entry = models.ForeignKey("core.Entry")
|
entry = models.ForeignKey("core.Entry", on_delete=models.CASCADE)
|
||||||
|
|
||||||
# Site - like 5HT2A for LSD
|
# Site - like 5HT2A for LSD
|
||||||
site = models.CharField(max_length=255, choices=SITE_CHOICES)
|
site = models.CharField(max_length=255, choices=SITE_CHOICES)
|
||||||
|
@ -216,6 +272,73 @@ class Action(models.Model):
|
||||||
# Free integer for binding affinity
|
# Free integer for binding affinity
|
||||||
affinity = models.IntegerField(blank=True)
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
class Drug(models.Model):
|
class Drug(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -225,42 +348,34 @@ class Drug(models.Model):
|
||||||
# Lysergic acid diethylamide, Phenibut
|
# Lysergic acid diethylamide, Phenibut
|
||||||
name = models.CharField(max_length=255, unique=True)
|
name = models.CharField(max_length=255, unique=True)
|
||||||
|
|
||||||
# Psychedelic, Sedative
|
# Psychedelic, Sedative, Stimulant
|
||||||
drug_class = models.CharField(max_length=255)
|
drug_class = models.CharField(max_length=255)
|
||||||
|
|
||||||
# LSD
|
# LSD
|
||||||
common_name = models.CharField(max_length=1024, unique=True)
|
common_name = models.CharField(max_length=1024, unique=True)
|
||||||
|
|
||||||
# Factsheets, posts
|
# Factsheets, posts
|
||||||
links = models.ManyToManyField(Entry)
|
links = models.ManyToManyField(Entry, blank=True)
|
||||||
|
|
||||||
# Dosages, how much to take to get a certain effect
|
# Dosages, how much to take to get a certain effect
|
||||||
dosages = models.ManyToManyField(Dosage)
|
dosages = models.ManyToManyField(Dosage, blank=True)
|
||||||
|
|
||||||
# Timings, how long to wait to reach maximum intensity (and others)
|
# Timings, how long to wait to reach maximum intensity (and others)
|
||||||
timings = models.ManyToManyField(Timing)
|
timings = models.ManyToManyField(Timing, blank=True)
|
||||||
|
|
||||||
# Effects, what does it do on a subjective level?
|
# Effects, what does it do on a subjective level?
|
||||||
effects = models.ManyToManyField(Effect)
|
effects = models.ManyToManyField(Effect, blank=True)
|
||||||
|
|
||||||
# Actions, what does it do on an objective level?
|
# Actions, what does it do on an objective level?
|
||||||
actions = models.ManyToManyField(Action)
|
actions = models.ManyToManyField(Action, blank=True)
|
||||||
|
|
||||||
|
# Experiences, what do people experience?
|
||||||
|
experiences = models.ManyToManyField(Experience, blank=True)
|
||||||
|
|
||||||
|
|
||||||
# class Perms(models.Model):
|
# class Perms(models.Model):
|
||||||
# class Meta:
|
# class Meta:
|
||||||
# permissions = (
|
# permissions = (
|
||||||
# ("bypass_hashing", "Can bypass field hashing"), #
|
# ("permission_name", "Permission description"),
|
||||||
# ("bypass_blacklist", "Can bypass the blacklist"), #
|
|
||||||
# ("bypass_encryption", "Can bypass field encryption"), #
|
|
||||||
# ("bypass_obfuscation", "Can bypass field obfuscation"), #
|
|
||||||
# ("bypass_delay", "Can bypass data delay"), #
|
|
||||||
# ("bypass_randomisation", "Can bypass data randomisation"), #
|
|
||||||
# ("post_irc", "Can post to IRC"),
|
|
||||||
# ("post_discord", "Can post to Discord"),
|
|
||||||
# ("query_search", "Can search with query strings"), #
|
|
||||||
# ("use_insights", "Can use the Insights page"),
|
|
||||||
# ("index_int", "Can use the internal index"),
|
|
||||||
# ("index_meta", "Can use the meta index"),
|
|
||||||
# ("restricted_sources", "Can access restricted sources"),
|
|
||||||
# )
|
# )
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="#">
|
<a class="navbar-item" href="{% url 'drugs' type='page' %}">
|
||||||
Admin1
|
Drugs
|
||||||
</a>
|
</a>
|
||||||
<a class="navbar-item" href="#">
|
<a class="navbar-item" href="#">
|
||||||
Admin2
|
Admin2
|
||||||
|
@ -292,8 +292,16 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
{% block content_wrapper %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% endblock %}
|
||||||
|
<div id="modals-here">
|
||||||
|
</div>
|
||||||
|
<div id="windows-here">
|
||||||
|
</div>
|
||||||
|
<div id="widgets-here" style="display: none;">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
{% load cache %}
|
||||||
|
{% load cachalot cache %}
|
||||||
|
{% get_last_invalidation 'core.Drug' as last %}
|
||||||
|
{% include 'mixins/partials/notify.html' %}
|
||||||
|
{% cache 600 objects_drugs request.user.id object_list last %}
|
||||||
|
<table
|
||||||
|
class="table is-fullwidth is-hoverable"
|
||||||
|
hx-target="#{{ context_object_name }}-table"
|
||||||
|
id="{{ context_object_name }}-table"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-trigger="{{ context_object_name_singular }}Event from:body"
|
||||||
|
hx-get="{{ list_url }}">
|
||||||
|
<thead>
|
||||||
|
<th>id</th>
|
||||||
|
<th>name</th>
|
||||||
|
<th>drug class</th>
|
||||||
|
<th>common name</th>
|
||||||
|
<th>links</th>
|
||||||
|
<th>dosages</th>
|
||||||
|
<th>timings</th>
|
||||||
|
<th>effects</th>
|
||||||
|
<th>actions</th>
|
||||||
|
<th>experiences</th>
|
||||||
|
<th>actions</th>
|
||||||
|
</thead>
|
||||||
|
{% for item in object_list %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ item.id }}</td>
|
||||||
|
<td>{{ item.name }}</td>
|
||||||
|
<td>{{ item.drug_class }}</td>
|
||||||
|
<td>{{ item.common_name }}s</td>
|
||||||
|
<td>{{ item.links.count }}</td>
|
||||||
|
<td>{{ item.dosages.count }}</td>
|
||||||
|
<td>{{ item.timings.count }}</td>
|
||||||
|
<td>{{ item.effects.count }}</td>
|
||||||
|
<td>{{ item.actions.count }}</td>
|
||||||
|
<td>{{ item.experiences.count }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="buttons">
|
||||||
|
<button
|
||||||
|
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||||
|
hx-get="{% url 'drug_update' type=type pk=item.id %}"
|
||||||
|
hx-trigger="click"
|
||||||
|
hx-target="#{{ type }}s-here"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
class="button">
|
||||||
|
<span class="icon-text">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fa-solid fa-pencil"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||||
|
hx-delete="{% url 'drug_delete' type=type pk=item.id %}"
|
||||||
|
hx-trigger="click"
|
||||||
|
hx-target="#modals-here"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
hx-confirm="Are you sure you wish to delete {{ item.name }}?"
|
||||||
|
class="button">
|
||||||
|
<span class="icon-text">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fa-solid fa-xmark"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</table>
|
||||||
|
{% endcache %}
|
|
@ -0,0 +1,52 @@
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.shortcuts import render
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
from core.forms import DrugForm
|
||||||
|
from core.models import Drug
|
||||||
|
from mxs.restrictions import StaffMemberRequiredMixin
|
||||||
|
from mxs.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate
|
||||||
|
|
||||||
|
|
||||||
|
class DrugList(LoginRequiredMixin, StaffMemberRequiredMixin, ObjectList):
|
||||||
|
list_template = "partials/drug-list.html"
|
||||||
|
model = Drug
|
||||||
|
page_title = "Global list of drugs"
|
||||||
|
|
||||||
|
list_url_name = "drugs"
|
||||||
|
list_url_args = ["type"]
|
||||||
|
|
||||||
|
submit_url_name = "drug_create"
|
||||||
|
delete_all_url_name = "drug_clear"
|
||||||
|
|
||||||
|
|
||||||
|
class DrugCreate(LoginRequiredMixin, StaffMemberRequiredMixin, ObjectCreate):
|
||||||
|
model = Drug
|
||||||
|
form_class = DrugForm
|
||||||
|
|
||||||
|
submit_url_name = "drug_create"
|
||||||
|
|
||||||
|
|
||||||
|
class DrugUpdate(LoginRequiredMixin, StaffMemberRequiredMixin, ObjectUpdate):
|
||||||
|
model = Drug
|
||||||
|
form_class = DrugForm
|
||||||
|
|
||||||
|
submit_url_name = "drug_update"
|
||||||
|
|
||||||
|
|
||||||
|
class DrugDelete(LoginRequiredMixin, StaffMemberRequiredMixin, ObjectDelete):
|
||||||
|
model = Drug
|
||||||
|
|
||||||
|
|
||||||
|
class DrugClear(LoginRequiredMixin, StaffMemberRequiredMixin, APIView):
|
||||||
|
def delete(self, request):
|
||||||
|
template_name = "mixins/partials/notify.html"
|
||||||
|
drugs_all = Drug.objects.all()
|
||||||
|
drugs_all.delete()
|
||||||
|
context = {
|
||||||
|
"message": "Deleted all drugs",
|
||||||
|
"class": "success",
|
||||||
|
}
|
||||||
|
response = render(request, template_name, context)
|
||||||
|
response["HX-Trigger"] = "drugEvent"
|
||||||
|
return response
|
|
@ -2,8 +2,8 @@ version: "2.2"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: xf/envelope:prod
|
image: xf/drugs:prod
|
||||||
container_name: envelope
|
container_name: drugs
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
args:
|
args:
|
||||||
|
@ -31,8 +31,8 @@ services:
|
||||||
- xf
|
- xf
|
||||||
|
|
||||||
migration:
|
migration:
|
||||||
image: xf/envelope:prod
|
image: xf/drugs:prod
|
||||||
container_name: migration_envelope
|
container_name: migration_drugs
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
args:
|
args:
|
||||||
|
@ -49,8 +49,8 @@ services:
|
||||||
- stack.env
|
- stack.env
|
||||||
|
|
||||||
collectstatic:
|
collectstatic:
|
||||||
image: xf/envelope:prod
|
image: xf/drugs:prod
|
||||||
container_name: collectstatic_envelope
|
container_name: collectstatic_drugs
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
args:
|
args:
|
||||||
|
@ -67,7 +67,7 @@ services:
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: nginx:latest
|
image: nginx:latest
|
||||||
container_name: nginx_envelope
|
container_name: nginx_drugs
|
||||||
ports:
|
ports:
|
||||||
- ${APP_PORT}:9999
|
- ${APP_PORT}:9999
|
||||||
ulimits:
|
ulimits:
|
||||||
|
@ -90,7 +90,7 @@ services:
|
||||||
|
|
||||||
tmp:
|
tmp:
|
||||||
image: busybox
|
image: busybox
|
||||||
container_name: tmp_envelope
|
container_name: tmp_drugs
|
||||||
command: chmod -R 777 /var/run/socks
|
command: chmod -R 777 /var/run/socks
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/socks
|
- /var/run/socks
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
|
# Boilerplate: https://git.zm.is/XF/django-crud-mixins
|
||||||
|
# Edited to not undertake user= checks
|
||||||
|
|
||||||
|
|
||||||
|
class StaffMemberRequiredMixin(UserPassesTestMixin):
|
||||||
|
def test_func(self):
|
||||||
|
return self.request.user.is_staff
|
||||||
|
|
||||||
|
|
||||||
|
class RestrictedViewMixinStaff:
|
||||||
|
"""
|
||||||
|
This mixin overrides two helpers in order to pass the user object to the filters.
|
||||||
|
get_queryset alters the objects returned for list views.
|
||||||
|
get_form_kwargs passes the request object to the form class. Remaining permissions
|
||||||
|
checks are in forms.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
allow_empty = True
|
||||||
|
queryset = None
|
||||||
|
model = None
|
||||||
|
paginate_by = None
|
||||||
|
paginate_orphans = 0
|
||||||
|
context_object_name = None
|
||||||
|
paginator_class = Paginator
|
||||||
|
page_kwarg = "page"
|
||||||
|
ordering = None
|
||||||
|
|
||||||
|
def set_extra_args(self, user):
|
||||||
|
"""
|
||||||
|
This function is overriden to filter the objects by the requesting user.
|
||||||
|
"""
|
||||||
|
self.extra_permission_args = {}
|
||||||
|
|
||||||
|
def get_queryset(self, **kwargs):
|
||||||
|
"""
|
||||||
|
This function is overriden to filter the objects by the requesting user.
|
||||||
|
"""
|
||||||
|
self.set_extra_args(self.request.user)
|
||||||
|
if self.queryset is not None:
|
||||||
|
queryset = self.queryset
|
||||||
|
if isinstance(queryset, QuerySet):
|
||||||
|
# queryset = queryset.all()
|
||||||
|
# queryset = queryset.filter(
|
||||||
|
# user=self.request.user, **self.extra_permission_args COMMENTED
|
||||||
|
# )
|
||||||
|
queryset = queryset.filter(**self.extra_permission_args)
|
||||||
|
elif self.model is not None:
|
||||||
|
# queryset = self.model._default_manager.filter(
|
||||||
|
# user=self.request.user, **self.extra_permission_args COMMENTED
|
||||||
|
# )
|
||||||
|
queryset = self.model._default_manager.filter(**self.extra_permission_args)
|
||||||
|
else:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
"%(cls)s is missing a QuerySet. Define "
|
||||||
|
"%(cls)s.model, %(cls)s.queryset, or override "
|
||||||
|
"%(cls)s.get_queryset()." % {"cls": self.__class__.__name__}
|
||||||
|
)
|
||||||
|
if hasattr(self, "get_ordering"):
|
||||||
|
ordering = self.get_ordering()
|
||||||
|
if ordering:
|
||||||
|
if isinstance(ordering, str):
|
||||||
|
ordering = (ordering,)
|
||||||
|
queryset = queryset.order_by(*ordering)
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
"""Passes the request object to the form class.
|
||||||
|
This is necessary to only display members that belong to a given user"""
|
||||||
|
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
kwargs["request"] = self.request
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class RestrictedFormMixinStaff:
|
||||||
|
"""
|
||||||
|
Just used to set the request object on the form class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
fieldargs = {}
|
||||||
|
|
||||||
|
# TODO: implement set_extra_args to check more permissions here
|
||||||
|
# for completeness, however as views open forms, the permissions
|
||||||
|
# are already checked there, so it may not be necessary.
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# self.fieldargs = {}
|
||||||
|
self.request = kwargs.pop("request")
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
# for field in self.fields:
|
||||||
|
# # Check it's not something like a CharField which has no queryset
|
||||||
|
# if not hasattr(self.fields[field], "queryset"):
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# model = self.fields[field].queryset.model
|
||||||
|
# Check if the model has a user field
|
||||||
|
# try:
|
||||||
|
# model._meta.get_field("user")
|
||||||
|
# # Add the user to the queryset filters
|
||||||
|
# self.fields[field].queryset = model.objects.filter(
|
||||||
|
# user=self.request.user, **self.fieldargs.get(field, {})
|
||||||
|
# )
|
||||||
|
# except FieldDoesNotExist:
|
||||||
|
# pass
|
|
@ -0,0 +1,461 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.http import Http404, HttpResponse, HttpResponseBadRequest
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.views.generic.detail import DetailView
|
||||||
|
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||||
|
from django.views.generic.list import ListView
|
||||||
|
from rest_framework.parsers import FormParser
|
||||||
|
|
||||||
|
from mxs.restrictions import RestrictedViewMixinStaff
|
||||||
|
|
||||||
|
# Boilerplate: https://git.zm.is/XF/django-crud-mixins
|
||||||
|
# Edited to not undertake user= checks
|
||||||
|
|
||||||
|
|
||||||
|
class AbortSave(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectNameMixin(object):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
if self.model is None:
|
||||||
|
self.title = self.context_object_name.title()
|
||||||
|
self.title_singular = self.context_object_name_singular.title()
|
||||||
|
else:
|
||||||
|
self.title_singular = self.model._meta.verbose_name.title() # Hook
|
||||||
|
self.context_object_name_singular = self.title_singular.lower() # hook
|
||||||
|
self.title = self.model._meta.verbose_name_plural.title() # Hooks
|
||||||
|
self.context_object_name = self.title.lower() # hooks
|
||||||
|
|
||||||
|
self.context_object_name = self.context_object_name.replace(" ", "")
|
||||||
|
self.context_object_name_singular = (
|
||||||
|
self.context_object_name_singular.replace(" ", "")
|
||||||
|
)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectList(RestrictedViewMixinStaff, ObjectNameMixin, ListView):
|
||||||
|
allowed_types = ["modal", "widget", "window", "page"]
|
||||||
|
window_content = "mixins/window-content/objects.html"
|
||||||
|
list_template = None
|
||||||
|
|
||||||
|
page_title = None
|
||||||
|
page_subtitle = None
|
||||||
|
|
||||||
|
list_url_name = None
|
||||||
|
# WARNING: TAKEN FROM locals()
|
||||||
|
list_url_args = ["type"]
|
||||||
|
|
||||||
|
submit_url_name = None
|
||||||
|
submit_url_args = ["type"]
|
||||||
|
|
||||||
|
delete_all_url_name = None
|
||||||
|
widget_options = None
|
||||||
|
|
||||||
|
extra_buttons = None
|
||||||
|
|
||||||
|
def queryset_mutate(self, queryset):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# copied from BaseListView
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
type = kwargs.get("type", None)
|
||||||
|
if not type:
|
||||||
|
return HttpResponseBadRequest("No type specified")
|
||||||
|
if type not in self.allowed_types:
|
||||||
|
return HttpResponseBadRequest("Invalid type specified")
|
||||||
|
|
||||||
|
self.request = request
|
||||||
|
self.object_list = self.get_queryset(**kwargs)
|
||||||
|
|
||||||
|
if isinstance(self.object_list, HttpResponse):
|
||||||
|
return self.object_list
|
||||||
|
if isinstance(self.object_list, HttpResponseBadRequest):
|
||||||
|
return self.object_list
|
||||||
|
|
||||||
|
self.queryset_mutate(self.object_list)
|
||||||
|
allow_empty = self.get_allow_empty()
|
||||||
|
|
||||||
|
self.template_name = f"mixins/wm/{type}.html"
|
||||||
|
unique = str(uuid.uuid4())[:8]
|
||||||
|
|
||||||
|
list_url_args = {}
|
||||||
|
for arg in self.list_url_args:
|
||||||
|
if arg in locals():
|
||||||
|
list_url_args[arg] = locals()[arg]
|
||||||
|
elif arg in kwargs:
|
||||||
|
list_url_args[arg] = kwargs[arg]
|
||||||
|
|
||||||
|
orig_type = type
|
||||||
|
if type == "page":
|
||||||
|
type = "modal"
|
||||||
|
|
||||||
|
if not allow_empty:
|
||||||
|
# When pagination is enabled and object_list is a queryset,
|
||||||
|
# it's better to do a cheap query than to load the unpaginated
|
||||||
|
# queryset in memory.
|
||||||
|
if self.get_paginate_by(self.object_list) is not None and hasattr(
|
||||||
|
self.object_list, "exists"
|
||||||
|
):
|
||||||
|
is_empty = not self.object_list.exists()
|
||||||
|
else:
|
||||||
|
is_empty = not self.object_list
|
||||||
|
if is_empty:
|
||||||
|
raise Http404("Empty list")
|
||||||
|
|
||||||
|
submit_url_args = {}
|
||||||
|
for arg in self.submit_url_args:
|
||||||
|
if arg in locals():
|
||||||
|
submit_url_args[arg] = locals()[arg]
|
||||||
|
elif arg in kwargs:
|
||||||
|
submit_url_args[arg] = kwargs[arg]
|
||||||
|
|
||||||
|
context = self.get_context_data()
|
||||||
|
context["title"] = self.title + f" ({type})"
|
||||||
|
context["title_singular"] = self.title_singular
|
||||||
|
context["unique"] = unique
|
||||||
|
context["window_content"] = self.window_content
|
||||||
|
context["list_template"] = self.list_template
|
||||||
|
context["page_title"] = self.page_title
|
||||||
|
context["page_subtitle"] = self.page_subtitle
|
||||||
|
context["type"] = type
|
||||||
|
context["context_object_name"] = self.context_object_name
|
||||||
|
context["context_object_name_singular"] = self.context_object_name_singular
|
||||||
|
|
||||||
|
if self.submit_url_name is not None:
|
||||||
|
context["submit_url"] = reverse(
|
||||||
|
self.submit_url_name, kwargs=submit_url_args
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.list_url_name is not None:
|
||||||
|
context["list_url"] = reverse(self.list_url_name, kwargs=list_url_args)
|
||||||
|
|
||||||
|
if self.delete_all_url_name:
|
||||||
|
context["delete_all_url"] = reverse(self.delete_all_url_name)
|
||||||
|
if self.widget_options:
|
||||||
|
context["widget_options"] = self.widget_options
|
||||||
|
if self.extra_buttons is not None:
|
||||||
|
context["extra_buttons"] = self.extra_buttons
|
||||||
|
|
||||||
|
# Return partials for HTMX
|
||||||
|
if self.request.htmx:
|
||||||
|
if request.headers["HX-Target"] == self.context_object_name + "-table":
|
||||||
|
self.template_name = self.list_template
|
||||||
|
elif orig_type == "page":
|
||||||
|
self.template_name = self.list_template
|
||||||
|
else:
|
||||||
|
context["window_content"] = self.list_template
|
||||||
|
return self.render_to_response(context)
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectCreate(RestrictedViewMixinStaff, ObjectNameMixin, CreateView):
|
||||||
|
allowed_types = ["modal", "widget", "window", "page"]
|
||||||
|
window_content = "mixins/window-content/object-form.html"
|
||||||
|
parser_classes = [FormParser]
|
||||||
|
|
||||||
|
page_title = None
|
||||||
|
page_subtitle = None
|
||||||
|
|
||||||
|
model = None
|
||||||
|
submit_url_name = None
|
||||||
|
submit_url_args = ["type"]
|
||||||
|
|
||||||
|
request = None
|
||||||
|
|
||||||
|
# Whether to hide the cancel button in the form
|
||||||
|
hide_cancel = False
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.title = "Create " + self.context_object_name_singular
|
||||||
|
|
||||||
|
def post_save(self, obj):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def pre_save_mutate(self, user, obj):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
obj = form.save(commit=False)
|
||||||
|
if self.request is None:
|
||||||
|
raise Exception("Request is None")
|
||||||
|
obj.user = self.request.user
|
||||||
|
try:
|
||||||
|
self.pre_save_mutate(self.request.user, obj)
|
||||||
|
except AbortSave as e:
|
||||||
|
context = {"message": f"Failed to save: {e}", "class": "danger"}
|
||||||
|
return self.render_to_response(context)
|
||||||
|
obj.save()
|
||||||
|
form.save_m2m()
|
||||||
|
self.post_save(obj)
|
||||||
|
context = {"message": "Object created", "class": "success"}
|
||||||
|
response = self.render_to_response(context)
|
||||||
|
response["HX-Trigger"] = f"{self.context_object_name_singular}Event"
|
||||||
|
return response
|
||||||
|
|
||||||
|
def form_invalid(self, form):
|
||||||
|
"""If the form is invalid, render the invalid form."""
|
||||||
|
return self.get(self.request, **self.kwargs, form=form)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
type = kwargs.get("type", None)
|
||||||
|
if not type:
|
||||||
|
return HttpResponseBadRequest("No type specified")
|
||||||
|
if type not in self.allowed_types:
|
||||||
|
return HttpResponseBadRequest("Invalid type specified")
|
||||||
|
self.template_name = f"mixins/wm/{type}.html"
|
||||||
|
unique = str(uuid.uuid4())[:8]
|
||||||
|
|
||||||
|
self.request = request
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
if type == "widget":
|
||||||
|
self.hide_cancel = True
|
||||||
|
|
||||||
|
if type == "page":
|
||||||
|
type = "modal"
|
||||||
|
|
||||||
|
self.object = None
|
||||||
|
|
||||||
|
submit_url_args = {}
|
||||||
|
for arg in self.submit_url_args:
|
||||||
|
if arg in locals():
|
||||||
|
submit_url_args[arg] = locals()[arg]
|
||||||
|
elif arg in kwargs:
|
||||||
|
submit_url_args[arg] = kwargs[arg]
|
||||||
|
submit_url = reverse(self.submit_url_name, kwargs=submit_url_args)
|
||||||
|
|
||||||
|
context = self.get_context_data()
|
||||||
|
form = kwargs.get("form", None)
|
||||||
|
if form:
|
||||||
|
context["form"] = form
|
||||||
|
context["unique"] = unique
|
||||||
|
context["window_content"] = self.window_content
|
||||||
|
context["context_object_name"] = self.context_object_name
|
||||||
|
context["context_object_name_singular"] = self.context_object_name_singular
|
||||||
|
context["submit_url"] = submit_url
|
||||||
|
context["type"] = type
|
||||||
|
context["hide_cancel"] = self.hide_cancel
|
||||||
|
if self.page_title:
|
||||||
|
context["page_title"] = self.page_title
|
||||||
|
if self.page_subtitle:
|
||||||
|
context["page_subtitle"] = self.page_subtitle
|
||||||
|
response = self.render_to_response(context)
|
||||||
|
# response["HX-Trigger"] = f"{self.context_object_name_singular}Event"
|
||||||
|
return response
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
self.request = request
|
||||||
|
self.template_name = "mixins/partials/notify.html"
|
||||||
|
return super().post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectRead(RestrictedViewMixinStaff, ObjectNameMixin, DetailView):
|
||||||
|
allowed_types = ["modal", "widget", "window", "page"]
|
||||||
|
window_content = "mixins/window-content/object.html"
|
||||||
|
detail_template = "mixins/partials/generic-detail.html"
|
||||||
|
|
||||||
|
page_title = None
|
||||||
|
page_subtitle = None
|
||||||
|
|
||||||
|
model = None
|
||||||
|
# submit_url_name = None
|
||||||
|
|
||||||
|
detail_url_name = None
|
||||||
|
# WARNING: TAKEN FROM locals()
|
||||||
|
detail_url_args = ["type"]
|
||||||
|
|
||||||
|
request = None
|
||||||
|
|
||||||
|
extra_buttons = None
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
type = kwargs.get("type", None)
|
||||||
|
if not type:
|
||||||
|
return HttpResponseBadRequest("No type specified")
|
||||||
|
if type not in self.allowed_types:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
self.template_name = f"mixins/wm/{type}.html"
|
||||||
|
unique = str(uuid.uuid4())[:8]
|
||||||
|
|
||||||
|
detail_url_args = {}
|
||||||
|
for arg in self.detail_url_args:
|
||||||
|
if arg in locals():
|
||||||
|
detail_url_args[arg] = locals()[arg]
|
||||||
|
elif arg in kwargs:
|
||||||
|
detail_url_args[arg] = kwargs[arg]
|
||||||
|
|
||||||
|
self.request = request
|
||||||
|
self.kwargs = kwargs
|
||||||
|
self.object = self.get_object(**kwargs)
|
||||||
|
if isinstance(self.object, HttpResponse):
|
||||||
|
return self.object
|
||||||
|
|
||||||
|
orig_type = type
|
||||||
|
if type == "page":
|
||||||
|
type = "modal"
|
||||||
|
|
||||||
|
context = self.get_context_data()
|
||||||
|
|
||||||
|
context["title"] = self.title + f" ({type})"
|
||||||
|
context["title_singular"] = self.title_singular
|
||||||
|
context["unique"] = unique
|
||||||
|
context["window_content"] = self.window_content
|
||||||
|
context["detail_template"] = self.detail_template
|
||||||
|
if self.page_title:
|
||||||
|
context["page_title"] = self.page_title
|
||||||
|
if self.page_subtitle:
|
||||||
|
context["page_subtitle"] = self.page_subtitle
|
||||||
|
context["type"] = type
|
||||||
|
context["context_object_name"] = self.context_object_name
|
||||||
|
context["context_object_name_singular"] = self.context_object_name_singular
|
||||||
|
|
||||||
|
if self.detail_url_name is not None:
|
||||||
|
context["detail_url"] = reverse(
|
||||||
|
self.detail_url_name, kwargs=detail_url_args
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.extra_buttons is not None:
|
||||||
|
context["extra_buttons"] = self.extra_buttons
|
||||||
|
|
||||||
|
# Return partials for HTMX
|
||||||
|
if self.request.htmx:
|
||||||
|
if request.headers["HX-Target"] == self.context_object_name + "-info":
|
||||||
|
self.template_name = self.detail_template
|
||||||
|
elif orig_type == "page":
|
||||||
|
self.template_name = self.detail_template
|
||||||
|
else:
|
||||||
|
context["window_content"] = self.detail_template
|
||||||
|
|
||||||
|
return self.render_to_response(context)
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectUpdate(RestrictedViewMixinStaff, ObjectNameMixin, UpdateView):
|
||||||
|
allowed_types = ["modal", "widget", "window", "page"]
|
||||||
|
window_content = "mixins/window-content/object-form.html"
|
||||||
|
parser_classes = [FormParser]
|
||||||
|
|
||||||
|
page_title = None
|
||||||
|
page_subtitle = None
|
||||||
|
|
||||||
|
model = None
|
||||||
|
submit_url_name = None
|
||||||
|
submit_url_args = ["type", "pk"]
|
||||||
|
|
||||||
|
request = None
|
||||||
|
|
||||||
|
# Whether pk is required in the get request
|
||||||
|
pk_required = True
|
||||||
|
|
||||||
|
# Whether to hide the cancel button in the form
|
||||||
|
hide_cancel = False
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.title = "Update " + self.context_object_name_singular
|
||||||
|
|
||||||
|
def post_save(self, obj):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
obj = form.save(commit=False)
|
||||||
|
if self.request is None:
|
||||||
|
raise Exception("Request is None")
|
||||||
|
obj.save()
|
||||||
|
form.save_m2m()
|
||||||
|
self.post_save(obj)
|
||||||
|
context = {"message": "Object updated", "class": "success"}
|
||||||
|
response = self.render_to_response(context)
|
||||||
|
response["HX-Trigger"] = f"{self.context_object_name_singular}Event"
|
||||||
|
return response
|
||||||
|
|
||||||
|
def form_invalid(self, form):
|
||||||
|
"""If the form is invalid, render the invalid form."""
|
||||||
|
return self.get(self.request, **self.kwargs, form=form)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.request = request
|
||||||
|
type = kwargs.get("type", None)
|
||||||
|
pk = kwargs.get("pk", None)
|
||||||
|
if not type:
|
||||||
|
return HttpResponseBadRequest("No type specified")
|
||||||
|
if not pk:
|
||||||
|
if self.pk_required:
|
||||||
|
return HttpResponseBadRequest("No pk specified")
|
||||||
|
if type not in self.allowed_types:
|
||||||
|
return HttpResponseBadRequest("Invalid type specified")
|
||||||
|
self.template_name = f"mixins/wm/{type}.html"
|
||||||
|
unique = str(uuid.uuid4())[:8]
|
||||||
|
if type == "widget":
|
||||||
|
self.hide_cancel = True
|
||||||
|
|
||||||
|
if type == "page":
|
||||||
|
type = "modal"
|
||||||
|
|
||||||
|
self.object = self.get_object()
|
||||||
|
|
||||||
|
submit_url_args = {}
|
||||||
|
for arg in self.submit_url_args:
|
||||||
|
if arg in locals():
|
||||||
|
submit_url_args[arg] = locals()[arg]
|
||||||
|
elif arg in kwargs:
|
||||||
|
submit_url_args[arg] = kwargs[arg]
|
||||||
|
submit_url = reverse(self.submit_url_name, kwargs=submit_url_args)
|
||||||
|
|
||||||
|
context = self.get_context_data()
|
||||||
|
form = kwargs.get("form", None)
|
||||||
|
if form:
|
||||||
|
context["form"] = form
|
||||||
|
context["title"] = self.title + f" ({type})"
|
||||||
|
context["title_singular"] = self.title_singular
|
||||||
|
context["unique"] = unique
|
||||||
|
context["window_content"] = self.window_content
|
||||||
|
context["context_object_name"] = self.context_object_name
|
||||||
|
context["context_object_name_singular"] = self.context_object_name_singular
|
||||||
|
context["submit_url"] = submit_url
|
||||||
|
context["type"] = type
|
||||||
|
context["hide_cancel"] = self.hide_cancel
|
||||||
|
if self.page_title:
|
||||||
|
context["page_title"] = self.page_title
|
||||||
|
if self.page_subtitle:
|
||||||
|
context["page_subtitle"] = self.page_subtitle
|
||||||
|
response = self.render_to_response(context)
|
||||||
|
# response["HX-Trigger"] = f"{self.context_object_name_singular}Event"
|
||||||
|
return response
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
self.request = request
|
||||||
|
self.template_name = "mixins/partials/notify.html"
|
||||||
|
return super().post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectDelete(RestrictedViewMixinStaff, ObjectNameMixin, DeleteView):
|
||||||
|
model = None
|
||||||
|
template_name = "mixins/partials/notify.html"
|
||||||
|
|
||||||
|
# Overriden to prevent success URL from being used
|
||||||
|
def delete(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Call the delete() method on the fetched object and then redirect to the
|
||||||
|
success URL.
|
||||||
|
"""
|
||||||
|
self.object = self.get_object()
|
||||||
|
# success_url = self.get_success_url()
|
||||||
|
self.object.delete()
|
||||||
|
context = {"message": "Object deleted", "class": "success"}
|
||||||
|
response = self.render_to_response(context)
|
||||||
|
response["HX-Trigger"] = f"{self.context_object_name_singular}Event"
|
||||||
|
return response
|
||||||
|
|
||||||
|
# This will be used in newer Django versions, until then we get a warning
|
||||||
|
def form_valid(self, form):
|
||||||
|
"""
|
||||||
|
Call the delete() method on the fetched object.
|
||||||
|
"""
|
||||||
|
self.object = self.get_object()
|
||||||
|
self.object.delete()
|
||||||
|
context = {"message": "Object deleted", "class": "success"}
|
||||||
|
response = self.render_to_response(context)
|
||||||
|
response["HX-Trigger"] = f"{self.context_object_name_singular}Event"
|
||||||
|
return response
|
|
@ -29,3 +29,5 @@ redis
|
||||||
hiredis
|
hiredis
|
||||||
django-cachalot
|
django-cachalot
|
||||||
django_redis
|
django_redis
|
||||||
|
drugbank_downloader
|
||||||
|
bioversions
|
Loading…
Reference in New Issue