django-api-usage 0.1.5__tar.gz → 0.1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {django_api_usage-0.1.5/src/django_api_usage.egg-info → django_api_usage-0.1.6}/PKG-INFO +7 -3
  2. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/README.md +6 -2
  3. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/pyproject.toml +6 -2
  4. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/__init__.py +1 -1
  5. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/admin.py +75 -2
  6. django_api_usage-0.1.6/src/django_api_usage/locale/eu/LC_MESSAGES/django.mo +0 -0
  7. django_api_usage-0.1.6/src/django_api_usage/locale/eu/LC_MESSAGES/django.po +112 -0
  8. {django_api_usage-0.1.5 → django_api_usage-0.1.6/src/django_api_usage.egg-info}/PKG-INFO +7 -3
  9. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage.egg-info/SOURCES.txt +3 -0
  10. django_api_usage-0.1.6/tests/test_filters.py +122 -0
  11. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/LICENSE +0 -0
  12. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/setup.cfg +0 -0
  13. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/apps.py +0 -0
  14. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/buffers.py +0 -0
  15. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/checks.py +0 -0
  16. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/conf.py +0 -0
  17. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/deprecation.py +0 -0
  18. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/drf.py +0 -0
  19. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/maintenance.py +0 -0
  20. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/management/__init__.py +0 -0
  21. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/management/commands/__init__.py +0 -0
  22. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/management/commands/api_usage_flush.py +0 -0
  23. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/management/commands/api_usage_report.py +0 -0
  24. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/middleware.py +0 -0
  25. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/migrations/0001_initial.py +0 -0
  26. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/migrations/0002_endpoint_route_path.py +0 -0
  27. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/migrations/__init__.py +0 -0
  28. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/models.py +0 -0
  29. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/resolvers.py +0 -0
  30. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/tasks.py +0 -0
  31. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/templates/admin/django_api_usage/endpointstat/change_list.html +0 -0
  32. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/templates/admin/django_api_usage/endpointstat/confirm_clear.html +0 -0
  33. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage.egg-info/dependency_links.txt +0 -0
  34. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage.egg-info/requires.txt +0 -0
  35. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage.egg-info/top_level.txt +0 -0
  36. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/tests/test_admin.py +0 -0
  37. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/tests/test_checks.py +0 -0
  38. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/tests/test_commands.py +0 -0
  39. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/tests/test_csv_export.py +0 -0
  40. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/tests/test_drf.py +0 -0
  41. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/tests/test_middleware.py +0 -0
  42. {django_api_usage-0.1.5 → django_api_usage-0.1.6}/tests/test_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-api-usage
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: Lightweight, privacy-first usage metering for Django APIs, with an optional deprecation lifecycle layer.
5
5
  Author-email: CodeSyntax <teknika@codesyntax.com>
6
6
  License-Expression: MIT
@@ -127,8 +127,12 @@ Django — **no dependency beyond Django itself** (no `django-object-actions` or
127
127
  similar):
128
128
 
129
129
  * **Endpoint stats** changelist: a search box (by endpoint path, route name, app,
130
- method or replacement), filters by date, profile and role, and the **sum of
131
- the `count` column** for the rows matching the current filters.
130
+ method or replacement), filters by date, client type, status class, and
131
+ **Application** / **Method**, plus the **sum of the `count` column** for the
132
+ rows matching the current filters. The Method filter lists the riskiest verbs
133
+ first (`DELETE`, `POST`, ...), so write and destructive calls stand out.
134
+ * **Endpoint** changelist: the same Application / Method filters, plus
135
+ deprecation state, the site and the CSV export.
132
136
  * **Flush now**: moves counters buffered in the cache into the database, so they
133
137
  appear immediately when you run with `BUFFER_BACKEND = "cache"`.
134
138
  * **Clear statistics**: empties the aggregated counters behind a confirmation
@@ -88,8 +88,12 @@ Django — **no dependency beyond Django itself** (no `django-object-actions` or
88
88
  similar):
89
89
 
90
90
  * **Endpoint stats** changelist: a search box (by endpoint path, route name, app,
91
- method or replacement), filters by date, profile and role, and the **sum of
92
- the `count` column** for the rows matching the current filters.
91
+ method or replacement), filters by date, client type, status class, and
92
+ **Application** / **Method**, plus the **sum of the `count` column** for the
93
+ rows matching the current filters. The Method filter lists the riskiest verbs
94
+ first (`DELETE`, `POST`, ...), so write and destructive calls stand out.
95
+ * **Endpoint** changelist: the same Application / Method filters, plus
96
+ deprecation state, the site and the CSV export.
93
97
  * **Flush now**: moves counters buffered in the cache into the database, so they
