diff --git a/cl/corpus_importer/management/commands/update_opinions_order.py b/cl/corpus_importer/management/commands/update_opinions_order.py new file mode 100644 index 0000000000..1f1e5308e9 --- /dev/null +++ b/cl/corpus_importer/management/commands/update_opinions_order.py @@ -0,0 +1,115 @@ +import argparse +import time + +from django.db import transaction +from django.db.models import Count + +from cl.lib.command_utils import VerboseCommand, logger +from cl.search.models import SOURCES, Opinion, OpinionCluster + + +def sort_harvard_opinions(options) -> None: + """Sort harvard opinions + + We assume that harvard data is already ordered, we just need to fill + the order field in each opinion + + The harvard importer created the opinions in order of appearance in the file + + :param options: dict of arguments passed to the command + :return: None + """ + + skip_until = options.get("skip_until", None) + limit = options.get("limit", None) + + # The filepath_json_harvard field can only be filled by the harvard importer, + # this helps us confirm that it was imported from a Harvard json. We exclude + # clusters merged with columbia because those may need some extra verification + harvard_clusters = ( + OpinionCluster.objects.exclude(filepath_json_harvard="") + .prefetch_related("sub_opinions") + .annotate(opinions_count=Count("sub_opinions")) + .filter(opinions_count__gt=1) + .exclude(source__contains=SOURCES.COLUMBIA_ARCHIVE) + .order_by("id") + ) + if skip_until: + harvard_clusters = harvard_clusters.filter(pk__gte=skip_until) + + if limit: + harvard_clusters = harvard_clusters[:limit] + + for cluster in harvard_clusters: + logger.info(f"Processing cluster id: {cluster}") + opinion_order = 1 + any_update = False + with transaction.atomic(): + # We need to make sure they are ordered by id + for cluster_op in cluster.sub_opinions.all().order_by("id"): + if cluster_op.type == Opinion.COMBINED: + continue + cluster_op.ordering_key = opinion_order + cluster_op.save() + opinion_order = opinion_order + 1 + any_update = True + if not any_update: + # We want to know if you found anything unexpected, like for example + # only having combined opinions + logger.info( + f"No sub_opinions updated for cluster id: {cluster}" + ) + continue + logger.info(msg=f"Opinions reordered for cluster id: {cluster.id}") + # Wait between each processed cluster to avoid issues with elastic + time.sleep(options["delay"]) + + +class Command(VerboseCommand): + help = "Add ordering Key for sub opinions" + + def __init__(self, *args, **kwargs): + super(Command, self).__init__(*args, **kwargs) + + def valid_actions(self, s): + if s.lower() not in self.VALID_ACTIONS: + raise argparse.ArgumentTypeError( + "Unable to parse action. Valid actions are: %s" + % (", ".join(self.VALID_ACTIONS.keys())) + ) + + return self.VALID_ACTIONS[s] + + def add_arguments(self, parser): + parser.add_argument( + "--skip-until", + help="Specific cluster id to skip until", + type=int, + required=False, + ) + parser.add_argument( + "--limit", + type=int, + help="Number of files to sort", + required=False, + ) + parser.add_argument( + "--action", + type=self.valid_actions, + required=True, + help="The action you wish to take. Valid choices are: %s" + % (", ".join(self.VALID_ACTIONS.keys())), + ) + parser.add_argument( + "--delay", + type=float, + default=0.2, + help="How long to wait to update each opinion (in seconds, allows " + "floating numbers).", + ) + + def handle(self, *args, **options): + super().handle(*args, **options) + options["action"](options) + + VALID_ACTIONS = {"sort-harvard": sort_harvard_opinions} diff --git a/cl/search/fixtures/functest_opinions.json b/cl/search/fixtures/functest_opinions.json index e4fa89a260..f1e6f2da44 100644 --- a/cl/search/fixtures/functest_opinions.json +++ b/cl/search/fixtures/functest_opinions.json @@ -64,7 +64,8 @@ "date_created": "2015-08-15T14:10:56.801Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 10 @@ -134,7 +135,8 @@ "date_created": "2015-08-15T14:10:56.801Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 11 @@ -184,7 +186,8 @@ "date_created": "2015-08-15T14:10:56.801Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 12 @@ -254,7 +257,8 @@ "date_created": "2015-08-15T14:10:56.801Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 12 diff --git a/cl/search/fixtures/opinions-issue-412.json b/cl/search/fixtures/opinions-issue-412.json index ca6ac33971..fa7d716ccb 100644 --- a/cl/search/fixtures/opinions-issue-412.json +++ b/cl/search/fixtures/opinions-issue-412.json @@ -64,7 +64,8 @@ "date_created": "2015-08-15T14:10:56.801Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 10 @@ -134,7 +135,8 @@ "date_created": "2015-08-15T14:10:56.801Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 11 diff --git a/cl/search/fixtures/test_objects_query_counts.json b/cl/search/fixtures/test_objects_query_counts.json index aa909b2fb2..ca69a08ccc 100644 --- a/cl/search/fixtures/test_objects_query_counts.json +++ b/cl/search/fixtures/test_objects_query_counts.json @@ -300,7 +300,8 @@ "date_created":"2015-08-15T14:10:56.801Z", "html_lawbox":"", "per_curiam":false, - "type":"020lead" + "type":"020lead", + "ordering_key": null }, "model":"search.opinion", "pk":1 @@ -324,7 +325,8 @@ "date_created":"2015-08-15T14:10:56.801Z", "html_lawbox":"", "per_curiam":false, - "type":"010combined" + "type":"010combined", + "ordering_key": null }, "model":"search.opinion", "pk":2 @@ -348,7 +350,8 @@ "date_created":"2015-08-15T14:10:56.801Z", "html_lawbox":"", "per_curiam":false, - "type":"010combined" + "type":"010combined", + "ordering_key": null }, "model":"search.opinion", "pk":3 @@ -371,7 +374,8 @@ "date_created":"2015-08-15T14:10:56.801Z", "html_lawbox":"", "per_curiam":false, - "type":"010combined" + "type":"010combined", + "ordering_key": null }, "model":"search.opinion", "pk":4 @@ -395,7 +399,8 @@ "date_created":"2015-08-15T14:10:56.801Z", "html_lawbox":"", "per_curiam":false, - "type":"010combined" + "type":"010combined", + "ordering_key": null }, "model":"search.opinion", "pk":5 @@ -418,7 +423,8 @@ "date_created":"2015-08-15T14:10:56.801Z", "html_lawbox":"", "per_curiam":false, - "type":"010combined" + "type":"010combined", + "ordering_key": null }, "model":"search.opinion", "pk":6 diff --git a/cl/search/fixtures/test_objects_search.json b/cl/search/fixtures/test_objects_search.json index 2255c7edcf..66c9915581 100644 --- a/cl/search/fixtures/test_objects_search.json +++ b/cl/search/fixtures/test_objects_search.json @@ -239,7 +239,8 @@ "date_created": "2015-08-15T14:10:56.801Z", "html_lawbox": "", "per_curiam": false, - "type": "020lead" + "type": "020lead", + "ordering_key": null }, "model": "search.opinion", "pk": 1 @@ -261,7 +262,8 @@ "date_created": "2015-08-15T14:10:56.801Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 2 @@ -283,7 +285,8 @@ "date_created": "2015-08-15T14:10:56.801Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 3 @@ -305,7 +308,8 @@ "date_created": "2015-08-15T14:10:56.801Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 4 @@ -327,7 +331,8 @@ "date_created": "2015-08-15T14:10:56.801Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 5 @@ -349,7 +354,8 @@ "date_created": "2015-08-15T14:10:56.801Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 6 diff --git a/cl/search/migrations/0033_order_opinions.py b/cl/search/migrations/0033_order_opinions.py new file mode 100644 index 0000000000..ce5ea91c13 --- /dev/null +++ b/cl/search/migrations/0033_order_opinions.py @@ -0,0 +1,72 @@ +# Generated by Django 5.0.7 on 2024-08-05 20:19 + +import pgtrigger.compiler +import pgtrigger.migrations +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ( + "people_db", + "0016_remove_abarating_update_or_delete_snapshot_update_and_more", + ), + ("search", "0032_update_docket_numbering_fields"), + ] + + operations = [ + pgtrigger.migrations.RemoveTrigger( + model_name="opinion", + name="update_or_delete_snapshot_delete", + ), + pgtrigger.migrations.RemoveTrigger( + model_name="opinion", + name="update_or_delete_snapshot_update", + ), + migrations.AddField( + model_name="opinion", + name="ordering_key", + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name="opinionevent", + name="ordering_key", + field=models.IntegerField(blank=True, null=True), + ), + pgtrigger.migrations.AddTrigger( + model_name="opinion", + trigger=pgtrigger.compiler.Trigger( + name="update_or_delete_snapshot_update", + sql=pgtrigger.compiler.UpsertTriggerSql( + condition='WHEN (OLD."id" IS DISTINCT FROM (NEW."id") OR OLD."date_created" IS DISTINCT FROM (NEW."date_created") OR OLD."cluster_id" IS DISTINCT FROM (NEW."cluster_id") OR OLD."author_id" IS DISTINCT FROM (NEW."author_id") OR OLD."author_str" IS DISTINCT FROM (NEW."author_str") OR OLD."per_curiam" IS DISTINCT FROM (NEW."per_curiam") OR OLD."joined_by_str" IS DISTINCT FROM (NEW."joined_by_str") OR OLD."type" IS DISTINCT FROM (NEW."type") OR OLD."sha1" IS DISTINCT FROM (NEW."sha1") OR OLD."page_count" IS DISTINCT FROM (NEW."page_count") OR OLD."download_url" IS DISTINCT FROM (NEW."download_url") OR OLD."local_path" IS DISTINCT FROM (NEW."local_path") OR OLD."plain_text" IS DISTINCT FROM (NEW."plain_text") OR OLD."html" IS DISTINCT FROM (NEW."html") OR OLD."html_lawbox" IS DISTINCT FROM (NEW."html_lawbox") OR OLD."html_columbia" IS DISTINCT FROM (NEW."html_columbia") OR OLD."html_anon_2020" IS DISTINCT FROM (NEW."html_anon_2020") OR OLD."xml_harvard" IS DISTINCT FROM (NEW."xml_harvard") OR OLD."html_with_citations" IS DISTINCT FROM (NEW."html_with_citations") OR OLD."extracted_by_ocr" IS DISTINCT FROM (NEW."extracted_by_ocr") OR OLD."ordering_key" IS DISTINCT FROM (NEW."ordering_key"))', + func='INSERT INTO "search_opinionevent" ("author_id", "author_str", "cluster_id", "date_created", "date_modified", "download_url", "extracted_by_ocr", "html", "html_anon_2020", "html_columbia", "html_lawbox", "html_with_citations", "id", "joined_by_str", "local_path", "ordering_key", "page_count", "per_curiam", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "plain_text", "sha1", "type", "xml_harvard") VALUES (OLD."author_id", OLD."author_str", OLD."cluster_id", OLD."date_created", OLD."date_modified", OLD."download_url", OLD."extracted_by_ocr", OLD."html", OLD."html_anon_2020", OLD."html_columbia", OLD."html_lawbox", OLD."html_with_citations", OLD."id", OLD."joined_by_str", OLD."local_path", OLD."ordering_key", OLD."page_count", OLD."per_curiam", _pgh_attach_context(), NOW(), \'update_or_delete_snapshot\', OLD."id", OLD."plain_text", OLD."sha1", OLD."type", OLD."xml_harvard"); RETURN NULL;', + hash="7137855274503cc2c50a17729f82e150d2b7d872", + operation="UPDATE", + pgid="pgtrigger_update_or_delete_snapshot_update_67ecd", + table="search_opinion", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="opinion", + trigger=pgtrigger.compiler.Trigger( + name="update_or_delete_snapshot_delete", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='INSERT INTO "search_opinionevent" ("author_id", "author_str", "cluster_id", "date_created", "date_modified", "download_url", "extracted_by_ocr", "html", "html_anon_2020", "html_columbia", "html_lawbox", "html_with_citations", "id", "joined_by_str", "local_path", "ordering_key", "page_count", "per_curiam", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "plain_text", "sha1", "type", "xml_harvard") VALUES (OLD."author_id", OLD."author_str", OLD."cluster_id", OLD."date_created", OLD."date_modified", OLD."download_url", OLD."extracted_by_ocr", OLD."html", OLD."html_anon_2020", OLD."html_columbia", OLD."html_lawbox", OLD."html_with_citations", OLD."id", OLD."joined_by_str", OLD."local_path", OLD."ordering_key", OLD."page_count", OLD."per_curiam", _pgh_attach_context(), NOW(), \'update_or_delete_snapshot\', OLD."id", OLD."plain_text", OLD."sha1", OLD."type", OLD."xml_harvard"); RETURN NULL;', + hash="98fb52aa60fd8e89a83f8f7ac77ba5892739fb37", + operation="DELETE", + pgid="pgtrigger_update_or_delete_snapshot_delete_1f4fd", + table="search_opinion", + when="AFTER", + ), + ), + ), + migrations.AddConstraint( + model_name="opinion", + constraint=models.UniqueConstraint( + fields=("cluster_id", "ordering_key"), + name="unique_opinion_ordering_key", + ), + ), + ] diff --git a/cl/search/migrations/0033_order_opinions.sql b/cl/search/migrations/0033_order_opinions.sql new file mode 100644 index 0000000000..e2e07aee39 --- /dev/null +++ b/cl/search/migrations/0033_order_opinions.sql @@ -0,0 +1,14 @@ +BEGIN; +-- +-- Add field ordering_key to opinion +-- +ALTER TABLE "search_opinion" ADD COLUMN "ordering_key" integer NULL; +-- +-- Add field ordering_key to opinionevent +-- +ALTER TABLE "search_opinionevent" ADD COLUMN "ordering_key" integer NULL; +-- +-- Create constraint unique_opinion_ordering_key on model opinion +-- +ALTER TABLE "search_opinion" ADD CONSTRAINT "unique_opinion_ordering_key" UNIQUE ("cluster_id", "ordering_key"); +COMMIT; diff --git a/cl/search/migrations/0033_order_opinions_customers.sql b/cl/search/migrations/0033_order_opinions_customers.sql new file mode 100644 index 0000000000..e7158e3002 --- /dev/null +++ b/cl/search/migrations/0033_order_opinions_customers.sql @@ -0,0 +1,10 @@ +BEGIN; +-- +-- Add field ordering_key to opinion +-- +ALTER TABLE "search_opinion" ADD COLUMN "ordering_key" integer NULL; +-- +-- Create constraint unique_opinion_ordering_key on model opinion +-- +ALTER TABLE "search_opinion" ADD CONSTRAINT "unique_opinion_ordering_key" UNIQUE ("cluster_id", "ordering_key"); +COMMIT; diff --git a/cl/search/models.py b/cl/search/models.py index 3e68dc476b..478f3f4f80 100644 --- a/cl/search/models.py +++ b/cl/search/models.py @@ -3369,6 +3369,15 @@ class Opinion(AbstractDateTimeModel): "sha1", ] ) + ordering_key = models.IntegerField(null=True, blank=True) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["cluster_id", "ordering_key"], + name="unique_opinion_ordering_key", + ) + ] @property def siblings(self) -> QuerySet: @@ -3387,6 +3396,10 @@ def get_absolute_url(self) -> str: def clean(self) -> None: if self.type == "": raise ValidationError("'type' is a required field.") + if isinstance(self.ordering_key, int) and self.ordering_key < 1: + raise ValidationError( + {"ordering_key": "Ordering key cannot be zero or negative"} + ) def save( self, @@ -3395,6 +3408,7 @@ def save( *args: List, **kwargs: Dict, ) -> None: + self.clean() super().save(*args, **kwargs) if index: from cl.search.tasks import add_items_to_solr diff --git a/cl/search/tests/tests.py b/cl/search/tests/tests.py index aa20691058..b8f85f719d 100644 --- a/cl/search/tests/tests.py +++ b/cl/search/tests/tests.py @@ -300,6 +300,92 @@ def test_custom_manager_chained_filter(self) -> None: ) self.assertEqual(cluster_count, expected_count) + def test_opinions_order(self) -> None: + """Test opinions order""" + + # Create court + court = CourtFactory(id="nyappdiv") + + # Create cluster + cluster = OpinionClusterFactory( + case_name="Foo v. Bar", + case_name_short="Foo v. Bar", + docket=DocketFactory( + court=court, + ), + date_filed=date(1978, 3, 10), + source="U", + precedential_status=PRECEDENTIAL_STATUS.PUBLISHED, + ) + + # Create three opinions + op_1 = OpinionFactory( + cluster=cluster, + type=Opinion.LEAD, + ordering_key=1, + ) + op_2 = OpinionFactory( + cluster=cluster, + type=Opinion.CONCURRENCE, + ordering_key=2, + ) + op_3 = OpinionFactory( + cluster=cluster, + type=Opinion.DISSENT, + ordering_key=3, + ) + + # Test that the value of the order field matches the order in which + # they were created + self.assertEqual(op_1.ordering_key, 1) + self.assertEqual(op_2.ordering_key, 2) + self.assertEqual(op_3.ordering_key, 3) + + # Can we swap orders? + op_1.ordering_key = None + op_1.save() + + op_2.ordering_key = 1 + op_2.save() + + op_1.ordering_key = 2 + op_1.save() + + # Can we update an opinion using an existing position? + with transaction.atomic(): + with self.assertRaises(IntegrityError): + op_3.ordering_key = 2 + op_3.save() + + # Validate unique cluster/order + with transaction.atomic(): + with self.assertRaises(IntegrityError): + op = OpinionFactory( + cluster=cluster, + type=Opinion.ADDENDUM, + ) + op.ordering_key = 3 + op.save() + + # Can we use avoid negative positions? + with transaction.atomic(): + with self.assertRaises(ValidationError): + op = OpinionFactory(cluster=cluster, type=Opinion.LEAD) + op.ordering_key = -1 + op.save() + + # Can we order the opinions from a cluster using the field? + qs = ( + cluster.sub_opinions.all() + .order_by("ordering_key") + .values_list("ordering_key", flat=True) + ) + self.assertEqual(list(qs), [1, 2, 3, None]) + + # Order default value is null + op_5 = OpinionFactory(cluster=cluster, type="Lead Opinion") + self.assertEqual(op_5.ordering_key, None) + class DocketValidationTest(TestCase): @classmethod diff --git a/cl/visualizations/fixtures/api_scotus_map_data.json b/cl/visualizations/fixtures/api_scotus_map_data.json index 5b4b19fe73..3bce46e664 100644 --- a/cl/visualizations/fixtures/api_scotus_map_data.json +++ b/cl/visualizations/fixtures/api_scotus_map_data.json @@ -121,7 +121,8 @@ "date_created": "2015-08-15T14:10:56.801Z", "html_lawbox": "", "per_curiam": false, - "type": "020lead" + "type": "020lead", + "ordering_key": null }, "model": "search.opinion", "pk": 1 @@ -143,7 +144,8 @@ "date_created": "2015-08-15T14:10:56.801Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 2 diff --git a/cl/visualizations/fixtures/scotus_map_data.json b/cl/visualizations/fixtures/scotus_map_data.json index ce504fe2c9..bf97605525 100644 --- a/cl/visualizations/fixtures/scotus_map_data.json +++ b/cl/visualizations/fixtures/scotus_map_data.json @@ -902,7 +902,8 @@ "date_created": "2016-02-16T19:49:54.525Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 111014 @@ -924,7 +925,8 @@ "date_created": "2016-02-16T19:49:54.545Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 111113 @@ -946,7 +948,8 @@ "date_created": "2016-02-16T19:49:54.565Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 111464 @@ -968,7 +971,8 @@ "date_created": "2016-02-16T19:49:54.610Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 111505 @@ -990,7 +994,8 @@ "date_created": "2016-02-16T19:49:54.629Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 111924 @@ -1012,7 +1017,8 @@ "date_created": "2016-02-16T19:49:54.575Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 112331 @@ -1034,7 +1040,8 @@ "date_created": "2016-02-16T19:49:54.537Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 112646 @@ -1056,7 +1063,8 @@ "date_created": "2016-02-16T19:49:54.583Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 112779 @@ -1078,7 +1086,8 @@ "date_created": "2016-02-16T19:49:54.592Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 112874 @@ -1100,7 +1109,8 @@ "date_created": "2016-02-16T19:49:54.602Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 117967 @@ -1122,7 +1132,8 @@ "date_created": "2016-02-16T19:49:54.553Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 118377 @@ -1144,7 +1155,8 @@ "date_created": "2016-02-16T19:49:54.621Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 121168 @@ -1166,7 +1178,8 @@ "date_created": "2016-02-16T19:49:54.658Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 136984 @@ -1188,7 +1201,8 @@ "date_created": "2016-02-16T19:49:54.647Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 142900 @@ -1210,7 +1224,8 @@ "date_created": "2016-02-16T19:49:54.666Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 799990 @@ -1232,7 +1247,8 @@ "date_created": "2016-02-16T19:49:54.636Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 799993 @@ -1254,7 +1270,8 @@ "date_created": "2016-02-16T19:49:54.513Z", "html_lawbox": "", "per_curiam": false, - "type": "010combined" + "type": "010combined", + "ordering_key": null }, "model": "search.opinion", "pk": 2674862 diff --git a/pyproject.toml b/pyproject.toml index a1f77afccd..3c7a4b038a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -117,6 +117,7 @@ openai = "^1.31.1" seal-rookery = "^2.2.3" types-pytz = "^2024.1.0.20240417" + [tool.poetry.group.dev.dependencies] pre-commit = "^3.7.0" types-redis = "^4.6.0.20240106"