from django.http import JsonResponse from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from django.forms.models import model_to_dict from django.contrib.auth import authenticate, logout from rest_framework.views import APIView from rest_framework.authtoken.models import Token import json from .models import User from mobile_api.utils import validate_token_and_get_user def _partner_user_to_dict(user, request=None): """Serialize partner-related User for JSON (same structure as _user_to_dict).""" data = model_to_dict( user, fields=[ "id", "username", "email", "phone_number", "role", "is_staff", "is_customer", "is_user", "pincode", "district", "state", "country", "place", "latitude", "longitude", "first_name", "last_name", ], ) # Add profile picture URL if exists if getattr(user, "profile_picture", None): if request: data["profile_picture"] = user.profile_picture.url else: data["profile_picture"] = user.profile_picture.url else: data["profile_picture"] = None return data def _user_to_dict(user, request=None): """Serialize any User for JSON (admin/staff or partner).""" return _partner_user_to_dict(user, request) @method_decorator(csrf_exempt, name="dispatch") class PartnerLoginAPI(APIView): """ Partner Login API. Body: username (or email), password (required). Returns: token, user data. """ def post(self, request): try: # Parse JSON or form data is_multipart = request.content_type and "multipart/form-data" in request.content_type if is_multipart: data = request.POST.dict() else: try: data = json.loads(request.body) except json.JSONDecodeError: return JsonResponse( {"status": "error", "message": "Invalid JSON"}, status=400, ) username = data.get("username") or data.get("email") password = data.get("password") if not username or not password: return JsonResponse( {"status": "error", "message": "username and password are required."}, status=400, ) # Authenticate user user = authenticate(request, username=username, password=password) if not user: return JsonResponse( {"status": "error", "message": "Invalid username or password."}, status=401, ) # Check if user has partner role partner_roles = ["partner", "partner_manager", "partner_staff"] if user.role not in partner_roles: return JsonResponse( {"status": "error", "message": "You are not authorized to access partner portal."}, status=403, ) # Get or create token token, _ = Token.objects.get_or_create(user=user) return JsonResponse( { "status": "success", "message": "Login successful", "token": token.key, "user": _partner_user_to_dict(user, request), }, status=200, ) except Exception as e: return JsonResponse({"status": "error", "message": str(e)}, status=500) @method_decorator(csrf_exempt, name="dispatch") class PartnerLogoutAPI(APIView): """ Partner Logout API. Body: token, username (required). Returns: success message. """ def post(self, request): try: user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) if error_response: return error_response # Check if user has partner role partner_roles = ["partner", "partner_manager", "partner_staff"] if user.role not in partner_roles: return JsonResponse( {"status": "error", "message": "You are not authorized to access partner portal."}, status=403, ) # Delete token token.delete() return JsonResponse( { "status": "success", "message": "Logged out successfully.", }, status=200, ) except Exception as e: return JsonResponse({"status": "error", "message": str(e)}, status=500) @method_decorator(csrf_exempt, name="dispatch") class PartnerDashboardAPI(APIView): """ Partner Dashboard API. Body: token, username (required). Returns: dashboard statistics. """ def post(self, request): try: user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) if error_response: return error_response # Check if user has partner role partner_roles = ["partner", "partner_manager", "partner_staff"] if user.role not in partner_roles: return JsonResponse( {"status": "error", "message": "You are not authorized to access this page."}, status=403, ) # Get statistics for partner users (including partner_customer) all_partner_roles = ["partner", "partner_manager", "partner_staff", "partner_customer"] partner_users = User.objects.filter(role__in=all_partner_roles) total_partner_users = partner_users.count() return JsonResponse( { "status": "success", "dashboard": { "total_partner_users": total_partner_users, }, }, status=200, ) except Exception as e: return JsonResponse({"status": "error", "message": str(e)}, status=500) @method_decorator(csrf_exempt, name="dispatch") class PartnerListUsersAPI(APIView): """ Partner List Users API. Body: token, username (required); role (optional filter: partner, partner_manager, partner_staff, partner_customer). Returns: list of partner users. """ def post(self, request): try: user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) if error_response: return error_response # Check if user has partner role partner_roles = ["partner", "partner_manager", "partner_staff"] if user.role not in partner_roles: return JsonResponse( {"status": "error", "message": "You are not authorized to access this page."}, status=403, ) # Filter users by partner-related roles all_partner_roles = ["partner", "partner_manager", "partner_staff", "partner_customer"] qs = User.objects.filter(role__in=all_partner_roles).order_by("-id") # Optional role filter role_filter = data.get("role") if role_filter: if role_filter not in all_partner_roles: return JsonResponse( { "status": "error", "message": f"Invalid role filter. Must be one of: {', '.join(all_partner_roles)}", }, status=400, ) qs = qs.filter(role=role_filter) users = [_partner_user_to_dict(u, request) for u in qs] return JsonResponse( { "status": "success", "users": users, "total_count": len(users), }, status=200, ) except Exception as e: return JsonResponse({"status": "error", "message": str(e)}, status=500) @method_decorator(csrf_exempt, name="dispatch") class PartnerCreateUserAPI(APIView): """ Partner Create User API. Body: token, username, username (for new user), email, password, role (required); full_name, phone_number, pincode, district, state, country, place, latitude, longitude (optional). Returns: created user data. """ def post(self, request): try: user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) if error_response: return error_response # Check if user has partner role partner_roles = ["partner", "partner_manager", "partner_staff"] if user.role not in partner_roles: return JsonResponse( {"status": "error", "message": "You are not authorized to access this page."}, status=403, ) # Extract user data new_username = data.get("username") email = data.get("email") password = data.get("password") role = data.get("role") full_name = data.get("full_name", "").strip() if not all([new_username, email, password, role]): return JsonResponse( { "status": "error", "message": "username, email, password, and role are required.", }, status=400, ) # Validate role - must be one of the partner-related roles valid_partner_roles = ["partner", "partner_manager", "partner_staff", "partner_customer"] if role not in valid_partner_roles: return JsonResponse( { "status": "error", "message": f"Invalid role. Must be one of: {', '.join(valid_partner_roles)}", }, status=400, ) # Check if username already exists if User.objects.filter(username=new_username).exists(): return JsonResponse( {"status": "error", "message": "Username already exists."}, status=400, ) # Check if email already exists if User.objects.filter(email=email).exists(): return JsonResponse( {"status": "error", "message": "Email already exists."}, status=400, ) # Create user new_user = User.objects.create_user( username=new_username, email=email, password=password, role=role, phone_number=data.get("phone_number"), pincode=data.get("pincode"), district=data.get("district"), state=data.get("state"), country=data.get("country"), place=data.get("place"), ) # Handle full_name - split into first_name and last_name if full_name: parts = full_name.split(None, 1) new_user.first_name = parts[0] if len(parts) > 1: new_user.last_name = parts[1] # Set location coordinates if provided if data.get("latitude") is not None: try: latitude = float(data["latitude"]) if latitude < -90 or latitude > 90: return JsonResponse( {"status": "error", "message": "latitude must be between -90 and 90."}, status=400, ) new_user.latitude = latitude except (TypeError, ValueError): return JsonResponse( {"status": "error", "message": "latitude must be numeric."}, status=400, ) if data.get("longitude") is not None: try: longitude = float(data["longitude"]) if longitude < -180 or longitude > 180: return JsonResponse( {"status": "error", "message": "longitude must be between -180 and 180."}, status=400, ) new_user.longitude = longitude except (TypeError, ValueError): return JsonResponse( {"status": "error", "message": "longitude must be numeric."}, status=400, ) # Handle profile picture upload if provided if "profile_picture" in request.FILES: new_user.profile_picture = request.FILES["profile_picture"] new_user.save() return JsonResponse( { "status": "success", "message": f"User created successfully with role: {role}.", "user": _partner_user_to_dict(new_user, request), }, status=201, ) except Exception as e: return JsonResponse({"status": "error", "message": str(e)}, status=500) @method_decorator(csrf_exempt, name="dispatch") class PartnerUpdateUserAPI(APIView): """ Partner Update User API. Body: token, username, user_id (required); email, phone_number, role, full_name, pincode, district, state, country, place, latitude, longitude, password, profile_picture (optional). Returns: updated user data. """ def post(self, request): try: user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) if error_response: return error_response # Check if user has partner role partner_roles = ["partner", "partner_manager", "partner_staff"] if user.role not in partner_roles: return JsonResponse( {"status": "error", "message": "You are not authorized to access this page."}, status=403, ) user_id = data.get("user_id") if not user_id: return JsonResponse( {"status": "error", "message": "user_id is required."}, status=400, ) try: target_user = User.objects.get(id=user_id) except User.DoesNotExist: return JsonResponse( {"status": "error", "message": "User not found."}, status=404, ) # Validate that the user has a partner-related role all_partner_roles = ["partner", "partner_manager", "partner_staff", "partner_customer"] if target_user.role not in all_partner_roles: return JsonResponse( { "status": "error", "message": "User is not a partner-related user. Only users with partner roles can be updated.", }, status=400, ) # Update fields if provided if data.get("email") is not None: new_email = data["email"] # Check if email already exists for another user if User.objects.filter(email=new_email).exclude(id=user_id).exists(): return JsonResponse( {"status": "error", "message": "Email already exists for another user."}, status=400, ) target_user.email = new_email if data.get("phone_number") is not None: target_user.phone_number = data["phone_number"] or None if data.get("role") is not None: new_role = data["role"] if new_role not in all_partner_roles: return JsonResponse( { "status": "error", "message": f"Invalid role. Must be one of: {', '.join(all_partner_roles)}", }, status=400, ) target_user.role = new_role # Handle full_name if data.get("full_name"): full_name = data["full_name"].strip() if full_name: parts = full_name.split(None, 1) target_user.first_name = parts[0] if len(parts) > 1: target_user.last_name = parts[1] else: target_user.last_name = "" if "pincode" in data: target_user.pincode = data["pincode"] or None if "district" in data: target_user.district = data["district"] or None if "state" in data: target_user.state = data["state"] or None if "country" in data: target_user.country = data["country"] or None if "place" in data: target_user.place = data["place"] or None if data.get("latitude") is not None: try: latitude = float(data["latitude"]) if latitude < -90 or latitude > 90: return JsonResponse( {"status": "error", "message": "latitude must be between -90 and 90."}, status=400, ) target_user.latitude = latitude except (TypeError, ValueError): return JsonResponse( {"status": "error", "message": "latitude must be numeric."}, status=400, ) if data.get("longitude") is not None: try: longitude = float(data["longitude"]) if longitude < -180 or longitude > 180: return JsonResponse( {"status": "error", "message": "longitude must be between -180 and 180."}, status=400, ) target_user.longitude = longitude except (TypeError, ValueError): return JsonResponse( {"status": "error", "message": "longitude must be numeric."}, status=400, ) # Handle profile picture upload if provided if "profile_picture" in request.FILES: target_user.profile_picture = request.FILES["profile_picture"] # Handle password update if provided if data.get("password"): target_user.set_password(data["password"]) target_user.save() return JsonResponse( { "status": "success", "message": "Partner user updated successfully.", "user": _partner_user_to_dict(target_user, request), }, status=200, ) except Exception as e: return JsonResponse({"status": "error", "message": str(e)}, status=500) @method_decorator(csrf_exempt, name="dispatch") class PartnerDeleteUserAPI(APIView): """ Partner Delete User API. Body: token, username, user_id (required). Returns: success message. """ def post(self, request): try: user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) if error_response: return error_response # Check if user has partner role partner_roles = ["partner", "partner_manager", "partner_staff"] if user.role not in partner_roles: return JsonResponse( {"status": "error", "message": "You are not authorized to access this page."}, status=403, ) user_id = data.get("user_id") if not user_id: return JsonResponse( {"status": "error", "message": "user_id is required."}, status=400, ) try: target_user = User.objects.get(id=user_id) except User.DoesNotExist: return JsonResponse( {"status": "error", "message": "User not found."}, status=404, ) # Validate that the user has a partner-related role all_partner_roles = ["partner", "partner_manager", "partner_staff", "partner_customer"] if target_user.role not in all_partner_roles: return JsonResponse( { "status": "error", "message": "User is not a partner-related user. Only users with partner roles can be deleted.", }, status=400, ) # Prevent deleting yourself if target_user.id == user.id: return JsonResponse( {"status": "error", "message": "You cannot delete your own account."}, status=400, ) username = target_user.username target_user.delete() return JsonResponse( { "status": "success", "message": f"Partner user '{username}' deleted successfully.", }, status=200, ) except Exception as e: return JsonResponse({"status": "error", "message": str(e)}, status=500) @method_decorator(csrf_exempt, name="dispatch") class LoginAPI(APIView): """ Admin/Staff Login API (accounts). Body: username (or email), password (required). Returns: token and user details for admin/manager/staff roles. """ def post(self, request): try: # Parse JSON or form data is_multipart = request.content_type and "multipart/form-data" in request.content_type if is_multipart: data = request.POST.dict() else: try: data = json.loads(request.body) except json.JSONDecodeError: return JsonResponse( {"status": "error", "message": "Invalid JSON"}, status=400, ) username = data.get("username") or data.get("email") password = data.get("password") if not username or not password: return JsonResponse( {"status": "error", "message": "username and password are required."}, status=400, ) user = authenticate(request, username=username, password=password) if not user: return JsonResponse( {"status": "error", "message": "Invalid username or password."}, status=401, ) # Only allow admin/manager/staff to use this login allowed_roles = ["admin", "manager", "staff"] if user.role not in allowed_roles: return JsonResponse( {"status": "error", "message": "You are not authorized to access the admin portal."}, status=403, ) token, _ = Token.objects.get_or_create(user=user) return JsonResponse( { "status": "success", "message": "Login successful", "token": token.key, "user": _user_to_dict(user, request), }, status=200, ) except Exception as e: return JsonResponse({"status": "error", "message": str(e)}, status=500) @method_decorator(csrf_exempt, name="dispatch") class LogoutAPI(APIView): """ Logout API for token-based sessions. Body: token, username (required). """ def post(self, request): try: user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) if error_response: return error_response logout(request) token.delete() return JsonResponse( { "status": "success", "message": "Logout successful.", }, status=200, ) except Exception as e: return JsonResponse({"status": "error", "message": str(e)}, status=500) @method_decorator(csrf_exempt, name="dispatch") class UserListAPI(APIView): """ List users (admin / manager / staff only). Body: token, username (required); optional role filter. """ def post(self, request): try: user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) if error_response: return error_response # Only allow admin/manager/staff to list users allowed_roles = ["admin", "manager", "staff"] if user.role not in allowed_roles: return JsonResponse( {"status": "error", "message": "You are not authorized to list users."}, status=403, ) qs = User.objects.all().order_by("-id") role_filter = data.get("role") if role_filter: qs = qs.filter(role=role_filter) users = [_user_to_dict(u, request) for u in qs] return JsonResponse( { "status": "success", "users": users, "total_count": len(users), }, status=200, ) except Exception as e: return JsonResponse({"status": "error", "message": str(e)}, status=500) @method_decorator(csrf_exempt, name="dispatch") class UserCreateAPI(APIView): """ Create a user (admin / manager / staff only). Body: token, username, new_username, email, password, role ('admin'|'manager'|'staff'), phone_number (optional). """ def post(self, request): try: user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) if error_response: return error_response allowed_roles = ["admin", "manager", "staff"] if user.role not in allowed_roles: return JsonResponse( {"status": "error", "message": "You are not authorized to create users."}, status=403, ) new_username = data.get("username") or data.get("new_username") email = data.get("email") password = data.get("password") role = data.get("role") if not all([new_username, email, password, role]): return JsonResponse( { "status": "error", "message": "username, email, password, and role are required.", }, status=400, ) valid_roles = ["admin", "manager", "staff"] if role not in valid_roles: return JsonResponse( { "status": "error", "message": f"Invalid role. Must be one of: {', '.join(valid_roles)}", }, status=400, ) if User.objects.filter(username=new_username).exists(): return JsonResponse( {"status": "error", "message": "Username already exists."}, status=400, ) if User.objects.filter(email=email).exists(): return JsonResponse( {"status": "error", "message": "Email already exists."}, status=400, ) new_user = User.objects.create_user( username=new_username, email=email, password=password, role=role, phone_number=data.get("phone_number"), ) return JsonResponse( { "status": "success", "message": f"User created successfully with role: {role}.", "user": _user_to_dict(new_user, request), }, status=201, ) except Exception as e: return JsonResponse({"status": "error", "message": str(e)}, status=500) @method_decorator(csrf_exempt, name="dispatch") class UserUpdateAPI(APIView): """ Update a user (admin / manager / staff only). Body: token, username, user_id (required); email, phone_number, role (optional). """ def post(self, request): try: user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) if error_response: return error_response allowed_roles = ["admin", "manager", "staff"] if user.role not in allowed_roles: return JsonResponse( {"status": "error", "message": "You are not authorized to update users."}, status=403, ) user_id = data.get("user_id") if not user_id: return JsonResponse( {"status": "error", "message": "user_id is required."}, status=400, ) try: target_user = User.objects.get(id=user_id) except User.DoesNotExist: return JsonResponse( {"status": "error", "message": "User not found."}, status=404, ) if data.get("email") is not None: new_email = data["email"] if User.objects.filter(email=new_email).exclude(id=user_id).exists(): return JsonResponse( {"status": "error", "message": "Email already exists for another user."}, status=400, ) target_user.email = new_email if data.get("phone_number") is not None: target_user.phone_number = data["phone_number"] or None if data.get("role") is not None: new_role = data["role"] valid_roles = ["admin", "manager", "staff"] if new_role not in valid_roles: return JsonResponse( { "status": "error", "message": f"Invalid role. Must be one of: {', '.join(valid_roles)}", }, status=400, ) target_user.role = new_role target_user.save() return JsonResponse( { "status": "success", "message": "User updated successfully.", "user": _user_to_dict(target_user, request), }, status=200, ) except Exception as e: return JsonResponse({"status": "error", "message": str(e)}, status=500) @method_decorator(csrf_exempt, name="dispatch") class UserDeleteAPI(APIView): """ Delete a user (admin / manager / staff only). Body: token, username, user_id (required). """ def post(self, request): try: user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) if error_response: return error_response allowed_roles = ["admin", "manager", "staff"] if user.role not in allowed_roles: return JsonResponse( {"status": "error", "message": "You are not authorized to delete users."}, status=403, ) user_id = data.get("user_id") if not user_id: return JsonResponse( {"status": "error", "message": "user_id is required."}, status=400, ) try: target_user = User.objects.get(id=user_id) except User.DoesNotExist: return JsonResponse( {"status": "error", "message": "User not found."}, status=404, ) if target_user.id == user.id: return JsonResponse( {"status": "error", "message": "You cannot delete your own account."}, status=400, ) username = target_user.username target_user.delete() return JsonResponse( { "status": "success", "message": f"User '{username}' deleted successfully.", }, status=200, ) except Exception as e: return JsonResponse({"status": "error", "message": str(e)}, status=500)