94
98
  appear immediately when you run with `BUFFER_BACKEND = "cache"`.
95
99
  * **Clear statistics**: empties the aggregated counters behind a confirmation
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "django-api-usage"
7
- version = "0.1.5"
7
+ version = "0.1.6"
8
8
  description = "Lightweight, privacy-first usage metering for Django APIs, with an optional deprecation lifecycle layer."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -42,7 +42,11 @@ Issues = "https://github.com/codesyntax/django-api-usage/issues"
42
42
  where = ["src"]
43
43
 
44
44
  [tool.setuptools.package-data]
45
- django_api_usage = ["templates/admin/django_api_usage/endpointstat/*.html"]
45
+ django_api_usage = [
46
+ "templates/admin/django_api_usage/endpointstat/*.html",
47
+ "locale/*/LC_MESSAGES/*.po",
48
+ "locale/*/LC_MESSAGES/*.mo",
49
+ ]
46
50
 
47
51
  [tool.black]
48
52
  line-length = 88
@@ -1,3 +1,3 @@
1
1
  """django-api-usage: usage metering for Django APIs, with deprecation support."""
2
2
 
3
- __version__ = "0.1.5"
3
+ __version__ = "0.1.6"
@@ -60,6 +60,68 @@ class CsvExportMixin:
60
60
  return response
61
61
 
62
62
 
63
+ #: HTTP methods, riskiest first: the point of the filter is spotting the
64
+ #: write/destructive calls (POST, DELETE, ...) instead of the read ones.
65
+ METHOD_ORDER = ("DELETE", "POST", "PUT", "PATCH", "GET", "HEAD", "OPTIONS")
66
+
67
+
68
+ class AppLabelListFilter(admin.SimpleListFilter):
69
+ """Distinct application labels, under a readable title."""
70
+
71
+ title = _("Application")
72
+ parameter_name = "app_label"
73
+ #: Lookup against the admin's model; overridden for related filters.
74
+ field_path = "app_label"
75
+
76
+ def lookups(self, request, model_admin):
77
+ values = (
78
+ model_admin.model.objects.order_by()
79
+ .values_list(self.field_path, flat=True)
80
+ .distinct()
81
+ )
82
+ return [(value, value) for value in sorted(v for v in values if v)]
83
+
84
+ def queryset(self, request, queryset):
85
+ if not self.value():
86
+ return queryset
87
+ return queryset.filter(**{self.field_path: self.value()})
88
+
89
+
90
+ class MethodListFilter(admin.SimpleListFilter):
91
+ """HTTP methods, riskiest (DELETE, POST, ...) first."""
92
+
93
+ title = _("Method")
94
+ parameter_name = "method"
95
+ field_path = "method"
96
+
97
+ def lookups(self, request, model_admin):
98
+ values = set(
99
+ model_admin.model.objects.order_by()
100
+ .values_list(self.field_path, flat=True)
101
+ .distinct()
102
+ )
103
+ ordered = [method for method in METHOD_ORDER if method in values]
104
+ rest = sorted(m for m in values if m and m not in METHOD_ORDER)
105
+ return [(method, method) for method in ordered + rest]
106
+
107
+ def queryset(self, request, queryset):
108
+ if not self.value():
109
+ return queryset
110
+ return queryset.filter(**{self.field_path: self.value()})
111
+
112
+
113
+ class StatAppLabelListFilter(AppLabelListFilter):
114
+ """Application filter for the stats changelist (through the endpoint)."""
115
+
116
+ field_path = "endpoint__app_label"
117
+
118
+
119
+ class StatMethodListFilter(MethodListFilter):
120
+ """Method filter for the stats changelist (through the endpoint)."""
121
+
122
+ field_path = "endpoint__method"
123
+
124
+
63
125
  @admin.register(Endpoint)
64
126
  class EndpointAdmin(CsvExportMixin, admin.ModelAdmin):
65
127
  actions = ("export_as_csv",)
@@ -86,7 +148,12 @@ class EndpointAdmin(CsvExportMixin, admin.ModelAdmin):
86
148
  "replacement",
87
149
  "owner",
88
150
  )
89
- list_filter = ("deprecated", "app_label", "method", "site_id")
151
+ list_filter = (
152
+ "deprecated",
153
+ AppLabelListFilter,
154
+ MethodListFilter,
155
+ "site_id",
156
+ )
90
157
  search_fields = ("route_path", "route_name", "replacement", "owner", "notes")
91
158
  list_editable = ("deprecated", "sunset_date", "replacement", "owner")
