Phase 4: Users & RBAC — 4 new user endpoints (list, metrics, detail, status)
This commit is contained in:
@@ -18,4 +18,8 @@ urlpatterns = [
|
|||||||
path('partners/<int:pk>/', views.PartnerDetailView.as_view(), name='partner-detail'),
|
path('partners/<int:pk>/', views.PartnerDetailView.as_view(), name='partner-detail'),
|
||||||
path('partners/<int:pk>/status/', views.PartnerStatusView.as_view(), name='partner-status'),
|
path('partners/<int:pk>/status/', views.PartnerStatusView.as_view(), name='partner-status'),
|
||||||
path('partners/<int:pk>/kyc/review/', views.PartnerKYCReviewView.as_view(), name='partner-kyc-review'),
|
path('partners/<int:pk>/kyc/review/', views.PartnerKYCReviewView.as_view(), name='partner-kyc-review'),
|
||||||
]
|
path('users/metrics/', views.UserMetricsView.as_view(), name='user-metrics'),
|
||||||
|
path('users/', views.UserListView.as_view(), name='user-list'),
|
||||||
|
path('users/<int:pk>/', views.UserDetailView.as_view(), name='user-detail'),
|
||||||
|
path('users/<int:pk>/status/', views.UserStatusView.as_view(), name='user-status'),
|
||||||
|
]
|
||||||
@@ -471,3 +471,134 @@ class PartnerKYCReviewView(APIView):
|
|||||||
'is_kyc_compliant': p.is_kyc_compliant,
|
'is_kyc_compliant': p.is_kyc_compliant,
|
||||||
'verificationStatus': 'Verified' if decision == 'approved' else 'Rejected',
|
'verificationStatus': 'Verified' if decision == 'approved' else 'Rejected',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Phase 4: Users API
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _user_status(u):
|
||||||
|
return 'Active' if u.is_active else 'Suspended'
|
||||||
|
|
||||||
|
_USER_ROLE_MAP = {
|
||||||
|
'admin': 'Admin', 'manager': 'Admin',
|
||||||
|
'staff': 'Support Agent',
|
||||||
|
'customer': 'User', 'is_user': 'User',
|
||||||
|
'partner': 'Partner', 'partner_manager': 'Partner',
|
||||||
|
'partner_staff': 'Partner', 'partner_customer': 'Partner',
|
||||||
|
}
|
||||||
|
|
||||||
|
def _serialize_user(u):
|
||||||
|
full_name = f'{u.first_name} {u.last_name}'.strip() or u.username
|
||||||
|
role_key = u.role if u.role else ('customer' if getattr(u, 'is_customer', False) else 'staff')
|
||||||
|
return {
|
||||||
|
'id': str(u.id),
|
||||||
|
'name': full_name,
|
||||||
|
'email': u.email,
|
||||||
|
'phone': getattr(u, 'phone_number', '') or '',
|
||||||
|
'countryCode': '+91',
|
||||||
|
'role': _USER_ROLE_MAP.get(role_key, 'User'),
|
||||||
|
'status': _user_status(u),
|
||||||
|
'tier': 'Bronze',
|
||||||
|
'healthScore': 'warm',
|
||||||
|
'isVerified': u.is_active,
|
||||||
|
'is2FAEnabled': False,
|
||||||
|
'totalSpent': 0,
|
||||||
|
'bookingsCount': 0,
|
||||||
|
'refundRate': 0,
|
||||||
|
'averageOrderValue': 0,
|
||||||
|
'language': 'en',
|
||||||
|
'timezone': 'Asia/Kolkata',
|
||||||
|
'currency': 'INR',
|
||||||
|
'emailNotifications': True,
|
||||||
|
'pushNotifications': True,
|
||||||
|
'smsNotifications': True,
|
||||||
|
'tags': [],
|
||||||
|
'pinnedNote': '',
|
||||||
|
'createdAt': u.date_joined.isoformat() if u.date_joined else '',
|
||||||
|
'updatedAt': u.date_joined.isoformat() if u.date_joined else '',
|
||||||
|
'lastLoginAt': u.last_login.isoformat() if u.last_login else '',
|
||||||
|
'lastActivityAt': u.last_login.isoformat() if u.last_login else '',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class UserMetricsView(APIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.utils import timezone
|
||||||
|
import datetime
|
||||||
|
User = get_user_model()
|
||||||
|
today = timezone.now().date()
|
||||||
|
week_ago = today - datetime.timedelta(days=7)
|
||||||
|
return Response({
|
||||||
|
'total': User.objects.count(),
|
||||||
|
'active': User.objects.filter(is_active=True).count(),
|
||||||
|
'suspended': User.objects.filter(is_active=False).count(),
|
||||||
|
'newThisWeek': User.objects.filter(date_joined__date__gte=week_ago).count(),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class UserListView(APIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.db.models import Q
|
||||||
|
User = get_user_model()
|
||||||
|
qs = User.objects.all()
|
||||||
|
if s := request.GET.get('status'):
|
||||||
|
if s == 'Active':
|
||||||
|
qs = qs.filter(is_active=True)
|
||||||
|
elif s in ('Suspended', 'Banned'):
|
||||||
|
qs = qs.filter(is_active=False)
|
||||||
|
if r := request.GET.get('role'):
|
||||||
|
role_reverse = {v: k for k, v in _USER_ROLE_MAP.items()}
|
||||||
|
backend_role = role_reverse.get(r)
|
||||||
|
if backend_role:
|
||||||
|
qs = qs.filter(role=backend_role)
|
||||||
|
if q := request.GET.get('search'):
|
||||||
|
qs = qs.filter(
|
||||||
|
Q(username__icontains=q) | Q(email__icontains=q) |
|
||||||
|
Q(first_name__icontains=q) | Q(last_name__icontains=q) |
|
||||||
|
Q(phone_number__icontains=q)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
page = max(1, int(request.GET.get('page', 1)))
|
||||||
|
page_size = min(100, int(request.GET.get('page_size', 20)))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
page, page_size = 1, 20
|
||||||
|
total = qs.count()
|
||||||
|
users = qs.order_by('-date_joined')[(page - 1) * page_size: page * page_size]
|
||||||
|
return Response({'count': total, 'results': [_serialize_user(u) for u in users]})
|
||||||
|
|
||||||
|
|
||||||
|
class UserDetailView(APIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get(self, request, pk):
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
User = get_user_model()
|
||||||
|
u = get_object_or_404(User, pk=pk)
|
||||||
|
return Response(_serialize_user(u))
|
||||||
|
|
||||||
|
|
||||||
|
class UserStatusView(APIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def patch(self, request, pk):
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
User = get_user_model()
|
||||||
|
u = get_object_or_404(User, pk=pk)
|
||||||
|
action = request.data.get('action')
|
||||||
|
if action in ('suspend', 'ban'):
|
||||||
|
u.is_active = False
|
||||||
|
elif action == 'reinstate':
|
||||||
|
u.is_active = True
|
||||||
|
else:
|
||||||
|
return Response({'error': 'action must be suspend, ban, or reinstate'}, status=400)
|
||||||
|
u.save(update_fields=['is_active'])
|
||||||
|
return Response({'id': str(u.id), 'status': _user_status(u)})
|
||||||
|
|||||||
Reference in New Issue
Block a user