diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..7918c7a --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13.1 \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index a7953a0..a6adea5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,9 @@ { "python.analysis.extraPaths": [ "./thoughtswap" - ] + ], + "cSpell.words": [ + "thoughtswap" + ], + "isort.check": true } \ No newline at end of file diff --git a/README.md b/README.md index 6799087..b7561b9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,112 @@ # ThoughtSwap +## Project Overview + +Thought Swap is a discussion facilitation app where a facilitator sends prompts to participants, who submit anonymous responses that are later redistributed for small-group discussion. + +**Github repository:** https://github.com/Lab-Lab-Lab/ThoughtSwap + +**Key technologies:** +- Django (backend) +- Django Channels (WebSocket communication) +- HTML, JavaScript (frontend templates) + +**Core features:** +- Prompt creation, management, and reuse +- Anonymous response submission +- Response “swap” feature for small-group discussion +- Facilitator dashboard with course and session controls + +## What Has Been Accomplished + +- Set up Django backend and database models + - Course, Enrollment, Prompt, Session, PromptUse, Thought +- Integrated WebSocket support (channels) for real-time updates to both the facilitator and participant +- Facilitators can switch their course from three states: Active, Draft, and Inactive +- Facilitators can create prompts ahead of time +- Facilitators can send prompts to participants to respond to +- Facilitators can disperse responses using the “swap” feature +- Participants can join a new course via a room code +- Participants can view the current prompt +- Participants can respond to the current prompt +- Participants can receive a thought to discuss + +## Key Decisions Made (with Explanations) + +- **What is PromptUse** + The `PromptUse` model acts as a connection between a prompt and a session. It allows the same prompt to be reused across multiple sessions while keeping track of responses specific to each session. This design improves flexibility by avoiding duplicate prompt entries and enables better tracking of when and where prompts were used. + +- **Why there are different HTML templates** + I separated the HTML templates into `facilitator_session.html`, `participant_session.html`, and `teacher_dashboard.html` to clearly divide the interfaces by user role. Each role has very different needs: + +- **Intended functionality of restrictions around swapping** + When swapping responses, the system should ensure that: + - Each participant receives a response from someone else (not their own) + - There must be at least two active student who have responded + - If there are more students than responses, responses are randomly duplicated + + +## Known Issues or Bugs + +- Swapping thoughts needs to be more robust +- Late joiners need to be fully addressed +- Safety and robustness of user contributions has not been fully tested + +## Next Steps + +- Front-end styling + UX +- Facilitator's ability to create a new course +- Facilitator's ability to create a new session +- “Demo mode,” where the facilitator and/or participants need not create a lasting account +- Functionality for late joiners +- When swapping: if one author is more prolific for a certain prompt, before assigning all their thoughts to others, first ensure that each author's thoughts are assigned (and specifically to authors (users who have submitted for this prompt)) +- Somehow allow for the participants indicate their thoughts about the distributed thought +- Start having semantic analysis +- Find some way to track how the discussion went (Form for after the discussion?) +- Participant view for the facilitator +- Offer rich text editor in prompt composition and thought composition + +## Important Files / Code to Know + +- `facilitator_session.html` + This is the main interface for facilitators. It allows them to: + - Write and send prompts + - Swap anonymous participant responses + - Access the prompt bank + - View active and past prompts + It also includes WebSocket logic to handle live updates from the server. + +- `participant_session.html` + This is the participant view during a session. Participants can: + - See the current prompt + - Submit their responses + - Receive a swapped response to discuss + It connects to the WebSocket server to listen for prompt updates and swapped thoughts. + +- `teacher_dashboard.html` + This is the dashboard where teachers manage their courses and sessions. + - Shows all enrolled courses + - Lets the teacher switch session states (Active, Inactive, Draft) + - Displays and manages the prompt bank, including adding new prompts + +- `models.py` + Contains all the Django data models, including: + - Course, Enrollment, Prompt, Session, PromptUse, Thought + +- `views.py` + Contains the Django views that: + - Handle requests and render the pages + - Manage course state changes + - Provide data to the templates + +- `consumers.py` + Contains the Django Channels consumers that: + - Handle WebSocket connections + - Receive and send real-time events like new prompts, new thoughts, swaps, and prompt bank data + + +# ThoughtSwap as an instance of Cookiecutter Django + A project, [![Built with Cookiecutter Django](https://img.shields.io/badge/built%20with-Cookiecutter%20Django-ff69b4.svg?logo=cookiecutter)](https://github.com/cookiecutter/cookiecutter-django/) diff --git a/config/asgi.py b/config/asgi.py index 289636c..14d2c7e 100644 --- a/config/asgi.py +++ b/config/asgi.py @@ -1,6 +1,7 @@ import os from pathlib import Path -from django.core.asgi import get_asgi_application # num 1 of 4ish that have to +from django.core.asgi import get_asgi_application # num 1 of 4ish that have to + # be in the correct order https://forum.djangoproject.com/t/i-get-the-error-apps-arent-loaded-yet-when-publishing-with-daphne/30320/14 from channels.routing import ProtocolTypeRouter, URLRouter from channels.security.websocket import AllowedHostsOriginValidator @@ -21,13 +22,14 @@ sett = env("DJANGO_SETTINGS_MODULE") print("DJANGO_SETTINGS_MODULE before setting in asgi", sett) # os.environ.setdefault("DJANGO_SETTINGS_MODULE", "thoughtswap.settings") -os.environ.setdefault("DJANGO_SETTINGS_MODULE", sett) # num2 - -django_asgi_app = get_asgi_application() # num 3 +os.environ.setdefault("DJANGO_SETTINGS_MODULE", sett) # num2 -from thoughtswap.thoughtswap.routing import websocket_urlpatterns # 4a -from thoughtswap.chat.routing import websocket_urlpatterns as thoughtswap_chat_routing #4b +django_asgi_app = get_asgi_application() # num 3 +from thoughtswap.thoughtswap.routing import websocket_urlpatterns # 4a +from thoughtswap.chat.routing import ( + websocket_urlpatterns as thoughtswap_chat_routing, +) # 4b application = ProtocolTypeRouter( diff --git a/config/settings/base.py b/config/settings/base.py index 083eb3d..9e8849a 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -1,7 +1,6 @@ # ruff: noqa: ERA001, E501 """Base settings to build other settings files upon.""" - from pathlib import Path import environ diff --git a/config/settings/test.py b/config/settings/test.py index 4082285..3ce3cb6 100644 --- a/config/settings/test.py +++ b/config/settings/test.py @@ -5,6 +5,7 @@ from .local import * # noqa: F403 from .local import TEMPLATES from .local import env + # GENERAL # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key diff --git a/config/urls.py b/config/urls.py index b45ba3e..2bca445 100644 --- a/config/urls.py +++ b/config/urls.py @@ -29,9 +29,8 @@ # ... # Media files *static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT), - path('prompt-bank/', views.prompt_bank_view, name='prompt_bank'), - path('add-prompt-to-bank/', views.add_prompt_to_bank, name='add_prompt_to_bank'), - + path("prompt-bank/", views.prompt_bank_view, name="prompt_bank"), + path("add-prompt-to-bank/", views.add_prompt_to_bank, name="add_prompt_to_bank"), ] diff --git a/docs/thoughtswap.md b/docs/thoughtswap.md deleted file mode 100644 index 6d26866..0000000 --- a/docs/thoughtswap.md +++ /dev/null @@ -1,106 +0,0 @@ -# ThoughtSwap - -## Project Overview - -Thought Swap is a discussion facilitation app where a facilitator sends prompts to participants, who submit anonymous responses that are later redistributed for small-group discussion. - -**Github repository:** https://github.com/Lab-Lab-Lab/ThoughtSwap - -**Key technologies:** -- Django (backend) -- Django Channels (WebSocket communication) -- HTML, JavaScript (frontend templates) - -**Core features:** -- Prompt creation, management, and reuse -- Anonymous response submission -- Response “swap” feature for small-group discussion -- Facilitator dashboard with course and session controls - -## What Has Been Accomplished - -- Set up Django backend and database models - - Course, Enrollment, Prompt, Session, PromptUse, Thought -- Integrated WebSocket support (channels) for real-time updates to both the facilitator and participant -- Facilitators can switch their course from three states: Active, Draft, and Inactive -- Facilitators can create prompts ahead of time -- Facilitators can send prompts to participants to respond to -- Facilitators can disperse responses using the “swap” feature -- Participants can join a new course via a room code -- Participants can view the current prompt -- Participants can respond to the current prompt -- Participants can receive a thought to discuss - -## Key Decisions Made (with Explanations) - -- **What is PromptUse** - The `PromptUse` model acts as a connection between a prompt and a session. It allows the same prompt to be reused across multiple sessions while keeping track of responses specific to each session. This design improves flexibility by avoiding duplicate prompt entries and enables better tracking of when and where prompts were used. - -- **Why there are different HTML templates** - I separated the HTML templates into `facilitator_session.html`, `participant_session.html`, and `teacher_dashboard.html` to clearly divide the interfaces by user role. Each role has very different needs: - -- **Intended functionality of restrictions around swapping** - When swapping responses, the system should ensure that: - - Each participant receives a response from someone else (not their own) - - There must be at least two active student who have responded - - If there are more students than responses, responses are randomly duplicated - - -## Known Issues or Bugs - -- Swapping thoughts needs to be more robust -- Late joiners need to be fully addressed -- Safety and robustness of user contributions has not been fully tested - -## Next Steps - -- Front-end styling + UX -- Facilitator's ability to create a new course -- Facilitator's ability to create a new session -- “Demo mode,” where the facilitator and/or participants need not create a lasting account -- Functionality for late joiners -- When swapping: if one author is more prolific for a certain prompt, before assigning all their thoughts to others, first ensure that each author's thoughts are assigned (and specifically to authors (users who have submitted for this prompt)) -- Somehow allow for the participants indicate their thoughts about the distributed thought -- Start having semantic analysis -- Find some way to track how the discussion went (Form for after the discussion?) -- Participant view for the facilitator -- Offer rich text editor in prompt composition and thought composition - -## Important Files / Code to Know - -- `facilitator_session.html` - This is the main interface for facilitators. It allows them to: - - Write and send prompts - - Swap anonymous participant responses - - Access the prompt bank - - View active and past prompts - It also includes WebSocket logic to handle live updates from the server. - -- `participant_session.html` - This is the participant view during a session. Participants can: - - See the current prompt - - Submit their responses - - Receive a swapped response to discuss - It connects to the WebSocket server to listen for prompt updates and swapped thoughts. - -- `teacher_dashboard.html` - This is the dashboard where teachers manage their courses and sessions. - - Shows all enrolled courses - - Lets the teacher switch session states (Active, Inactive, Draft) - - Displays and manages the prompt bank, including adding new prompts - -- `models.py` - Contains all the Django data models, including: - - Course, Enrollment, Prompt, Session, PromptUse, Thought - -- `views.py` - Contains the Django views that: - - Handle requests and render the pages - - Manage course state changes - - Provide data to the templates - -- `consumers.py` - Contains the Django Channels consumers that: - - Handle WebSocket connections - - Receive and send real-time events like new prompts, new thoughts, swaps, and prompt bank data - diff --git a/thoughtswap/chat/apps.py b/thoughtswap/chat/apps.py index 2fe899a..5f75238 100644 --- a/thoughtswap/chat/apps.py +++ b/thoughtswap/chat/apps.py @@ -2,5 +2,5 @@ class ChatConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'chat' + default_auto_field = "django.db.models.BigAutoField" + name = "chat" diff --git a/thoughtswap/chat/views.py b/thoughtswap/chat/views.py index 0a46255..5b4ba57 100644 --- a/thoughtswap/chat/views.py +++ b/thoughtswap/chat/views.py @@ -9,4 +9,3 @@ def index(request): def room(request, room_name): return render(request, "chat/room.html", {"room_name": room_name}) - \ No newline at end of file diff --git a/thoughtswap/thoughtswap/consumers.py b/thoughtswap/thoughtswap/consumers.py index 6560c60..654c39d 100644 --- a/thoughtswap/thoughtswap/consumers.py +++ b/thoughtswap/thoughtswap/consumers.py @@ -22,31 +22,31 @@ async def connect(self): await self.channel_layer.group_add(self.room_group_name, self.channel_name) await self.channel_layer.group_add( - f"user_{self.user.id}", - self.channel_name + f"user_{self.user.id}", self.channel_name ) await self.accept() if self.session.is_swapping: - await self.send(text_data=json.dumps({ - "type": "session_phase", - "phase": "swapping" - })) + await self.send( + text_data=json.dumps({"type": "session_phase", "phase": "swapping"}) + ) active_prompt_use = await self.get_active_prompt_use(self.session) if active_prompt_use: prompt_content = await self.get_prompt_content(active_prompt_use) - await self.send(text_data=json.dumps({ - "type": "new_prompt", - "content": prompt_content, - "prompt_id": active_prompt_use.id, - })) + await self.send( + text_data=json.dumps( + { + "type": "new_prompt", + "content": prompt_content, + "prompt_id": active_prompt_use.id, + } + ) + ) else: await self.close() - - @database_sync_to_async def is_facilitator(self): return self.course.creator == self.user @@ -61,7 +61,6 @@ async def disconnect(self, close_code): async def receive(self, text_data): data = json.loads(text_data) msg_type = data.get("type") - if msg_type == "disperse_prompt" and await self.is_facilitator(): content = data.get("content") @@ -75,17 +74,20 @@ async def receive(self, text_data): "type": "broadcast_prompt", "content": prompt_content, "prompt_id": prompt_use.id, - } + }, ) elif msg_type == "submit_thought": content = data.get("content") thought = await self.store_thought(content) - print("About to send via WebSocket:", { - "type": "new_thought", - "content": thought.content, - "prompt_id": thought.prompt_use.id, - }) + print( + "About to send via WebSocket:", + { + "type": "new_thought", + "content": thought.content, + "prompt_id": thought.prompt_use.id, + }, + ) if thought: await self.channel_layer.group_send( self.room_group_name, @@ -95,7 +97,7 @@ async def receive(self, text_data): "prompt_id": thought.prompt_use_id, }, ) - + elif msg_type == "swap_responses": print("Swapping responses!!!!") @@ -103,11 +105,7 @@ async def receive(self, text_data): await database_sync_to_async(self.session.save)() await self.channel_layer.group_send( - self.room_group_name, - { - "type": "session_phase", - "phase": "swapping" - } + self.room_group_name, {"type": "session_phase", "phase": "swapping"} ) await self.swap_responses() @@ -115,13 +113,14 @@ async def receive(self, text_data): elif msg_type == "prompt_bank": print("Getting prompt bank data\n\n\n") prompts = await database_sync_to_async(list)( - Prompt.objects.filter(author=self.user, in_bank=True).values("id", "content") + Prompt.objects.filter(author=self.user, in_bank=True).values( + "id", "content" + ) ) print("Prompts:", prompts) - await self.send(text_data=json.dumps({ - "type": "prompt_bank_data", - "prompts": prompts - })) + await self.send( + text_data=json.dumps({"type": "prompt_bank_data", "prompts": prompts}) + ) elif msg_type == "send_bank_prompt": prompt_id = data.get("prompt_id") @@ -134,11 +133,9 @@ async def receive(self, text_data): "type": "new_prompt", "content": prompt.content, "prompt_id": prompt_use.id, - } + }, ) - - async def broadcast_prompt(self, event): await self.send( text_data=json.dumps({"type": "new_prompt", "content": event["content"]}) @@ -156,7 +153,6 @@ async def new_thought(self, event): ) ) - @database_sync_to_async def get_course_by_code(self, code): try: @@ -188,8 +184,9 @@ def create_prompt(self, content): @database_sync_to_async def create_prompt_use(self, prompt): PromptUse.objects.filter(session=self.session).update(is_active=False) - return PromptUse.objects.create(prompt=prompt, session=self.session, is_active=True) - + return PromptUse.objects.create( + prompt=prompt, session=self.session, is_active=True + ) @database_sync_to_async def get_thoughts(self, prompt_use): @@ -198,7 +195,9 @@ def get_thoughts(self, prompt_use): @database_sync_to_async def get_all_participant_users(self, course): print("Getting all participant users") - enrollments = Enrollment.objects.filter(course_id=course.id, role='s').select_related("user") + enrollments = Enrollment.objects.filter( + course_id=course.id, role="s" + ).select_related("user") print("Enrollments:", enrollments) return [enrollment.user for enrollment in enrollments] @@ -211,20 +210,16 @@ def store_thought(self, content): ) if active: thought = Thought.objects.create( - prompt_use=active, - author=self.user, - content=content + prompt_use=active, author=self.user, content=content ) return Thought.objects.select_related("prompt_use").get(id=thought.id) return None - - async def swap_responses(self): print("Swapping responses inside function") user = self.scope["user"] - course = self.course - # pass in session already being used + course = self.course + # pass in session already being used session = await self.get_active_session(course) print("Session:", session) print("Course:", course.id) @@ -238,19 +233,19 @@ async def swap_responses(self): thoughts = await self.get_thoughts(latest_prompt_use) if len(thoughts) < 2: - return # TODO: maybe return an error message object? maybe like the channels/js version of https://docs.djangoproject.com/en/5.1/ref/contrib/messages/ + return # TODO: maybe return an error message object? maybe like the channels/js version of https://docs.djangoproject.com/en/5.1/ref/contrib/messages/ student_ids_who_authored = list(set(t.author_id for t in thoughts)) print("Student IDs:", student_ids_who_authored) # All students may include students who are not in the current session (great for testing actually) # What happens if someone joins late? I know we want them to get something to discuss but how? - # - if there were more sresponses than students, they shoud get one that hasnt been seen before + # - if there were more sresponses than students, they shoud get one that hasnt been seen before # - if there are more students than responses, they should get a random one all_students = await self.get_all_participant_users(course) print("All students:", all_students) if len(student_ids_who_authored) < 2 or len(all_students) < 2: - print("Not enough students to swap responses.") + print("Not enough students to swap responses.") return responses = [t for t in thoughts] @@ -258,7 +253,6 @@ async def swap_responses(self): distribution_pool = responses[:] print("Distribution pool:", distribution_pool) - # make sure its not 2+ responses from the same student # duplicate enough thoughts to have 1 available for every enrolled student while len(distribution_pool) < len(all_students): @@ -279,44 +273,39 @@ async def swap_responses(self): assigned = random.choice(distribution_pool) student_response_map[student.id] = assigned distribution_pool.remove(assigned) - + print("Student response map:", student_response_map) for student_id, response in student_response_map.items(): print(f"Sending to user_{student_id}: {response.content}") await self.channel_layer.group_send( f"user_{student_id}", - { - "type": "distribute_thought", - "content": response.content - } + {"type": "distribute_thought", "content": response.content}, ) async def distribute_thought(self, event): print("Inside distribute_thought, sending to client:", event["content"]) - await self.send(text_data=json.dumps({ - "type": "received_thought", - "content": event["content"] - })) + await self.send( + text_data=json.dumps( + {"type": "received_thought", "content": event["content"]} + ) + ) async def session_phase(self, event): - await self.send(text_data=json.dumps({ - "type": "session_phase", - "phase": event["phase"] - })) - + await self.send( + text_data=json.dumps({"type": "session_phase", "phase": event["phase"]}) + ) async def new_prompt(self, event): content = event["content"] prompt_id = event["prompt_id"] - await self.send(text_data=json.dumps({ - "type": "new_prompt", - "content": content, - "prompt_id": prompt_id, - })) - - - - - + await self.send( + text_data=json.dumps( + { + "type": "new_prompt", + "content": content, + "prompt_id": prompt_id, + } + ) + ) diff --git a/thoughtswap/thoughtswap/models.py b/thoughtswap/thoughtswap/models.py index f79b4b9..29400d1 100644 --- a/thoughtswap/thoughtswap/models.py +++ b/thoughtswap/thoughtswap/models.py @@ -89,6 +89,7 @@ class Session(models.Model): def __str__(self): return f"{self.state}" + # 2 use cases: # 1. prof wants to use this prompt in class tmr, so make it (the Prompt) today so they can just click it in realtime tmr (which would then instantiate the PromptUse). to be fully motivating we actually have to imagine they have 2 prompts in mind for tmr # 2. prof wants to use the prompt at the beginning of sem and then again at the end diff --git a/thoughtswap/thoughtswap/views.py b/thoughtswap/thoughtswap/views.py index 6d3ddd2..2c16d15 100644 --- a/thoughtswap/thoughtswap/views.py +++ b/thoughtswap/thoughtswap/views.py @@ -1,11 +1,14 @@ -from django.shortcuts import render, redirect, get_object_or_404 -from thoughtswap.users.models import User -from .models import Enrollment, Prompt, Course, Session +import json + from django.contrib.auth.decorators import login_required -from django.utils.timezone import now from django.http import JsonResponse +from django.shortcuts import get_object_or_404, redirect, render +from django.utils.timezone import now from django.views.decorators.csrf import csrf_exempt -import json + +from thoughtswap.users.models import User + +from .models import Course, Enrollment, Prompt, Session def prompt_list(request): @@ -25,7 +28,7 @@ def teacher_dashboard(request): get_latest_session(courses) sessions = Session.objects.filter(course__creator=facilitator).select_related( - "course" + "course", ) course_id = request.GET.get("course_id") @@ -35,7 +38,7 @@ def teacher_dashboard(request): if course_id: selected_course = get_object_or_404(Course, id=course_id, creator=facilitator) students = User.objects.filter( - enrollment__course=selected_course, enrollment__role="s" + enrollment__course=selected_course, enrollment__role="s", ) if request.method == "POST": @@ -97,42 +100,47 @@ def student_dashboard(request): {"enrollments": enrollments, "active_sessions": active_sessions}, ) + @login_required def thoughtswap_room(request, join_code): course = get_object_or_404(Course, join_code=join_code) session = course.sessions.filter(state="a").first() if not session: - return render(request, "thoughtswap/facilitator_session.html", { - "course": course, - "error": "No active session." - }) + return render( + request, + "thoughtswap/facilitator_session.html", + {"course": course, "error": "No active session."}, + ) if request.user == course.creator: prompt_data = [] - for pu in session.promptuse_set.select_related('prompt').prefetch_related('thought_set').order_by('-created_at'): - prompt_data.append({ - "prompt": pu.prompt.content, - "prompt_use_id": pu.id, - "thoughts": [t.content for t in pu.thought_set.all()] - }) + for pu in ( + session.promptuse_set.select_related("prompt") + .prefetch_related("thought_set") + .order_by("-created_at") + ): + prompt_data.append( + { + "prompt": pu.prompt.content, + "prompt_use_id": pu.id, + "thoughts": [t.content for t in pu.thought_set.all()], + }, + ) prompts = {} - for group in prompt_data: - prompts[group["prompt_use_id"]]={ - "content": group["prompt"], #FIXME: this is possible injection if we don't sanitize (eg with escapejs) - "responses": group["thoughts"] + for group in prompt_data: + prompts[group["prompt_use_id"]] = { + "content": group[ + "prompt" + ], # FIXME: this is possible injection if we don't sanitize (eg with escapejs) + "responses": group["thoughts"], } - return render( request, "thoughtswap/facilitator_session.html", - { - "course": course, - "session": session, - "prompts": prompts - }, + {"course": course, "session": session, "prompts": prompts}, ) else: return render( @@ -143,25 +151,22 @@ def thoughtswap_room(request, join_code): "session": session, }, ) - + def prompt_bank_view(request): - prompts = Prompt.objects.filter(author=request.user, in_bank=True).order_by('-id') - prompt_data = list(prompts.values('id', 'content')) - return JsonResponse({'prompts': prompt_data}) + prompts = Prompt.objects.filter(author=request.user, in_bank=True).order_by("-id") + prompt_data = list(prompts.values("id", "content")) + return JsonResponse({"prompts": prompt_data}) @csrf_exempt def add_prompt_to_bank(request): if request.method == "POST": body = json.loads(request.body) - content = body.get('content') + content = body.get("content") prompt = Prompt.objects.create( - author=request.user, - content=content, - in_bank=True + author=request.user, content=content, in_bank=True, ) - return JsonResponse({'status': 'ok', 'id': prompt.id}) - return JsonResponse({'error': 'invalid method'}, status=400) - + return JsonResponse({"status": "ok", "id": prompt.id}) + return JsonResponse({"error": "invalid method"}, status=400) diff --git a/thoughtswap/users/models.py b/thoughtswap/users/models.py index c3a19cc..9cc0f2a 100644 --- a/thoughtswap/users/models.py +++ b/thoughtswap/users/models.py @@ -35,4 +35,3 @@ def get_absolute_url(self) -> str: def is_facilitator(self) -> bool: return self.groups.filter(name="Facilitator").count() > 0 - diff --git a/thoughtswap/users/views.py b/thoughtswap/users/views.py index 9579f23..e4afa27 100644 --- a/thoughtswap/users/views.py +++ b/thoughtswap/users/views.py @@ -28,7 +28,7 @@ def get_success_url(self) -> str: assert self.request.user.is_authenticated # type guard return self.request.user.get_absolute_url() - def get_object(self, queryset: QuerySet | None=None) -> User: + def get_object(self, queryset: QuerySet | None = None) -> User: assert self.request.user.is_authenticated # type guard return self.request.user