django-api-usage 0.1.8__tar.gz → 0.1.9__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.8/src/django_api_usage.egg-info → django_api_usage-0.1.9}/PKG-INFO +1 -1
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/pyproject.toml +1 -1
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/__init__.py +1 -1
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/admin.py +88 -2
- django_api_usage-0.1.9/src/django_api_usage/locale/eu/LC_MESSAGES/django.mo +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/locale/eu/LC_MESSAGES/django.po +26 -16
- {django_api_usage-0.1.8 → django_api_usage-0.1.9/src/django_api_usage.egg-info}/PKG-INFO +1 -1
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage.egg-info/SOURCES.txt +2 -1
- django_api_usage-0.1.9/tests/test_sites.py +92 -0
- django_api_usage-0.1.8/src/django_api_usage/locale/eu/LC_MESSAGES/django.mo +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/LICENSE +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/README.md +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/setup.cfg +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/apps.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/buffers.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/checks.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/conf.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/deprecation.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/drf.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/maintenance.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/management/__init__.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/management/commands/__init__.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/management/commands/api_usage_backfill_paths.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/management/commands/api_usage_flush.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/management/commands/api_usage_report.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/middleware.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/migrations/0001_initial.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/migrations/0002_endpoint_route_path.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/migrations/0003_alter_endpointstat_unique_together_and_more.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/migrations/0004_remove_clientapp_accounts_clientappaccount.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/migrations/__init__.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/models.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/resolvers.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/tasks.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/templates/admin/django_api_usage/endpointstat/change_list.html +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/templates/admin/django_api_usage/endpointstat/confirm_clear.html +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage.egg-info/dependency_links.txt +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage.egg-info/requires.txt +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage.egg-info/top_level.txt +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/tests/test_admin.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/tests/test_backfill_paths.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/tests/test_checks.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/tests/test_client_app.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/tests/test_commands.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/tests/test_csv_export.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/tests/test_drf.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/tests/test_filters.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/tests/test_middleware.py +0 -0
- {django_api_usage-0.1.8 → django_api_usage-0.1.9}/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.9
|
|
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
|
|
@@ -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.9"
|
|
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"
|
|
@@ -14,6 +14,7 @@ Everything is plain Django: no extra dependency beyond Django itself.
|
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
import csv
|
|
17
|
+
import time
|
|
17
18
|
|
|
18
19
|
from django.contrib import admin, messages
|
|
19
20
|
from django.db.models import Count, OuterRef, Subquery, Sum
|
|
@@ -78,6 +79,45 @@ METHOD_COLORS = {
|
|
|
78
79
|
DEFAULT_METHOD_COLORS = ("#e2e3e5", "#41464b")
|
|
79
80
|
|
|
80
81
|
|
|
82
|
+
#: Site labels are read for every row of the changelist, so they are cached for
|
|
83
|
+
#: a short while (the table changes very rarely).
|
|
84
|
+
SITE_LABELS_TTL = 60
|
|
85
|
+
_site_labels_cache = {"loaded_at": 0.0, "value": None}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _load_site_labels():
|
|
89
|
+
"""``{pk: domain}`` when django.contrib.sites is installed, else ``{}``."""
|
|
90
|
+
try:
|
|
91
|
+
from django.contrib.sites.models import Site
|
|
92
|
+
except (ImportError, RuntimeError):
|
|
93
|
+
return {}
|
|
94
|
+
return dict(Site.objects.values_list("pk", "domain"))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _site_labels():
|
|
98
|
+
now = time.monotonic()
|
|
99
|
+
if (
|
|
100
|
+
_site_labels_cache["value"] is None
|
|
101
|
+
or now - _site_labels_cache["loaded_at"] > SITE_LABELS_TTL
|
|
102
|
+
):
|
|
103
|
+
_site_labels_cache["value"] = _load_site_labels()
|
|
104
|
+
_site_labels_cache["loaded_at"] = now
|
|
105
|
+
return _site_labels_cache["value"]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def reset_site_labels_cache(**kwargs):
|
|
109
|
+
"""Forget the cached site domains (used by tests and after editing sites)."""
|
|
110
|
+
_site_labels_cache["value"] = None
|
|
111
|
+
_site_labels_cache["loaded_at"] = 0.0
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _site_label(site_id):
|
|
115
|
+
"""Domain of a site, falling back to the raw id (or ``None``)."""
|
|
116
|
+
if site_id is None:
|
|
117
|
+
return None
|
|
118
|
+
return _site_labels().get(site_id, str(site_id))
|
|
119
|
+
|
|
120
|
+
|
|
81
121
|
def _method_badge_html(method):
|
|
82
122
|
"""Coloured pill for an HTTP verb, so writes stand out at a glance."""
|
|
83
123
|
verb = (method or "").upper()
|
|
@@ -148,6 +188,42 @@ class StatMethodListFilter(MethodListFilter):
|
|
|
148
188
|
field_path = "endpoint__method"
|
|
149
189
|
|
|
150
190
|
|
|
191
|
+
class SiteListFilter(admin.SimpleListFilter):
|
|
192
|
+
"""Sites, shown by domain when django.contrib.sites is available.
|
|
193
|
+
|
|
194
|
+
Useful when several front ends (several sites) call the same backend: the
|
|
195
|
+
endpoint's site tells them apart.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
title = _("Site")
|
|
199
|
+
parameter_name = "site"
|
|
200
|
+
field_path = "site_id"
|
|
201
|
+
|
|
202
|
+
def lookups(self, request, model_admin):
|
|
203
|
+
labels = _site_labels()
|
|
204
|
+
if labels:
|
|
205
|
+
return [(pk, domain) for pk, domain in sorted(labels.items())]
|
|
206
|
+
used = (
|
|
207
|
+
model_admin.model.objects.order_by()
|
|
208
|
+
.values_list(self.field_path, flat=True)
|
|
209
|
+
.distinct()
|
|
210
|
+
)
|
|
211
|
+
return [
|
|
212
|
+
(value, str(value)) for value in sorted(v for v in used if v is not None)
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
def queryset(self, request, queryset):
|
|
216
|
+
if not self.value():
|
|
217
|
+
return queryset
|
|
218
|
+
return queryset.filter(**{self.field_path: self.value()})
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class StatSiteListFilter(SiteListFilter):
|
|
222
|
+
"""Site filter for the stats changelist (through the endpoint)."""
|
|
223
|
+
|
|
224
|
+
field_path = "endpoint__site_id"
|
|
225
|
+
|
|
226
|
+
|
|
151
227
|
#: Sentinel for the calls that could not be attributed to any application.
|
|
152
228
|
UNATTRIBUTED = "__none__"
|
|
153
229
|
|
|
@@ -207,7 +283,7 @@ class EndpointAdmin(CsvExportMixin, admin.ModelAdmin):
|
|
|
207
283
|
"route_path",
|
|
208
284
|
"route_name",
|
|
209
285
|
"method_badge",
|
|
210
|
-
"
|
|
286
|
+
"site",
|
|
211
287
|
"deprecated",
|
|
212
288
|
"sunset_date",
|
|
213
289
|
"replacement",
|
|
@@ -217,7 +293,7 @@ class EndpointAdmin(CsvExportMixin, admin.ModelAdmin):
|
|
|
217
293
|
"deprecated",
|
|
218
294
|
AppLabelListFilter,
|
|
219
295
|
MethodListFilter,
|
|
220
|
-
|
|
296
|
+
SiteListFilter,
|
|
221
297
|
)
|
|
222
298
|
search_fields = ("route_path", "route_name", "replacement", "owner", "notes")
|
|
223
299
|
list_editable = ("deprecated", "sunset_date", "replacement", "owner")
|
|
@@ -226,6 +302,10 @@ class EndpointAdmin(CsvExportMixin, admin.ModelAdmin):
|
|
|
226
302
|
def method_badge(self, obj):
|
|
227
303
|
return _method_badge_html(obj.method)
|
|
228
304
|
|
|
305
|
+
@admin.display(description=_("Site"), ordering="site_id")
|
|
306
|
+
def site(self, obj):
|
|
307
|
+
return _site_label(obj.site_id)
|
|
308
|
+
|
|
229
309
|
|
|
230
310
|
@admin.register(EndpointStat)
|
|
231
311
|
class EndpointStatAdmin(CsvExportMixin, admin.ModelAdmin):
|
|
@@ -246,6 +326,7 @@ class EndpointStatAdmin(CsvExportMixin, admin.ModelAdmin):
|
|
|
246
326
|
)
|
|
247
327
|
list_display = (
|
|
248
328
|
"date",
|
|
329
|
+
"site",
|
|
249
330
|
"application",
|
|
250
331
|
"method_badge",
|
|
251
332
|
"endpoint_path",
|
|
@@ -259,6 +340,7 @@ class EndpointStatAdmin(CsvExportMixin, admin.ModelAdmin):
|
|
|
259
340
|
"client_type",
|
|
260
341
|
ClientAppListFilter,
|
|
261
342
|
"status_class",
|
|
343
|
+
StatSiteListFilter,
|
|
262
344
|
StatAppLabelListFilter,
|
|
263
345
|
StatMethodListFilter,
|
|
264
346
|
)
|
|
@@ -284,6 +366,10 @@ class EndpointStatAdmin(CsvExportMixin, admin.ModelAdmin):
|
|
|
284
366
|
def application(self, obj):
|
|
285
367
|
return obj.endpoint.app_label
|
|
286
368
|
|
|
369
|
+
@admin.display(description=_("Site"), ordering="endpoint__site_id")
|
|
370
|
+
def site(self, obj):
|
|
371
|
+
return _site_label(obj.endpoint.site_id)
|
|
372
|
+
|
|
287
373
|
@admin.display(description=_("Method"), ordering="endpoint__method")
|
|
288
374
|
def method_badge(self, obj):
|
|
289
375
|
return _method_badge_html(obj.endpoint.method)
|
|
@@ -7,7 +7,7 @@ msgid ""
|
|
|
7
7
|
msgstr ""
|
|
8
8
|
"Project-Id-Version: django-api-usage\n"
|
|
9
9
|
"Report-Msgid-Bugs-To: \n"
|
|
10
|
-
"POT-Creation-Date: 2026-10-08
|
|
10
|
+
"POT-Creation-Date: 2026-10-08 08:11-0500\n"
|
|
11
11
|
"PO-Revision-Date: 2026-10-08 06:55-0500\n"
|
|
12
12
|
"Last-Translator: Urtzi Odriozola <uodriozola@codesyntax.com>\n"
|
|
13
13
|
"Language-Team: Basque\n"
|
|
@@ -17,27 +17,33 @@ msgstr ""
|
|
|
17
17
|
"Content-Transfer-Encoding: 8bit\n"
|
|
18
18
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
19
19
|
|
|
20
|
-
#: src/django_api_usage/admin.py:
|
|
20
|
+
#: src/django_api_usage/admin.py:48
|
|
21
21
|
msgid "Export selected rows to CSV"
|
|
22
22
|
msgstr "Esportatu hautatutako errenkadak CSVra"
|
|
23
23
|
|
|
24
|
-
#: src/django_api_usage/admin.py:
|
|
24
|
+
#: src/django_api_usage/admin.py:131 src/django_api_usage/admin.py:359
|
|
25
25
|
msgid "Application"
|
|
26
26
|
msgstr "Aplikazioa"
|
|
27
27
|
|
|
28
|
-
#: src/django_api_usage/admin.py:
|
|
28
|
+
#: src/django_api_usage/admin.py:153 src/django_api_usage/admin.py:295
|
|
29
|
+
#: src/django_api_usage/admin.py:367
|
|
29
30
|
msgid "Method"
|
|
30
31
|
msgstr "Metodoa"
|
|
31
32
|
|
|
32
|
-
#: src/django_api_usage/admin.py:
|
|
33
|
+
#: src/django_api_usage/admin.py:192 src/django_api_usage/admin.py:299
|
|
34
|
+
#: src/django_api_usage/admin.py:363
|
|
35
|
+
msgid "Site"
|
|
36
|
+
msgstr "Webgunea"
|
|
37
|
+
|
|
38
|
+
#: src/django_api_usage/admin.py:232 src/django_api_usage/models.py:100
|
|
33
39
|
msgid "Client application"
|
|
34
40
|
msgstr "Bezero-aplikazioa"
|
|
35
41
|
|
|
36
|
-
#: src/django_api_usage/admin.py:
|
|
42
|
+
#: src/django_api_usage/admin.py:248
|
|
37
43
|
msgid "Not attributed"
|
|
38
44
|
msgstr "Esleitu gabea"
|
|
39
45
|
|
|
40
|
-
#: src/django_api_usage/admin.py:
|
|
46
|
+
#: src/django_api_usage/admin.py:352
|
|
41
47
|
msgid ""
|
|
42
48
|
"Search by endpoint path, route name, app, method or replacement (for example "
|
|
43
49
|
"'herriak' or 'artikuluak')."
|
|
@@ -45,12 +51,16 @@ msgstr ""
|
|
|
45
51
|
"Bilatu endpointaren bidearen, route-izenaren, aplikazioaren, metodoaren edo "
|
|
46
52
|
"ordezkoaren arabera (adibidez 'herriak' edo 'artikuluak')."
|
|
47
53
|
|
|
48
|
-
#: src/django_api_usage/admin.py:
|
|
54
|
+
#: src/django_api_usage/admin.py:371
|
|
55
|
+
msgid "Endpoint"
|
|
56
|
+
msgstr "Endpointa"
|
|
57
|
+
|
|
58
|
+
#: src/django_api_usage/admin.py:427
|
|
49
59
|
#, python-brace-format
|
|
50
60
|
msgid "Flushed {count} buffered counter(s)."
|
|
51
61
|
msgstr "{count} kontagailu eraman dira bufferretik datu-basera."
|
|
52
62
|
|
|
53
|
-
#: src/django_api_usage/admin.py:
|
|
63
|
+
#: src/django_api_usage/admin.py:434
|
|
54
64
|
msgid ""
|
|
55
65
|
"Nothing was buffered: with BUFFER_BACKEND='db' counters are already in the "
|
|
56
66
|
"database."
|
|
@@ -58,22 +68,22 @@ msgstr ""
|
|
|
58
68
|
"Ez zegoen ezer bufferrean: BUFFER_BACKEND='db' denean kontagailuak jada datu-"
|
|
59
69
|
"basean daude."
|
|
60
70
|
|
|
61
|
-
#: src/django_api_usage/admin.py:
|
|
71
|
+
#: src/django_api_usage/admin.py:446
|
|
62
72
|
#: src/django_api_usage/templates/admin/django_api_usage/endpointstat/change_list.html:12
|
|
63
73
|
#: src/django_api_usage/templates/admin/django_api_usage/endpointstat/confirm_clear.html:8
|
|
64
74
|
msgid "Clear statistics"
|
|
65
75
|
msgstr "Estatistikak hustu"
|
|
66
76
|
|
|
67
|
-
#: src/django_api_usage/admin.py:
|
|
77
|
+
#: src/django_api_usage/admin.py:458
|
|
68
78
|
#, python-brace-format
|
|
69
79
|
msgid "Deleted {count} statistic row(s)."
|
|
70
80
|
msgstr "{count} estatistika-errenkada ezabatu dira."
|
|
71
81
|
|
|
72
|
-
#: src/django_api_usage/admin.py:
|
|
82
|
+
#: src/django_api_usage/admin.py:522
|
|
73
83
|
msgid "Matching rules"
|
|
74
84
|
msgstr "Bat-etortze arauak"
|
|
75
85
|
|
|
76
|
-
#: src/django_api_usage/admin.py:
|
|
86
|
+
#: src/django_api_usage/admin.py:526
|
|
77
87
|
msgid ""
|
|
78
88
|
"Checked in this order: account, request host, client network, user agent. "
|
|
79
89
|
"Between applications, the lowest priority wins. Accounts are assigned from "
|
|
@@ -83,15 +93,15 @@ msgstr ""
|
|
|
83
93
|
"sarea eta user agent-a. Aplikazioen artean, lehentasun txikiena duenak "
|
|
84
94
|
"irabazten du. Kontuak 'Bezero-aplikazioko kontuak' taulatik esleitzen dira."
|
|
85
95
|
|
|
86
|
-
#: src/django_api_usage/admin.py:
|
|
96
|
+
#: src/django_api_usage/admin.py:550
|
|
87
97
|
msgid "Accounts"
|
|
88
98
|
msgstr "Kontuak"
|
|
89
99
|
|
|
90
|
-
#: src/django_api_usage/admin.py:
|
|
100
|
+
#: src/django_api_usage/admin.py:554
|
|
91
101
|
msgid "Counters"
|
|
92
102
|
msgstr "Kontagailuak"
|
|
93
103
|
|
|
94
|
-
#: src/django_api_usage/admin.py:
|
|
104
|
+
#: src/django_api_usage/admin.py:582 src/django_api_usage/models.py:134
|
|
95
105
|
msgid "Account"
|
|
96
106
|
msgstr "Kontua"
|
|
97
107
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-api-usage
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
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
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Tests for the Site column and filter.
|
|
2
|
+
|
|
3
|
+
Several front ends (several sites) can call the same backend, so the endpoint's
|
|
4
|
+
site is how they are told apart.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
from django.contrib.auth.models import User
|
|
10
|
+
from django.contrib.sites.models import Site
|
|
11
|
+
from django.urls import reverse
|
|
12
|
+
|
|
13
|
+
from django_api_usage.admin import reset_site_labels_cache
|
|
14
|
+
from django_api_usage.models import Endpoint, EndpointStat
|
|
15
|
+
|
|
16
|
+
from .base import UsageTestCase
|
|
17
|
+
|
|
18
|
+
STAT_CHANGELIST = "admin:django_api_usage_endpointstat_changelist"
|
|
19
|
+
ENDPOINT_CHANGELIST = "admin:django_api_usage_endpoint_changelist"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SiteColumnTest(UsageTestCase):
|
|
23
|
+
def setUp(self):
|
|
24
|
+
super().setUp()
|
|
25
|
+
# The labels are cached per process, so every test starts from scratch.
|
|
26
|
+
reset_site_labels_cache()
|
|
27
|
+
self.admin = User.objects.create_superuser("admin", "a@example.com", "pw")
|
|
28
|
+
self.client.force_login(self.admin)
|
|
29
|
+
|
|
30
|
+
self.goiena = Site.objects.create(domain="goiena.eus", name="Goiena")
|
|
31
|
+
self.arrasate = Site.objects.create(domain="arrasate.eus", name="Arrasate")
|
|
32
|
+
|
|
33
|
+
self.endpoint_a = Endpoint.objects.create(
|
|
34
|
+
site_id=self.goiena.pk,
|
|
35
|
+
app_label="api",
|
|
36
|
+
route_path="api/3.0/a/",
|
|
37
|
+
method="GET",
|
|
38
|
+
)
|
|
39
|
+
self.endpoint_b = Endpoint.objects.create(
|
|
40
|
+
site_id=self.arrasate.pk,
|
|
41
|
+
app_label="api",
|
|
42
|
+
route_path="api/3.0/b/",
|
|
43
|
+
method="GET",
|
|
44
|
+
)
|
|
45
|
+
self.stat_a = EndpointStat.objects.create(
|
|
46
|
+
endpoint=self.endpoint_a, date="2026-10-08", client_type="anon", count=1
|
|
47
|
+
)
|
|
48
|
+
self.stat_b = EndpointStat.objects.create(
|
|
49
|
+
endpoint=self.endpoint_b, date="2026-10-08", client_type="anon", count=2
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def cells(self, changelist, **params):
|
|
53
|
+
html = self.client.get(reverse(changelist), params).content.decode()
|
|
54
|
+
return re.findall(r'<td class="field-site">([^<]*)</td>', html)
|
|
55
|
+
|
|
56
|
+
def rows(self, **params):
|
|
57
|
+
response = self.client.get(reverse(STAT_CHANGELIST), params)
|
|
58
|
+
self.assertEqual(response.status_code, 200)
|
|
59
|
+
return set(response.context["cl"].queryset)
|
|
60
|
+
|
|
61
|
+
# -- column -------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
def test_the_stats_column_shows_the_domain(self):
|
|
64
|
+
self.assertCountEqual(
|
|
65
|
+
self.cells(STAT_CHANGELIST), ["goiena.eus", "arrasate.eus"]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def test_the_endpoints_column_shows_the_domain(self):
|
|
69
|
+
self.assertCountEqual(
|
|
70
|
+
self.cells(ENDPOINT_CHANGELIST), ["goiena.eus", "arrasate.eus"]
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# -- filter -------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
def test_the_stats_can_be_filtered_by_site(self):
|
|
76
|
+
self.assertEqual(self.rows(site=self.goiena.pk), {self.stat_a})
|
|
77
|
+
self.assertEqual(self.rows(site=self.arrasate.pk), {self.stat_b})
|
|
78
|
+
|
|
79
|
+
def test_the_filter_offers_every_site_by_domain(self):
|
|
80
|
+
html = self.client.get(reverse(STAT_CHANGELIST)).content.decode()
|
|
81
|
+
|
|
82
|
+
self.assertIn(f"?site={self.goiena.pk}", html)
|
|
83
|
+
self.assertIn(">goiena.eus</a>", html)
|
|
84
|
+
self.assertIn(">arrasate.eus</a>", html)
|
|
85
|
+
|
|
86
|
+
def test_the_endpoints_can_be_filtered_by_site(self):
|
|
87
|
+
response = self.client.get(
|
|
88
|
+
reverse(ENDPOINT_CHANGELIST), {"site": self.arrasate.pk}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
self.assertEqual(response.status_code, 200)
|
|
92
|
+
self.assertEqual(set(response.context["cl"].queryset), {self.endpoint_b})
|
|
Binary file
|
|
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
|
|
File without changes
|
{django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage/management/__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.8 → django_api_usage-0.1.9}/src/django_api_usage/migrations/0001_initial.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_api_usage-0.1.8 → django_api_usage-0.1.9}/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.8 → django_api_usage-0.1.9}/src/django_api_usage.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{django_api_usage-0.1.8 → django_api_usage-0.1.9}/src/django_api_usage.egg-info/requires.txt
RENAMED
|
File without changes
|
{django_api_usage-0.1.8 → django_api_usage-0.1.9}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|