The new updates of partners and user

Made-with: Cursor
This commit is contained in:
Vivek P Prakash
2026-03-15 00:29:17 +05:30
parent 88b3aafb0b
commit c04395afc9
65 changed files with 5242 additions and 341 deletions

946
accounts/api.py Normal file
View File

@@ -0,0 +1,946 @@
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"] = request.build_absolute_uri(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)

View File

@@ -88,3 +88,110 @@ class LoginForm(AuthenticationForm):
"placeholder": "Enter password"
})
)
class PartnerUserForm(forms.ModelForm):
full_name = forms.CharField(
max_length=150,
required=True,
label="Full Name"
)
password = forms.CharField(
widget=forms.PasswordInput,
label="Password",
required=True,
help_text="Required for new users. Leave blank if you don't want to change the password when editing."
)
confirm_password = forms.CharField(
widget=forms.PasswordInput,
label="Confirm Password",
required=True
)
phone_number = forms.CharField(
max_length=15,
required=False,
label="Phone Number"
)
ROLE_CHOICES = [
('partner', 'Partner'),
('partner_manager', 'Partner Manager'),
('partner_staff', 'Partner Staff'),
('partner_customer', 'Partner Customer'),
]
role = forms.ChoiceField(
choices=ROLE_CHOICES,
required=True,
label="Role"
)
class Meta:
model = User
fields = ["username", "email", "phone_number", "role"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs.update({"class": "form-control"})
# Make password fields optional for updates, required for new users
if self.instance and self.instance.pk:
self.fields['password'].required = False
self.fields['confirm_password'].required = False
# Pre-populate full_name from first_name and last_name
if self.instance.first_name or self.instance.last_name:
self.fields['full_name'].initial = f"{self.instance.first_name} {self.instance.last_name}".strip()
else:
# For new users, password is required
self.fields['password'].required = True
self.fields['confirm_password'].required = True
def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get("password")
confirm_password = cleaned_data.get("confirm_password")
# For new users, password is required
if not self.instance or not self.instance.pk:
if not password:
self.add_error("password", "Password is required for new users.")
if not confirm_password:
self.add_error("confirm_password", "Please confirm your password.")
# Validate password match if password is provided
if password or confirm_password:
if password != confirm_password:
self.add_error("confirm_password", "Passwords do not match!")
return cleaned_data
def save(self, commit=True):
user = super().save(commit=False)
# Set password - required for new users, optional for updates
password = self.cleaned_data.get('password')
if password:
user.set_password(password)
elif not user.pk:
# New user must have a password
raise ValueError("Password is required for new users.")
# Save phone_number and role to the User model
user.phone_number = self.cleaned_data.get("phone_number")
user.role = self.cleaned_data.get("role")
# Handle full_name - split into first_name and last_name
full_name = self.cleaned_data.get("full_name", "").strip()
if full_name:
parts = full_name.split(None, 1)
user.first_name = parts[0]
if len(parts) > 1:
user.last_name = parts[1]
else:
user.last_name = ""
if commit:
user.save()
return user

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.27 on 2026-03-13 16:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0007_alter_user_profile_picture'),
]
operations = [
migrations.AlterField(
model_name='user',
name='role',
field=models.CharField(choices=[('admin', 'Admin'), ('manager', 'Manager'), ('staff', 'Staff'), ('customer', 'Customer'), ('partner', 'Partner'), ('partner_manager', 'Partner Manager'), ('partner_staff', 'Partner Staff'), ('partner_customer', 'Partner Customer')], default='Staff', max_length=20),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 4.2.27 on 2026-03-14 07:00
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('partner', '0001_initial'),
('accounts', '0008_alter_user_role'),
]
operations = [
migrations.AddField(
model_name='user',
name='partner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='partner.partner'),
),
]

View File

@@ -2,18 +2,24 @@ from django.contrib.auth.models import AbstractUser
from django.db import models
from accounts.manager import UserManager
from partner.models import Partner
ROLE_CHOICES = (
('admin', 'Admin'),
('manager', 'Manager'),
('staff', 'Staff'),
('customer', 'Customer'),
('partner', 'Partner'),
('partner_manager', 'Partner Manager'),
('partner_staff', 'Partner Staff'),
('partner_customer', 'Partner Customer'),
)
class User(AbstractUser):
phone_number = models.CharField(max_length=15, blank=True, null=True)
role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='Staff')
partner = models.ForeignKey(Partner, on_delete=models.CASCADE, blank=True, null=True)
is_staff = models.BooleanField(default=False)
is_customer = models.BooleanField(default=False)
is_user = models.BooleanField(default=False)

