Skip to content

Commit

Permalink
#104: system admin listing approval (#116)
Browse files Browse the repository at this point in the history
Co-authored-by: aakashshankar <[email protected]>
  • Loading branch information
deepjyotk and ahhcash authored Nov 6, 2024
1 parent 679f1ce commit c2c8099
Show file tree
Hide file tree
Showing 11 changed files with 277 additions and 27 deletions.
22 changes: 21 additions & 1 deletion src/home/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from decimal import Decimal
import boto3
from urllib.parse import quote
from boto3.dynamodb.conditions import Attr, And, Key
from boto3.dynamodb.conditions import Attr, And, Key, Or
from django.conf import settings
from botocore.exceptions import ClientError
from geopy import distance as dist
Expand All @@ -22,6 +22,7 @@ def fetch_items_with_filter(
):
filter_expression = None

# Create filter based on search query and category
if search_query and category_filter:
filter_expression = And(
Attr("Name").contains(search_query),
Expand All @@ -32,13 +33,31 @@ def fetch_items_with_filter(
elif category_filter:
filter_expression = Attr("Category").contains(category_filter)

# Add ServiceStatus filter (if exists, it should be "APPROVED")
service_status_filter = Or(
Attr(
"ServiceStatus"
).not_exists(), # Include items where ServiceStatus does not exist
Attr("ServiceStatus").eq(
"APPROVED"
), # Include items where ServiceStatus is "APPROVED"
)

# Combine the existing filter expression with the new ServiceStatus filter
if filter_expression:
filter_expression = And(filter_expression, service_status_filter)
else:
filter_expression = service_status_filter

# Scan with the combined filter expression
scan_kwargs = {}
if filter_expression:
scan_kwargs["FilterExpression"] = filter_expression

response = self.services_table.scan(**scan_kwargs)
items = response.get("Items", [])

# Filter items based on radius if provided
if radius and ulat and ulon:
filtered_items = []
for item in items:
Expand Down Expand Up @@ -122,6 +141,7 @@ def update_service_rating(self, service_id, new_rating):

def fetch_reviews_for_service(self, service_id):
try:

# Query to get all reviews with matching ServiceId
response = self.reviews_table.scan(
FilterExpression=Key("ServiceId").eq(service_id)
Expand Down
49 changes: 49 additions & 0 deletions src/public_service_finder/templates/admin_only.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% block title %}Admin Only Page{% endblock %}

{% block content %}
<div class="pt-12">
<div class="container mx-auto mt-32 mb-10 p-10 bg-white shadow-lg rounded-lg">
<h1 class="text-2xl font-bold text-center mb-6">Pending Approval Listings</h1>

{% if pending_services %}
<div class="overflow-x-auto">
<table class="min-w-full bg-white border border-gray-200 rounded-lg">
<thead>
<tr class="w-full bg-gray-200 text-left text-gray-600 uppercase text-sm leading-normal">
<th class="py-3 px-6">Service Name</th>
<th class="py-3 px-6">Description</th>
<th class="py-3 px-6">Status</th>
<th class="py-3 px-6 text-center">Actions</th>
</tr>
</thead>
<tbody id="service-table-body" class="text-gray-700 text-sm font-light">
{% for service in pending_services %}
<tr id="service-row-{{ service.id }}" class="border-b border-gray-200 hover:bg-gray-100">
<td class="py-3 px-6 font-medium">{{ service.name }}</td>
<td class="py-3 px-6">{{ service.description }}</td>
<td class="py-3 px-6">{{ service.service_status }}</td>
<td class="py-3 px-6 text-center">
<form action="{% url 'admin_update_listing' service.id %}" method="post" style="display:inline;">
{% csrf_token %}
<button type="submit" name="status" value="approve"
class="bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-3 rounded mx-1">
Approve
</button>
<button type="submit" name="status" value="reject"
class="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-3 rounded mx-1">
Reject
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-center text-gray-500">No pending listings at the moment.</p>
{% endif %}
</div>
</div>
{% endblock %}
59 changes: 40 additions & 19 deletions src/public_service_finder/templates/partials/_navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,30 @@
</div>

<!-- Navigation Items -->
<div class="flex items-center space-x-6">
<div class="flex items-center justify-end space-x-6">
{% if user.is_authenticated and user.user_type == 'service_provider' %}
<a href="{% url 'services:list' %}" class="hidden md:flex items-center space-x-2 text-gray-600 hover:text-blue-700 font-medium p-2 rounded-md hover:bg-gray-100">
<i class="fas fa-home"></i>
<span>Home</span>
</a>
{% else %}
<a href="{% url 'home' %}" class="hidden md:flex items-center space-x-2 text-gray-600 hover:text-blue-700 font-medium p-2 rounded-md hover:bg-gray-100">
<i class="fas fa-home"></i>
<span>Home</span>
</a>
{% endif %}
<a href="{% url 'forum:category_list' %}" class="hidden md:flex items-center space-x-2 text-gray-600 hover:text-blue-700 font-medium p-2 rounded-md hover:bg-gray-100">
<i class="fas fa-comments"></i>
<span>Forum</span>
</a>

{% if user.is_superuser %}
<a href="{% url 'admin_only_view_new_listings' %}" class="hidden md:flex items-center space-x-2 text-gray-600 hover:text-blue-700 font-medium p-2 rounded-md hover:bg-gray-100">
<i class="fas fa-list"></i>
<span>New Service Listings</span>
</a>
{% endif %}

<!-- Profile Section -->
<div class="relative">
<button id="profileButton" class="flex items-center space-x-2 focus:outline-none hover:bg-gray-100 p-2 rounded-md">
Expand All @@ -35,25 +49,25 @@
<i class="fas fa-caret-down text-gray-500"></i>
</button>

<!-- Dropdown Menu -->
<div id="profileDropdown" class="absolute right-0 mt-2 w-48 bg-white border border-gray-200 shadow-md rounded-md text-gray-700 z-20 hidden">
<a href="/accounts/profile" class="block px-4 py-2 hover:bg-gray-100 flex items-center space-x-2">
<i class="fas fa-user-circle text-blue-500"></i>
<span>View Profile</span>
</a>
<a href="#" class="block px-4 py-2 hover:bg-gray-100 flex items-center space-x-2">
<i class="fas fa-cog text-blue-500"></i>
<span>Settings</span>
</a>
<form method="post" action="{% url 'logout' %}">
{% csrf_token %}
<button type="submit" class="block w-full text-left px-4 py-2 hover:bg-gray-100 flex items-center space-x-2">
<i class="fas fa-sign-out-alt text-red-500"></i>
<span>Logout</span>
</button>
</form>
</div>
<!-- Dropdown Menu -->
<div id="profileDropdown" class="absolute right-0 mt-2 w-48 bg-white border border-gray-200 shadow-md rounded-md text-gray-700 z-20 hidden">
<a href="/accounts/profile" class="block px-4 py-2 hover:bg-gray-100 flex items-center space-x-2">
<i class="fas fa-user-circle text-blue-500"></i>
<span>View Profile</span>
</a>
<a href="#" class="block px-4 py-2 hover:bg-gray-100 flex items-center space-x-2">
<i class="fas fa-cog text-blue-500"></i>
<span>Settings</span>
</a>
<form method="post" action="{% url 'logout' %}">
{% csrf_token %}
<button type="submit" class="block w-full text-left px-4 py-2 hover:bg-gray-100 flex items-center space-x-2">
<i class="fas fa-sign-out-alt text-red-500"></i>
<span>Logout</span>
</button>
</form>
</div>

</div>
</div>
</nav>
Expand All @@ -62,10 +76,17 @@
<div class="md:hidden bg-white border-b">
<div class="container mx-auto py-2 px-4">
<div class="flex justify-center space-x-4">
{% if user.is_authenticated and user.user_type == 'service_provider' %}
<a href="{% url 'services:list' %}" class="flex items-center space-x-2 text-gray-600 hover:text-blue-700 font-medium">
<i class="fas fa-home"></i>
<span>Home</span>
</a>
{% else %}
<a href="{% url 'home' %}" class="flex items-center space-x-2 text-gray-600 hover:text-blue-700 font-medium">
<i class="fas fa-home"></i>
<span>Home</span>
</a>
{% endif %}
<a href="{% url 'forum:category_list' %}" class="flex items-center space-x-2 text-gray-600 hover:text-blue-700 font-medium">
<i class="fas fa-comments"></i>
<span>Forum</span>
Expand Down
16 changes: 15 additions & 1 deletion src/public_service_finder/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,27 @@

from django.contrib import admin
from django.urls import path, include
from .views import root_redirect_view # Import the redirect view
from .views import (
admin_only_view_new_listings,
admin_update_listing,
root_redirect_view,
) # Import the redirect view

urlpatterns = [
path("admin/", admin.site.urls),
path("accounts/", include("accounts.urls")),
path("home/", include("home.urls")),
path("", root_redirect_view, name="root_redirect"), # Use the custom redirect view
path("services/", include("services.urls", namespace="services")),
path(
"admin-only-view-new-listings/",
admin_only_view_new_listings,
name="admin_only_view_new_listings",
),
path(
"admin-listing/update/<uuid:service_id>/",
admin_update_listing,
name="admin_update_listing",
), # Changed prefix
path("forum/", include("forum.urls", namespace="forum")),
]
7 changes: 7 additions & 0 deletions src/public_service_finder/utils/enums/service_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from enum import Enum


class ServiceStatus(Enum):
PENDING_APPROVAL = "PENDING_APPROVAL"
APPROVED = "APPROVED"
REJECTED = "REJECTED"
53 changes: 50 additions & 3 deletions src/public_service_finder/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# public_service_finder/views.py

from django.shortcuts import redirect

# from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import login_required, user_passes_test
from django.shortcuts import render
from public_service_finder.utils.enums.service_status import ServiceStatus
from services.repositories import ServiceRepository
from django.contrib import messages


def root_redirect_view(request):
Expand All @@ -14,3 +16,48 @@ def root_redirect_view(request):
)
else:
return redirect("user_login") # Redirect to user login if not logged in


@login_required
@user_passes_test(lambda user: user.is_superuser)
def admin_only_view_new_listings(request):
service_repo = ServiceRepository()
pending_services = service_repo.get_pending_approval_services()
return render(request, "admin_only.html", {"pending_services": pending_services})


@login_required
@user_passes_test(lambda user: user.is_superuser)
def admin_update_listing(request, service_id):
if request.method == "POST":
new_status = request.POST.get("status")

# Only allow "approve" or "reject" as valid status values
if new_status not in ["approve", "reject"]:
messages.error(request, "Invalid status value.")
return redirect("admin_only_view_new_listings")

service_repo = ServiceRepository()
try:
service_repo.update_service_status(
service_id,
(
ServiceStatus.APPROVED.value
if new_status == "approve"
else ServiceStatus.REJECTED.value
),
)
messages.success(
request,
f"Service ID {service_id} has been successfully updated to '{new_status}'.",
)
except Exception as e:
messages.error(
request, f"An error occurred while updating the service: {str(e)}"
)

# Redirect to the listings page to see updated statuses
return redirect("admin_only_view_new_listings")

# If the request is not POST, redirect back to the listings page
return redirect("admin_only_view_new_listings")
14 changes: 14 additions & 0 deletions src/services/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from typing import Dict, Any
import uuid

from public_service_finder.utils.enums.service_status import ServiceStatus


@dataclass
class ServiceDTO:
Expand All @@ -20,10 +22,16 @@ class ServiceDTO:
description: Dict[str, Any]
category: str
provider_id: str
service_status: str
service_created_timestamp: str
service_approved_timestamp: str

@classmethod
def from_dynamodb_item(cls, item: Dict[str, Any]) -> "ServiceDTO":
"""Create ServiceDTO from DynamoDB item"""
service_status = item.get("ServiceStatus", "PENDING_APPROVAL")
if service_status.startswith("ServiceStatus."):
service_status = service_status.split(".")[1]
return cls(
id=item["Id"],
name=item["Name"],
Expand All @@ -34,6 +42,9 @@ def from_dynamodb_item(cls, item: Dict[str, Any]) -> "ServiceDTO":
description=item["Description"],
category=item["Category"],
provider_id=item["ProviderId"],
service_status=ServiceStatus(service_status).value,
service_created_timestamp=item.get("CreatedTimestamp", "NONE"),
service_approved_timestamp=item.get("ApprovedTimestamp", "NONE"),
)

def to_dynamodb_item(self) -> Dict[str, Any]:
Expand All @@ -48,4 +59,7 @@ def to_dynamodb_item(self) -> Dict[str, Any]:
"Description": self.description,
"Category": self.category,
"ProviderId": self.provider_id,
"ServiceStatus": self.service_status,
"CreatedTimestamp": self.service_created_timestamp,
"ApprovedTimestamp": self.service_approved_timestamp,
}
Loading

0 comments on commit c2c8099

Please sign in to comment.