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.
- {django_api_usage-0.1.5/src/django_api_usage.egg-info → django_api_usage-0.1.6}/PKG-INFO +7 -3
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/README.md +6 -2
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/pyproject.toml +6 -2
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/__init__.py +1 -1
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/admin.py +75 -2
- django_api_usage-0.1.6/src/django_api_usage/locale/eu/LC_MESSAGES/django.mo +0 -0
- django_api_usage-0.1.6/src/django_api_usage/locale/eu/LC_MESSAGES/django.po +112 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6/src/django_api_usage.egg-info}/PKG-INFO +7 -3
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage.egg-info/SOURCES.txt +3 -0
- django_api_usage-0.1.6/tests/test_filters.py +122 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/LICENSE +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/setup.cfg +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/apps.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/buffers.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/checks.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/conf.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/deprecation.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/drf.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/maintenance.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/management/__init__.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/management/commands/__init__.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/management/commands/api_usage_flush.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/management/commands/api_usage_report.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/middleware.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/migrations/0001_initial.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/migrations/0002_endpoint_route_path.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/migrations/__init__.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/models.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/resolvers.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/tasks.py +0 -0
- {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
- {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
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage.egg-info/dependency_links.txt +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage.egg-info/requires.txt +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage.egg-info/top_level.txt +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/tests/test_admin.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/tests/test_checks.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/tests/test_commands.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/tests/test_csv_export.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/tests/test_drf.py +0 -0
- {django_api_usage-0.1.5 → django_api_usage-0.1.6}/tests/test_middleware.py +0 -0
- {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.
|
|
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,
|
|
131
|
-
the `count` column** for the
|
|
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,
|
|
92
|
-
the `count` column** for the
|
|
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.
|
|
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 = [
|
|
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
|
|
@@ -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 = (
|
|
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 = (
|
|
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.
|
|
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,
|
|
131
|
-
the `count` column** for the
|
|
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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/management/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/migrations/0001_initial.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage/migrations/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage.egg-info/requires.txt
RENAMED
|
File without changes
|
{django_api_usage-0.1.5 → django_api_usage-0.1.6}/src/django_api_usage.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|