Skip to content

feat(search.models): add ClusterRedirection model #5996

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion cl/opinion_page/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
from cl.search.models import (
SEARCH_TYPES,
Citation,
ClusterRedirection,
Court,
Docket,
DocketEntry,
Expand All @@ -104,6 +105,27 @@
HYPERSCAN_TOKENIZER = HyperscanTokenizer(cache_dir=".hyperscan")


async def aget_cluster_or_404(qs, pk: int) -> OpinionCluster:
"""If a cluster does not exist, check the ClusterRedirection table
before raising a 404
"""
try:
cluster: OpinionCluster = await aget_object_or_404(await qs, pk=pk)
except Http404 as e:
try:
redirection = ClusterRedirection.objects.aget(
deleted_cluster_id=pk
)
cluster = redirection.cluster
if not cluster:
# TODO return sealed page
raise e
except ClusterRedirection.DoesNotExist:
raise e

return cluster


async def court_homepage(request: HttpRequest, pk: str) -> HttpResponse:
"""Individual Court Home Pages"""

Expand Down Expand Up @@ -983,7 +1005,7 @@ async def view_opinion(request: HttpRequest, pk: int, _: str) -> HttpResponse:
:param _: url slug
:return: The old or new opinion HTML
"""
cluster: OpinionCluster = await aget_object_or_404(
cluster: OpinionCluster = await aget_cluster_or_404(
await get_opinions_base_queryset(), pk=pk
)
return await render_opinion_view(request, cluster, "opinions")
Expand Down
23 changes: 23 additions & 0 deletions cl/search/migrations/0041_add_cluster_redirection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.1.8 on 2025-07-17 16:49

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('search', '0040_add_opinion_main_version_field'),
]

operations = [
migrations.CreateModel(
name='ClusterRedirection',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('deleted_cluster_id', models.IntegerField()),
('reason', models.SmallIntegerField(choices=[(1, 'Opinion versions clusters were consolidated in a single one'), (2, 'Clusters were duplicated'), (3, 'Opinion types clusters were consolidated in a single one'), (4, 'Cluster was deleted due to sealing')], help_text='The reason why the old cluster was deleted')),
('cluster', models.ForeignKey(help_text='The existing cluster the deleted clusters now point to', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='merged_clusters', to='search.opinioncluster')),
],
),
]
8 changes: 8 additions & 0 deletions cl/search/migrations/0041_add_cluster_redirection.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
BEGIN;
--
-- Create model ClusterRedirection
--
CREATE TABLE "search_clusterredirection" ("id" integer NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "deleted_cluster_id" integer NOT NULL, "reason" smallint NOT NULL, "cluster_id" integer NULL);
ALTER TABLE "search_clusterredirection" ADD CONSTRAINT "search_clusterredire_cluster_id_82cdb249_fk_search_op" FOREIGN KEY ("cluster_id") REFERENCES "search_opinioncluster" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "search_clusterredirection_cluster_id_82cdb249" ON "search_clusterredirection" ("cluster_id");
COMMIT;
38 changes: 38 additions & 0 deletions cl/search/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3786,3 +3786,41 @@ class Meta:
models.Index(fields=["date_created"]),
]
verbose_name_plural = "Search Queries"


class ClusterRedirection(models.Model):
"""Model to prevent dead /opinion/ links"""

VERSIONING = 1
DUPLICATE = 2
CONSOLIDATION = 3
SEALED = 4
REDIRECTION_REASON = (
(
VERSIONING,
"Opinion versions clusters were consolidated in a single one",
),
(DUPLICATE, "Clusters were duplicated"),
(
CONSOLIDATION,
"Opinion types clusters were consolidated in a single one",
),
(SEALED, "Cluster was deleted due to sealing"),
)

deleted_cluster_id = models.IntegerField()
cluster = models.ForeignKey(
OpinionCluster,
help_text="The existing cluster the deleted clusters now point to",
related_name="merged_clusters",
# if a cluster with `merged_cluster` is to be deleted, it will raise
# an IntegrityError. User should assign a different cluster before
# deleting the current cluster
on_delete=models.DO_NOTHING,
# need null values for SEALED opinions
null=True,
)
reason = models.SmallIntegerField(
help_text="The reason why the old cluster was deleted",
choices=REDIRECTION_REASON,
)
Loading