Implement initial drug search
This commit is contained in:
parent
b924c0556c
commit
6131c58857
|
@ -49,3 +49,16 @@ if DEBUG:
|
||||||
]
|
]
|
||||||
|
|
||||||
SETTINGS_EXPORT = ["BILLING_ENABLED"]
|
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",
|
"django_htmx",
|
||||||
"crispy_forms",
|
"crispy_forms",
|
||||||
"crispy_bulma",
|
"crispy_bulma",
|
||||||
# "django_tables2",
|
"django_tables2",
|
||||||
# "django_tables2_bulma_template",
|
"django_tables2_bulma_template",
|
||||||
"django_otp",
|
"django_otp",
|
||||||
"django_otp.plugins.otp_totp",
|
"django_otp.plugins.otp_totp",
|
||||||
# "django_otp.plugins.otp_email",
|
# "django_otp.plugins.otp_email",
|
||||||
|
|
|
@ -24,7 +24,8 @@ from core.views import base, demo, drugs, notifications, search
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("__debug__/", include("debug_toolbar.urls")),
|
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),
|
path("admin/", admin.site.urls),
|
||||||
# 2FA login urls
|
# 2FA login urls
|
||||||
path("", include(tf_urls)),
|
path("", include(tf_urls)),
|
||||||
|
@ -67,5 +68,6 @@ urlpatterns = [
|
||||||
name="drug_pull_merge",
|
name="drug_pull_merge",
|
||||||
),
|
),
|
||||||
# Drug search
|
# 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)
|
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|
|
@ -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
|
|
@ -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
|
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
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
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
File diff suppressed because one or more lines are too long
|
@ -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);
|
||||||
|
|
||||||
|
});
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -6,16 +6,24 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<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="shortcut icon" href="{% static 'favicon.ico' %}">
|
||||||
<link rel="manifest" href="{% static 'manifest.webmanifest' %}">
|
<link rel="manifest" href="{% static 'manifest.webmanifest' %}">
|
||||||
<link rel="stylesheet" href="{% static 'css/bulma.min.css' %}">
|
<link rel="stylesheet" href="{% static 'css/bulma.min.css' %}">
|
||||||
<link rel="stylesheet" href="{% static 'css/bulma-tooltip.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="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' %}">
|
<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 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/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 src="{% static 'js/gridstack-all.js' %}"></script>
|
||||||
<script defer src="{% static 'js/magnet.min.js' %}"></script>
|
<script defer src="{% static 'js/magnet.min.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -217,10 +225,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.is_authenticated %}
|
<a class="navbar-item" href="{% url 'home' %}">
|
||||||
<a class="navbar-item" href="{% url 'search' type='page' %}">
|
|
||||||
Search
|
Search
|
||||||
</a>
|
</a>
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
|
||||||
<div class="navbar-item has-dropdown is-hoverable">
|
<div class="navbar-item has-dropdown is-hoverable">
|
||||||
<a class="navbar-link">
|
<a class="navbar-link">
|
||||||
Account
|
Account
|
||||||
|
|
|
@ -1,7 +1,59 @@
|
||||||
{% extends "base.html" %}
|
{% extends 'base.html' %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load joinsep %}
|
{% load joinsep %}
|
||||||
{% block outer_content %}
|
{% 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" id="grid-stack-main">
|
||||||
<div class="grid-stack-item" gs-w="7" gs-h="10" gs-y="0" gs-x="1">
|
<div class="grid-stack-item" gs-w="7" gs-h="10" gs-y="0" gs-x="1">
|
||||||
|
@ -9,10 +61,10 @@
|
||||||
<nav class="panel">
|
<nav class="panel">
|
||||||
<p class="panel-heading" style="padding: .2em; line-height: .5em;">
|
<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>
|
<i class="fa-solid fa-arrows-up-down-left-right has-text-grey-light"></i>
|
||||||
Home
|
Search
|
||||||
</p>
|
</p>
|
||||||
<article class="panel-block is-active">
|
<article class="panel-block is-active">
|
||||||
{% include 'window-content/main.html' %}
|
{% include 'window-content/search.html' %}
|
||||||
</article>
|
</article>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,6 +83,7 @@
|
||||||
animate: true,
|
animate: true,
|
||||||
});
|
});
|
||||||
// GridStack.init();
|
// GridStack.init();
|
||||||
|
setupTags();
|
||||||
|
|
||||||
// a widget is ready to be loaded
|
// a widget is ready to be loaded
|
||||||
document.addEventListener('load-widget', function(event) {
|
document.addEventListener('load-widget', function(event) {
|
||||||
|
@ -53,15 +106,22 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear the queue element
|
// clear the queue element
|
||||||
container.outerHTML = "";
|
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
|
// re-create the HTMX JS listeners, otherwise HTMX won't work inside the grid
|
||||||
htmx.process(widgetelement);
|
htmx.process(widgetelement);
|
||||||
|
|
||||||
// update the size of the widget according to its content
|
// update size when the widget is loaded
|
||||||
var added_widget = htmx.find(grid_element, "#"+new_id);
|
document.addEventListener('load-widget-results', function(evt) {
|
||||||
|
var added_widget = htmx.find(grid_element, '#widget-results');
|
||||||
var itemContent = htmx.find(added_widget, ".control");
|
var itemContent = htmx.find(added_widget, ".control");
|
||||||
var scrollheight = itemContent.scrollHeight+80;
|
var scrollheight = itemContent.scrollHeight+80;
|
||||||
var verticalmargin = 0;
|
var verticalmargin = 0;
|
||||||
|
@ -74,22 +134,19 @@
|
||||||
added_widget,
|
added_widget,
|
||||||
opts
|
opts
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// run the JS scripts inside the added element again
|
// run the JS scripts inside the added element again
|
||||||
|
// for instance, this will fix the dropdown
|
||||||
for (var i = 0; i < scripts.length; i++) {
|
for (var i = 0; i < scripts.length; i++) {
|
||||||
eval(scripts[i].innerHTML);
|
eval(scripts[i].innerHTML);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="modals-here">
|
{% endblock %}
|
||||||
</div>
|
{% block widgets %}
|
||||||
<div id="items-here">
|
{% if table or message is not None %}
|
||||||
</div>
|
{% include 'partials/results_load.html' %}
|
||||||
<div id="widgets-here" style="display: none;">
|
{% endif %}
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -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 %}
|
|
@ -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">
|
<div class="buttons">
|
||||||
<button
|
<button
|
||||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||||
hx-get="{% url 'search' type='widget' %}"
|
hx-get="{% url 'home' %}"
|
||||||
hx-trigger="click"
|
hx-trigger="click"
|
||||||
hx-target="#modals-here"
|
hx-target="#modals-here"
|
||||||
class="button is-info">
|
class="button is-info">
|
||||||
|
|
|
@ -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' #}
|
|
@ -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
|
# Create your views here
|
||||||
|
|
||||||
|
|
||||||
class Home(View):
|
# class Home(View):
|
||||||
template_name = "index.html"
|
# template_name = "index.html"
|
||||||
|
|
||||||
def get(self, request):
|
# def get(self, request):
|
||||||
return render(request, self.template_name)
|
# return render(request, self.template_name)
|
||||||
|
|
||||||
|
|
||||||
class Signup(CreateView):
|
class Signup(CreateView):
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
|
import urllib
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.http import HttpResponse, JsonResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.urls import reverse
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
from django_tables2 import SingleTableView
|
||||||
from mixins.views import (
|
from mixins.views import (
|
||||||
ObjectCreate,
|
ObjectCreate,
|
||||||
ObjectDelete,
|
ObjectDelete,
|
||||||
|
@ -11,19 +16,239 @@ from mixins.views import (
|
||||||
ObjectUpdate,
|
ObjectUpdate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from core import db
|
||||||
from core.models import Drug
|
from core.models import Drug
|
||||||
|
from core.views.ui.tables import DrugsTable
|
||||||
# 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})
|
|
||||||
|
|
||||||
|
|
||||||
class Search(LoginRequiredMixin, ObjectRead):
|
def make_table(context):
|
||||||
model = Drug
|
object_list = [x.__dict__ for x in context["object_list"]]
|
||||||
detail_template = "window-content/drug_search.html"
|
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)
|
||||||
|
|
|
@ -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
|
|
@ -33,3 +33,5 @@ django-cachalot
|
||||||
django_redis
|
django_redis
|
||||||
drugbank_downloader
|
drugbank_downloader
|
||||||
bioversions
|
bioversions
|
||||||
|
django-tables2
|
||||||
|
django-tables2-bulma-template
|
Loading…
Reference in New Issue