Skip to content
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

feat(admin): django admin tweaks #4842

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8e18830
feat(admin): add date_created to ProcessingQueueAdmin and PacerFetchQ…
elisa-a-v Dec 18, 2024
d76d64e
feat(admin): add autocomplete field for 'court' in DocketAdmin
elisa-a-v Dec 18, 2024
2a3a40a
feat(admin): add autocomplete field for 'user' in SCOTUSMapAdmin
elisa-a-v Dec 18, 2024
e85e13b
feat(admin): add date_created and date_modified to EmailProcessingQue…
elisa-a-v Dec 18, 2024
94c4640
feat(admin): add jurisdiction to CourtAdmin list_display
elisa-a-v Dec 19, 2024
83132b1
feat(admin): optimize queryset for user_permissions in User form
elisa-a-v Dec 19, 2024
5e77e85
feat(admin): include admin for UserProxyEvent and UserProfileEvent mo…
elisa-a-v Dec 19, 2024
85c8a3e
feat(admin): add links to related Events admins in User instance detail
elisa-a-v Dec 19, 2024
36d7002
refactor(admin): move get_email_confirmed and get_stub_account to Use…
elisa-a-v Dec 20, 2024
e210304
Merge branch 'main' into 2988-django-admin-tweaks
elisa-a-v Dec 20, 2024
3cca3c5
refactor(admin): use decorator to register UserAdmin
elisa-a-v Dec 20, 2024
10e6d08
feat(admin): make all events fields readonly
elisa-a-v Dec 20, 2024
265058e
feat(admin): add list_filter fields to UserAdmin
elisa-a-v Dec 20, 2024
50aa258
feat(admin): enhance DocketEntryAdmin list view
elisa-a-v Dec 20, 2024
60b05f7
feat(admin): replace DocketEntryInline in DocketAdmin
elisa-a-v Dec 20, 2024
1ec8c0f
feat(admin): enhance DocketAdmin list view
elisa-a-v Dec 20, 2024
58f99e5
feat(admin): add search_help_text to admins with search fields
elisa-a-v Dec 20, 2024
e114241
fix(admin): remove non-indexed fields from DocketEntryAdmin search fi…
elisa-a-v Dec 20, 2024
e779c3c
feat(admin): replace DocketAlertInline in DocketAdmin
elisa-a-v Dec 21, 2024
13832c0
feat(admin): add appeal_from to autocomplete fields in DocketAdmin
elisa-a-v Dec 23, 2024
89e6d58
fix(admin): update UserAdmin change_form_template path
elisa-a-v Dec 23, 2024
a1e9d4a
refactor(admin): introduce method to build admin URLs for a given model
elisa-a-v Dec 23, 2024
d64ecf7
Merge branch 'main' into 2988-django-admin-tweaks
elisa-a-v Dec 23, 2024
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
19 changes: 19 additions & 0 deletions cl/assets/templates/admin/docket_change_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% extends "admin/change_form.html" %}

{% block object-tools-items %}
{% if docket_entries_url %}
<li>
<a class="historylink" href="{{ docket_entries_url }}">
mlissner marked this conversation as resolved.
Show resolved Hide resolved
View Docket Entries
</a>
</li>
{% endif %}
{% if docket_alerts_url %}
<li>
<a class="historylink" href="{{ docket_alerts_url }}">
mlissner marked this conversation as resolved.
Show resolved Hide resolved
View Docket Alerts
</a>
</li>
{% endif %}
{{ block.super }}
{% endblock object-tools-items %}
19 changes: 19 additions & 0 deletions cl/assets/templates/admin/user_change_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% extends "admin/change_form.html" %}

{% block object-tools-items %}
{% if proxy_events_url %}
<li>
<a class="historylink" href="{{ proxy_events_url }}">
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:

Detected a template variable used in an anchor tag with the 'href' attribute. This allows a malicious actor to input the 'javascript:' URI and is subject to cross- site scripting (XSS) attacks. If using Flask, use 'url_for()' to safely generate a URL. If using Django, use the 'url' filter to safely generate a URL. If using Mustache, use a URL encoding library, or prepend a slash '/' to the variable for relative links (href="/{{link}}"). You may also consider setting the Content Security Policy (CSP) header.

