@@ -46,6 +46,9 @@ def get(self, request):
4646
4747
4848class AchievementCreateAPIView (APIView ):
49+ from rest_framework .parsers import MultiPartParser , FormParser
50+ parser_classes = [MultiPartParser , FormParser ]
51+
4952 def post (self , request ):
5053 user_id = JWTUtils .fetch_user_id (request )
5154
@@ -61,14 +64,31 @@ def post(self, request):
6164 ).get_failure_response ()
6265
6366 data = request .data
64- required_fields = ["name" , "description" , "icon" , "tags" , "type" , "has_vc" ]
65-
67+ # Icon can be either a file upload or a text URL
68+ icon_file = request .FILES .get ("icon" )
69+ icon_url = data .get ("icon" , "" ) if not icon_file else ""
70+
71+ required_fields = ["name" , "description" , "tags" , "type" , "has_vc" ]
6672 missing_fields = [field for field in required_fields if field not in data ]
6773 if missing_fields :
6874 return CustomResponse (
6975 general_message = f"Missing required fields: { ', ' .join (missing_fields )} "
7076 ).get_failure_response ()
7177
78+ # Parse has_vc from string to boolean (FormData sends strings)
79+ has_vc_value = data .get ("has_vc" )
80+ if isinstance (has_vc_value , str ):
81+ has_vc_value = has_vc_value .lower () in ("true" , "1" , "yes" )
82+
83+ # Parse tags from JSON string to list (FormData sends strings)
84+ tags_value = data .get ("tags" , [])
85+ if isinstance (tags_value , str ):
86+ import json
87+ try :
88+ tags_value = json .loads (tags_value )
89+ except json .JSONDecodeError :
90+ tags_value = []
91+
7292 if Achievement .objects .filter (name = data ["name" ]).exists ():
7393 return CustomResponse (
7494 general_message = "Name already exists"
@@ -83,15 +103,51 @@ def post(self, request):
83103 general_message = "Invalid level_id"
84104 ).get_failure_response ()
85105
106+ # Handle icon file upload
107+ icon_path = icon_url # Default to URL if provided
108+ if icon_file :
109+ import os
110+ from django .conf import settings
111+
112+ # Validate file type
113+ allowed_extensions = ['jpg' , 'jpeg' , 'png' , 'gif' , 'webp' , 'svg' ]
114+ file_ext = icon_file .name .split ('.' )[- 1 ].lower ()
115+ if file_ext not in allowed_extensions :
116+ return CustomResponse (
117+ general_message = f"Invalid file type. Allowed: { ', ' .join (allowed_extensions )} "
118+ ).get_failure_response ()
119+
120+ # Validate file size (max 5MB)
121+ if icon_file .size > 5 * 1024 * 1024 :
122+ return CustomResponse (
123+ general_message = "File size exceeds 5MB limit"
124+ ).get_failure_response ()
125+
126+ # Create directory if it doesn't exist
127+ upload_dir = os .path .join (settings .MEDIA_ROOT , 'achievements' , 'icons' )
128+ os .makedirs (upload_dir , exist_ok = True )
129+
130+ # Generate unique filename
131+ unique_filename = f"{ uuid .uuid4 ()} .{ file_ext } "
132+ file_path = os .path .join (upload_dir , unique_filename )
133+
134+ # Save file
135+ with open (file_path , 'wb+' ) as destination :
136+ for chunk in icon_file .chunks ():
137+ destination .write (chunk )
138+
139+ # Store relative path for database
140+ icon_path = f"achievements/icons/{ unique_filename } "
141+
86142 achievement = Achievement .objects .create (
87143 id = str (uuid .uuid4 ()),
88144 name = data ["name" ],
89145 description = data ["description" ],
90- icon = data [ "icon" ] ,
91- tags = data [ "tags" ] ,
146+ icon = icon_path ,
147+ tags = tags_value ,
92148 type = data ["type" ],
93149 level_id = level ,
94- has_vc = data [ "has_vc" ] ,
150+ has_vc = has_vc_value ,
95151 template_id = data .get ("template_id" ),
96152 created_by = user ,
97153 updated_by = user ,
@@ -105,6 +161,9 @@ def post(self, request):
105161
106162
107163class AchievementUpdateAPIView (APIView ):
164+ from rest_framework .parsers import MultiPartParser , FormParser
165+ parser_classes = [MultiPartParser , FormParser ]
166+
108167 def put (self , request , achievement_id = None ):
109168 user_id = JWTUtils .fetch_user_id (request )
110169
@@ -134,6 +193,45 @@ def put(self, request, achievement_id=None):
134193 data = request .data .copy ()
135194 data ["updated_by" ] = user_id
136195
196+ # Handle icon file upload
197+ icon_file = request .FILES .get ("icon" )
198+ if icon_file :
199+ import os
200+ from django .conf import settings
201+
202+ # Validate file type
203+ allowed_extensions = ['jpg' , 'jpeg' , 'png' , 'gif' , 'webp' , 'svg' ]
204+ file_ext = icon_file .name .split ('.' )[- 1 ].lower ()
205+ if file_ext not in allowed_extensions :
206+ return CustomResponse (
207+ general_message = f"Invalid file type. Allowed: { ', ' .join (allowed_extensions )} "
208+ ).get_failure_response ()
209+
210+ # Validate file size (max 5MB)
211+ if icon_file .size > 5 * 1024 * 1024 :
212+ return CustomResponse (
213+ general_message = "File size exceeds 5MB limit"
214+ ).get_failure_response ()
215+
216+ # Create directory if it doesn't exist
217+ upload_dir = os .path .join (settings .MEDIA_ROOT , 'achievements' , 'icons' )
218+ os .makedirs (upload_dir , exist_ok = True )
219+
220+ # Generate unique filename
221+ unique_filename = f"{ uuid .uuid4 ()} .{ file_ext } "
222+ file_path = os .path .join (upload_dir , unique_filename )
223+
224+ # Save file
225+ with open (file_path , 'wb+' ) as destination :
226+ for chunk in icon_file .chunks ():
227+ destination .write (chunk )
228+
229+ # Store relative path for database
230+ data ["icon" ] = f"achievements/icons/{ unique_filename } "
231+ elif "icon" not in data or not data ["icon" ]:
232+ # Keep existing icon if no new file or URL provided
233+ data ["icon" ] = achievement .icon
234+
137235 if "level_id" in data :
138236 if data ["level_id" ]:
139237 try :
@@ -146,6 +244,22 @@ def put(self, request, achievement_id=None):
146244 else :
147245 data ["level_id" ] = None
148246
247+ # Parse has_vc from string to boolean (FormData sends strings)
248+ if "has_vc" in data :
249+ has_vc_value = data .get ("has_vc" )
250+ if isinstance (has_vc_value , str ):
251+ data ["has_vc" ] = has_vc_value .lower () in ("true" , "1" , "yes" )
252+
253+ # Parse tags from JSON string to list (FormData sends strings)
254+ if "tags" in data :
255+ tags_value = data .get ("tags" , [])
256+ if isinstance (tags_value , str ):
257+ import json
258+ try :
259+ data ["tags" ] = json .loads (tags_value )
260+ except json .JSONDecodeError :
261+ data ["tags" ] = []
262+
149263 serializer = achievement_serializer .AchievementSerializer (
150264 achievement , data = data , partial = True
151265 )
0 commit comments