Upgrade Bulma and begin drug info screen and favourites

This commit is contained in:
Mark Veidemanis 2024-05-17 22:47:16 +01:00
parent 4c8411b863
commit 46b1858897
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
20 changed files with 745 additions and 517 deletions

View File

@ -61,4 +61,5 @@ DRUGS_DEFAULT_PARAMS = {
"size": "15", "size": "15",
"sorting": "desc", "sorting": "desc",
"source": "substances", "source": "substances",
"index": "main",
} }

View File

@ -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, drugs, notifications, search from core.views import base, demo, drugs, favourites, notifications, search
urlpatterns = [ urlpatterns = [
path("__debug__/", include("debug_toolbar.urls")), path("__debug__/", include("debug_toolbar.urls")),
@ -57,6 +57,11 @@ urlpatterns = [
drugs.DrugDelete.as_view(), drugs.DrugDelete.as_view(),
name="drug_delete", name="drug_delete",
), ),
path(
"drugs/<str:type>/detail/<str:pk>/",
drugs.DrugDetail.as_view(),
name="drug_detail",
),
path( path(
"drugs/clear/all/", "drugs/clear/all/",
drugs.DrugClear.as_view(), drugs.DrugClear.as_view(),
@ -70,4 +75,28 @@ urlpatterns = [
# Drug search # Drug search
path("search/", search.DrugsTableView.as_view(), name="search"), path("search/", search.DrugsTableView.as_view(), name="search"),
path("search/partial/", search.DrugsTableView.as_view(), name="search_partial"), path("search/partial/", search.DrugsTableView.as_view(), name="search_partial"),
# Favourites
path(
"favourites/<str:type>/", favourites.FavouriteList.as_view(), name="favourites"
),
path(
"favourites/<str:type>/create/",
favourites.FavouriteCreate.as_view(),
name="favourite_create",
),
path(
"favourites/<str:type>/update/<str:pk>/",
favourites.FavouriteUpdate.as_view(),
name="favourite_update",
),
path(
"favourites/<str:type>/delete/<str:pk>/",
favourites.FavouriteDelete.as_view(),
name="favourite_delete",
),
path(
"favourites/<str:type>/detail/<str:pk>/",
favourites.FavouriteDetail.as_view(),
name="favourite_detail",
),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View File

@ -11,6 +11,7 @@ from .models import (
Entry, Entry,
Experience, Experience,
ExperienceDose, ExperienceDose,
Favourite,
NotificationSettings, NotificationSettings,
Source, Source,
Timing, Timing,
@ -57,3 +58,4 @@ admin.site.register(Experience)
admin.site.register(Source) admin.site.register(Source)
admin.site.register(SEI) admin.site.register(SEI)
admin.site.register(ExperienceDose) admin.site.register(ExperienceDose)
admin.site.register(Favourite)

View File

@ -21,6 +21,7 @@ class PsychWikiClient(GraphQLClient, BaseClient):
Store the data in the database. Store the data in the database.
""" """
for drug in data["substances"]: for drug in data["substances"]:
print("DRUG ITER", drug)
try: try:
drug_obj = Drug.objects.get(name=drug["name"]) drug_obj = Drug.objects.get(name=drug["name"])
except Drug.DoesNotExist: except Drug.DoesNotExist:
@ -89,6 +90,7 @@ class PsychWikiClient(GraphQLClient, BaseClient):
) )
if created or dosage not in drug_obj.dosages.all(): if created or dosage not in drug_obj.dosages.all():
drug_obj.dosages.add(dosage) drug_obj.dosages.add(dosage)
print("YES DOSAGE", drug_obj.dosages)
# Parsing timing information # Parsing timing information
timing = roa["duration"] timing = roa["duration"]

View File

@ -89,6 +89,8 @@ def drug_query(request, query_params, size=None, tags=None):
# Q/T - Query/Tags # Q/T - Query/Tags
result = run_query(query_params, tags, size, sources, ranges, sort) result = run_query(query_params, tags, size, sources, ranges, sort)
for x in result:
print(x.dosages)
rtrn = { rtrn = {
"object_list": result, "object_list": result,
} }

View File

@ -5,7 +5,7 @@ from mixins.restrictions import RestrictedFormMixin
from mxs.restrictions import RestrictedFormMixinStaff from mxs.restrictions import RestrictedFormMixinStaff
from .models import Drug, NotificationSettings, User from .models import Drug, Favourite, NotificationSettings, User
# Create your forms here. # Create your forms here.
@ -78,3 +78,20 @@ class DrugForm(RestrictedFormMixinStaff, ModelForm):
"actions": "Actions, what does it do on an objective level?", "actions": "Actions, what does it do on an objective level?",
"experiences": "Experiences, what do people experience?", "experiences": "Experiences, what do people experience?",
} }
class FavouriteForm(RestrictedFormMixin, ModelForm):
class Meta:
model = Favourite
fields = (
"nickname",
"name",
"drug_class",
"common_name",
)
help_texts = {
"nickname": "Call it whatever you like.",
"name": "Lysergic acid diethylamide, Phenibut",
"drug_class": "Psychedelic, Sedative, Stimulant",
"common_name": "LSD",
}

View File

@ -0,0 +1,44 @@
# Generated by Django 5.0.3 on 2024-05-17 19:01
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0009_alter_drug_common_name_alter_drug_drug_class'),
]
operations = [
migrations.CreateModel(
name='Price',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount_mg', models.IntegerField()),
('price_gbp', models.FloatField()),
('note', models.CharField(blank=True, max_length=255, null=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='FavouriteDrug',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nickname', models.CharField(blank=True, max_length=255, null=True)),
('name', models.CharField(max_length=255)),
('drug_class', models.CharField(blank=True, max_length=255, null=True)),
('common_name', models.CharField(blank=True, max_length=1024, null=True)),
('actions', models.ManyToManyField(blank=True, to='core.action')),
('dosages', models.ManyToManyField(blank=True, to='core.dosage')),
('effects', models.ManyToManyField(blank=True, to='core.effect')),
('experiences', models.ManyToManyField(blank=True, to='core.experience')),
('links', models.ManyToManyField(blank=True, to='core.entry')),
('original', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.drug')),
('timings', models.ManyToManyField(blank=True, to='core.timing')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('prices', models.ManyToManyField(blank=True, null=True, to='core.price')),
],
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 5.0.3 on 2024-05-17 19:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0010_price_favouritedrug'),
]
operations = [
migrations.RenameModel(
old_name='FavouriteDrug',
new_name='Favourite',
),
]

View File

@ -188,7 +188,7 @@ class Dosage(models.Model):
def __str__(self): def __str__(self):
text = ( text = (
f"{self.threshold_lower} {self.light_lower} {self.common_lower} " f"{self.threshold_lower} {self.light_lower} {self.common_lower} "
"{self.strong_lower} {self.heavy_lower}" f"{self.strong_lower} {self.heavy_lower}"
) )
return f"{self.roa} {text} ({self.unit})" return f"{self.roa} {text} ({self.unit})"
@ -409,6 +409,69 @@ class Drug(models.Model):
return f"{self.name} ({self.common_name})" 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.common_name})"
# class Perms(models.Model): # class Perms(models.Model):
# class Meta: # class Meta:
# permissions = ( # permissions = (

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -209,6 +209,12 @@
<a class="navbar-item" href="{% url 'home' %}"> <a class="navbar-item" href="{% url 'home' %}">
Home Home
</a> </a>
<a class="navbar-item" href="{% url 'home' %}">
Search
</a>
<a class="navbar-item" href="{% url 'favourites' type='page' %}">
Favourites
</a>
{% if user.is_superuser %} {% if user.is_superuser %}
<div class="navbar-item has-dropdown is-hoverable"> <div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link"> <a class="navbar-link">
@ -225,9 +231,7 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
<a class="navbar-item" href="{% url 'home' %}">
Search
</a>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<div class="navbar-item has-dropdown is-hoverable"> <div class="navbar-item has-dropdown is-hoverable">

View File

@ -0,0 +1,76 @@
{% load pretty %}
{% load cache %}
{% cache 600 favourite_detail request.user.id object %}
{% include 'mixins/partials/notify.html' %}
{% if object is not None %}
<h1 class="title">{{ object.name }} - {{ object.nickname }} - {{ object.common_name }}</h1>
<p class="subtitle"><strong>{{ object.drug_class }}</strong></p>
<div class="block">
<a class="button is-info" href="#">Prices</a>
<a class="button is-info" href="#">More info</a>
</div>
<div class="grid">
<div class="cell">
<div class="box">
<h2 class="subtitle">Dosage</h2>
<ul>
{% for dose in object.dosages.all %}
<li>{{ dose }}</li>
{% endfor %}
</ul>
</div>
<div class="box">
<h2 class="subtitle">Timing</h2>
<ul>
{% for timing in object.timings.all %}
<li>{{ timing }}</li>
{% endfor %}
</ul>
</div>
<div class="box">
<h2 class="subtitle">Links</h2>
<ul>
{% for link in object.links.all %}
<li>{{ link }}</li>
{% endfor %}
</ul>
</div>
</div>
<div class="cell">
<div class="box">
<h2 class="subtitle">Actions</h2>
<ul>
{% for action in object.actions.all %}
<li>{{ action }}</li>
{% endfor %}
</ul>
</div>
<div class="box">
<h2 class="subtitle">Effects</h2>
<ul>
{% for effect in object.effects.all %}
<li>{{ effect }}</li>
{% endfor %}
</ul>
</div>
</div>
</div>
<div class="box">
<h2 class="subtitle">Experiences</h2>
<ul>
{% for exp in object.experiences.all %}
<li>{{ exp }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endcache %}

View File

@ -64,6 +64,15 @@
</span> </span>
</span> </span>
</button> </button>
<a href="{% url 'drug_detail' type='page' pk=item.id %}"><button
class="button">
<span class="icon-text">
<span class="icon">
<i class="fa-solid fa-eye"></i>
</span>
</span>
</button>
</a>
</div> </div>
</td> </td>
</tr> </tr>

View File

@ -0,0 +1,86 @@
{% load cache %}
{% load cachalot cache %}
{% get_last_invalidation 'core.Favourite' as last %}
{% include 'mixins/partials/notify.html' %}
{% cache 600 objects_favourittes 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>nickname</th>
<th>prices</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.nickname }}</td>
<td>{{ item.prices.count }}</td>
<td>{{ item.name }}</td>
<td>{{ item.drug_class }}</td>
<td>{{ item.common_name }}</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 'favourite_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 'favourite_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>
<a href="{% url 'favourite_detail' type='page' pk=item.id %}"><button
class="button">
<span class="icon-text">
<span class="icon">
<i class="fa-solid fa-eye"></i>
</span>
</span>
</button>
</a>
</div>
</td>
</tr>
{% endfor %}
</table>
{% endcache %}

View File

@ -5,523 +5,289 @@
{% load urlsafe %} {% load urlsafe %}
{% load cache %} {% load cache %}
{% cache 3600 results_table_full request.user.id table %} {# cache 3600 results_table_full request.user.id table #}
{% block table-wrapper %} {% block table-wrapper %}
<script src="{% static 'js/column-shifter.js' %}"></script> <script src="{% static 'js/column-shifter.js' %}"></script>
<div id="drilldown-table" class="column-shifter-container" style="position:relative; z-index:1;"> <div id="drilldown-table" class="column-shifter-container" style="position:relative; z-index:1;">
{% block table %} {% block table %}
<div class="nowrap-parent"> <div class="nowrap-parent">
<div class="nowrap-child"> <div class="nowrap-child">
<div class="dropdown" id="dropdown"> <div class="dropdown" id="dropdown">
<div class="dropdown-trigger"> <div class="dropdown-trigger">
<button id="dropdown-trigger" class="button dropdown-toggle" aria-haspopup="true" aria-controls="dropdown-menu"> <button id="dropdown-trigger" class="button dropdown-toggle" aria-haspopup="true" aria-controls="dropdown-menu">
<span>Show/hide fields</span> <span>Show/hide fields</span>
<span class="icon is-small"> <span class="icon is-small">
<i class="fas fa-angle-down" aria-hidden="true"></i> <i class="fas fa-angle-down" aria-hidden="true"></i>
</span> </span>
</button> </button>
</div> </div>
<div class="dropdown-menu" id="dropdown-menu" role="menu"> <div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content" style="position:absolute; z-index:2;"> <div class="dropdown-content" style="position:absolute; z-index:2;">
{% for column in table.columns %} {% for column in table.columns %}
{% if column.name in show %} {% if column.name in show %}
<a class="btn-shift-column dropdown-item" <a class="btn-shift-column dropdown-item"
data-td-class="{{ column.name }}" data-td-class="{{ column.name }}"
data-state="on" data-state="on"
{% if not forloop.last %} style="border-bottom:1px solid #ccc;" {%endif %} {% if not forloop.last %} style="border-bottom:1px solid #ccc;" {%endif %}
data-table-class-container="drilldown-table"> data-table-class-container="drilldown-table">
<span class="check icon" data-tooltip="Visible" style="display:none;"> <span class="check icon" data-tooltip="Visible" style="display:none;">
<i class="fa-solid fa-check"></i> <i class="fa-solid fa-check"></i>
</span> </span>
<span class="uncheck icon" data-tooltip="Hidden" style="display:none;"> <span class="uncheck icon" data-tooltip="Hidden" style="display:none;">
<i class="fa-solid fa-xmark"></i> <i class="fa-solid fa-xmark"></i>
</span> </span>
{{ column.header }} {{ column.header }}
</a> </a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="nowrap-child">
<span id="loader" class="button is-light has-text-link is-loading">Static</span>
</div>
</div> </div>
<script> <div class="nowrap-child">
var dropdown_button = document.getElementById("dropdown-trigger"); <span id="loader" class="button is-light has-text-link is-loading">Static</span>
var dropdown = document.getElementById("dropdown"); </div>
dropdown_button.addEventListener('click', function(e) { </div>
// elements[i].preventDefault(); <script>
dropdown.classList.toggle('is-active'); var dropdown_button = document.getElementById("dropdown-trigger");
}); var dropdown = document.getElementById("dropdown");
dropdown_button.addEventListener('click', function(e) {
// elements[i].preventDefault();
dropdown.classList.toggle('is-active');
});
</script> </script>
<div id="table-container" style="display:none;"> <div id="table-container" style="display:none;">
<table {% render_attrs table.attrs class="table drilldown-results-table is-fullwidth" %}> <table {% render_attrs table.attrs class="table drilldown-results-table is-fullwidth" %}>
{% block table.thead %} {% block table.thead %}
{% if table.show_header %} {% if table.show_header %}
<thead {% render_attrs table.attrs.thead class="" %}> <thead {% render_attrs table.attrs.thead class="" %}>
{% block table.thead.row %} {% block table.thead.row %}
<tr> <tr>
{% for column in table.columns %} {% for column in table.columns %}
{% if column.name in show %} {% if column.name in show %}
{% block table.thead.th %} {% block table.thead.th %}
<th class="orderable {{ column.name }}"> <th class="orderable {{ column.name }}">
<div class="nowrap-parent"> <div class="nowrap-parent">
{% if column.orderable %} {% if column.orderable %}
<div class="nowrap-child"> <div class="nowrap-child">
{% if column.is_ordered %} {% if column.is_ordered %}
{% is_descending column.order_by as descending %} {% is_descending column.order_by as descending %}
{% if descending %} {% if descending %}
<span class="icon" aria-hidden="true">{% block table.desc_icon %}<i class="fa-solid fa-sort-down"></i>{% endblock table.desc_icon %}</span> <span class="icon" aria-hidden="true">{% block table.desc_icon %}<i class="fa-solid fa-sort-down"></i>{% endblock table.desc_icon %}</span>
{% else %}
<span class="icon" aria-hidden="true">{% block table.asc_icon %}<i class="fa-solid fa-sort-up"></i>{% endblock table.asc_icon %}</span>
{% endif %}
{% else %} {% else %}
<span class="icon" aria-hidden="true">{% block table.orderable_icon %}<i class="fa-solid fa-sort"></i>{% endblock table.orderable_icon %}</span> <span class="icon" aria-hidden="true">{% block table.asc_icon %}<i class="fa-solid fa-sort-up"></i>{% endblock table.asc_icon %}</span>
{% endif %} {% endif %}
</div> {% else %}
<div class="nowrap-child"> <span class="icon" aria-hidden="true">{% block table.orderable_icon %}<i class="fa-solid fa-sort"></i>{% endblock table.orderable_icon %}</span>
<a {% endif %}
hx-get="search/partial/{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}&{{ uri }}" </div>
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' <div class="nowrap-child">
hx-trigger="click"
hx-target="#drilldown-table"
hx-swap="outerHTML"
hx-indicator="#spinner"
style="cursor: pointer;">
{{ column.header }}
</a>
</div>
{% else %}
<div class="nowrap-child">
{{ column.header }}
</div>
{% endif %}
</div>
</th>
{% endblock table.thead.th %}
{% endif %}
{% endfor %}
</tr>
{% endblock table.thead.row %}
</thead>
{% endif %}
{% endblock table.thead %}
{% block table.tbody %}
<tbody {{ table.attrs.tbody.as_html }}>
{% for row in table.paginated_rows %}
{% block table.tbody.row %}
{% if row.cells.type == 'control' %}
<tr>
<td></td>
<td>
<span class="icon has-text-grey" data-tooltip="Hidden">
<i class="fa-solid fa-file-slash"></i>
</span>
</td>
<td>
<p class="has-text-grey">Hidden {{ row.cells.hidden }} similar result{% if row.cells.hidden > 1%}s{% endif %}</p>
</td>
</tr>
{% else %}
<tr class="
{% if row.cells.exemption == True %}has-background-grey-lighter
{% elif cell == 'join' %}has-background-success-light
{% elif cell == 'quit' %}has-background-danger-light
{% elif cell == 'kick' %}has-background-danger-light
{% elif cell == 'part' %}has-background-warning-light
{% elif cell == 'mode' %}has-background-info-light
{% endif %}">
{% for column, cell in row.items %}
{% if column.name in show %}
{% block table.tbody.td %}
{% if cell == '—' %}
<td class="{{ column.name }}">
<span class="icon">
<i class="fa-solid fa-file-slash"></i>
</span>
</td>
{% elif column.name == 'src' %}
<td class="{{ column.name }}">
<a <a
class="has-text-grey" hx-get="search/partial/{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}&{{ uri }}"
onclick="populateSearch('src', '{{ cell|escapejs }}')">
{% if row.cells.src == 'irc' %}
<span class="icon" data-tooltip="IRC">
<i class="fa-solid fa-hashtag" aria-hidden="true"></i>
</span>
{% elif row.cells.src == 'dis' %}
<span class="icon" data-tooltip="Discord">
<i class="fa-brands fa-discord" aria-hidden="true"></i>
</span>
{% elif row.cells.src == '4ch' %}
<span class="icon" data-tooltip="4chan">
<i class="fa-solid fa-leaf" aria-hidden="true"></i>
</span>
{% endif %}
</a>
</td>
{% elif column.name == 'ts' %}
<td class="{{ column.name }}">
<p>{{ row.cells.date }}</p>
<p>{{ row.cells.time }}</p>
</td>
{% elif column.name == 'type' or column.name == 'mtype' %}
<td class="{{ column.name }}">
<a
class="has-text-grey"
onclick="populateSearch('{{ column.name }}', '{{ cell|escapejs }}')">
{% if cell == 'msg' %}
<span class="icon" data-tooltip="Message">
<i class="fa-solid fa-message"></i>
</span>
{% elif cell == 'join' %}
<span class="icon" data-tooltip="Join">
<i class="fa-solid fa-person-to-portal"></i>
</span>
{% elif cell == 'part' %}
<span class="icon" data-tooltip="Part">
<i class="fa-solid fa-person-from-portal"></i>
</span>
{% elif cell == 'quit' %}
<span class="icon" data-tooltip="Quit">
<i class="fa-solid fa-circle-xmark"></i>
</span>
{% elif cell == 'kick' %}
<span class="icon" data-tooltip="Kick">
<i class="fa-solid fa-user-slash"></i>
</span>
{% elif cell == 'nick' %}
<span class="icon" data-tooltip="Nick">
<i class="fa-solid fa-signature"></i>
</span>
{% elif cell == 'mode' %}
<span class="icon" data-tooltip="Mode">
<i class="fa-solid fa-gear"></i>
</span>
{% elif cell == 'action' %}
<span class="icon" data-tooltip="Action">
<i class="fa-solid fa-exclamation"></i>
</span>
{% elif cell == 'notice' %}
<span class="icon" data-tooltip="Notice">
<i class="fa-solid fa-message-code"></i>
</span>
{% elif cell == 'conn' %}
<span class="icon" data-tooltip="Connection">
<i class="fa-solid fa-cloud-exclamation"></i>
</span>
{% elif cell == 'znc' %}
<span class="icon" data-tooltip="ZNC">
<i class="fa-brands fa-unity"></i>
</span>
{% elif cell == 'query' %}
<span class="icon" data-tooltip="Query">
<i class="fa-solid fa-message"></i>
</span>
{% elif cell == 'highlight' %}
<span class="icon" data-tooltip="Highlight">
<i class="fa-solid fa-exclamation"></i>
</span>
{% elif cell == 'who' %}
<span class="icon" data-tooltip="Who">
<i class="fa-solid fa-passport"></i>
</span>
{% elif cell == 'topic' %}
<span class="icon" data-tooltip="Topic">
<i class="fa-solid fa-sign"></i>
</span>
{% else %}
{{ cell }}
{% endif %}
</a>
</td>
{% elif column.name == 'msg' %}
<td class="{{ column.name }} wrap">
<a
class="has-text-grey is-underlined"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'modal_context' %}"
hx-vals='{"net": "{{ row.cells.net|escapejs }}",
"num": "{{ row.cells.num|escapejs }}",
"source": "{{ row.cells.src|escapejs }}",
"channel": "{{ row.cells.channel|escapejs }}",
"time": "{{ row.cells.time|escapejs }}",
"date": "{{ row.cells.date|escapejs }}",
"index": "{% if row.cells.index != '—' %}{{row.cells.index}}{% else %}{{ params.index }}{% endif %}",
"type": "{{ row.cells.type }}",
"mtype": "{{ row.cells.mtype }}",
"nick": "{{ row.cells.nick|escapejs }}",
"dedup": "{{ params.dedup }}"}'
hx-target="#modals-here"
hx-trigger="click" hx-trigger="click"
href="/?modal=context&net={{row.cells.net|escapejs}}&num={{row.cells.num|escapejs}}&source={{row.cells.src|escapejs}}&channel={{row.cells.channel|urlsafe}}&time={{row.cells.time|escapejs}}&date={{row.cells.date|escapejs}}&index={{params.index}}&type={{row.cells.type}}&mtype={{row.cells.mtype}}&nick={{row.cells.mtype|escapejs}}"> hx-target="#drilldown-table"
{{ row.cells.msg }} hx-swap="outerHTML"
hx-indicator="#spinner"
style="cursor: pointer;">
{{ column.header }}
</a> </a>
</td> </div>
{% elif column.name == 'nick' %}
<td class="{{ column.name }}">
<div class="nowrap-parent">
<div class="nowrap-child">
{% if row.cells.online is True %}
<span class="icon has-text-success has-tooltip-success" data-tooltip="Online">
<i class="fa-solid fa-circle"></i>
</span>
{% elif row.cells.online is False %}
<span class="icon has-text-danger has-tooltip-danger" data-tooltip="Offline">
<i class="fa-solid fa-circle"></i>
</span>
{% else %}
<span class="icon has-text-warning has-tooltip-warning" data-tooltip="Unknown">
<i class="fa-solid fa-circle"></i>
</span>
{% endif %}
</div>
<a class="nowrap-child has-text-grey" onclick="populateSearch('nick', '{{ cell|escapejs }}')">
{{ cell }}
</a>
<div class="nowrap-child">
{% if row.cells.src == 'irc' %}
<a
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'modal_drilldown' %}"
hx-vals='{"net": "{{ row.cells.net }}", "nick": "{{ row.cells.nick }}", "channel": "{{ row.cells.channel }}"}'
hx-target="#modals-here"
hx-trigger="click"
class="has-text-black">
<span class="icon" data-tooltip="Open drilldown modal">
<i class="fa-solid fa-album"></i>
</span>
</a>
<a
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'modal_drilldown' type='window' %}"
hx-vals='{"net": "{{ row.cells.net }}", "nick": "{{ row.cells.nick }}", "channel": "{{ row.cells.channel }}"}'
hx-target="#windows-here"
hx-swap="afterend"
hx-trigger="click"
class="has-text-black">
<span class="icon" data-tooltip="Open drilldown window">
<i class="fa-solid fa-album"></i>
</span>
</a>
<a
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'modal_drilldown' type='widget' %}"
hx-vals='{"net": "{{ row.cells.net }}", "nick": "{{ row.cells.nick }}", "channel": "{{ row.cells.channel }}"}'
hx-target="#widgets-here"
hx-trigger="click"
class="has-text-black">
<span class="icon" data-tooltip="Open drilldown widget">
<i class="fa-solid fa-album"></i>
</span>
</a>
{% endif %}
</div>
{% if row.cells.num_chans != '—' %}
<div class="nowrap-child">
<span class="tag">
{{ row.cells.num_chans }}
</span>
</div>
{% endif %}
</div>
</td>
{% elif column.name == 'channel' %}
<td class="{{ column.name }}">
{% if cell != '—' %}
<div class="nowrap-parent">
<a
class="nowrap-child has-text-grey"
onclick="populateSearch('channel', '{{ cell|escapejs }}')">
{{ cell }}
</a>
{% if row.cells.num_users != '—' %}
<div class="nowrap-child">
<span class="tag">
{{ row.cells.num_users }}
</span>
</div>
{% endif %}
</div>
{% else %}
{{ cell }}
{% endif %}
</td>
{% elif cell is True or cell is False %}
<td class="{{ column.name }}">
{% if cell is True %}
<span class="icon has-text-success">
<i class="fa-solid fa-check"></i>
</span>
{% else %}
<span class="icon">
<i class="fa-solid fa-xmark"></i>
</span>
{% endif %}
</td>
{% elif column.name == "tokens" %}
<td class="{{ column.name }}">
<div class="tags">
{% for word in cell %}
<a
class="tag"
onclick="populateSearch('{{ column.name }}', '{{ word }}')">
{{ word }}
</a>
{% endfor %}
</div>
</td>
{% elif column.name == "meta" %}
<td class="{{ column.name }}">
<pre class="small-field" style="cursor: pointer;">{{ cell }}</pre>
</td>
{% elif 'id' in column.name and column.name != "ident" %}
<td class="{{ column.name }}">
<div class="buttons">
<div class="nowrap-parent">
<!-- <input class="input" type="text" value="{{ cell }}" style="width: 50px;" readonly> -->
<a
class="has-text-grey button nowrap-child"
onclick="populateSearch('{{ column.name }}', '{{ cell|escapejs }}')">
<span class="icon" data-tooltip="Populate {{ cell }}">
<i class="fa-solid fa-arrow-left-long-to-line" aria-hidden="true"></i>
</span>
</a>
<a
class="has-text-grey button nowrap-child"
onclick="window.prompt('Copy to clipboard: Ctrl+C, Enter', '{{ cell|escapejs }}');">
<span class="icon" data-tooltip="Copy to clipboard">
<i class="fa-solid fa-copy" aria-hidden="true"></i>
</span>
</a>
</div>
</div>
</td>
{% else %} {% else %}
<td class="{{ column.name }}"> <div class="nowrap-child">
<a {{ column.header }}
class="has-text-grey" </div>
onclick="populateSearch('{{ column.name }}', '{{ cell|escapejs }}')">
{{ cell }}
</a>
</td>
{% endif %} {% endif %}
{% endblock table.tbody.td %} </div>
{% endif %} </th>
{% endfor %} {% endblock table.thead.th %}
</tr> {% endif %}
{% endif %}
{% endblock table.tbody.row %} {% endfor %}
{% empty %} </tr>
{% if table.empty_text %} {% endblock table.thead.row %}
{% block table.tbody.empty_text %} </thead>
<tr><td class="{{ column.name }}" colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr> {% endif %}
{% endblock table.tbody.empty_text %} {% endblock table.thead %}
{% endif %} {% block table.tbody %}
{% endfor %} <tbody {{ table.attrs.tbody.as_html }}>
</tbody> {% for row in table.paginated_rows %}
{% endblock table.tbody %} {% block table.tbody.row %}
{% block table.tfoot %} {% if row.cells.type == 'control' %}
{% if table.has_footer %}
<tfoot {{ table.attrs.tfoot.as_html }}>
{% block table.tfoot.row %}
<tr> <tr>
{% for column in table.columns %} <td></td>
{% block table.tfoot.td %} <td>
<td class="{{ column.name }}" {{ column.attrs.tf.as_html }}>{{ column.footer }}</td> <span class="icon has-text-grey" data-tooltip="Hidden">
{% endblock table.tfoot.td %} <i class="fa-solid fa-file-slash"></i>
</span>
</td>
<td>
<p class="has-text-grey">Hidden {{ row.cells.hidden }} similar result{% if row.cells.hidden > 1%}s{% endif %}</p>
</td>
</tr>
{% else %}
<tr>
{% for column, cell in row.items %}
{% if column.name in show %}
{% block table.tbody.td %}
{% if cell == '—' %}
<td class="{{ column.name }}">
<span class="icon">
<i class="fa-solid fa-file-slash"></i>
</span>
</td>
{% elif cell is True or cell is False %}
<td class="{{ column.name }}">
{% if cell is True %}
<span class="icon has-text-success">
<i class="fa-solid fa-check"></i>
</span>
{% else %}
<span class="icon">
<i class="fa-solid fa-xmark"></i>
</span>
{% endif %}
</td>
{% elif column.name == "dosages" %}
<td class="{{ column.name }}">
{{ cell.entry }}
</td>
{% elif column.name == "name" %}
<td class="{{ column.name }}">
<a href="{% url 'drug_detail' type='page' pk=row.cells.id %}"><button
class="button">
<span class="icon-text">
<span class="icon">
<i class="fa-solid fa-eye"></i>
</span>
</span>
</button>
</a>
{{ cell }}
</td>
{% else %}
<td class="{{ column.name }}">
<a
class="has-text-grey"
onclick="populateSearch('{{ column.name }}', '{{ cell|escapejs }}')">
{{ cell }}
</a>
</td>
{% endif %}
{% endblock table.tbody.td %}
{% endif %}
{% endfor %} {% endfor %}
</tr> </tr>
{% endblock table.tfoot.row %} {% endif %}
</tfoot> {% endblock table.tbody.row %}
{% endif %} {% empty %}
{% endblock table.tfoot %} {% if table.empty_text %}
</table> {% block table.tbody.empty_text %}
</div> <tr><td class="{{ column.name }}" colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>
{% endblock table %} {% endblock table.tbody.empty_text %}
{% block pagination %}
{% if table.page and table.paginator.num_pages > 1 %}
<nav class="pagination is-justify-content-flex-end" role="navigation" aria-label="pagination">
{% block pagination.previous %}
<a
class="pagination-previous is-flex-grow-0 {% if not table.page.has_previous %}is-hidden-mobile{% endif %}"
{% if table.page.has_previous %}
hx-get="search/partial/{% querystring table.prefixed_page_field=table.page.previous_page_number %}&{{ uri }}"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-trigger="click"
hx-target="#drilldown-table"
hx-swap="outerHTML"
hx-indicator="#spinner"
{% else %}
href="#"
disabled
{% endif %} {% endif %}
style="order:1;"> {% endfor %}
{% block pagination.previous.text %} </tbody>
<span aria-hidden="true">&laquo;</span> {% endblock table.tbody %}
{% endblock pagination.previous.text %} {% block table.tfoot %}
</a> {% if table.has_footer %}
{% endblock pagination.previous %} <tfoot {{ table.attrs.tfoot.as_html }}>
{% block pagination.next %} {% block table.tfoot.row %}
<a <tr>
class="pagination-next is-flex-grow-0 {% if not table.page.has_next %}is-hidden-mobile{% endif %}" {% for column in table.columns %}
{% if table.page.has_next %} {% block table.tfoot.td %}
hx-get="search/partial/{% querystring table.prefixed_page_field=table.page.next_page_number %}&{{ uri }}" <td class="{{ column.name }}" {{ column.attrs.tf.as_html }}>{{ column.footer }}</td>
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' {% endblock table.tfoot.td %}
hx-trigger="click" {% endfor %}
hx-target="#drilldown-table" </tr>
hx-swap="outerHTML" {% endblock table.tfoot.row %}
hx-indicator="#spinner" </tfoot>
{% else %}
href="#"
disabled
{% endif %}
style="order:3;"
>
{% block pagination.next.text %}
<span aria-hidden="true">&raquo;</span>
{% endblock pagination.next.text %}
</a>
{% endblock pagination.next %}
{% if table.page.has_previous or table.page.has_next %}
{% block pagination.range %}
<ul class="pagination-list is-flex-grow-0" style="order:2;">
{% for p in table.page|table_page_range:table.paginator %}
<li>
<a
class="pagination-link {% if p == table.page.number %}is-current{% endif %}"
aria-label="Page {{ p }}" block
{% if p == table.page.number %}aria-current="page"{% endif %}
{% if p == table.page.number %}
href="#"
{% else %}
hx-get="search/partial/{% querystring table.prefixed_page_field=p %}&{{ uri }}"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-trigger="click"
hx-target="#drilldown-table"
hx-swap="outerHTML"
hx-indicator="#spinner"
{% endif %}
>
{% if p == '...' %}
<span class="pagination-ellipsis">&hellip;</span>
{% else %}
{{ p }}
{% endif %}
</a>
</li>
{% endfor %}
</ul>
{% endblock pagination.range %}
{% endif %} {% endif %}
</nav> {% endblock table.tfoot %}
{% endif %} </table>
{% endblock pagination %} </div>
</div> {% endblock table %}
{% endblock table-wrapper %} {% block pagination %}
{% endcache %} {% if table.page and table.paginator.num_pages > 1 %}
<nav class="pagination is-justify-content-flex-end" role="navigation" aria-label="pagination">
{% block pagination.previous %}
<a
class="pagination-previous is-flex-grow-0 {% if not table.page.has_previous %}is-hidden-mobile{% endif %}"
{% if table.page.has_previous %}
hx-get="search/partial/{% querystring table.prefixed_page_field=table.page.previous_page_number %}&{{ uri }}"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-trigger="click"
hx-target="#drilldown-table"
hx-swap="outerHTML"
hx-indicator="#spinner"
{% else %}
href="#"
disabled
{% endif %}
style="order:1;">
{% block pagination.previous.text %}
<span aria-hidden="true">&laquo;</span>
{% endblock pagination.previous.text %}
</a>
{% endblock pagination.previous %}
{% block pagination.next %}
<a
class="pagination-next is-flex-grow-0 {% if not table.page.has_next %}is-hidden-mobile{% endif %}"
{% if table.page.has_next %}
hx-get="search/partial/{% querystring table.prefixed_page_field=table.page.next_page_number %}&{{ uri }}"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-trigger="click"
hx-target="#drilldown-table"
hx-swap="outerHTML"
hx-indicator="#spinner"
{% else %}
href="#"
disabled
{% endif %}
style="order:3;"
>
{% block pagination.next.text %}
<span aria-hidden="true">&raquo;</span>
{% endblock pagination.next.text %}
</a>
{% endblock pagination.next %}
{% if table.page.has_previous or table.page.has_next %}
{% block pagination.range %}
<ul class="pagination-list is-flex-grow-0" style="order:2;">
{% for p in table.page|table_page_range:table.paginator %}
<li>
<a
class="pagination-link {% if p == table.page.number %}is-current{% endif %}"
aria-label="Page {{ p }}" block
{% if p == table.page.number %}aria-current="page"{% endif %}
{% if p == table.page.number %}
href="#"
{% else %}
hx-get="search/partial/{% querystring table.prefixed_page_field=p %}&{{ uri }}"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-trigger="click"
hx-target="#drilldown-table"
hx-swap="outerHTML"
hx-indicator="#spinner"
{% endif %}
>
{% if p == '...' %}
<span class="pagination-ellipsis">&hellip;</span>
{% else %}
{{ p }}
{% endif %}
</a>
</li>
{% endfor %}
</ul>
{% endblock pagination.range %}
{% endif %}
</nav>
{% endif %}
{% endblock pagination %}
</div>
{% endblock table-wrapper %}
{# endcache #}

View File

@ -0,0 +1,9 @@
import orjson
from django import template
register = template.Library()
@register.filter
def pretty(data):
return orjson.dumps(data, option=orjson.OPT_INDENT_2).decode("utf-8")

View File

@ -8,7 +8,9 @@ from core.forms import DrugForm
from core.models import Drug from core.models import Drug
from core.views.helpers import synchronize_async_helper from core.views.helpers import synchronize_async_helper
from mxs.restrictions import StaffMemberRequiredMixin from mxs.restrictions import StaffMemberRequiredMixin
from mxs.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate from mxs.views import ObjectCreate, ObjectDelete, ObjectList, ObjectRead, ObjectUpdate
# from mixins.views import ObjectRead
class DrugList(LoginRequiredMixin, StaffMemberRequiredMixin, ObjectList): class DrugList(LoginRequiredMixin, StaffMemberRequiredMixin, ObjectList):
@ -54,6 +56,25 @@ class DrugDelete(LoginRequiredMixin, StaffMemberRequiredMixin, ObjectDelete):
model = Drug model = Drug
class DrugDetail(LoginRequiredMixin, StaffMemberRequiredMixin, ObjectRead):
model = Drug
form_class = DrugForm
detail_template = "partials/drug-detail.html"
detail_url_name = "drug_detail"
detail_url_args = ["type", "pk"]
def get_object(self, **kwargs):
print("GET")
pk = kwargs.get("pk")
info = Drug.objects.get(pk=pk)
# self.extra_context = {}
print("info", info)
# return dictionary
return info.__dict__
class DrugClear(LoginRequiredMixin, StaffMemberRequiredMixin, APIView): class DrugClear(LoginRequiredMixin, StaffMemberRequiredMixin, APIView):
def delete(self, request): def delete(self, request):
template_name = "mixins/partials/notify.html" template_name = "mixins/partials/notify.html"

85
core/views/favourites.py Normal file
View File

@ -0,0 +1,85 @@
from django.contrib.auth.mixins import LoginRequiredMixin
# from mixins.restrictions import StaffMemberRequiredMixin
from mixins.views import (
ObjectCreate,
ObjectDelete,
ObjectList,
ObjectRead,
ObjectUpdate,
)
from core.forms import FavouriteForm
from core.models import Favourite
class FavouriteList(LoginRequiredMixin, ObjectList):
list_template = "partials/favourite-list.html"
model = Favourite
page_title = "Global list of favourites"
list_url_name = "favourites"
list_url_args = ["type"]
submit_url_name = "favourite_create"
class FavouriteCreate(LoginRequiredMixin, ObjectCreate):
model = Favourite
form_class = FavouriteForm
submit_url_name = "favourite_create"
class FavouriteUpdate(LoginRequiredMixin, ObjectUpdate):
model = Favourite
form_class = FavouriteForm
submit_url_name = "favourite_update"
class FavouriteDelete(LoginRequiredMixin, ObjectDelete):
model = Favourite
class FavouriteDetail(LoginRequiredMixin, ObjectRead):
model = Favourite
form_class = FavouriteForm
detail_template = "partials/drug-detail.html"
detail_url_name = "favourite_detail"
detail_url_args = ["type", "pk"]
def get_object(self, **kwargs):
pk = kwargs.get("pk")
info = Favourite.objects.get(pk=pk, user=self.request.user)
return info.__dict__
# class FavouriteClear(LoginRequiredMixin, APIView):
# def delete(self, request):
# template_name = "mixins/partials/notify.html"
# favourites_all = Favourite.objects.all()
# favourites_all.delete()
# context = {
# "message": "Deleted all favourites",
# "class": "success",
# }
# response = render(request, template_name, context)
# response["HX-Trigger"] = "drugEvent"
# return response
# class FavouritePullMerge(LoginRequiredMixin, APIView):
# def post(self, request):
# template_name = "mixins/partials/notify.html"
# # Do something
# run = synchronize_async_helper(PsychWikiClient())
# result = synchronize_async_helper(run.update_favourites())
# context = {
# "message": f"Favourites fetched: {result}",
# "class": "success",
# }
# response = render(request, template_name, context)
# response["HX-Trigger"] = "drugEvent"
# return response