To resolve this comment:

No guidance has been designated for this issue. Fix according to your organization's approved methods.

💬 Ignore this finding

Leave a nosemgrep comment directly above or at the end of line 6 like so // nosemgrep: generic.html-templates.security.var-in-href.var-in-href

Take care to validate that this is not a true positive finding before ignoring it.
Learn more about ignoring code, files and folders here.

You can view more details about this finding in the Semgrep AppSec Platform.

View UserProxy Events
</a>
</li>
{% endif %}
{% if profile_events_url %}
<li>
<a class="historylink" href="{{ profile_events_url }}">
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:

Detected a template variable used in an anchor tag with the 'href' attribute. This allows a malicious actor to input the 'javascript:' URI and is subject to cross- site scripting (XSS) attacks. If using Flask, use 'url_for()' to safely generate a URL. If using Django, use the 'url' filter to safely generate a URL. If using Mustache, use a URL encoding library, or prepend a slash '/' to the variable for relative links (href="/{{link}}"). You may also consider setting the Content Security Policy (CSP) header.

To resolve this comment:

No guidance has been designated for this issue. Fix according to your organization's approved methods.

💬 Ignore this finding

Leave a nosemgrep comment directly above or at the end of line 13 like so // nosemgrep: generic.html-templates.security.var-in-href.var-in-href

Take care to validate that this is not a true positive finding before ignoring it.
Learn more about ignoring code, files and folders here.

You can view more details about this finding in the Semgrep AppSec Platform.

View UserProfile Events
</a>
</li>
{% endif %}
{{ block.super }}
{% endblock object-tools-items %}
28 changes: 28 additions & 0 deletions cl/lib/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from urllib.parse import urlencode

from django.contrib.contenttypes.admin import GenericTabularInline
from django.urls import reverse

from cl.lib.models import Note

Expand All @@ -13,3 +16,28 @@ class Media:
class NotesInline(GenericTabularInline):
model = Note
extra = 1