View File

@@ -1,14 +1,37 @@
from django.urls import path
from . import views
from . import views, api
app_name = 'accounts'
app_name = "accounts"
urlpatterns = [
path('login/', views.login_view, name='login'),
path('logout/', views.logout_view, name='logout'),
path('dashboard/', views.dashboard, name='dashboard'),
path('users/', views.UserListView.as_view(), name='user_list'),
path('users/add/', views.UserCreateView.as_view(), name='user_add'),
path('users/<int:pk>/edit/', views.UserUpdateView.as_view(), name='user_edit'),
path('users/<int:pk>/delete/', views.UserDeleteView.as_view(), name='user_delete'),
path("login/", views.login_view, name="login"),
path("logout/", views.logout_view, name="logout"),
path("dashboard/", views.dashboard, name="dashboard"),
path("users/", views.UserListView.as_view(), name="user_list"),
path("users/add/", views.UserCreateView.as_view(), name="user_add"),
path("users/<int:pk>/edit/", views.UserUpdateView.as_view(), name="user_edit"),
path("users/<int:pk>/delete/", views.UserDeleteView.as_view(), name="user_delete"),
]
# Core account APIs (admin/staff)
urlpatterns += [
path("api/login/", api.LoginAPI.as_view(), name="api_login"),
path("api/logout/", api.LogoutAPI.as_view(), name="api_logout"),
path("api/users/list/", api.UserListAPI.as_view(), name="api_user_list"),
path("api/users/create/", api.UserCreateAPI.as_view(), name="api_user_create"),
path("api/users/update/", api.UserUpdateAPI.as_view(), name="api_user_update"),
path("api/users/delete/", api.UserDeleteAPI.as_view(), name="api_user_delete"),
]
# Partner APIs
urlpatterns += [
path("api/partner/login/", api.PartnerLoginAPI.as_view(), name="partner_api_login"),
path("api/partner/logout/", api.PartnerLogoutAPI.as_view(), name="partner_api_logout"),
path("api/partner/dashboard/", api.PartnerDashboardAPI.as_view(), name="partner_api_dashboard"),
path("api/partner/users/list/", api.PartnerListUsersAPI.as_view(), name="partner_api_user_list"),
path("api/partner/users/create/", api.PartnerCreateUserAPI.as_view(), name="partner_api_user_create"),
path("api/partner/users/update/", api.PartnerUpdateUserAPI.as_view(), name="partner_api_user_update"),
path("api/partner/users/delete/", api.PartnerDeleteUserAPI.as_view(), name="partner_api_user_delete"),
]

View File

