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