def build_admin_url(model_class, query_params=None, view_name="changelist"):
"""
Construct a URL for a given model's admin view, optionally appending query parameters.
:param model_class: The Django model class for which the admin URL will be built.
:param query_params: A dictionary of query parameters to append to the URL (e.g. {"docket": 123}).
:param view_name: An admin view suffix, such as "changelist", "change", or "delete". Defaults to "changelist".
:return: A string representing the fully constructed admin URL, including any query parameters.
Example usage:
>>> from cl.search.models import DocketEntry
>>> build_admin_url(DocketEntry, {"docket": "1234"})
'/admin/search/docketentry/?docket=1234'
"""
query_params = query_params or {}
app_label = model_class._meta.app_label
model_name = model_class._meta.model_name
# "admin:app_label_modelname_changelist" is the standard naming for admin changelist
entries_changelist_url = reverse(
f"admin:{app_label}_{model_name}_{view_name}"
)
query_params = urlencode(query_params)
return f"{entries_changelist_url}?{query_params}"
26 changes: 11 additions & 15 deletions cl/recap/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ class ProcessingQueueAdmin(CursorPaginatorAdmin):
"pacer_case_id",
"document_number",
"attachment_number",
"date_created",
)
list_filter = ("status",)
list_filter = ("status", "date_created")
search_help_text = "Search ProcessingQueues by pacer_case_id or court__pk."
search_fields = (
"pacer_case_id",
"court__pk",
Expand All @@ -41,15 +43,8 @@ class ProcessingQueueAdmin(CursorPaginatorAdmin):

@admin.register(PacerFetchQueue)
class PacerFetchQueueAdmin(CursorPaginatorAdmin):
list_display = (
"__str__",
"court",
"request_type",
)
list_filter = (
"status",
"request_type",
)
list_display = ("__str__", "court", "request_type", "date_created")
list_filter = ("status", "request_type", "date_created")
readonly_fields = (
"date_created",
"date_modified",
Expand Down Expand Up @@ -94,14 +89,15 @@ def reprocess_failed_epq(modeladmin, request, queryset):

@admin.register(EmailProcessingQueue)
class EmailProcessingQueueAdmin(CursorPaginatorAdmin):
list_display = (
"__str__",
"status",
)
list_filter = ("status",)
list_display = ("__str__", "status", "date_created")
list_filter = ("status", "date_created")
actions = [reprocess_failed_epq]
raw_id_fields = ["uploader", "court"]
exclude = ["recap_documents", "filepath"]
readonly_fields = (
"date_created",
"date_modified",
)


admin.site.register(FjcIntegratedDatabase)
79 changes: 69 additions & 10 deletions cl/search/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
from django.db.models import QuerySet
from django.http import HttpRequest

from cl.alerts.admin import DocketAlertInline
from cl.alerts.models import DocketAlert
from cl.lib.admin import build_admin_url
from cl.lib.cloud_front import invalidate_cloudfront
from cl.lib.models import THUMBNAIL_STATUSES
from cl.lib.string_utils import trunc
from cl.recap.management.commands.delete_document_from_ia import delete_from_ia
from cl.search.models import (
BankruptcyInformation,
Expand Down Expand Up @@ -88,7 +90,14 @@ class OpinionClusterAdmin(CursorPaginatorAdmin):

@admin.register(Court)
class CourtAdmin(admin.ModelAdmin):
list_display = ("full_name", "short_name", "position", "in_use", "pk")
list_display = (
"full_name",
"short_name",
"position",
"in_use",
"pk",
"jurisdiction",
)
list_filter = (
"jurisdiction",
"in_use",
Expand Down Expand Up @@ -215,17 +224,36 @@ class RECAPDocumentInline(admin.StackedInline):
@admin.register(DocketEntry)
class DocketEntryAdmin(CursorPaginatorAdmin):
inlines = (RECAPDocumentInline,)
search_help_text = (
"Search DocketEntries by Docket ID or RECAP sequence number."
)
search_fields = (
"docket__id",
"recap_sequence_number",
)
list_display = (
"get_pk",
"get_trunc_description",
"date_filed",
"time_filed",
"entry_number",
"recap_sequence_number",
"pacer_sequence_number",
)
raw_id_fields = ("docket", "tags")
readonly_fields = (
"date_created",
"date_modified",
)
list_filter = ("date_filed", "date_created", "date_modified")

@admin.display(description="Docket entry")
def get_pk(self, obj):
return obj.pk

class DocketEntryInline(admin.TabularInline):
model = DocketEntry
extra = 1
raw_id_fields = ("tags",)
@admin.display(description="Description")
def get_trunc_description(self, obj):
return trunc(obj.description, 35, ellipsis="...")


@admin.register(OriginatingCourtInformation)
Expand All @@ -238,17 +266,26 @@ class OriginatingCourtInformationAdmin(admin.ModelAdmin):

@admin.register(Docket)
class DocketAdmin(CursorPaginatorAdmin):
change_form_template = "admin/docket_change_form.html"
prepopulated_fields = {"slug": ["case_name"]}
inlines = (
DocketEntryInline,
BankruptcyInformationInline,
DocketAlertInline,
list_display = (
"__str__",
"pacer_case_id",
"docket_number",
"pacer_case_id",
)
search_help_text = "Search dockets by PK, PACER case ID, or Docket number."
search_fields = ("pk", "pacer_case_id", "docket_number")
inlines = (BankruptcyInformationInline,)
readonly_fields = (
"date_created",
"date_modified",
"view_count",
)
autocomplete_fields = (
"court",
"appeal_from",
)
raw_id_fields = (
"panel",
"tags",
Expand All @@ -259,6 +296,28 @@ class DocketAdmin(CursorPaginatorAdmin):
"parent_docket",
)

def change_view(self, request, object_id, form_url="", extra_context=None):
"""Add links to pre-filtered related admin pages."""
extra_context = extra_context or {}
docket = self.get_object(request, object_id)
query_params = {"docket": object_id}

if docket and hasattr(docket, "docket_entries"):
extra_context["docket_entries_url"] = build_admin_url(
DocketEntry,
query_params,
)

if docket and hasattr(docket, "alerts"):
extra_context["docket_alerts_url"] = build_admin_url(
DocketAlert,
query_params,
)

return super().change_view(
request, object_id, form_url, extra_context=extra_context
)


@admin.register(OpinionsCited)
class OpinionsCitedAdmin(CursorPaginatorAdmin):
Expand Down
Loading
Loading