diff --git a/cl/search/migrations/0035_searchquery.py b/cl/search/migrations/0035_searchquery.py new file mode 100644 index 0000000000..507871a000 --- /dev/null +++ b/cl/search/migrations/0035_searchquery.py @@ -0,0 +1,77 @@ +# Generated by Django 5.1.1 on 2024-09-19 00:54 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("search", "0034_add_harvard_pdf_to_opinioncluster"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="SearchQuery", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "date_created", + models.DateTimeField( + auto_now_add=True, + db_index=True, + help_text="The moment when the item was created.", + ), + ), + ( + "date_modified", + models.DateTimeField( + auto_now=True, + db_index=True, + help_text="The last moment when the item was modified. A value in year 1750 indicates the value is unknown", + ), + ), + ( + "get_params", + models.TextField( + help_text="The GET parameters of the search query." + ), + ), + ( + "query_time_ms", + models.IntegerField( + help_text="The time taken to execute the query, in milliseconds." + ), + ), + ( + "hit_cache", + models.BooleanField( + help_text="Whether the query hit the cache or not." + ), + ), + ( + "user", + models.ForeignKey( + blank=True, + help_text="The user who performed this search query.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="search_queries", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/cl/search/migrations/0035_searchquery.sql b/cl/search/migrations/0035_searchquery.sql new file mode 100644 index 0000000000..63694c5a6c --- /dev/null +++ b/cl/search/migrations/0035_searchquery.sql @@ -0,0 +1,10 @@ +BEGIN; +-- +-- Create model SearchQuery +-- +CREATE TABLE "search_searchquery" ("id" integer NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "date_created" timestamp with time zone NOT NULL, "date_modified" timestamp with time zone NOT NULL, "get_params" text NOT NULL, "query_time_ms" integer NOT NULL, "hit_cache" boolean NOT NULL, "user_id" integer NULL); +ALTER TABLE "search_searchquery" ADD CONSTRAINT "search_searchquery_user_id_8918791c_fk_auth_user_id" FOREIGN KEY ("user_id") REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED; +CREATE INDEX "search_searchquery_date_created_89023a7b" ON "search_searchquery" ("date_created"); +CREATE INDEX "search_searchquery_date_modified_ec5bc897" ON "search_searchquery" ("date_modified"); +CREATE INDEX "search_searchquery_user_id_8918791c" ON "search_searchquery" ("user_id"); +COMMIT; diff --git a/cl/search/migrations/0035_searchquery_customers.sql b/cl/search/migrations/0035_searchquery_customers.sql new file mode 100644 index 0000000000..63694c5a6c --- /dev/null +++ b/cl/search/migrations/0035_searchquery_customers.sql @@ -0,0 +1,10 @@ +BEGIN; +-- +-- Create model SearchQuery +-- +CREATE TABLE "search_searchquery" ("id" integer NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "date_created" timestamp with time zone NOT NULL, "date_modified" timestamp with time zone NOT NULL, "get_params" text NOT NULL, "query_time_ms" integer NOT NULL, "hit_cache" boolean NOT NULL, "user_id" integer NULL); +ALTER TABLE "search_searchquery" ADD CONSTRAINT "search_searchquery_user_id_8918791c_fk_auth_user_id" FOREIGN KEY ("user_id") REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED; +CREATE INDEX "search_searchquery_date_created_89023a7b" ON "search_searchquery" ("date_created"); +CREATE INDEX "search_searchquery_date_modified_ec5bc897" ON "search_searchquery" ("date_modified"); +CREATE INDEX "search_searchquery_user_id_8918791c" ON "search_searchquery" ("user_id"); +COMMIT; diff --git a/cl/search/models.py b/cl/search/models.py index a0c808f3d3..75555218d8 100644 --- a/cl/search/models.py +++ b/cl/search/models.py @@ -6,6 +6,7 @@ import pytz from asgiref.sync import sync_to_async from celery.canvas import chain +from django.contrib.auth.models import User from django.contrib.contenttypes.fields import GenericRelation from django.contrib.postgres.indexes import HashIndex from django.core.exceptions import ValidationError @@ -233,6 +234,26 @@ class SOURCES: ) +class SearchQuery(AbstractDateTimeModel): + user = models.ForeignKey( + User, + help_text="The user who performed this search query.", + related_name="search_queries", + on_delete=models.SET_NULL, + null=True, + blank=True, + ) + get_params = models.TextField( + help_text="The GET parameters of the search query." + ) + query_time_ms = models.IntegerField( + help_text="The time taken to execute the query, in milliseconds." + ) + hit_cache = models.BooleanField( + help_text="Whether the query hit the cache or not." + ) + + @pghistory.track(AfterUpdateOrDeleteSnapshot()) class OriginatingCourtInformation(AbstractDateTimeModel): """Lower court metadata to associate with appellate cases. diff --git a/cl/search/views.py b/cl/search/views.py index 539efaaed3..37538bed9c 100644 --- a/cl/search/views.py +++ b/cl/search/views.py @@ -79,7 +79,13 @@ UnbalancedQuotesQuery, ) from cl.search.forms import SearchForm, _clean_form -from cl.search.models import SEARCH_TYPES, Court, Opinion, OpinionCluster +from cl.search.models import ( + SEARCH_TYPES, + Court, + Opinion, + OpinionCluster, + SearchQuery, +) from cl.stats.models import Stat from cl.stats.utils import tally_stat from cl.visualizations.models import SCOTUSMap @@ -514,6 +520,13 @@ def show_results(request: HttpRequest) -> HttpResponse: if not is_bot(request): async_to_sync(tally_stat)("search.results") + # Create and save the SearchQuery object + SearchQuery.objects.create( + get_params=request.GET.urlencode(), + query_time_ms=render_dict["query_time"], + hit_cache=render_dict["hit_cache"], + ) + # Create bare-bones alert form. alert_form = CreateAlertForm( initial={"query": get_string, "rate": "dly"},