Implement initial drug search
This commit is contained in:
parent
b924c0556c
commit
6131c58857
@ -49,3 +49,16 @@ if DEBUG:
|
||||
]
|
||||
|
||||
SETTINGS_EXPORT = ["BILLING_ENABLED"]
|
||||
|
||||
MAIN_SIZES = ["1", "5", "15", "30", "50", "100", "250", "500", "1000"]
|
||||
MAIN_SIZES_ANON = ["1", "5", "15", "30", "50", "100"]
|
||||
MAIN_SOURCES = ["substances", "experiences", "all"]
|
||||
# CACHE = False
|
||||
# CACHE_TIMEOUT = 2
|
||||
|
||||
DRUGS_RESULTS_PER_PAGE = 15
|
||||
DRUGS_DEFAULT_PARAMS = {
|
||||
"size": "15",
|
||||
"sorting": "desc",
|
||||
"source": "substances",
|
||||
}
|
||||
|
@ -41,8 +41,8 @@ INSTALLED_APPS = [
|
||||
"django_htmx",
|
||||
"crispy_forms",
|
||||
"crispy_bulma",
|
||||
# "django_tables2",
|
||||
# "django_tables2_bulma_template",
|
||||
"django_tables2",
|
||||
"django_tables2_bulma_template",
|
||||
"django_otp",
|
||||
"django_otp.plugins.otp_totp",
|
||||
# "django_otp.plugins.otp_email",
|
||||
|
@ -24,7 +24,8 @@ from core.views import base, demo, drugs, notifications, search
|
||||
|
||||
urlpatterns = [
|
||||
path("__debug__/", include("debug_toolbar.urls")),
|
||||
path("", base.Home.as_view(), name="home"),
|
||||
path("", search.DrugsTableView.as_view(), name="home"),
|
||||
# path("", base.Home.as_view(), name="home"),
|
||||
path("admin/", admin.site.urls),
|
||||
# 2FA login urls
|
||||
path("", include(tf_urls)),
|
||||
@ -67,5 +68,6 @@ urlpatterns = [
|
||||
name="drug_pull_merge",
|
||||
),
|
||||
# Drug search
|
||||
path("search/<str:type>/", search.Search.as_view(), name="search"),
|
||||
path("search/", search.DrugsTableView.as_view(), name="search"),
|
||||
path("search/partial/", search.DrugsTableView.as_view(), name="search_partial"),
|
||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
16
core/db/__init__.py
Normal file
16
core/db/__init__.py
Normal file
@ -0,0 +1,16 @@
|
||||
from django.conf import settings
|
||||
|
||||
from core.db import orm
|
||||
|
||||
|
||||
def remove_defaults(query_params):
|
||||
for field, value in list(query_params.items()):
|
||||
if field in settings.DRUGS_DEFAULT_PARAMS:
|
||||
if value == settings.DRUGS_DEFAULT_PARAMS[field]:
|
||||
del query_params[field]
|
||||
|
||||
|
||||
def add_defaults(query_params):
|
||||
for field, value in settings.DRUGS_DEFAULT_PARAMS.items():
|
||||
if field not in query_params:
|
||||
query_params[field] = value
|
95
core/db/orm.py
Normal file
95
core/db/orm.py
Normal file
@ -0,0 +1,95 @@
|
||||
from django.conf import settings
|
||||
|
||||
from core import db
|
||||
from core.models import Drug
|
||||
|
||||
|
||||
class QueryError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def parse_size(query_params, sizes):
|
||||
if "size" in query_params:
|
||||
size = query_params["size"]
|
||||
if size not in sizes:
|
||||
message = "Size is not permitted"
|
||||
message_class = "danger"
|
||||
return {"message": message, "class": message_class}
|
||||
size = int(size)
|
||||
else:
|
||||
size = 15
|
||||
|
||||
return size
|
||||
|
||||
|
||||
def parse_source(user, query_params):
|
||||
source = None
|
||||
if "source" in query_params:
|
||||
source = query_params["source"]
|
||||
|
||||
# Check validity of source
|
||||
if source not in settings.MAIN_SOURCES:
|
||||
message = f"Invalid source: {source}"
|
||||
message_class = "danger"
|
||||
return {"message": message, "class": message_class}
|
||||
|
||||
if source == "all":
|
||||
source = None # the next block will populate it
|
||||
|
||||
if source:
|
||||
sources = [source]
|
||||
else:
|
||||
# Here we need to populate what "all" means for the user.
|
||||
# They may only have access to a subset of the sources.
|
||||
# We build a custom source list with ones they have access
|
||||
# to, and then remove "all" from the list.
|
||||
sources = list(settings.MAIN_SOURCES)
|
||||
|
||||
# Get rid of "all", it's just a meta-source
|
||||
if "all" in sources:
|
||||
sources.remove("all")
|
||||
|
||||
return sources
|
||||
|
||||
|
||||
def run_query(query_params, tags, size, sources, ranges, sort):
|
||||
if "query" in query_params:
|
||||
query = query_params["query"]
|
||||
results = Drug.objects.filter(name__icontains=query)[:size]
|
||||
return results
|
||||
|
||||
|
||||
def drug_query(request, query_params, size=None, tags=None):
|
||||
db.add_defaults(query_params)
|
||||
|
||||
print("PARAMS11", query_params)
|
||||
print("SIZE", size)
|
||||
print("TAGS", tags)
|
||||
|
||||
# S - Size
|
||||
if request.user.is_anonymous:
|
||||
sizes = settings.MAIN_SIZES_ANON
|
||||
else:
|
||||
sizes = settings.MAIN_SIZES
|
||||
if not size:
|
||||
size = parse_size(query_params, sizes)
|
||||
if isinstance(size, dict):
|
||||
return size
|
||||
|
||||
# S - Source
|
||||
sources = parse_source(request.user, query_params)
|
||||
if isinstance(sources, dict):
|
||||
return sources
|
||||
|
||||
# R - Ranges
|
||||
ranges = None
|
||||
|
||||
# S - Sort
|
||||
sort = None
|
||||
|
||||
# Q/T - Query/Tags
|
||||
result = run_query(query_params, tags, size, sources, ranges, sort)
|
||||
rtrn = {
|
||||
"object_list": result,
|
||||
}
|
||||
return rtrn
|
1
core/static/css/bulma-calendar.min.css
vendored
Normal file
1
core/static/css/bulma-calendar.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
core/static/css/bulma-slider.min.css
vendored
Normal file
1
core/static/css/bulma-slider.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
core/static/css/bulma-switch.min.css
vendored
Normal file
1
core/static/css/bulma-switch.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
2
core/static/css/bulma-tagsinput.min.css
vendored
Normal file
2
core/static/css/bulma-tagsinput.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
core/static/css/bulma.min copy.css
Normal file
1
core/static/css/bulma.min copy.css
Normal file
File diff suppressed because one or more lines are too long
6
core/static/css/icons copy.css
Normal file
6
core/static/css/icons copy.css
Normal file
File diff suppressed because one or more lines are too long
1
core/static/js/bulma-calendar.min.js
vendored
Normal file
1
core/static/js/bulma-calendar.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
core/static/js/bulma-slider.min.js
vendored
Normal file
1
core/static/js/bulma-slider.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
core/static/js/bulma-tagsinput.min.js
vendored
Normal file
2
core/static/js/bulma-tagsinput.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
13
core/static/js/chart.js
Normal file
13
core/static/js/chart.js
Normal file
File diff suppressed because one or more lines are too long
259
core/static/js/column-shifter.js
Normal file
259
core/static/js/column-shifter.js
Normal file
@ -0,0 +1,259 @@
|
||||
// Author: Grzegorz Tężycki
|
||||
|
||||
$(document).ready(function(){
|
||||
|
||||
// In web storage is saved structure like that:
|
||||
// localStorage['django_tables2_column_shifter'] = {
|
||||
// 'table_class_container1' : {
|
||||
// 'id' : 'on',
|
||||
// 'col1' : 'off',
|
||||
// 'col2' : 'on',
|
||||
// 'col3' : 'on',
|
||||
// },
|
||||
// 'table_class_container2' : {
|
||||
// 'id' : 'on',
|
||||
// 'col1' : 'on'
|
||||
// },
|
||||
// }
|
||||
|
||||
// main name for key in web storage
|
||||
var COLUMN_SHIFTER_STORAGE_ACCESOR = "django_tables2_column_shifter";
|
||||
|
||||
// Return storage structure for shifter
|
||||
// If structure does'n exist in web storage
|
||||
// will be return empty object
|
||||
var get_column_shifter_storage = function(){
|
||||
var storage = localStorage.getItem(COLUMN_SHIFTER_STORAGE_ACCESOR);
|
||||
if (storage === null) {
|
||||
storage = {
|
||||
"drilldown-table": {
|
||||
"date": "off",
|
||||
"time": "off",
|
||||
"id": "off",
|
||||
"host": "off",
|
||||
"ident": "off",
|
||||
"channel": "off",
|
||||
"net": "off",
|
||||
"num": "off",
|
||||
"channel_nsfw": "off",
|
||||
"channel_category": "off",
|
||||
"channel_category_id": "off",
|
||||
"channel_category_nsfw": "off",
|
||||
"channel_id": "off",
|
||||
"guild_member_count": "off",
|
||||
"bot": "off",
|
||||
"msg_id": "off",
|
||||
"user": "off",
|
||||
"net_id": "off",
|
||||
"user_id": "off",
|
||||
"nick_id": "off",
|
||||
"status": "off",
|
||||
"num_users": "off",
|
||||
"num_chans": "off",
|
||||
"exemption": "off",
|
||||
// "version_sentiment": "off",
|
||||
"sentiment": "off",
|
||||
"num": "off",
|
||||
"online": "off",
|
||||
"mtype": "off",
|
||||
"realname": "off",
|
||||
"server": "off",
|
||||
"mtype": "off",
|
||||
"hidden": "off",
|
||||
"filename": "off",
|
||||
"file_md5": "off",
|
||||
"file_ext": "off",
|
||||
"file_size": "off",
|
||||
"lang_code": "off",
|
||||
"tokens": "off",
|
||||
"rule_id": "off",
|
||||
"index": "off",
|
||||
"meta": "off",
|
||||
"match_ts": "off",
|
||||
"batch_id": "off"
|
||||
//"lang_name": "off",
|
||||
// "words_noun": "off",
|
||||
// "words_adj": "off",
|
||||
// "words_verb": "off",
|
||||
// "words_adv": "off"
|
||||
},
|
||||
};
|
||||
} else {
|
||||
storage = JSON.parse(storage);
|
||||
}
|
||||
return storage;
|
||||
};
|
||||
|
||||
// Save structure in web storage
|
||||
var set_column_shifter_storage = function(storage){
|
||||
var json_storage = JSON.stringify(storage)
|
||||
localStorage.setItem(COLUMN_SHIFTER_STORAGE_ACCESOR, json_storage);
|
||||
};
|
||||
|
||||
// Remember state for single button
|
||||
var save_btn_state = function($btn){
|
||||
|
||||
// Take css class for container with table
|
||||
var table_class_container = $btn.data("table-class-container");
|
||||
// Take html object with table
|
||||
var $table_class_container = $("#" + table_class_container);
|
||||
// Take single button statne ("on" / "off")
|
||||
var state = $btn.data("state");
|
||||
// td-class is a real column name in table
|
||||
var td_class = $btn.data("td-class");
|
||||
var storage = get_column_shifter_storage();
|
||||
// Table id
|
||||
var id = $table_class_container.attr("id");
|
||||
|
||||
// Checking if the ID is already in storage
|
||||
if (id in storage) {
|
||||
data = storage[id]
|
||||
} else {
|
||||
data = {}
|
||||
storage[id] = data;
|
||||
}
|
||||
|
||||
// Save state for table column in storage
|
||||
data[td_class] = state;
|
||||
set_column_shifter_storage(storage);
|
||||
};
|
||||
|
||||
// Load states for buttons from storage for single tabel
|
||||
var load_states = function($table_class_container) {
|
||||
var storage = get_column_shifter_storage();
|
||||
// Table id
|
||||
var id = $table_class_container.attr("id");
|
||||
var data = {};
|
||||
|
||||
// Checking if the ID is already in storage
|
||||
if (id in storage) {
|
||||
data = storage[id]
|
||||
|
||||
// For each shifter button set state
|
||||
$table_class_container.find(".btn-shift-column").each(function(){
|
||||
var $btn = $(this);
|
||||
var td_class = $btn.data("td-class");
|
||||
|
||||
// If name of column is in store then get state
|
||||
// and set state
|
||||
if (td_class in data) {
|
||||
var state = data[td_class]
|
||||
set_btn_state($btn, state);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Show table content and hide spiner
|
||||
var show_table_content = function($table_class_container){
|
||||
$table_class_container.find("#loader").hide();
|
||||
$table_class_container.find("#table-container").show();
|
||||
};
|
||||
|
||||
// Load buttons states for all button in page
|
||||
var load_state_for_all_containters = function(){
|
||||
$(".column-shifter-container").each(function(){
|
||||
$table_class_container = $(this);
|
||||
|
||||
// Load states for all buttons in single container
|
||||
load_states($table_class_container);
|
||||
|
||||
// When states was loaded then table must be show and
|
||||
// loader (spiner) must be hide
|
||||
show_table_content($table_class_container);
|
||||
});
|
||||
};
|
||||
|
||||
// change visibility column for single button
|
||||
// if button has state "on" then show column
|
||||
// else then column will be hide
|
||||
shift_column = function( $btn ){
|
||||
// button state
|
||||
var state = $btn.data("state");
|
||||
|
||||
// td-class is a real column name in table
|
||||
var td_class = $btn.data("td-class");
|
||||
var table_class_container = $btn.data("table-class-container");
|
||||
var $table_class_container = $("#" + table_class_container);
|
||||
var $table = $table_class_container.find("table");
|
||||
var $cels = $table.find("." + td_class);
|
||||
|
||||
if ( state === "on" ) {
|
||||
$cels.show();
|
||||
} else {
|
||||
$cels.hide();
|
||||
}
|
||||
};
|
||||
|
||||
// Shift visibility for all columns
|
||||
shift_columns = function(){
|
||||
var cols = $(".btn-shift-column");
|
||||
var i, len = cols.length;
|
||||
for (i=0; i < len; i++) {
|
||||
shift_column($(cols[i]));
|
||||
}
|
||||
};
|
||||
|
||||
// Set icon imgae visibility for button state
|
||||
var set_icon_for_state = function( $btn, state ) {
|
||||
if (state === "on") {
|
||||
$btn.find("span.uncheck").hide();
|
||||
$btn.find("span.check").show();
|
||||
} else {
|
||||
$btn.find("span.check").hide();
|
||||
$btn.find("span.uncheck").show();
|
||||
}
|
||||
};
|
||||
|
||||
// Set state for single button
|
||||
var set_btn_state = function($btn, state){
|
||||
$btn.data('state', state);
|
||||
set_icon_for_state($btn, state);
|
||||
}
|
||||
|
||||
// Change state for single button
|
||||
var change_btn_state = function($btn){
|
||||
var state = $btn.data("state");
|
||||
|
||||
if (state === "on") {
|
||||
state = "off"
|
||||
} else {
|
||||
state = "on"
|
||||
}
|
||||
set_btn_state($btn, state);
|
||||
};
|
||||
|
||||
// Run show/hide when click on button
|
||||
$(".btn-shift-column").on("click", function(event){
|
||||
var $btn = $(this);
|
||||
event.stopPropagation();
|
||||
change_btn_state($btn);
|
||||
shift_column($btn);
|
||||
save_btn_state($btn);
|
||||
});
|
||||
|
||||
// Load saved states for all tables
|
||||
load_state_for_all_containters();
|
||||
|
||||
// show or hide columns based on data from web storage
|
||||
shift_columns();
|
||||
|
||||
// Add API method for retrieving non-visible cols for table
|
||||
// Pass the 0-based index of the table or leave the parameter
|
||||
// empty to return the hidden cols for the 1st table found
|
||||
$.django_tables2_column_shifter_hidden = function(idx) {
|
||||
if(idx==undefined) {
|
||||
idx = 0;
|
||||
}
|
||||
return $('#table-container').eq(idx).find('.btn-shift-column').filter(function(z) {
|
||||
return $(this).data('state')=='off'
|
||||
}).map(function(z) {
|
||||
return $(this).data('td-class')
|
||||
}).toArray();
|
||||
}
|
||||
const event = new Event('restore-scroll');
|
||||
document.dispatchEvent(event);
|
||||
const event2 = new Event('load-widget-results');
|
||||
document.dispatchEvent(event2);
|
||||
|
||||
});
|
1
core/static/js/htmx.min copy.js
Normal file
1
core/static/js/htmx.min copy.js
Normal file
File diff suppressed because one or more lines are too long
2
core/static/js/jquery.min.js
vendored
Normal file
2
core/static/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -6,16 +6,24 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>XF - {{ request.path_info }}</title>
|
||||
<title>DIMAR - {{ request.path_info }}</title>
|
||||
<link rel="shortcut icon" href="{% static 'favicon.ico' %}">
|
||||
<link rel="manifest" href="{% static 'manifest.webmanifest' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bulma.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bulma-tooltip.min.css' %}">
|
||||
<link rel="stylesheet" href="https://site-assets.fontawesome.com/releases/v6.1.1/css/all.css">
|
||||
<link rel="stylesheet" href="{% static 'css/bulma-slider.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bulma-calendar.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bulma-tagsinput.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bulma-switch.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/gridstack.min.css' %}">
|
||||
<script src="{% static 'js/bulma-calendar.min.js' %}" integrity="sha384-DThNif0xGXbopX7+PE+UabkuClfI/zELNhaVqoGLutaWB76dyMw0vIQBGmUxSfVQ" crossorigin="anonymous"></script>
|
||||
<script src="{% static 'js/bulma-slider.min.js' %}" integrity="sha384-wbyps8iLG8QzJE02viYc/27BtT5HSa11+b5V7QPR1/huVuA8f4LRTNGc82qAIeIZ" crossorigin="anonymous"></script>
|
||||
<script src="{% static 'js/htmx.min.js' %}" integrity="sha384-cZuAZ+ZbwkNRnrKi05G/fjBX+azI9DNOkNYysZ0I/X5ZFgsmMiBXgDZof30F5ofc" crossorigin="anonymous"></script>
|
||||
<script defer src="{% static 'js/hyperscript.min.js' %}" integrity="sha384-6GYN8BDHOJkkru6zcpGOUa//1mn+5iZ/MyT6mq34WFIpuOeLF52kSi721q0SsYF9" crossorigin="anonymous"></script>
|
||||
<script defer src="{% static 'js/remove-me.js' %}" integrity="sha384-6fHcFNoQ8QEI3ZDgw9Z/A6Brk64gF7AnFbLgdrumo8/kBbsKQ/wo7wPegj5WkzuG" crossorigin="anonymous"></script>
|
||||
<script defer src="{% static 'js/hyperscript.min.js' %}" integrity="sha384-6GYN8BDHOJkkru6zcpGOUa//1mn+5iZ/MyT6mq34WFIpuOeLF52kSi721q0SsYF9" crossorigin="anonymous"></script>
|
||||
<script src="{% static 'js/bulma-tagsinput.min.js' %}"></script>
|
||||
<script src="{% static 'js/jquery.min.js' %}"></script>
|
||||
<script src="{% static 'js/gridstack-all.js' %}"></script>
|
||||
<script defer src="{% static 'js/magnet.min.js' %}"></script>
|
||||
<script>
|
||||
@ -217,10 +225,11 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<a class="navbar-item" href="{% url 'home' %}">
|
||||
Search
|
||||
</a>
|
||||
{% if user.is_authenticated %}
|
||||
<a class="navbar-item" href="{% url 'search' type='page' %}">
|
||||
Search
|
||||
</a>
|
||||
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<a class="navbar-link">
|
||||
Account
|
||||
|
@ -1,7 +1,59 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load joinsep %}
|
||||
{% block outer_content %}
|
||||
{% if params.modal == 'context' %}
|
||||
<div
|
||||
style="display: none;"
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-post="{% url 'modal_context' %}"
|
||||
hx-vals='{"net": "{{ params.net|escapejs }}",
|
||||
"num": "{{ params.num|escapejs }}",
|
||||
"source": "{{ params.source|escapejs }}",
|
||||
"channel": "{{ params.channel|escapejs }}",
|
||||
"time": "{{ params.time|escapejs }}",
|
||||
"date": "{{ params.date|escapejs }}",
|
||||
"index": "{{ params.index }}",
|
||||
"type": "{{ params.type|escapejs }}",
|
||||
"mtype": "{{ params.mtype|escapejs }}",
|
||||
"nick": "{{ params.nick|escapejs }}"}'
|
||||
hx-target="#modals-here"
|
||||
hx-trigger="load">
|
||||
</div>
|
||||
{% endif %}
|
||||
<script src="{% static 'js/chart.js' %}"></script>
|
||||
<script src="{% static 'tabs.js' %}"></script>
|
||||
<script>
|
||||
function setupTags() {
|
||||
var inputTags = document.getElementById('tags');
|
||||
new BulmaTagsInput(inputTags);
|
||||
|
||||
inputTags.BulmaTagsInput().on('before.add', function(item) {
|
||||
if (item.includes(": ")) {
|
||||
var spl = item.split(": ");
|
||||
} else {
|
||||
var spl = item.split(":");
|
||||
}
|
||||
var field = spl[0];
|
||||
try {
|
||||
var value = JSON.parse(spl[1]);
|
||||
} catch {
|
||||
var value = spl[1];
|
||||
}
|
||||
return `${field}: ${value}`;
|
||||
});
|
||||
inputTags.BulmaTagsInput().on('after.remove', function(item) {
|
||||
var spl = item.split(": ");
|
||||
var field = spl[0];
|
||||
var value = spl[1].trim();
|
||||
});
|
||||
}
|
||||
function populateSearch(field, value) {
|
||||
var inputTags = document.getElementById('tags');
|
||||
inputTags.BulmaTagsInput().add(field+": "+value);
|
||||
//htmx.trigger("#search", "click");
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid-stack" id="grid-stack-main">
|
||||
<div class="grid-stack-item" gs-w="7" gs-h="10" gs-y="0" gs-x="1">
|
||||
@ -9,10 +61,10 @@
|
||||
<nav class="panel">
|
||||
<p class="panel-heading" style="padding: .2em; line-height: .5em;">
|
||||
<i class="fa-solid fa-arrows-up-down-left-right has-text-grey-light"></i>
|
||||
Home
|
||||
Search
|
||||
</p>
|
||||
<article class="panel-block is-active">
|
||||
{% include 'window-content/main.html' %}
|
||||
{% include 'window-content/search.html' %}
|
||||
</article>
|
||||
</nav>
|
||||
</div>
|
||||
@ -31,6 +83,7 @@
|
||||
animate: true,
|
||||
});
|
||||
// GridStack.init();
|
||||
setupTags();
|
||||
|
||||
// a widget is ready to be loaded
|
||||
document.addEventListener('load-widget', function(event) {
|
||||
@ -53,43 +106,47 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clear the queue element
|
||||
container.outerHTML = "";
|
||||
grid.addWidget(widgetelement);
|
||||
|
||||
// temporary workaround, other widgets can be duplicated, but not results
|
||||
if (widgetelement.id == 'widget-results') {
|
||||
grid.removeWidget("widget-results");
|
||||
}
|
||||
|
||||
grid.addWidget(widgetelement);
|
||||
// re-create the HTMX JS listeners, otherwise HTMX won't work inside the grid
|
||||
htmx.process(widgetelement);
|
||||
|
||||
// update the size of the widget according to its content
|
||||
var added_widget = htmx.find(grid_element, "#"+new_id);
|
||||
var itemContent = htmx.find(added_widget, ".control");
|
||||
var scrollheight = itemContent.scrollHeight+80;
|
||||
var verticalmargin = 0;
|
||||
var cellheight = grid.opts.cellHeight;
|
||||
var height = Math.ceil((scrollheight + verticalmargin) / (cellheight + verticalmargin));
|
||||
var opts = {
|
||||
h: height,
|
||||
}
|
||||
grid.update(
|
||||
added_widget,
|
||||
opts
|
||||
);
|
||||
// update size when the widget is loaded
|
||||
document.addEventListener('load-widget-results', function(evt) {
|
||||
var added_widget = htmx.find(grid_element, '#widget-results');
|
||||
var itemContent = htmx.find(added_widget, ".control");
|
||||
var scrollheight = itemContent.scrollHeight+80;
|
||||
var verticalmargin = 0;
|
||||
var cellheight = grid.opts.cellHeight;
|
||||
var height = Math.ceil((scrollheight + verticalmargin) / (cellheight + verticalmargin));
|
||||
var opts = {
|
||||
h: height,
|
||||
}
|
||||
grid.update(
|
||||
added_widget,
|
||||
opts
|
||||
);
|
||||
});
|
||||
|
||||
// run the JS scripts inside the added element again
|
||||
// for instance, this will fix the dropdown
|
||||
for (var i = 0; i < scripts.length; i++) {
|
||||
eval(scripts[i].innerHTML);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="modals-here">
|
||||
</div>
|
||||
<div id="items-here">
|
||||
</div>
|
||||
<div id="widgets-here" style="display: none;">
|
||||
</div>
|
||||
<script>
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
{% block widgets %}
|
||||
{% if table or message is not None %}
|
||||
{% include 'partials/results_load.html' %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
21
core/templates/partials/results_load.html
Normal file
21
core/templates/partials/results_load.html
Normal file
@ -0,0 +1,21 @@
|
||||
{% extends 'mixins/wm/widget.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block heading %}
|
||||
Results
|
||||
{% endblock %}
|
||||
|
||||
{% block panel_content %}
|
||||
{% include 'mixins/partials/notify.html' %}
|
||||
{% if cache is not None %}
|
||||
<span class="icon has-tooltip-bottom" data-tooltip="Cached">
|
||||
<i class="fa-solid fa-database"></i>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
fetched {{ table.data|length }} hits
|
||||
|
||||
|
||||
{% include 'partials/results_table.html' %}
|
||||
{# include 'partials/sentiment_chart.html' #}
|
||||
{% endblock %}
|
527
core/templates/partials/results_table.html
Normal file
527
core/templates/partials/results_table.html
Normal file
@ -0,0 +1,527 @@
|
||||
{% load django_tables2 %}
|
||||
{% load django_tables2_bulma_template %}
|
||||
{% load static %}
|
||||
{% load joinsep %}
|
||||
{% load urlsafe %}
|
||||
{% load cache %}
|
||||
|
||||
{% cache 3600 results_table_full request.user.id table %}
|
||||
{% block table-wrapper %}
|
||||
<script src="{% static 'js/column-shifter.js' %}"></script>
|
||||
<div id="drilldown-table" class="column-shifter-container" style="position:relative; z-index:1;">
|
||||
{% block table %}
|
||||
<div class="nowrap-parent">
|
||||
<div class="nowrap-child">
|
||||
<div class="dropdown" id="dropdown">
|
||||
<div class="dropdown-trigger">
|
||||
<button id="dropdown-trigger" class="button dropdown-toggle" aria-haspopup="true" aria-controls="dropdown-menu">
|
||||
<span>Show/hide fields</span>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-angle-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-menu" id="dropdown-menu" role="menu">
|
||||
<div class="dropdown-content" style="position:absolute; z-index:2;">
|
||||
{% for column in table.columns %}
|
||||
{% if column.name in show %}
|
||||
<a class="btn-shift-column dropdown-item"
|
||||
data-td-class="{{ column.name }}"
|
||||
data-state="on"
|
||||
{% if not forloop.last %} style="border-bottom:1px solid #ccc;" {%endif %}
|
||||
data-table-class-container="drilldown-table">
|
||||
<span class="check icon" data-tooltip="Visible" style="display:none;">
|
||||
<i class="fa-solid fa-check"></i>
|
||||
</span>
|
||||
<span class="uncheck icon" data-tooltip="Hidden" style="display:none;">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</span>
|
||||
{{ column.header }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nowrap-child">
|
||||
<span id="loader" class="button is-light has-text-link is-loading">Static</span>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
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>
|
||||
<div id="table-container" style="display:none;">
|
||||
<table {% render_attrs table.attrs class="table drilldown-results-table is-fullwidth" %}>
|
||||
{% block table.thead %}
|
||||
{% if table.show_header %}
|
||||
<thead {% render_attrs table.attrs.thead class="" %}>
|
||||
{% block table.thead.row %}
|
||||
<tr>
|
||||
{% for column in table.columns %}
|
||||
{% if column.name in show %}
|
||||
{% block table.thead.th %}
|
||||
<th class="orderable {{ column.name }}">
|
||||
<div class="nowrap-parent">
|
||||
{% if column.orderable %}
|
||||
<div class="nowrap-child">
|
||||
{% if column.is_ordered %}
|
||||
{% is_descending column.order_by as 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>
|
||||
{% 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 %}
|
||||
<span class="icon" aria-hidden="true">{% block table.orderable_icon %}<i class="fa-solid fa-sort"></i>{% endblock table.orderable_icon %}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="nowrap-child">
|
||||
<a
|
||||
hx-get="search/partial/{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}&{{ uri }}"
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
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
|
||||
class="has-text-grey"
|
||||
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-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"
|
||||
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}}">
|
||||
{{ row.cells.msg }}
|
||||
</a>
|
||||
</td>
|
||||
{% 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 %}
|
||||
<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 %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endblock table.tbody.row %}
|
||||
{% empty %}
|
||||
{% if table.empty_text %}
|
||||
{% block table.tbody.empty_text %}
|
||||
<tr><td class="{{ column.name }}" colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>
|
||||
{% endblock table.tbody.empty_text %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endblock table.tbody %}
|
||||
{% block table.tfoot %}
|
||||
{% if table.has_footer %}
|
||||
<tfoot {{ table.attrs.tfoot.as_html }}>
|
||||
{% block table.tfoot.row %}
|
||||
<tr>
|
||||
{% for column in table.columns %}
|
||||
{% block table.tfoot.td %}
|
||||
<td class="{{ column.name }}" {{ column.attrs.tf.as_html }}>{{ column.footer }}</td>
|
||||
{% endblock table.tfoot.td %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endblock table.tfoot.row %}
|
||||
</tfoot>
|
||||
{% endif %}
|
||||
{% endblock table.tfoot %}
|
||||
</table>
|
||||
</div>
|
||||
{% endblock table %}
|
||||
{% 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 %}
|
||||
style="order:1;">
|
||||
{% block pagination.previous.text %}
|
||||
<span aria-hidden="true">«</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">»</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">…</span>
|
||||
{% else %}
|
||||
{{ p }}
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock pagination.range %}
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% endblock pagination %}
|
||||
</div>
|
||||
{% endblock table-wrapper %}
|
||||
{% endcache %}
|
@ -1,68 +0,0 @@
|
||||
<form class="skipEmptyFields" method="POST" hx-post="#"
|
||||
hx-trigger="change"
|
||||
hx-target="#widgets-here"
|
||||
hx-swap="innerHTML"
|
||||
hx-indicator="#spinner">
|
||||
{% csrf_token %}
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="field has-addons">
|
||||
<div id="query" class="control is-expanded has-icons-left">
|
||||
<input
|
||||
hx-post="#"
|
||||
hx-trigger="keyup changed delay:200ms"
|
||||
hx-target="#widgets-here"
|
||||
hx-swap="innerHTML"
|
||||
name="query"
|
||||
value=""
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="Search something">
|
||||
<span class="icon is-small is-left">
|
||||
<i class="fas fa-magnifying-glass"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="field">
|
||||
<button
|
||||
id="search"
|
||||
class="button is-fullwidth"
|
||||
hx-post="#"
|
||||
hx-trigger="click"
|
||||
hx-target="#widgets-here"
|
||||
hx-swap="innerHTML">
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="nowrap-parent">
|
||||
<div
|
||||
data-script="on click toggle .is-hidden on #options"
|
||||
class="button is-right nowrap-child">
|
||||
Options
|
||||
</div>
|
||||
<div class="nowrap-child">
|
||||
<span id="spinner" class="button is-light has-text-link is-loading htmx-indicator">Static</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<input
|
||||
hx-trigger="change"
|
||||
hx-post="#"
|
||||
hx-target="#widgets-here"
|
||||
hx-swap="innerHTML"
|
||||
id="tags"
|
||||
class="input"
|
||||
type="tags"
|
||||
name="tags"
|
||||
placeholder="Tag search: nick: john"
|
||||
value="##">
|
||||
</div>
|
||||
<div class="is-hidden"></div>
|
||||
|
||||
</form>
|
@ -3,7 +3,7 @@
|
||||
<div class="buttons">
|
||||
<button
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{% url 'search' type='widget' %}"
|
||||
hx-get="{% url 'home' %}"
|
||||
hx-trigger="click"
|
||||
hx-target="#modals-here"
|
||||
class="button is-info">
|
||||
|
14
core/templates/window-content/results.html
Normal file
14
core/templates/window-content/results.html
Normal file
@ -0,0 +1,14 @@
|
||||
{% load static %}
|
||||
|
||||
{% include 'mixins/partials/notify.html' %}
|
||||
{% if cache is not None %}
|
||||
<span class="icon has-tooltip-bottom" data-tooltip="Cached">
|
||||
<i class="fa-solid fa-database"></i>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
fetched {{ table.data|length }} hits
|
||||
|
||||
|
||||
{% include 'partials/results_table.html' %}
|
||||
{# include 'partials/sentiment_chart.html' #}
|
269
core/templates/window-content/search.html
Normal file
269
core/templates/window-content/search.html
Normal file
@ -0,0 +1,269 @@
|
||||
<form class="skipEmptyFields" method="POST" hx-post="{% url 'search' %}"
|
||||
hx-trigger="change"
|
||||
hx-target="#widgets-here"
|
||||
hx-swap="innerHTML"
|
||||
hx-indicator="#spinner">
|
||||
{% csrf_token %}
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="field has-addons">
|
||||
<div id="query" class="control is-expanded has-icons-left">
|
||||
<input
|
||||
hx-post="{% url 'search' %}"
|
||||
hx-trigger="keyup changed delay:200ms"
|
||||
hx-target="#widgets-here"
|
||||
hx-swap="innerHTML"
|
||||
name="query"
|
||||
value="{{ params.query }}"
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="Search something">
|
||||
<span class="icon is-small is-left">
|
||||
<i class="fas fa-magnifying-glass"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="field">
|
||||
<button
|
||||
id="search"
|
||||
class="button is-fullwidth"
|
||||
hx-post="{% url 'search' %}"
|
||||
hx-trigger="click"
|
||||
hx-target="#widgets-here"
|
||||
hx-swap="innerHTML">
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="nowrap-parent">
|
||||
<div
|
||||
data-script="on click toggle .is-hidden on #options"
|
||||
class="button is-right nowrap-child">
|
||||
Options
|
||||
</div>
|
||||
<div class="nowrap-child">
|
||||
<span id="spinner" class="button is-light has-text-link is-loading htmx-indicator">Static</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="options" class="block is-hidden">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-narrow">
|
||||
<div class="field has-addons">
|
||||
<div class="control has-icons-left">
|
||||
<span class="select">
|
||||
<select name="size">
|
||||
{% for size in sizes %}
|
||||
{% if size == params.size %}
|
||||
<option selected value="{{ size }}">{{ size }}</option>
|
||||
{% else %}
|
||||
<option value="{{ size }}">{{ size }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<span class="icon is-small is-left">
|
||||
<i class="fas fa-magnifying-glass"></i>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<p class="control">
|
||||
<a class="button is-static">
|
||||
results
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
<div class="field has-addons block">
|
||||
<div class="control has-icons-left">
|
||||
<span class="select">
|
||||
<select id="source" name="source">
|
||||
{% if params.source == 'experiences' %}
|
||||
<option selected value="experiences">Experiences</option>
|
||||
{% else %}
|
||||
<option value="experiences">Experiences</option>
|
||||
{% endif %}
|
||||
|
||||
{% if params.source == 'Other' %}
|
||||
<option selected value="other">Other</option>
|
||||
{% else %}
|
||||
<option value="other">Other</option>
|
||||
{% endif %}
|
||||
|
||||
{% if params.source == None %}
|
||||
<option selected value="substances">Substances</option>
|
||||
{% elif params.source == 'substances' %}
|
||||
<option selected value="substances">Substances</option>
|
||||
{% else %}
|
||||
<option value="substances">Substances</option>
|
||||
{% endif %}
|
||||
|
||||
{% if params.source == 'all' %}
|
||||
<option selected value="all">All</option>
|
||||
{% else %}
|
||||
<option value="all">All</option>
|
||||
{% endif %}
|
||||
|
||||
</select>
|
||||
<span class="icon is-small is-left">
|
||||
<i class="fas fa-magnifying-glass"></i>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<p class="control">
|
||||
<a class="button is-static">
|
||||
source
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
<div id="date">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input type="date" name="dates" value="{{ params.date }}">
|
||||
<script>
|
||||
var options = {
|
||||
"type": "datetime",
|
||||
"isRange": true,
|
||||
"color": "info",
|
||||
"validateLabel": "Save",
|
||||
"dateFormat": "yyyy-MM-dd",
|
||||
"startDate": "{{ params.from_date|escapejs }}",
|
||||
"startTime": "{{ params.from_time|escapejs }}",
|
||||
"endDate": "{{ params.to_date|escapejs }}",
|
||||
"endTime": "{{ params.to_time|escapejs }}",
|
||||
"displayMode": "dialog"
|
||||
};
|
||||
// Initialize all input of type date
|
||||
var calendars = bulmaCalendar.attach('[type="date"]', options);
|
||||
|
||||
// Loop on each calendar initialized
|
||||
for(var i = 0; i < calendars.length; i++) {
|
||||
// Add listener to select event
|
||||
calendars[i].on('save', date => {
|
||||
htmx.trigger("#search", "click");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<label class="radio button has-text-link">
|
||||
<input
|
||||
type="radio"
|
||||
value="desc"
|
||||
name="sorting"
|
||||
{% if params.sorting == None %}
|
||||
checked
|
||||
{% elif params.sorting == 'desc' %}
|
||||
checked
|
||||
{% endif %}>
|
||||
<span class="icon" data-tooltip="Sort descending">
|
||||
<i class="fa-solid fa-sort-down"></i>
|
||||
</span>
|
||||
</label>
|
||||
<label class="radio button">
|
||||
<input
|
||||
type="radio"
|
||||
value="asc"
|
||||
name="sorting"
|
||||
{% if params.sorting == 'asc' %}
|
||||
checked
|
||||
{% endif %}>
|
||||
<span class="icon" data-tooltip="Sort ascending">
|
||||
<i class="fa-solid fa-sort-up"></i>
|
||||
</span>
|
||||
</label>
|
||||
<label class="radio button">
|
||||
<input
|
||||
type="radio"
|
||||
value="none"
|
||||
name="sorting"
|
||||
{% if params.sorting == 'none' %}
|
||||
checked
|
||||
{% endif %}>
|
||||
<span class="icon" data-tooltip="No sort">
|
||||
<i class="fa-solid fa-sort"></i>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if params.rule is None %}
|
||||
<div class="column is-narrow rounded-tooltip">
|
||||
<div class="field has-addons">
|
||||
<div class="control has-icons-left">
|
||||
<span class="select is-warning">
|
||||
<select {% if not user.is_superuser %}disabled{% endif %} id="index" name="index">
|
||||
{% if params.index == 'main' %}
|
||||
<option selected value="main">Main</option>
|
||||
{% elif params.index == None %}
|
||||
<option selected value="main">Main</option>
|
||||
{% else %}
|
||||
<option value="main">Main</option>
|
||||
{% endif %}
|
||||
|
||||
{% if params.index == 'internal' %}
|
||||
<option selected value="internal">Internal</option>
|
||||
{% else %}
|
||||
<option value="internal">Internal</option>
|
||||
{% endif %}
|
||||
|
||||
{% if params.index == 'meta' %}
|
||||
<option selected value="meta">Meta</option>
|
||||
{% else %}
|
||||
<option value="meta">Meta</option>
|
||||
{% endif %}
|
||||
|
||||
{% if params.index == 'restricted' %}
|
||||
<option selected value="restricted">Restricted</option>
|
||||
{% else %}
|
||||
<option value="restricted">Restricted</option>
|
||||
{% endif %}
|
||||
|
||||
</select>
|
||||
<span class="icon is-small is-left">
|
||||
<i class="fas fa-magnifying-glass"></i>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<p class="control">
|
||||
<a class="button is-static">
|
||||
index
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{% if not user.is_superuser %}
|
||||
<span class="tooltiptext tag is-danger is-light">No access</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<input
|
||||
hx-trigger="change"
|
||||
hx-post="{% url 'search' %}"
|
||||
hx-target="#widgets-here"
|
||||
hx-swap="innerHTML"
|
||||
id="tags"
|
||||
class="input"
|
||||
type="tags"
|
||||
name="tags"
|
||||
placeholder="Tag search: nick: john"
|
||||
value="{{ params.tags }}">
|
||||
</div>
|
||||
<div class="is-hidden"></div>
|
||||
{% if params.rule is not None %}
|
||||
<div style="display:none;">
|
||||
<input name="rule" value="{{ params.rule }}">
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
@ -14,11 +14,11 @@ logger = logging.getLogger(__name__)
|
||||
# Create your views here
|
||||
|
||||
|
||||
class Home(View):
|
||||
template_name = "index.html"
|
||||
# class Home(View):
|
||||
# template_name = "index.html"
|
||||
|
||||
def get(self, request):
|
||||
return render(request, self.template_name)
|
||||
# def get(self, request):
|
||||
# return render(request, self.template_name)
|
||||
|
||||
|
||||
class Signup(CreateView):
|
||||
|
@ -1,8 +1,13 @@
|
||||
import urllib
|
||||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse
|
||||
from django.views import View
|
||||
from django_tables2 import SingleTableView
|
||||
from mixins.views import (
|
||||
ObjectCreate,
|
||||
ObjectDelete,
|
||||
@ -11,19 +16,239 @@ from mixins.views import (
|
||||
ObjectUpdate,
|
||||
)
|
||||
|
||||
from core import db
|
||||
from core.models import Drug
|
||||
|
||||
# class Search(View):
|
||||
# template_name = "widgets/widget.html"
|
||||
|
||||
# def get(self, request):
|
||||
# unique = str(uuid.uuid4())[:8]
|
||||
# return render(request, self.template_name, {"unique": unique})
|
||||
from core.views.ui.tables import DrugsTable
|
||||
|
||||
|
||||
class Search(LoginRequiredMixin, ObjectRead):
|
||||
model = Drug
|
||||
detail_template = "window-content/drug_search.html"
|
||||
def make_table(context):
|
||||
object_list = [x.__dict__ for x in context["object_list"]]
|
||||
table = DrugsTable(object_list)
|
||||
context["table"] = table
|
||||
# del context["results"]
|
||||
return context
|
||||
|
||||
def get_object(self, type):
|
||||
return {}
|
||||
|
||||
def parse_dates(dates):
|
||||
spl = dates.split(" - ")
|
||||
if all(spl):
|
||||
spl = [f"{x.replace(' ', 'T')}" for x in spl]
|
||||
if not len(spl) == 2:
|
||||
message = "Invalid dates"
|
||||
message_class = "danger"
|
||||
return {"message": message, "class": message_class}
|
||||
from_ts, to_ts = spl
|
||||
from_date, from_time = from_ts.split("T")
|
||||
to_date, to_time = to_ts.split("T")
|
||||
|
||||
return {
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
"from_time": from_time,
|
||||
"to_time": to_time,
|
||||
}
|
||||
|
||||
|
||||
def create_tags(query):
|
||||
"""
|
||||
Grab the tags out of the query and make a list
|
||||
we can add to the Bulma tags element when the page loads.
|
||||
"""
|
||||
spl = query.split("AND")
|
||||
spl = [x.strip() for x in spl if ":" in x]
|
||||
spl = [x.replace('"', "") for x in spl]
|
||||
tags = [f"{tag}: {elem}" for tag, elem in [x.split(":")[:2] for x in spl]]
|
||||
return tags
|
||||
|
||||
|
||||
def parse_tags(tags_pre):
|
||||
"""
|
||||
Parse the tags from the variable tags_pre.
|
||||
"""
|
||||
tags = []
|
||||
tags_spl = tags_pre.split(",")
|
||||
if tags_spl:
|
||||
for tag in tags_spl:
|
||||
tag = tag.split(": ")
|
||||
if len(tag) == 2:
|
||||
key, val = tag
|
||||
tags.append({key: val})
|
||||
return tags
|
||||
|
||||
|
||||
class DrugsTableView(SingleTableView):
|
||||
table_class = DrugsTable
|
||||
template_name = "mixins/wm/widget.html"
|
||||
window_content = "window-content/results.html"
|
||||
# htmx_partial = "partials/"
|
||||
paginate_by = settings.DRUGS_RESULTS_PER_PAGE
|
||||
widget_options = 'gs-w="10" gs-h="1" gs-y="10" gs-x="1"'
|
||||
|
||||
def common_request(self, request, **kwargs):
|
||||
extra_params = {}
|
||||
|
||||
if request.user.is_anonymous:
|
||||
sizes = settings.MAIN_SIZES_ANON
|
||||
else:
|
||||
sizes = settings.MAIN_SIZES
|
||||
|
||||
if request.GET:
|
||||
self.template_name = "index.html"
|
||||
# GET arguments in URL like ?query=xyz
|
||||
query_params = request.GET.dict()
|
||||
if request.htmx:
|
||||
if request.resolver_match.url_name == "search_partial":
|
||||
self.template_name = "partials/results_table.html"
|
||||
elif request.POST:
|
||||
query_params = request.POST.dict()
|
||||
else:
|
||||
self.template_name = "index.html"
|
||||
# No query, this is a fresh page load
|
||||
# Don't try to search, since there's clearly nothing to do
|
||||
params_with_defaults = {}
|
||||
db.add_defaults(params_with_defaults)
|
||||
context = {
|
||||
"sizes": sizes,
|
||||
"params": params_with_defaults,
|
||||
"unique": "results",
|
||||
"widget_options": self.widget_options,
|
||||
"window_content": self.window_content,
|
||||
"title": "Results",
|
||||
}
|
||||
return render(request, self.template_name, context)
|
||||
|
||||
# Merge everything together just in case
|
||||
tmp_post = request.POST.dict()
|
||||
tmp_get = request.GET.dict()
|
||||
tmp_post = {k: v for k, v in tmp_post.items() if v and not v == "None"}
|
||||
tmp_get = {k: v for k, v in tmp_get.items() if v and not v == "None"}
|
||||
query_params.update(tmp_post)
|
||||
query_params.update(tmp_get)
|
||||
|
||||
# URI we're passing to the template for linking
|
||||
if "csrfmiddlewaretoken" in query_params:
|
||||
del query_params["csrfmiddlewaretoken"]
|
||||
|
||||
# Parse the dates
|
||||
if "dates" in query_params:
|
||||
dates = parse_dates(query_params["dates"])
|
||||
del query_params["dates"]
|
||||
if dates:
|
||||
if "message" in dates:
|
||||
return render(request, self.template_name, dates)
|
||||
query_params["from_date"] = dates["from_date"]
|
||||
query_params["to_date"] = dates["to_date"]
|
||||
query_params["from_time"] = dates["from_time"]
|
||||
query_params["to_time"] = dates["to_time"]
|
||||
|
||||
# Remove null values
|
||||
if "query" in query_params:
|
||||
if query_params["query"] == "":
|
||||
del query_params["query"]
|
||||
|
||||
# Remove null tags values
|
||||
# TODO: TAGS
|
||||
if "tags" in query_params:
|
||||
if query_params["tags"] == "":
|
||||
del query_params["tags"]
|
||||
else:
|
||||
# Parse the tags and populate cast to pass to search function
|
||||
tags = parse_tags(query_params["tags"])
|
||||
extra_params["tags"] = tags
|
||||
|
||||
context = db.orm.drug_query(request, query_params, **extra_params)
|
||||
|
||||
print("CONTEXT", context)
|
||||
|
||||
# Unique is for identifying the widgets.
|
||||
# We don't want a random one since we only want one results pane.
|
||||
context["unique"] = "results"
|
||||
context["window_content"] = self.window_content
|
||||
context["widget_options"] = self.widget_options
|
||||
context["title"] = "Results"
|
||||
|
||||
# Valid sizes
|
||||
context["sizes"] = sizes
|
||||
|
||||
# Add any default parameters to the context
|
||||
params_with_defaults = dict(query_params)
|
||||
db.add_defaults(params_with_defaults)
|
||||
context["params"] = params_with_defaults
|
||||
|
||||
# Remove anything that we or the user set to a default for
|
||||
# pretty URLs
|
||||
db.remove_defaults(query_params)
|
||||
url_params = urllib.parse.urlencode(query_params)
|
||||
context["client_uri"] = url_params
|
||||
|
||||
# There's an error
|
||||
if "message" in context:
|
||||
response = render(request, self.template_name, context)
|
||||
# Still push the URL so they can share it to get assistance
|
||||
if request.GET:
|
||||
if request.htmx:
|
||||
response["HX-Replace-Url"] = reverse("home") + "?" + url_params
|
||||
elif request.POST:
|
||||
response["HX-Replace-Url"] = reverse("home") + "?" + url_params
|
||||
return response
|
||||
|
||||
# Create data for chart.js sentiment graph
|
||||
# graph = make_graph(context["object_list"])
|
||||
# context["data"] = graph
|
||||
|
||||
# Create the table
|
||||
context = make_table(context)
|
||||
print("CONTEXT", context)
|
||||
|
||||
# URI we're passing to the template for linking, table fields removed
|
||||
table_fields = ["page", "sort"]
|
||||
clean_params = {k: v for k, v in query_params.items() if k not in table_fields}
|
||||
clean_url_params = urllib.parse.urlencode(clean_params)
|
||||
context["uri"] = clean_url_params
|
||||
|
||||
# unique = str(uuid.uuid4())[:8]
|
||||
# self.context = context
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
print("GET")
|
||||
self.context = self.common_request(request)
|
||||
if isinstance(self.context, HttpResponse):
|
||||
return self.context
|
||||
self.object_list = self.context["object_list"]
|
||||
show = []
|
||||
show = set().union(
|
||||
*([x.name for x in d._meta.get_fields()] for d in self.object_list)
|
||||
)
|
||||
allow_empty = self.get_allow_empty()
|
||||
|
||||
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() # noqa
|
||||
else:
|
||||
is_empty = not self.object_list # noqa
|
||||
context = self.get_context_data()
|
||||
|
||||
for k, v in self.context.items():
|
||||
if k not in context:
|
||||
context[k] = v
|
||||
context["show"] = show
|
||||
|
||||
# if request.htmx:
|
||||
# self.template_name = self.window_content
|
||||
# if request.method == "GET":
|
||||
# if not request.htmx:
|
||||
# self.template_name = "ui/drilldown/drilldown.html"
|
||||
response = self.render_to_response(context)
|
||||
# if not request.method == "GET":
|
||||
if "client_uri" in context:
|
||||
response["HX-Replace-Url"] = reverse("home") + "?" + context["client_uri"]
|
||||
return response
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
43
core/views/ui/tables.py
Normal file
43
core/views/ui/tables.py
Normal file
@ -0,0 +1,43 @@
|
||||
from django.conf import settings
|
||||
from django_tables2 import Column, Table
|
||||
|
||||
# from django_tables2.columns.base import BoundColumn
|
||||
|
||||
# Make the table column headings lowercase
|
||||
# orig_Column_header = BoundColumn.header
|
||||
|
||||
|
||||
# @property
|
||||
# def format_header(self):
|
||||
# header = orig_Column_header.__get__(self)
|
||||
# header = header.lower()
|
||||
# header = header.title()
|
||||
# if header != "Ident":
|
||||
# header = header.replace("Id", "ID")
|
||||
# header = header.replace("id", "ID")
|
||||
# if header == "Ts":
|
||||
# header = "TS"
|
||||
# if header == "Match Ts":
|
||||
# header = "Match TS"
|
||||
# header = header.replace("Nsfw", "NSFW")
|
||||
|
||||
# return header
|
||||
|
||||
|
||||
# BoundColumn.header = format_header
|
||||
|
||||
|
||||
class DrugsTable(Table):
|
||||
id = Column()
|
||||
name = Column()
|
||||
drug_class = Column()
|
||||
common_name = Column()
|
||||
links = Column()
|
||||
dosages = Column()
|
||||
timings = Column()
|
||||
effects = Column()
|
||||
actions = Column()
|
||||
experiences = Column()
|
||||
|
||||
template_name = "ui/search/table_results.html"
|
||||
paginate_by = settings.DRUGS_RESULTS_PER_PAGE
|
@ -32,4 +32,6 @@ hiredis
|
||||
django-cachalot
|
||||
django_redis
|
||||
drugbank_downloader
|
||||
bioversions
|
||||
bioversions
|
||||
django-tables2
|
||||
django-tables2-bulma-template
|
Loading…
Reference in New Issue
Block a user