feat: add user search/filter, banned metric, mobile review API, event detail improvements

- admin_api/views.py: Add banned count to UserMetrics, fix server-side search/filter in UserListView
- admin_api/models.py: Add ReviewInteraction model, display_name/is_verified/helpful_count/flag_count to Review
- mobile_api/views/reviews.py: Customer-facing review submit/list/helpful/flag endpoints
- mobile_api/urls.py: Wire review API routes
- mobile_api/views/events.py: Event detail and listing improvements
- Security hardening across API modules
This commit is contained in:
2026-03-26 09:50:03 +00:00
parent 5a2752a2de
commit 388057b641
11 changed files with 371 additions and 91 deletions

View File

@@ -34,6 +34,10 @@ class Review(models.Model):
reject_reason = models.CharField(
max_length=15, choices=REJECT_CHOICES, null=True, blank=True
)
display_name = models.CharField(max_length=100, blank=True, default='')
is_verified = models.BooleanField(default=False)
helpful_count = models.IntegerField(default=0)
flag_count = models.IntegerField(default=0)
class Meta:
ordering = ['-submission_date']
@@ -44,3 +48,18 @@ class Review(models.Model):
def __str__(self):
return f'Review #{self.pk} by {self.reviewer_id}{self.status}'
class ReviewInteraction(models.Model):
INTERACTION_CHOICES = [('HELPFUL', 'Helpful'), ('FLAG', 'Flag')]
review = models.ForeignKey(Review, on_delete=models.CASCADE, related_name='interactions')
username = models.CharField(max_length=255)
interaction_type = models.CharField(max_length=20, choices=INTERACTION_CHOICES)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ('review', 'username', 'interaction_type')
def __str__(self):
return f'{self.username} {self.interaction_type} on Review #{self.review_id}'

View File

@@ -545,7 +545,7 @@ class UserMetricsView(APIView):
'total': customer_qs.count(),
'active': customer_qs.filter(is_active=True).count(),
'suspended': customer_qs.filter(is_active=False).count(),
'banned': 0, # Reserved for future explicit ban field
'newThisWeek': customer_qs.filter(date_joined__date__gte=week_ago).count(),
})
@@ -557,19 +557,11 @@ class UserListView(APIView):
from django.contrib.auth import get_user_model
from django.db.models import Q
User = get_user_model()
# Customers = all non-superuser accounts (end users registered via mobile/web)
qs = User.objects.filter(is_superuser=False)
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'):
# Server-side search
search = request.query_params.get('search', '').strip()
if search:
qs = qs.filter(
Q(first_name__icontains=search) |
Q(last_name__icontains=search) |
@@ -578,12 +570,14 @@ class UserListView(APIView):
Q(phone_number__icontains=search)
)
# Status filter
status_filter = request.query_params.get('status', '').strip().lower()
if status_filter == 'active':
qs = qs.filter(is_active=True)
elif status_filter == 'suspended':
qs = qs.filter(is_active=False)
# Role filter
role_filter = request.query_params.get('role', '').strip()
if role_filter:
qs = qs.filter(role__iexact=role_filter)