import json import urllib from django.conf import settings 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 rest_framework.parsers import FormParser from rest_framework.views import APIView from core.lib.opensearch import query_results from core.lib.threshold import ( annotate_num_chans, annotate_num_users, get_chans, get_users, ) from core.views.ui.tables import DrilldownTable 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 make_table(context): table = DrilldownTable(context["object_list"]) context["table"] = table # del context["results"] return context def make_graph(results): graph = json.dumps( [ { "text": item.get("msg", None) or item.get("id"), "nick": item.get("nick", None), "value": item.get("sentiment", None) or None, "date": item.get("ts"), } for item in results ] ) return graph def drilldown_search(request, return_context=False, template=None): if not template: template_name = "ui/drilldown/table_results.html" else: template_name = template if request.user.is_anonymous: sizes = settings.OPENSEARCH_MAIN_SIZES_ANON else: sizes = settings.OPENSEARCH_MAIN_SIZES if request.GET: if not request.htmx: template_name = "ui/drilldown/drilldown.html" query_params = request.GET.dict() elif request.POST: query_params = request.POST.dict() else: template_name = "ui/drilldown/drilldown.html" context = {"sizes": sizes} return render(request, template_name, context) if "index" in query_params: if not request.user.is_superuser: message = "You can't use the index parameter" message_class = "danger" context = {"message": message, "class": message_class} return render(request, template_name, context) # 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, 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"] if request.GET: context = query_results(request, query_params) elif request.POST: context = query_results(request, query_params) # URI we're passing to the template for linking if "csrfmiddlewaretoken" in query_params: del query_params["csrfmiddlewaretoken"] url_params = urllib.parse.urlencode(query_params) context["client_uri"] = url_params # Turn the query into tags for populating the taglist if "query" in query_params: tags = create_tags(query_params["query"]) context["tags"] = tags context["params"] = query_params if "message" in context: response = render(request, template_name, context) if request.GET: if request.htmx: response["HX-Push"] = reverse("home") + "?" + url_params elif request.POST: response["HX-Push"] = reverse("home") + "?" + url_params return response # Create data for chart.js sentiment graph graph = make_graph(context["object_list"]) context["data"] = graph if context: context["sizes"] = sizes context = make_table(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 response = render(request, template_name, context) if request.GET: if request.htmx: response["HX-Push"] = reverse("home") + "?" + url_params elif request.POST: response["HX-Push"] = reverse("home") + "?" + url_params if return_context: return context return response else: return HttpResponse("No results") class DrilldownTableView(SingleTableView): table_class = DrilldownTable template_name = "ui/drilldown/table_results.html" paginate_by = settings.DRILLDOWN_RESULTS_PER_PAGE def get_queryset(self, request, **kwargs): context = drilldown_search(request, return_context=True) # Save the context as we will need to merge other attributes later self.context = context if "object_list" in context: return context["object_list"] else: return [] def get(self, request, *args, **kwargs): self.object_list = self.get_queryset(request) show = [] show = set().union(*(d.keys() 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() if isinstance(self.context, HttpResponse): return self.context for k, v in self.context.items(): if k not in context: context[k] = v context["show"] = show 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-Push"] = reverse("home") + "?" + context["client_uri"] return response def post(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) class Drilldown(View): template_name = "ui/drilldown/drilldown.html" plan_name = "drilldown" def get(self, request): return drilldown_search(request) def post(self, request): return drilldown_search(request) class DrilldownContextModal(APIView): parser_classes = [FormParser] plan_name = "drilldown" template_name = "modals/context.html" def post(self, request): # if not request.user.has_plan(self.plan_name): # return JsonResponse({"success": False}) num = "" if "net" not in request.data: return JsonResponse({"success": False}) if "channel" not in request.data: return JsonResponse({"success": False}) if "src" not in request.data: return JsonResponse({"success": False}) if "time" not in request.data: return JsonResponse({"success": False}) if "date" not in request.data: return JsonResponse({"success": False}) if "num" in request.data: num = request.data["num"] net = request.data["net"] channel = request.data["channel"] # results = context = { "net": net, "channel": channel, "num": num, "src": request.data["src"], "ts": f"{request.data['date']} {request.data['time']}", } return render(request, self.template_name, context) class ThresholdInfoModal(APIView): parser_classes = [FormParser] plan_name = "drilldown" template_name = "modals/drilldown.html" def post(self, request): # if not request.user.has_plan(self.plan_name): # return JsonResponse({"success": False}) if "net" not in request.data: return JsonResponse({"success": False}) if "nick" not in request.data: return JsonResponse({"success": False}) if "channel" not in request.data: return JsonResponse({"success": False}) net = request.data["net"] nick = request.data["nick"] channel = request.data["channel"] channels = get_chans(net, [nick]) users = get_users(net, [channel]) num_users = annotate_num_users(net, channels) num_chans = annotate_num_chans(net, users) if channels: inter_users = get_users(net, channels) else: inter_users = [] if users: inter_chans = get_chans(net, users) else: inter_chans = [] context = { "net": net, "nick": nick, "channel": channel, "chans": channels, "users": users, "inter_chans": inter_chans, "inter_users": inter_users, "num_users": num_users, "num_chans": num_chans, } return render(request, self.template_name, context)