92
159
 
@@ -108,7 +175,13 @@ class EndpointStatAdmin(CsvExportMixin, admin.ModelAdmin):
108
175
  ("count", "count"),
109
176
  )
110
177
  list_display = ("date", "endpoint", "client_type", "status_class", "count")
111
- list_filter = ("date", "client_type", "status_class")
178
+ list_filter = (
179
+ "date",
180
+ "client_type",
181
+ "status_class",
182
+ StatAppLabelListFilter,
183
+ StatMethodListFilter,
184
+ )
112
185
  list_select_related = ("endpoint",)
113
186
  date_hierarchy = "date"
114
187
  # Search across the related endpoint (case-insensitive "contains").
@@ -0,0 +1,112 @@
1
+ # Basque translation of django-api-usage.
2
+ # Copyright (C) 2026 CodeSyntax
3
+ # This file is distributed under the same license as the django-api-usage package.
4
+ # Urtzi Odriozola <uodriozola@codesyntax.com>, 2026.
5
+ #
6
+ msgid ""
7
+ msgstr ""
8
+ "Project-Id-Version: django-api-usage\n"
9
+ "Report-Msgid-Bugs-To: \n"
10
+ "POT-Creation-Date: 2026-10-08 06:10-0500\n"
11
+ "PO-Revision-Date: 2026-10-08 06:20-0500\n"
12
+ "Last-Translator: Urtzi Odriozola <uodriozola@codesyntax.com>\n"
13
+ "Language-Team: Basque\n"
14
+ "Language: eu\n"
15
+ "MIME-Version: 1.0\n"
16
+ "Content-Type: text/plain; charset=UTF-8\n"
17
+ "Content-Transfer-Encoding: 8bit\n"
18
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19
+
20
+ #: src/django_api_usage/admin.py:46
21
+ msgid "Export selected rows to CSV"
22
+ msgstr "Esportatu hautatutako errenkadak CSVra"
23
+
24
+ #: src/django_api_usage/admin.py:71
25
+ msgid "Application"
26
+ msgstr "Aplikazioa"
27
+
28
+ #: src/django_api_usage/admin.py:93
29
+ msgid "Method"
30
+ msgstr "Metodoa"
31
+
32
+ #: src/django_api_usage/admin.py:196
33
+ msgid ""
34
+ "Search by endpoint path, route name, app, method or replacement (for example "
35
+ "'herriak' or 'artikuluak')."
36
+ msgstr ""
37
+ "Bilatu endpointaren bidearen, route-izenaren, aplikazioaren, metodoaren edo "
38
+ "ordezkoaren arabera (adibidez 'herriak' edo 'artikuluak')."
39
+
40
+ #: src/django_api_usage/admin.py:251
41
+ #, python-brace-format
42
+ msgid "Flushed {count} buffered counter(s)."
43
+ msgstr "{count} kontagailu eraman dira bufferretik datu-basera."
44
+
45
+ #: src/django_api_usage/admin.py:258
46
+ msgid ""
47
+ "Nothing was buffered: with BUFFER_BACKEND='db' counters are already in the "
48
+ "database."
49
+ msgstr ""
50
+ "Ez zegoen ezer bufferrean: BUFFER_BACKEND='db' denean kontagailuak jada datu-"
51
+ "basean daude."
52
+
53
+ #: src/django_api_usage/admin.py:270
54
+ #: src/django_api_usage/templates/admin/django_api_usage/endpointstat/change_list.html:12
55
+ #: src/django_api_usage/templates/admin/django_api_usage/endpointstat/confirm_clear.html:8
56
+ msgid "Clear statistics"
57
+ msgstr "Estatistikak hustu"
58
+
59
+ #: src/django_api_usage/admin.py:282
60
+ #, python-brace-format
61
+ msgid "Deleted {count} statistic row(s)."
62
+ msgstr "{count} estatistika-errenkada ezabatu dira."
63
+
64
+ #: src/django_api_usage/templates/admin/django_api_usage/endpointstat/change_list.html:8
65
+ msgid "Flush now"
66
+ msgstr "Flush orain"
67
+
68
+ #: src/django_api_usage/templates/admin/django_api_usage/endpointstat/change_list.html:21
69
+ #, python-format
70
+ msgid "TOTAL COUNT: %(total)s"
71
+ msgstr ""
72
+
73
+ #: src/django_api_usage/templates/admin/django_api_usage/endpointstat/confirm_clear.html:6
74
+ msgid "Home"
75
+ msgstr ""
76
+
77
+ #: src/django_api_usage/templates/admin/django_api_usage/endpointstat/confirm_clear.html:7
78
+ msgid "Endpoint stats"
79
+ msgstr "Endpoint estatistikak"
80
+
81
+ #: src/django_api_usage/templates/admin/django_api_usage/endpointstat/confirm_clear.html:14
82
+ #, python-format
83
+ msgid ""
84
+ "\n"
85
+ " This will delete the only aggregated statistic row. Endpoints and "
86
+ "their\n"
87
+ " deprecation metadata are kept.\n"
88
+ " "
89
+ msgid_plural ""
90
+ "\n"
91
+ " This will delete all %(counter)s aggregated statistic rows. Endpoints\n"
92
+ " and their deprecation metadata are kept.\n"
93
+ " "
94
+ msgstr[0] ""
95
+ "\n"
96
+ " Estatistika-errenkada agregatu bakarra ezabatuko da. Endpointak eta "
97
+ "haien\n"
98
+ " deprecation-metadatuak mantendu egingo dira.\n"
99
+ " "
100
+ msgstr[1] ""
101
+ "\n"
102
+ " %(counter)s estatistika-errenkada agregatu ezabatuko dira. Endpointak\n"
103
+ " eta haien deprecation-metadatuak mantendu egingo dira.\n"
104
+ " "
105
+
106
+ #: src/django_api_usage/templates/admin/django_api_usage/endpointstat/confirm_clear.html:24
107
+ msgid "Yes, clear statistics"
108
+ msgstr "Bai, estatistikak hustu"
109
+
110
+ #: src/django_api_usage/templates/admin/django_api_usage/endpointstat/confirm_clear.html:25
111
+ msgid "Cancel"
112
+ msgstr ""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-api-usage
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: Lightweight, privacy-first usage metering for Django APIs, with an optional deprecation lifecycle layer.
5
5
  Author-email: CodeSyntax <teknika@codesyntax.com>
