Repository: koladev32/drf-simple-apikey (language: python)

Issue:
Add Django admin to manage API keys



Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90fe0b0..c5529f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,4 +4,5 @@ Changelog
 [Unreleased]
 ------------
 
--  Add apikey model (#9)
\ No newline at end of file
+-  Add apikey model (#9)
+-  Add Django admin to manage API keys (#11)
\ No newline at end of file
diff --git a/rest_framework_simple_api_key/admin.py b/rest_framework_simple_api_key/admin.py
new file mode 100644
index 0000000..fb027b9
--- /dev/null
+++ b/rest_framework_simple_api_key/admin.py
@@ -0,0 +1,51 @@
+import typing
+
+from django.contrib import admin, messages
+from django.http.request import HttpRequest
+
+from .models import APIKey
+
+
+class ApiKeyAdmin(admin.ModelAdmin):
+    list_display = ("entity", "revoked", "expires_at")
+
+    list_filter = ("entity", "revoked", "expires_at")
+
+    def get_readonly_fields(
+        self, request: HttpRequest, obj: APIKey = None
+    ) -> typing.Tuple[str, ...]:
+        fields = ("entity",)
+
+        if obj and obj.revoked:
+            fields += ("name", "revoked", "expires_at")
+
+        return fields
+
+    def save_model(
+        self,
+        request: HttpRequest,
+        obj: APIKey,
+        form: typing.Any = None,
+        change: bool = False,
+    ) -> None:
+        """
+        If there is obj.pk, it means that the object is been created. We need then to display the
+        `api_key` value in the Django admin dashboard.
+        """
+
+        if not obj.pk:
+            obj.save()
+
+            key = self.model.objects.assign_key(obj)
+
+            message = (
+                "The API key is: {}. ".format(key)
+                + "Please store it somewhere safe: "
+                + "you will not be able to see it again."
+            )
+            messages.add_message(request, messages.WARNING, message)
+        else:
+            obj.save()
+
+
+admin.site.register(APIKey, ApiKeyAdmin)


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index d0fd3e8..d6f55e9 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -28,6 +28,7 @@ def pytest_configure():
         MIDDLEWARE_CLASSES=MIDDLEWARE,
         INSTALLED_APPS=(
             "django.contrib.auth",
+            "django.contrib.admin",
             "django.contrib.contenttypes",
             "django.contrib.sessions",
             "django.contrib.sites",
diff --git a/tests/test_admin.py b/tests/test_admin.py
new file mode 100644
index 0000000..532903d
--- /dev/null
+++ b/tests/test_admin.py
@@ -0,0 +1,78 @@
+import pytest
+from django.contrib.admin import site
+from django.contrib.messages import get_messages
+from django.contrib.messages.middleware import MessageMiddleware
+from django.contrib.sessions.middleware import SessionMiddleware
+from django.http.request import HttpRequest
+from django.http.response import HttpResponse
+from django.test import RequestFactory
+
+from rest_framework_simple_api_key.admin import ApiKeyAdmin
+from rest_framework_simple_api_key.models import APIKey
+
+from .fixtures.user import user
+
+
+def build_admin_request(rf: RequestFactory) -> HttpRequest:
+    request = rf.post("/")
+
+    def get_response(request: HttpRequest) -> HttpResponse:
+        raise NotImplementedError  # pragma: no cover  # Unused in these tests.
+
+    # NOTE: all middleware must be instantiated before
+    # any middleware can process the request.
+    sessions = SessionMiddleware(get_response)
+    messages = MessageMiddleware(sessions.get_response)
+
+    sessions.process_request(request)
+    messages.process_request(request)
+
+    return request
+
+
+@pytest.mark.django_db
+def test_admin_readonly_fields(rf: RequestFactory, user) -> None:
+    request = build_admin_request(rf)
+
+    admin = ApiKeyAdmin(APIKey, site)
+
+    assert admin.get_readonly_fields(request) == ("entity",)
+
+    api_key = APIKey(name="test", entity=user)
+    assert admin.get_readonly_fields(request, obj=api_key) == ("entity",)
+
+    api_key = APIKey(name="test", entity=user, revoked=True)
+    assert admin.get_readonly_fields(request, obj=api_key) == (
+        "entity",
+        "name",
+        "revoked",
+        "expires_at",
+    )
+
+
+@pytest.mark.django_db
+def test_admin_create_api_key(rf: RequestFactory, user) -> None:
+    request = build_admin_request(rf)
+
+    admin = ApiKeyAdmin(APIKey, site)
+    api_key = APIKey(name="test", entity=user)
+
+    assert not api_key.pk
+    admin.save_model(request, obj=api_key)
+    assert api_key.pk
+
+    messages = get_messages(request)
+    assert len(messages) == 1
+
+
+@pytest.mark.django_db
+def test_admin_update_api_key(rf: RequestFactory, user) -> None:
+    request = build_admin_request(rf)
+
+    admin = ApiKeyAdmin(APIKey, site)
+    api_key, _ = APIKey.objects.create_key(name="test", entity=user)
+
+    api_key.name = "another-test"
+    admin.save_model(request, obj=api_key)
+    refreshed = APIKey.objects.get(pk=api_key.pk)
+    assert refreshed.name == "another-test"