44from django .db .utils import IntegrityError
55from django .http import JsonResponse
66from django .core .exceptions import ValidationError as DjangoValidationError
7- from django .db import models
7+ from django .db import models , transaction
8+
89from pydantic import ValidationError
910
1011from django_email_learning .api import serializers
1112from django_email_learning .models import (
1213 Course ,
1314 CourseContent ,
1415 ImapConnection ,
15- Lesson ,
1616 OrganizationUser ,
1717 Organization ,
1818)
@@ -41,7 +41,7 @@ def post(self, request, *args, **kwargs) -> JsonResponse: # type: ignore[no-unt
4141 status = 201 ,
4242 )
4343 except ValidationError as e :
44- return JsonResponse ({"error" : e .errors ()}, status = 400 )
44+ return JsonResponse ({"error" : e .json ()}, status = 400 )
4545 except (IntegrityError , ValueError ) as e :
4646 return JsonResponse ({"error" : str (e )}, status = 409 )
4747
@@ -89,7 +89,7 @@ def post(self, request, *args, **kwargs) -> JsonResponse: # type: ignore[no-unt
8989 except Course .DoesNotExist :
9090 return JsonResponse ({"error" : "Course not found" }, status = 404 )
9191 except ValidationError as e :
92- return JsonResponse ({"error" : e .errors ()}, status = 400 )
92+ return JsonResponse ({"error" : e .json ()}, status = 400 )
9393 except DjangoValidationError as e :
9494 return JsonResponse ({"error" : e .messages }, status = 400 )
9595
@@ -111,6 +111,7 @@ def get(self, request, *args, **kwargs) -> JsonResponse: # type: ignore[no-unty
111111
112112@method_decorator (accessible_for (roles = {"admin" , "editor" , "viewer" }), name = "get" )
113113@method_decorator (accessible_for (roles = {"admin" , "editor" }), name = "delete" )
114+ @method_decorator (accessible_for (roles = {"admin" , "editor" }), name = "post" )
114115class SingleCourseContentView (View ):
115116 def get (self , request , * args , ** kwargs ) -> JsonResponse : # type: ignore[no-untyped-def]
116117 try :
@@ -124,7 +125,7 @@ def get(self, request, *args, **kwargs) -> JsonResponse: # type: ignore[no-unty
124125 except CourseContent .DoesNotExist :
125126 return JsonResponse ({"error" : "Course content not found" }, status = 404 )
126127 except ValidationError as e :
127- return JsonResponse ({"error" : e .errors ()}, status = 400 )
128+ return JsonResponse ({"error" : e .json ()}, status = 400 )
128129
129130 def delete (self , request , * args , ** kwargs ): # type: ignore[no-untyped-def]
130131 try :
@@ -136,11 +137,87 @@ def delete(self, request, *args, **kwargs): # type: ignore[no-untyped-def]
136137 except CourseContent .DoesNotExist :
137138 return JsonResponse ({"error" : "Course content not found" }, status = 404 )
138139 except ValidationError as e :
139- return JsonResponse ({"error" : e .errors ()}, status = 400 )
140+ return JsonResponse ({"error" : e .json ()}, status = 400 )
140141 except (IntegrityError , ValueError ) as e :
141142 return JsonResponse ({"error" : str (e )}, status = 409 )
142143
143- # TODO: Implement POST method for updating course content.
144+ def post (self , request , * args , ** kwargs ) -> JsonResponse : # type: ignore[no-untyped-def]
145+ payload = json .loads (request .body )
146+ try :
147+ serializer = serializers .UpdateCourseContentRequest .model_validate (payload )
148+ except ValidationError as e :
149+ return JsonResponse ({"error" : e .json ()}, status = 400 )
150+ except ValueError as e :
151+ return JsonResponse ({"error" : str (e )}, status = 400 )
152+
153+ try :
154+ return self ._update_course_content_atomic (
155+ serializer , kwargs ["course_content_id" ]
156+ )
157+ except CourseContent .DoesNotExist :
158+ return JsonResponse ({"error" : "Course content not found" }, status = 404 )
159+ except ValidationError as e :
160+ return JsonResponse ({"error" : e .json ()}, status = 400 )
161+ except (IntegrityError , ValueError ) as e :
162+ return JsonResponse ({"error" : str (e )}, status = 409 )
163+
164+ @transaction .atomic
165+ def _update_course_content_atomic (
166+ self , serializer : serializers .UpdateCourseContentRequest , course_content_id : int
167+ ) -> JsonResponse :
168+ course_content = CourseContent .objects .get (id = course_content_id )
169+
170+ if serializer .priority is not None :
171+ course_content .priority = serializer .priority
172+ if serializer .waiting_period is not None :
173+ course_content .waiting_period = serializer .waiting_period .to_seconds ()
174+
175+ if serializer .is_published is not None :
176+ if course_content .type == "lesson" and course_content .lesson is not None :
177+ lesson = course_content .lesson
178+ lesson .is_published = serializer .is_published
179+ lesson .save ()
180+ elif course_content .type == "quiz" and course_content .quiz is not None :
181+ quiz = course_content .quiz
182+ quiz .is_published = serializer .is_published
183+ quiz .save ()
184+
185+ if serializer .lesson is not None and course_content .lesson is not None :
186+ lesson_serializer = serializer .lesson
187+ lesson = course_content .lesson
188+ if lesson_serializer .title is not None :
189+ lesson .title = lesson_serializer .title
190+ if lesson_serializer .content is not None :
191+ lesson .content = lesson_serializer .content
192+ lesson .save ()
193+
194+ if serializer .quiz is not None and course_content .quiz is not None :
195+ quiz_serializer = serializer .quiz
196+ quiz = course_content .quiz
197+ if quiz_serializer .title is not None :
198+ quiz .title = quiz_serializer .title
199+ if quiz_serializer .required_score is not None :
200+ quiz .required_score = quiz_serializer .required_score
201+ if quiz_serializer .questions is not None :
202+ # Clear existing questions and answers
203+ quiz .questions .all ().delete ()
204+ for question_data in quiz_serializer .questions :
205+ question = quiz .questions .create (
206+ text = question_data .text , priority = question_data .priority
207+ )
208+ for answer_data in question_data .answers :
209+ question .answers .create (
210+ text = answer_data .text , is_correct = answer_data .is_correct
211+ )
212+ quiz .save ()
213+
214+ course_content .save ()
215+ return JsonResponse (
216+ serializers .CourseContentResponse .model_validate (
217+ course_content
218+ ).model_dump (),
219+ status = 200 ,
220+ )
144221
145222
146223@method_decorator (accessible_for (roles = {"admin" , "editor" }), name = "post" )
@@ -157,7 +234,7 @@ def get(self, request, *args, **kwargs) -> JsonResponse: # type: ignore[no-unty
157234 except Course .DoesNotExist :
158235 return JsonResponse ({"error" : "Course not found" }, status = 404 )
159236 except ValidationError as e :
160- return JsonResponse ({"error" : e .errors ()}, status = 400 )
237+ return JsonResponse ({"error" : e .json ()}, status = 400 )
161238 except (IntegrityError , ValueError ) as e :
162239 return JsonResponse ({"error" : str (e )}, status = 409 )
163240
@@ -172,7 +249,7 @@ def post(self, request, *args, **kwargs) -> JsonResponse: # type: ignore[no-unt
172249 status = 200 ,
173250 )
174251 except ValidationError as e :
175- return JsonResponse ({"error" : e .errors ()}, status = 400 )
252+ return JsonResponse ({"error" : e .json ()}, status = 400 )
176253 except (IntegrityError , ValueError ) as e :
177254 return JsonResponse ({"error" : str (e )}, status = 409 )
178255
@@ -184,7 +261,7 @@ def delete(self, request, *args, **kwargs): # type: ignore[no-untyped-def]
184261 except Course .DoesNotExist :
185262 return JsonResponse ({"error" : "Course not found" }, status = 404 )
186263 except ValidationError as e :
187- return JsonResponse ({"error" : e .errors ()}, status = 400 )
264+ return JsonResponse ({"error" : e .json ()}, status = 400 )
188265 except (IntegrityError , ValueError ) as e :
189266 return JsonResponse ({"error" : str (e )}, status = 409 )
190267
@@ -220,7 +297,7 @@ def post(self, request, *args, **kwargs) -> JsonResponse: # type: ignore[no-unt
220297 status = 201 ,
221298 )
222299 except ValidationError as e :
223- return JsonResponse ({"error" : e .errors ()}, status = 400 )
300+ return JsonResponse ({"error" : e .json ()}, status = 400 )
224301 except IntegrityError as e :
225302 return JsonResponse ({"error" : str (e )}, status = 409 )
226303
@@ -262,34 +339,11 @@ def post(self, request, *args, **kwargs) -> JsonResponse: # type: ignore[no-unt
262339 status = 201 ,
263340 )
264341 except ValidationError as e :
265- return JsonResponse ({"error" : e .errors ()}, status = 400 )
342+ return JsonResponse ({"error" : e .json ()}, status = 400 )
266343 except IntegrityError as e :
267344 return JsonResponse ({"error" : str (e )}, status = 409 )
268345
269346
270- @method_decorator (accessible_for (roles = {"admin" , "editor" }), name = "post" )
271- class LessonView (View ):
272- def post (self , request , * args , ** kwargs ) -> JsonResponse : # type: ignore[no-untyped-def]
273- payload = json .loads (request .body )
274- try :
275- serializer = serializers .LessonUpdate .model_validate (payload )
276- lesson = Lesson .objects .get (id = kwargs ["lesson_id" ])
277- if serializer .title is not None :
278- lesson .title = serializer .title
279- if serializer .content is not None :
280- lesson .content = serializer .content
281- lesson .save ()
282-
283- return JsonResponse (
284- {},
285- status = 204 ,
286- )
287- except Lesson .DoesNotExist :
288- return JsonResponse ({"error" : "Lesson not found" }, status = 404 )
289- except ValidationError as e :
290- return JsonResponse ({"error" : e .errors ()}, status = 400 )
291-
292-
293347@method_decorator (is_an_organization_member (), name = "post" )
294348class UpdateSessionView (View ):
295349 def post (self , request , * args , ** kwargs ) -> JsonResponse : # type: ignore[no-untyped-def]
@@ -298,7 +352,7 @@ def post(self, request, *args, **kwargs) -> JsonResponse: # type: ignore[no-unt
298352 serializer = serializers .UpdateSessionRequest .model_validate (payload )
299353 organization_id = serializer .active_organization_id
300354 except ValidationError as e :
301- return JsonResponse ({"error" : e .errors ()}, status = 400 )
355+ return JsonResponse ({"error" : e .json ()}, status = 400 )
302356
303357 if (
304358 not OrganizationUser .objects .filter (
0 commit comments