diff --git a/django_ai_assistant/conf.py b/django_ai_assistant/conf.py index 24eb586..6d25e77 100644 --- a/django_ai_assistant/conf.py +++ b/django_ai_assistant/conf.py @@ -11,8 +11,10 @@ DEFAULTS = { "CAN_CREATE_THREAD_FN": "django_ai_assistant.permissions.allow_all", "CAN_VIEW_THREAD_FN": "django_ai_assistant.permissions.owns_thread", + "CAN_UPDATE_THREAD_FN": "django_ai_assistant.permissions.owns_thread", "CAN_DELETE_THREAD_FN": "django_ai_assistant.permissions.owns_thread", "CAN_CREATE_MESSAGE_FN": "django_ai_assistant.permissions.owns_thread", + "CAN_UPDATE_MESSAGE_FN": "django_ai_assistant.permissions.owns_thread", "CAN_DELETE_MESSAGE_FN": "django_ai_assistant.permissions.owns_thread", "CAN_RUN_ASSISTANT": "django_ai_assistant.permissions.allow_all", } diff --git a/django_ai_assistant/helpers/assistants.py b/django_ai_assistant/helpers/assistants.py index 93763bf..85118dd 100644 --- a/django_ai_assistant/helpers/assistants.py +++ b/django_ai_assistant/helpers/assistants.py @@ -399,6 +399,20 @@ def get_threads( return list(Thread.objects.filter(created_by=user)) +def update_thread( + thread: Thread, + name: str, + user: Any, + request: HttpRequest | None = None, +): + if not can_delete_thread(thread=thread, user=user, request=request): + raise AIUserNotAllowedError("User is not allowed to update this thread") + + thread.name = name + thread.save() + return thread + + def delete_thread( thread: Thread, user: Any, diff --git a/django_ai_assistant/permissions.py b/django_ai_assistant/permissions.py index 0720169..015330b 100644 --- a/django_ai_assistant/permissions.py +++ b/django_ai_assistant/permissions.py @@ -1,31 +1,41 @@ from typing import Any from django.http import HttpRequest -from django.views import View from django_ai_assistant.conf import app_settings from django_ai_assistant.models import Message, Thread -def _get_default_kwargs(user: Any, request: HttpRequest | None, view: View | None): - if view and not request: - request = view.request +def _get_default_kwargs(user: Any, request: HttpRequest | None): return { "user": user, "request": request, - "view": view if view else None, } def can_create_thread( user: Any, request: HttpRequest | None = None, - view: View | None = None, **kwargs, ) -> bool: return app_settings.call_fn( "CAN_CREATE_THREAD_FN", - **_get_default_kwargs(user, request, view), + **_get_default_kwargs(user, request), + **kwargs, + ) + + +def can_update_thread( + thread: Thread, + user: Any, + request: HttpRequest | None = None, + **kwargs, +) -> bool: + return app_settings.call_fn( + "CAN_UPDATE_THREAD_FN", + **_get_default_kwargs(user, request), + thread=thread, + **kwargs, ) @@ -33,13 +43,13 @@ def can_delete_thread( thread: Thread, user: Any, request: HttpRequest | None = None, - view: View | None = None, **kwargs, ) -> bool: return app_settings.call_fn( "CAN_DELETE_THREAD_FN", - **_get_default_kwargs(user, request, view), + **_get_default_kwargs(user, request), thread=thread, + **kwargs, ) @@ -47,13 +57,28 @@ def can_create_message( thread, user: Any, request: HttpRequest | None = None, - view: View | None = None, **kwargs, ) -> bool: return app_settings.call_fn( "CAN_CREATE_MESSAGE_FN", - **_get_default_kwargs(user, request, view), + **_get_default_kwargs(user, request), thread=thread, + **kwargs, + ) + + +def can_update_message( + message: Message, + user: Any, + request: HttpRequest | None = None, + **kwargs, +) -> bool: + return app_settings.call_fn( + "CAN_UPDATE_MESSAGE_FN", + **_get_default_kwargs(user, request), + message=message, + thread=message.thread, + **kwargs, ) @@ -61,14 +86,14 @@ def can_delete_message( message: Message, user: Any, request: HttpRequest | None = None, - view: View | None = None, **kwargs, ) -> bool: return app_settings.call_fn( "CAN_DELETE_MESSAGE_FN", - **_get_default_kwargs(user, request, view), + **_get_default_kwargs(user, request), message=message, thread=message.thread, + **kwargs, ) @@ -76,13 +101,13 @@ def can_run_assistant( assistant_cls, user: Any, request: HttpRequest | None = None, - view: View | None = None, **kwargs, ) -> bool: return app_settings.call_fn( "CAN_RUN_ASSISTANT", - **_get_default_kwargs(user, request, view), + **_get_default_kwargs(user, request), assistant_cls=assistant_cls, + **kwargs, ) diff --git a/django_ai_assistant/views.py b/django_ai_assistant/views.py index e9ec53b..e0e5035 100644 --- a/django_ai_assistant/views.py +++ b/django_ai_assistant/views.py @@ -60,13 +60,20 @@ def create_thread(request, payload: ThreadSchemaIn): return assistants.create_thread(name=name, user=request.user, request=request) -@api.get("threads/{thread_id}/", response=ThreadSchema, url_name="thread_detail_delete") +@api.get("threads/{thread_id}/", response=ThreadSchema, url_name="thread_detail_update_delete") def get_thread(request, thread_id: str): thread = get_single_thread(thread_id=thread_id, user=request.user, request=request) return thread -@api.delete("threads/{thread_id}/", response={204: None}, url_name="thread_detail_delete") +@api.patch("threads/{thread_id}/", response=ThreadSchema, url_name="thread_detail_update_delete") +def update_thread(request, thread_id: str, payload: ThreadSchemaIn): + thread = get_object_or_404(Thread, id=thread_id) + name = payload.name + return assistants.update_thread(thread=thread, name=name, user=request.user, request=request) + + +@api.delete("threads/{thread_id}/", response={204: None}, url_name="thread_detail_update_delete") def delete_thread(request, thread_id: str): thread = get_object_or_404(Thread, id=thread_id) assistants.delete_thread(thread=thread, user=request.user, request=request) diff --git a/example/example/settings.py b/example/example/settings.py index a36d38c..b30acb7 100644 --- a/example/example/settings.py +++ b/example/example/settings.py @@ -160,8 +160,10 @@ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") AI_ASSISTANT_CAN_CREATE_THREAD_FN = "django_ai_assistant.permissions.allow_all" AI_ASSISTANT_CAN_VIEW_THREAD_FN = "django_ai_assistant.permissions.owns_thread" +AI_ASSISTANT_CAN_UPDATE_THREAD_FN = "django_ai_assistant.permissions.owns_thread" AI_ASSISTANT_CAN_DELETE_THREAD_FN = "django_ai_assistant.permissions.owns_thread" AI_ASSISTANT_CAN_CREATE_MESSAGE_FN = "django_ai_assistant.permissions.owns_thread" +AI_ASSISTANT_CAN_UPDATE_MESSAGE_FN = "django_ai_assistant.permissions.owns_thread" AI_ASSISTANT_CAN_DELETE_MESSAGE_FN = "django_ai_assistant.permissions.owns_thread" AI_ASSISTANT_CAN_RUN_ASSISTANT = "django_ai_assistant.permissions.allow_all" diff --git a/frontend/openapi_schema.json b/frontend/openapi_schema.json index 9e5544c..fb4f982 100644 --- a/frontend/openapi_schema.json +++ b/frontend/openapi_schema.json @@ -136,6 +136,43 @@ } } }, + "patch": { + "operationId": "django_ai_assistant_views_update_thread", + "summary": "Update Thread", + "parameters": [ + { + "in": "path", + "name": "thread_id", + "schema": { + "title": "Thread Id", + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ThreadSchema" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ThreadSchemaIn" + } + } + }, + "required": true + } + }, "delete": { "operationId": "django_ai_assistant_views_delete_thread", "summary": "Delete Thread", diff --git a/frontend/src/client/services.gen.ts b/frontend/src/client/services.gen.ts index bfa032c..4efb537 100644 --- a/frontend/src/client/services.gen.ts +++ b/frontend/src/client/services.gen.ts @@ -3,7 +3,7 @@ import type { CancelablePromise } from './core/CancelablePromise'; import { OpenAPI } from './core/OpenAPI'; import { request as __request } from './core/request'; -import type { DjangoAiAssistantViewsListAssistantsResponse, DjangoAiAssistantViewsGetAssistantData, DjangoAiAssistantViewsGetAssistantResponse, DjangoAiAssistantViewsListThreadsResponse, DjangoAiAssistantViewsCreateThreadData, DjangoAiAssistantViewsCreateThreadResponse, DjangoAiAssistantViewsGetThreadData, DjangoAiAssistantViewsGetThreadResponse, DjangoAiAssistantViewsDeleteThreadData, DjangoAiAssistantViewsDeleteThreadResponse, DjangoAiAssistantViewsListThreadMessagesData, DjangoAiAssistantViewsListThreadMessagesResponse, DjangoAiAssistantViewsCreateThreadMessageData, DjangoAiAssistantViewsCreateThreadMessageResponse, DjangoAiAssistantViewsDeleteThreadMessageData, DjangoAiAssistantViewsDeleteThreadMessageResponse } from './types.gen'; +import type { DjangoAiAssistantViewsListAssistantsResponse, DjangoAiAssistantViewsGetAssistantData, DjangoAiAssistantViewsGetAssistantResponse, DjangoAiAssistantViewsListThreadsResponse, DjangoAiAssistantViewsCreateThreadData, DjangoAiAssistantViewsCreateThreadResponse, DjangoAiAssistantViewsGetThreadData, DjangoAiAssistantViewsGetThreadResponse, DjangoAiAssistantViewsUpdateThreadData, DjangoAiAssistantViewsUpdateThreadResponse, DjangoAiAssistantViewsDeleteThreadData, DjangoAiAssistantViewsDeleteThreadResponse, DjangoAiAssistantViewsListThreadMessagesData, DjangoAiAssistantViewsListThreadMessagesResponse, DjangoAiAssistantViewsCreateThreadMessageData, DjangoAiAssistantViewsCreateThreadMessageResponse, DjangoAiAssistantViewsDeleteThreadMessageData, DjangoAiAssistantViewsDeleteThreadMessageResponse } from './types.gen'; /** * List Assistants @@ -69,6 +69,24 @@ export const djangoAiAssistantViewsGetThread = (data: DjangoAiAssistantViewsGetT } }); }; +/** + * Update Thread + * @param data The data for the request. + * @param data.threadId + * @param data.requestBody + * @returns ThreadSchema OK + * @throws ApiError + */ +export const djangoAiAssistantViewsUpdateThread = (data: DjangoAiAssistantViewsUpdateThreadData): CancelablePromise => { return __request(OpenAPI, { + method: 'PATCH', + url: '/threads/{thread_id}/', + path: { + thread_id: data.threadId + }, + body: data.requestBody, + mediaType: 'application/json' +}); }; + /** * Delete Thread * @param data The data for the request. diff --git a/frontend/src/client/types.gen.ts b/frontend/src/client/types.gen.ts index 5ba83de..4766c4e 100644 --- a/frontend/src/client/types.gen.ts +++ b/frontend/src/client/types.gen.ts @@ -51,6 +51,13 @@ export type DjangoAiAssistantViewsGetThreadData = { export type DjangoAiAssistantViewsGetThreadResponse = ThreadSchema; +export type DjangoAiAssistantViewsUpdateThreadData = { + requestBody: ThreadSchemaIn; + threadId: string; +}; + +export type DjangoAiAssistantViewsUpdateThreadResponse = ThreadSchema; + export type DjangoAiAssistantViewsDeleteThreadData = { threadId: string; }; @@ -128,6 +135,15 @@ export type $OpenApiTs = { 200: ThreadSchema; }; }; + patch: { + req: DjangoAiAssistantViewsUpdateThreadData; + res: { + /** + * OK + */ + 200: ThreadSchema; + }; + }; delete: { req: DjangoAiAssistantViewsDeleteThreadData; res: { diff --git a/tests/settings.py b/tests/settings.py index 30f7741..82ddef4 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -110,7 +110,9 @@ OPENAI_API_KEY = "sk-fake-test-key-123" AI_ASSISTANT_CAN_CREATE_THREAD_FN = "django_ai_assistant.permissions.allow_all" AI_ASSISTANT_CAN_VIEW_THREAD_FN = "django_ai_assistant.permissions.owns_thread" +AI_ASSISTANT_CAN_UPDATE_THREAD_FN = "django_ai_assistant.permissions.owns_thread" AI_ASSISTANT_CAN_DELETE_THREAD_FN = "django_ai_assistant.permissions.owns_thread" AI_ASSISTANT_CAN_CREATE_MESSAGE_FN = "django_ai_assistant.permissions.owns_thread" +AI_ASSISTANT_CAN_UPDATE_MESSAGE_FN = "django_ai_assistant.permissions.owns_thread" AI_ASSISTANT_CAN_DELETE_MESSAGE_FN = "django_ai_assistant.permissions.owns_thread" AI_ASSISTANT_CAN_RUN_ASSISTANT = "django_ai_assistant.permissions.allow_all"