6
6
  License-Expression: MIT
@@ -127,8 +127,12 @@ Django — **no dependency beyond Django itself** (no `django-object-actions` or
127
127
  similar):
128
128
 
129
129
  * **Endpoint stats** changelist: a search box (by endpoint path, route name, app,
130
- method or replacement), filters by date, profile and role, and the **sum of
131
- the `count` column** for the rows matching the current filters.
130
+ method or replacement), filters by date, client type, status class, and
131
+ **Application** / **Method**, plus the **sum of the `count` column** for the
132
+ rows matching the current filters. The Method filter lists the riskiest verbs
133
+ first (`DELETE`, `POST`, ...), so write and destructive calls stand out.
134
+ * **Endpoint** changelist: the same Application / Method filters, plus
135
+ deprecation state, the site and the CSV export.
132
136
  * **Flush now**: moves counters buffered in the cache into the database, so they
133
137
  appear immediately when you run with `BUFFER_BACKEND = "cache"`.
134
138
  * **Clear statistics**: empties the aggregated counters behind a confirmation
@@ -19,6 +19,8 @@ src/django_api_usage.egg-info/SOURCES.txt
19
19
  src/django_api_usage.egg-info/dependency_links.txt
20
20
  src/django_api_usage.egg-info/requires.txt
21
21
  src/django_api_usage.egg-info/top_level.txt
22
+ src/django_api_usage/locale/eu/LC_MESSAGES/django.mo
23
+ src/django_api_usage/locale/eu/LC_MESSAGES/django.po
22
24
  src/django_api_usage/management/__init__.py
23
25
  src/django_api_usage/management/commands/__init__.py
24
26
  src/django_api_usage/management/commands/api_usage_flush.py
@@ -33,5 +35,6 @@ tests/test_checks.py
33
35
  tests/test_commands.py
34
36
  tests/test_csv_export.py
35
37
  tests/test_drf.py
38
+ tests/test_filters.py
36
39
  tests/test_middleware.py
37
40
  tests/test_models.py