@@ -1,16 +1,16 @@
from django.shortcuts import render
from django.shortcuts import render, redirect
from django.views import generic
from django.urls import reverse_lazy
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.contrib import messages
from django.contrib.auth import authenticate, login, logout
from .models import User
from .forms import LoginForm
from .forms import UserForm
from .forms import LoginForm, UserForm, PartnerUserForm
from events.models import Event
from master_data.models import EventType
from django.contrib.auth import authenticate, login, logout
from django.shortcuts import redirect
from django.contrib import messages
from eventify_logger.services import log
def dashboard(request):
@@ -62,16 +62,150 @@ def login_view(request):
user = form.get_user()
login(request, user)
if user.role == 'admin' or user.role == 'manager' or user.role == 'staff':
log("info", "Admin/Manager/Staff login", request=request, user=user)
return redirect("accounts:dashboard")
else:
log("warning", "Login attempt - user not authorized", request=request, user=user)
messages.error(request, "You are not authorized to access this page.")
else:
log("warning", "Invalid login attempt", request=request)
messages.error(request, "Invalid username or password")
return render(request, "accounts/login.html", {"form": form})
def logout_view(request):
if request.user.is_authenticated:
log("info", "User logout", request=request, user=request.user)
logout(request)
messages.success(request, "You have been logged out successfully.")
return redirect("accounts:login")
return redirect("accounts:login")
# Partner Views Mixin
class PartnerRequiredMixin(LoginRequiredMixin):
"""Mixin to ensure user has partner role (partner, partner_manager, partner_staff)"""
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
partner_roles = ['partner', 'partner_manager', 'partner_staff']
if request.user.role not in partner_roles:
raise PermissionDenied("You are not authorized to access this page.")
return super().dispatch(request, *args, **kwargs)
# Partner Login/Logout/Dashboard
def partner_login_view(request):
if request.user.is_authenticated:
partner_roles = ['partner', 'partner_manager', 'partner_staff']
if request.user.role in partner_roles:
return redirect("accounts:partner_dashboard")
else:
messages.error(request, "You are not authorized to access partner portal.")
return redirect("accounts:login")
form = LoginForm(request, data=request.POST or None)
if request.method == "POST":
if form.is_valid():
user = form.get_user()
partner_roles = ['partner', 'partner_manager', 'partner_staff']
if user.role in partner_roles:
log("info", "Partner portal login", request=request, user=user)
login(request, user)
return redirect("accounts:partner_dashboard")
else:
log("warning", "Partner login - user not authorized", request=request, user=user)
messages.error(request, "You are not authorized to access partner portal.")
else:
log("warning", "Partner portal - invalid login attempt", request=request)
messages.error(request, "Invalid username or password")
return render(request, "partner/login.html", {"form": form})
def partner_logout_view(request):
if request.user.is_authenticated:
log("info", "Partner portal logout", request=request, user=request.user)
logout(request)
messages.success(request, "You have been logged out successfully.")
return redirect("accounts:partner_login")
def partner_dashboard(request):
"""Partner dashboard view"""
partner_roles = ['partner', 'partner_manager', 'partner_staff']
if not request.user.is_authenticated or request.user.role not in partner_roles:
messages.error(request, "You are not authorized to access this page.")
return redirect("accounts:partner_login")
# 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()
# You can add more partner-specific statistics here
# For example, events created by partner, bookings, etc.
return render(request, 'partner/dashboard.html', {
'total_partner_users': total_partner_users,
})
# Partner User Management Views
class PartnerUserListView(PartnerRequiredMixin, generic.ListView):
model = User
template_name = 'partner/user_list.html'
context_object_name = 'users'
paginate_by = 20
def get_queryset(self):
"""Filter users to show only partner-related roles"""
partner_roles = ['partner', 'partner_manager', 'partner_staff', 'partner_customer']
return User.objects.filter(role__in=partner_roles).order_by('-id')
class PartnerUserCreateView(PartnerRequiredMixin, generic.CreateView):
model = User
form_class = PartnerUserForm
template_name = 'partner/user_form.html'
success_url = reverse_lazy('accounts:partner_user_list')
def form_valid(self, form):
messages.success(self.request, "Partner user created successfully.")
return super().form_valid(form)
class PartnerUserUpdateView(PartnerRequiredMixin, generic.UpdateView):
model = User
form_class = PartnerUserForm
template_name = 'partner/user_form.html'
success_url = reverse_lazy('accounts:partner_user_list')
def get_queryset(self):
"""Only allow editing users with partner-related roles"""
partner_roles = ['partner', 'partner_manager', 'partner_staff', 'partner_customer']
return User.objects.filter(role__in=partner_roles)
def form_valid(self, form):
messages.success(self.request, "Partner user updated successfully.")
return super().form_valid(form)
class PartnerUserDeleteView(PartnerRequiredMixin, generic.DeleteView):
model = User
template_name = 'partner/user_confirm_delete.html'
success_url = reverse_lazy('accounts:partner_user_list')
def get_queryset(self):
"""Only allow deleting users with partner-related roles"""
partner_roles = ['partner', 'partner_manager', 'partner_staff', 'partner_customer']
return User.objects.filter(role__in=partner_roles)
def delete(self, request, *args, **kwargs):
# Prevent users from deleting themselves
if self.get_object().id == request.user.id:
messages.error(request, "You cannot delete your own account.")
return redirect(self.success_url)
messages.success(request, "Partner user deleted successfully.")
return super().delete(request, *args, **kwargs)