Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.10
3.12
5 changes: 4 additions & 1 deletion config/settings/production.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@

# STATIC
# ------------------------
STATICFILES_STORAGE = "config.settings.production.StaticRootS3Boto3Storage"
STORAGES = {
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {"BACKEND": "config.settings.production.StaticRootS3Boto3Storage"},
}
STATIC_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/static/"

# MEDIA
Expand Down
22 changes: 22 additions & 0 deletions documentcloud/addons/migrations/0031_addonevent_dismissed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 5.2.13 on 2026-07-20 21:38

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("addons", "0030_addonevent_site_indexes"),
]

operations = [
migrations.AddField(
model_name="addonevent",
name="dismissed",
field=models.BooleanField(
default=False,
help_text="If this event has been dismissed from view and should no longer be shown to the user",
verbose_name="dismissed",
),
),
]
9 changes: 9 additions & 0 deletions documentcloud/addons/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,15 @@ class AddOnEvent(models.Model):
help_text=_("Timestamp of when the add-on event was last updated"),
)

dismissed = models.BooleanField(
_("dismissed"),
default=False,
help_text=_(
"If this event has been dismissed from view and should no longer be "
"shown to the user"
),
)

class Meta:
indexes = [
models.Index(
Expand Down
1 change: 1 addition & 0 deletions documentcloud/addons/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ class Meta:
"scratch",
"created_at",
"updated_at",
"dismissed",
]
extra_kwargs = {
"addon": {"queryset": AddOn.objects.none()},
Expand Down
80 changes: 80 additions & 0 deletions documentcloud/addons/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,3 +438,83 @@ def test_filter_message_absent_is_noop(self, client):
response = client.get("/api/addon_runs/")
assert response.status_code == status.HTTP_200_OK
assert len(response.json()["results"]) == 3

def test_default_not_dismissed(self):
"""A newly created event is not dismissed"""
event = AddOnEventFactory()
assert event.dismissed is False

def test_dismissed_in_serializer(self, client):
"""The dismissed field is exposed on the event"""
event = AddOnEventFactory()
client.force_authenticate(user=event.user)
response = client.get(f"/api/addon_events/{event.pk}/")
assert response.status_code == status.HTTP_200_OK
assert response.json()["dismissed"] is False

def test_dismiss(self, client):
"""The owner can dismiss their event"""
event = AddOnEventFactory(dismissed=False)
client.force_authenticate(user=event.user)
response = client.patch(
f"/api/addon_events/{event.pk}/", {"dismissed": True}, format="json"
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["dismissed"] is True
event.refresh_from_db()
assert event.dismissed is True

def test_undismiss(self, client):
"""The owner can un-dismiss their event"""
event = AddOnEventFactory(dismissed=True)
client.force_authenticate(user=event.user)
response = client.patch(
f"/api/addon_events/{event.pk}/", {"dismissed": False}, format="json"
)
assert response.status_code == status.HTTP_200_OK
event.refresh_from_db()
assert event.dismissed is False

def test_dismiss_non_owner(self, client):
"""A non-owner can't see the event, so the patch 404s and nothing changes"""
event = AddOnEventFactory(dismissed=False)
other = UserFactory()
client.force_authenticate(user=other)
response = client.patch(
f"/api/addon_events/{event.pk}/", {"dismissed": True}, format="json"
)
assert response.status_code == status.HTTP_404_NOT_FOUND
event.refresh_from_db()
assert event.dismissed is False

def test_filter_dismissed_false(self, client):
"""?dismissed=false returns only non-dismissed events"""
user = UserFactory()
visible = AddOnEventFactory(user=user, dismissed=False)
AddOnEventFactory(user=user, dismissed=True)
client.force_authenticate(user=user)
response = client.get("/api/addon_events/", {"dismissed": "false"})
assert response.status_code == status.HTTP_200_OK
ids = [r["id"] for r in response.json()["results"]]
assert ids == [visible.pk]

def test_filter_dismissed_true(self, client):
"""?dismissed=true returns only dismissed events"""
user = UserFactory()
AddOnEventFactory(user=user, dismissed=False)
dismissed = AddOnEventFactory(user=user, dismissed=True)
client.force_authenticate(user=user)
response = client.get("/api/addon_events/", {"dismissed": "true"})
assert response.status_code == status.HTTP_200_OK
ids = [r["id"] for r in response.json()["results"]]
assert ids == [dismissed.pk]

def test_filter_dismissed_absent_is_noop(self, client):
"""Omitting the dismissed filter returns all viewable events"""
user = UserFactory()
AddOnEventFactory(user=user, dismissed=False)
AddOnEventFactory(user=user, dismissed=True)
client.force_authenticate(user=user)
response = client.get("/api/addon_events/")
assert response.status_code == status.HTTP_200_OK
assert len(response.json()["results"]) == 2
4 changes: 4 additions & 0 deletions documentcloud/addons/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,10 @@ class Filter(django_filters.FilterSet):
"`https://www.nifc.gov`."
),
)
dismissed = django_filters.BooleanFilter(
field_name="dismissed",
help_text="Filter events by whether they have been dismissed.",
)

def site_filter(self, queryset, name, value):
# pylint: disable=unused-argument
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-r cloud-requirements.txt

Pillow==12.1.1
Pillow==12.3.0
aioboto3==15.5.0
django-environ==0.13.0
furl==2.1.0
Expand Down
4 changes: 2 additions & 2 deletions documentcloud/documents/processing/ocr/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
boto3==1.40.61
smart-open==7.5.1
Pillow==12.1.1
Pillow==12.3.0
cpuprofile==1.0.1
django-environ==0.13.0
furl==2.1.0
Expand All @@ -9,4 +9,4 @@ redis==3.4.1
requests==2.33.0
sentry-sdk==2.57.0
pymupdf==1.25.3
setuptools<81
setuptools<81
13 changes: 7 additions & 6 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ click-repl==0.3.0
# via celery
cpuprofile==1.0.1
# via -r requirements/base.in
cryptography==46.0.7
cryptography==49.0.0
# via
# -r requirements/base.in
# google-auth
Expand All @@ -99,7 +99,7 @@ defusedxml==0.7.1
# social-auth-core
deprecated==1.3.1
# via pikepdf
django==5.2.13
django==5.2.15
# via
# -r requirements/base.in
# daily-active-users
Expand Down Expand Up @@ -272,8 +272,9 @@ logzio-python-handler==4.1.9
# via -r requirements/base.in
luqum==0.8.1
# via -r requirements/base.in
lxml==6.0.2
lxml==6.1.1
# via
# -r requirements/base.in
# pikepdf
# premailer
markdown==3.8.1
Expand Down Expand Up @@ -322,7 +323,7 @@ pickleshare==0.7.5
# via ipython
pikepdf==10.5.1
# via -r requirements/base.in
pillow==12.1.1
pillow==12.3.0
# via
# -r requirements/base.in
# pdfplumber
Expand Down Expand Up @@ -368,7 +369,7 @@ pycparser==2.19
# via cffi
pygments==2.20.0
# via ipython
pyjwt==2.12.1
pyjwt==2.13.0
# via
# djangorestframework-simplejwt
# social-auth-core
Expand Down Expand Up @@ -477,7 +478,7 @@ unidecode==1.1.1
# via -r requirements/base.in
uritemplate==4.1.1
# via drf-spectacular
urllib3==2.6.3
urllib3==2.7.0
# via
# botocore
# requests
Expand Down
14 changes: 7 additions & 7 deletions requirements/local.txt
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ coverage==4.5.4
# django-coverage-plugin
cpuprofile==1.0.1
# via -r requirements/base.txt
cryptography==46.0.7
cryptography==49.0.0
# via
# -r requirements/base.txt
# google-auth
Expand Down Expand Up @@ -177,7 +177,7 @@ deprecated==1.3.1
# pikepdf
dill==0.4.1
# via pylint
django==5.2.13
django==5.2.15
# via
# -r requirements/base.txt
# daily-active-users
Expand Down Expand Up @@ -215,7 +215,7 @@ django-cprofile-middleware==1.0.5
# via -r requirements/base.txt
django-debug-toolbar==6.2.0
# via -r requirements/base.txt
django-environ==0.4.5
django-environ==0.13.0
# via -r requirements/base.txt
django-extensions==3.2.3
# via -r requirements/base.txt
Expand Down Expand Up @@ -423,7 +423,7 @@ logzio-python-handler==4.1.9
# via -r requirements/base.txt
luqum==0.8.1
# via -r requirements/base.txt
lxml==6.0.2
lxml==6.1.1
# via
# -r requirements/base.txt
# pikepdf
Expand Down Expand Up @@ -522,7 +522,7 @@ pickleshare==0.7.5
# ipython
pikepdf==10.5.1
# via -r requirements/base.txt
pillow==12.1.1
pillow==12.3.0
# via
# -r requirements/base.txt
# pdfplumber
Expand Down Expand Up @@ -613,7 +613,7 @@ pygments==2.20.0
# pytest
# rich
# sphinx
pyjwt==2.12.1
pyjwt==2.13.0
# via
# -r requirements/base.txt
# djangorestframework-simplejwt
Expand Down Expand Up @@ -831,7 +831,7 @@ uritemplate==4.1.1
# via
# -r requirements/base.txt
# drf-spectacular
urllib3==2.6.3
urllib3==2.7.0
# via
# -r requirements/base.txt
# botocore
Expand Down
17 changes: 9 additions & 8 deletions requirements/production.txt
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ click-repl==0.3.0
# celery
cpuprofile==1.0.1
# via -r requirements/base.txt
cryptography==46.0.7
cryptography==49.0.0
# via
# -r requirements/base.txt
# google-auth
Expand Down Expand Up @@ -150,7 +150,7 @@ deprecated==1.3.1
# via
# -r requirements/base.txt
# pikepdf
django==5.2.13
django==5.2.15
# via
# -r requirements/base.txt
# daily-active-users
Expand Down Expand Up @@ -194,7 +194,7 @@ django-cprofile-middleware==1.0.5
# via -r requirements/base.txt
django-debug-toolbar==6.2.0
# via -r requirements/base.txt
django-environ==0.4.5
django-environ==0.13.0
# via -r requirements/base.txt
django-extensions==3.2.3
# via -r requirements/base.txt
Expand Down Expand Up @@ -329,7 +329,7 @@ grpcio-status==1.63.0rc1
# -r requirements/base.txt
# google-api-core
# google-cloud-pubsub
gunicorn==20.1.0
gunicorn==26.0.0
# via -r requirements/production.in
html2text==2020.1.16
# via -r requirements/base.txt
Expand Down Expand Up @@ -373,7 +373,7 @@ logzio-python-handler==4.1.9
# via -r requirements/base.txt
luqum==0.8.1
# via -r requirements/base.txt
lxml==6.0.2
lxml==6.1.1
# via
# -r requirements/base.txt
# pikepdf
Expand Down Expand Up @@ -422,6 +422,7 @@ orderedmultidict==1.0.1
packaging==24.1
# via
# -r requirements/base.txt
# gunicorn
# pikepdf
parso==0.8.3
# via
Expand All @@ -445,7 +446,7 @@ pickleshare==0.7.5
# ipython
pikepdf==10.5.1
# via -r requirements/base.txt
pillow==12.1.1
pillow==12.3.0
# via
# -r requirements/base.txt
# pdfplumber
Expand Down Expand Up @@ -515,7 +516,7 @@ pygments==2.20.0
# via
# -r requirements/base.txt
# ipython
pyjwt==2.12.1
pyjwt==2.13.0
# via
# -r requirements/base.txt
# djangorestframework-simplejwt
Expand Down Expand Up @@ -656,7 +657,7 @@ uritemplate==4.1.1
# via
# -r requirements/base.txt
# drf-spectacular
urllib3==2.6.3
urllib3==2.7.0
# via
# -r requirements/base.txt
# botocore
Expand Down
10 changes: 10 additions & 0 deletions tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@
)
WEB_OPEN = "xdg-open {} > /dev/null 2>&1"

@task
def up(c):
"""Start the docker images"""
c.run("docker compose up -d")


@task
def down(c):
"""Shut down the docker images"""
c.run(f"docker compose down")

@task
def test(
Expand Down
Loading