@@ -0,0 +1,122 @@
1
+ """Tests for the Application / Method changelist filters."""
2
+
3
+ from django.contrib import admin as django_admin
4
+ from django.contrib.auth.models import User
5
+ from django.urls import reverse
6
+ from django.utils import translation
7
+
8
+ from django_api_usage.admin import MethodListFilter
9
+ from django_api_usage.models import Endpoint, EndpointStat
10
+
11
+ from .base import UsageTestCase
12
+
13
+ ENDPOINT_CHANGELIST = "admin:django_api_usage_endpoint_changelist"
14
+ STAT_CHANGELIST = "admin:django_api_usage_endpointstat_changelist"
15
+
16
+
17
+ class FilterTest(UsageTestCase):
18
+ def setUp(self):
19
+ super().setUp()
20
+ self.admin = User.objects.create_superuser("admin", "a@example.com", "pw")
21
+ self.client.force_login(self.admin)
22
+
23
+ self.get_endpoint = Endpoint.objects.create(
24
+ app_label="api",
25
+ route_path="api/3.0/artikuluak/",
26
+ route_name="artikuluak-list",
27
+ method="GET",
28
+ )
29
+ self.post_endpoint = Endpoint.objects.create(
30
+ app_label="api",
31
+ route_path="api/3.0/artikuluak/",
32
+ route_name="artikuluak-create",
33
+ method="POST",
34
+ )
35
+ self.delete_endpoint = Endpoint.objects.create(
36
+ app_label="gida",
37
+ route_path="api/3.0/gida/",
38
+ route_name="gida-list",
39
+ method="DELETE",
40
+ )
41
+ # Stats for the risky ones only; GET has no usage.
42
+ EndpointStat.objects.create(
43
+ endpoint=self.post_endpoint,
44
+ date="2026-10-08",
45
+ client_type="anon",
46
+ status_class="2xx",
47
+ count=3,
48
+ )
49
+ self.delete_stat = EndpointStat.objects.create(
50
+ endpoint=self.delete_endpoint,
51
+ date="2026-10-08",
52
+ client_type="anon",
53
+ status_class="2xx",
54
+ count=1,
55
+ )
56
+
57
+ def endpoints(self, **params):
58
+ response = self.client.get(reverse(ENDPOINT_CHANGELIST), params)
59
+ self.assertEqual(response.status_code, 200)
60
+ return set(response.context["cl"].queryset)
61
+
62
+ def stats(self, **params):
63
+ response = self.client.get(reverse(STAT_CHANGELIST), params)
64
+ self.assertEqual(response.status_code, 200)
65
+ return set(response.context["cl"].queryset)
66
+
67
+ # -- endpoints ----------------------------------------------------------
68
+
69
+ def test_endpoint_admin_filters_by_method(self):
70
+ self.assertEqual(self.endpoints(method="POST"), {self.post_endpoint})
71
+
72
+ def test_endpoint_admin_filters_by_application(self):
73
+ self.assertEqual(self.endpoints(app_label="gida"), {self.delete_endpoint})
74
+
75
+ def test_endpoint_admin_without_filter_lists_everything(self):
76
+ self.assertEqual(
77
+ self.endpoints(),
78
+ {self.get_endpoint, self.post_endpoint, self.delete_endpoint},
79
+ )
80
+
81
+ # -- stats (through the endpoint) ---------------------------------------
82
+
83
+ def test_stats_admin_filters_by_method(self):
84
+ rows = self.stats(method="DELETE")
85
+ self.assertEqual([row.endpoint for row in rows], [self.delete_endpoint])
86
+
87
+ def test_stats_admin_filters_by_application(self):
88
+ rows = self.stats(app_label="api")
89
+ self.assertEqual([row.endpoint for row in rows], [self.post_endpoint])
90
+
91
+ # -- filter presentation ------------------------------------------------
92
+
93
+ def test_titles_are_readable_instead_of_raw_field_names(self):
94
+ html = self.client.get(reverse(STAT_CHANGELIST)).content.decode()
95
+
96
+ self.assertIn("Application", html)
97
+ self.assertIn("Method", html)
98
+ self.assertNotIn("Irizpidea: app label", html)
99
+
100
+ def test_method_lookups_put_the_risky_ones_first(self):
101
+ model_admin = django_admin.site._registry[Endpoint]
102
+ list_filter = MethodListFilter(
103
+ request=None, params={}, model=Endpoint, model_admin=model_admin
104
+ )
105
+
106
+ lookups = [value for value, _ in list_filter.lookups(None, model_admin)]
107
+
108
+ self.assertEqual(lookups, ["DELETE", "POST", "GET"])
109
+
110
+ def test_basque_catalog_is_shipped(self):
111
+ with translation.override("eu"):
112
+ html = self.client.get(reverse(ENDPOINT_CHANGELIST)).content.decode()
113
+
114
+ self.assertIn("Aplikazioa", html)
115
+ self.assertIn("Metodoa", html)
116
+
117
+ def test_total_count_label_is_left_untranslated(self):
118
+ """The label was requested verbatim, so the eu catalog keeps it English."""
119
+ with translation.override("eu"):
120
+ html = self.client.get(reverse(STAT_CHANGELIST)).content.decode()
121
+
122
+ self.assertIn("TOTAL COUNT", html)