add apis
Run Tests / Run Tests (push) Successful in 46s Details

This commit is contained in:
James Ravenscroft 2024-12-21 11:45:16 +00:00
parent d323754153
commit 5d337def7e
10 changed files with 174 additions and 12 deletions

View File

@ -65,6 +65,10 @@ REST_FRAMEWORK = {
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.TokenAuthentication',
]
}

View File

View File

View File

@ -0,0 +1,23 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.request import Request
from penparse.models import ImageMemo
class ImageMemoSerializer(serializers.ModelSerializer):
image = serializers.SerializerMethodField()
class Meta:
model = ImageMemo
fields = ('id', 'image', 'content', 'model_name', 'status',
'error_message', 'created_at', 'updated_at')
def get_image(self, obj):
request = self.context.get('request')
if request is not None and isinstance(request, Request):
url = reverse('document_image', args=[obj.pk])
return request.build_absolute_uri(url)
else:
# Fallback to relative URL if request is not available
return reverse('document_image', args=[obj.pk])

View File

@ -0,0 +1,14 @@
from django.urls import include, path
from rest_framework import routers
from .views import ImageMemoViewSet
router = routers.DefaultRouter()
router.register(r'memos', ImageMemoViewSet)
urlpatterns = [
path('', include(router.urls)),
]

View File

@ -0,0 +1,16 @@
from rest_framework import permissions, viewsets
from .serializers import ImageMemoSerializer
from penparse.models import ImageMemo
class ImageMemoViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = ImageMemo.objects.all()
serializer_class = ImageMemoSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return ImageMemo.objects.filter(author=self.request.user).all().order_by('-updated_at')

View File

@ -94,6 +94,12 @@ class User(AbstractUser):
"""Return string representation of our user"""
return self.email
@property
def api_key(self):
"""Return the API key (token) for the user"""
token = Token.objects.filter(user_id=self.id).first()
return token.key if token else None
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):

View File

@ -0,0 +1,88 @@
{% extends "main.html" %} {% block content %}
<section class="mb-16">
<h1 class="text-4xl font-bold text-gray-800 mb-4">Your Settings</h1>
<p class="text-xl text-gray-600 mb-8">
Manage your account settings and API key
</p>
<div class="mb-8">
<a href="{% url 'dashboard' %}" class="text-blue-500 hover:text-blue-600"
>Back to Dashboard</a
>
</div>
<div
class="bg-white p-6 rounded-lg shadow border-2 border-dotted border-gray-300 max-w-2xl mx-auto"
>
<h2 class="text-2xl font-bold text-gray-800 mb-4">API Key</h2>
<div class="mb-4">
<label for="api-key" class="block text-sm font-medium text-gray-700 mb-2"
>Your API Key:</label
>
<div class="flex">
<input
type="password"
id="api-key"
value="{{ user.api_key }}"
readonly
class="flex-grow px-3 py-2 bg-gray-100 text-gray-800 border border-gray-300 rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onclick="toggleApiKeyVisibility()"
id="toggle-visibility"
class="bg-gray-300 text-gray-700 px-4 py-2 hover:bg-gray-400 transition duration-300"
>
Show
</button>
<button
onclick="copyApiKey()"
class="bg-blue-500 text-white px-4 py-2 rounded-r-md hover:bg-blue-600 transition duration-300"
>
Copy
</button>
</div>
</div>
<form action="{% url 'regenerate_api_key' %}" method="post" class="mt-6">
{% csrf_token %}
<button
type="submit"
class="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 transition duration-300"
onclick="return confirm('Are you sure you want to regenerate your API key? This will invalidate your current key.');"
>
Regenerate API Key
</button>
</form>
</div>
</section>
<script>
function toggleApiKeyVisibility() {
const apiKeyInput = document.getElementById("api-key");
const toggleButton = document.getElementById("toggle-visibility");
if (apiKeyInput.type === "password") {
apiKeyInput.type = "text";
toggleButton.textContent = "Hide";
} else {
apiKeyInput.type = "password";
toggleButton.textContent = "Show";
}
}
function copyApiKey() {
const apiKeyInput = document.getElementById("api-key");
const currentType = apiKeyInput.type;
// Temporarily change to text to allow copying
apiKeyInput.type = "text";
apiKeyInput.select();
document.execCommand("copy");
// Restore the original input type
apiKeyInput.type = currentType;
alert("API key copied to clipboard!");
}
</script>
{% endblock %}

View File

@ -1,4 +1,4 @@
from django.urls import path
from django.urls import path, include
from . import views
@ -6,6 +6,8 @@ urlpatterns = [
path("", views.index, name="index"),
path("dashboard", views.dashboard, name="dashboard"),
path("settings", views.settings, name="settings"),
path("settings/regenerate-key", views.regenerate_api_key,
name="regenerate_api_key"),
path("documents/upload", views.upload_document, name="upload_document"),
path("documents/<str:pk>", views.view_document, name="view_document"),
path(
@ -21,6 +23,11 @@ urlpatterns = [
path(
"documents/<str:pk>/download", views.download_document, name="download_document"
),
path("documents/<str:pk>/delete", views.delete_document, name="delete_document"),
path("documents/<str:pk>/delete",
views.delete_document, name="delete_document"),
path("auth/register", views.register, name="register"),
path(
"api/v1/", include(("penparse.api.v1.urls", "api"), namespace="api")
),
]

View File

@ -1,3 +1,7 @@
from .delete import delete_document
from .upload import upload_document
from .register import register
from .thumbnail import document_thumbnail, document_image
import logging
import os
import markdown
@ -14,11 +18,6 @@ from django.contrib.auth.decorators import login_required
logger = logging.getLogger(__name__)
from .thumbnail import document_thumbnail, document_image
from .register import register
from .upload import upload_document
from .delete import delete_document
def index(request):
# return HttpResponse("Hello, world. You're at the polls index.")
@ -39,6 +38,11 @@ __all__ = [
]
@login_required
def regenerate_api_key(request):
return HttpResponse("TODO: regenerate api key")
@login_required
def dashboard(request: HttpRequest):
@ -58,18 +62,18 @@ def settings(request):
@login_required
def view_document(request: HttpRequest, pk: str):
## check that the document exists and belongs to the user
# check that the document exists and belongs to the user
# find document with given ID (pk path param) and current user id
document = ImageMemo.objects.filter(id=pk, author__id=request.user.id).first()
document = ImageMemo.objects.filter(
id=pk, author__id=request.user.id).first()
doc_markup = markdown.markdown(document.content)
if not document:
logger.debug(f"No memo found for user={request.user.id} and memo_id={pk}")
logger.debug(
f"No memo found for user={request.user.id} and memo_id={pk}")
return HttpResponse(content="Document not found", status=404)
return render(request, "document.html", context={"document": document, "markup": doc_markup})